Python
# !/usr/bin/env python3
"""
sprout0 - minimal prototype
Just enough for: .x : 5
"""

import sqlite3

# — Database —

def init_db():
    db = sqlite3.connect(":memory:")
    db.execute("""
    CREATE TABLE cells (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        parent_id INTEGER,
        name TEXT,
        value TEXT,
        value_type TEXT,
        FOREIGN KEY (parent_id) REFERENCES cells(id)
    )
    """)
    # Root cell (id=1)
    db.execute("INSERT INTO cells (name, value, value_type) VALUES ('root', NULL, 'cell')")
    db.commit()
    return db

ROOT_ID = 1

# Block storage: cell_id -> { 'tokens': [...], 'args': [...], 'scope_id': int }

BLOCKS = {}

# — Cell operations —

def create_field(db, parent_id, name, value):
    """The : method with . flag – create a new field on parent cell."""
    # If value is a block reference, store specially
    if isinstance(value, str) and value.startswith("<block:"):
        block_id = int(value.split(":")[1].rstrip(">"))
        db.execute(
            "INSERT INTO cells (parent_id, name, value, value_type) VALUES (?, ?, ?, ?)",
            (parent_id, name, str(block_id), "block")
        )
        db.commit()
        new_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
        if block_id in BLOCKS:
            BLOCKS[new_id] = BLOCKS[block_id].copy()
        print(f"  [create] {name} = <block> (id={new_id}, parent={parent_id})")
        return new_id

    # If value is a cell reference
    if isinstance(value, str) and value.startswith("<cell:"):
        ref_id = int(value.split(":")[1].rstrip(">"))
        db.execute(
            "INSERT INTO cells (parent_id, name, value, value_type) VALUES (?, ?, ?, ?)",
            (parent_id, name, str(ref_id), "cell")
        )
        db.commit()
        new_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
        print(f"  [create] {name} = <cell:{ref_id}> (id={new_id}, parent={parent_id})")
        return new_id

    # [] is a real empty cell
    if value == [] or value is None:
        db.execute(
            "INSERT INTO cells (parent_id, name, value, value_type) VALUES (?, ?, ?, ?)",
            (parent_id, name, None, "cell")
        )
        db.commit()
        new_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
        print(f"  [create] {name} = [] (id={new_id}, parent={parent_id})")
        return new_id

    value_str, value_type = encode_value(value)
    db.execute(
        "INSERT INTO cells (parent_id, name, value, value_type) VALUES (?, ?, ?, ?)",
        (parent_id, name, value_str, value_type)
    )
    db.commit()
    new_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
    print(f"  [create] {name} = {value} (id={new_id}, parent={parent_id})")
    return new_id


def lookup(db, scope_id, name):
    """Lookup a field by name, walking up scope."""
    is_meta_lookup = name.startswith('#')

    current = scope_id
    while current is not None:
        row = db.execute(
            "SELECT id, value, value_type FROM cells WHERE parent_id = ? AND name = ?",
            (current, name)
        ).fetchone()
        if row:
            return decode_value(row[0], row[1], row[2])

        if not is_meta_lookup:
            mm_row = db.execute(
                "SELECT id, value, value_type FROM cells WHERE parent_id = ? AND name = '#methodMissing'",
                (current,)
            ).fetchone()
            if mm_row and mm_row[0] in BLOCKS:
                print(f"  [methodMissing] '{name}' at cell:{current}")
                result = call_block(db, mm_row[0], [name], caller_scope=current)
                if result != [] and result is not None:
                    return result

        parent = db.execute(
            "SELECT parent_id FROM cells WHERE id = ?",
            (current,)
        ).fetchone()
        current = parent[0] if parent else None
    return None


def lookup_cell_id(db, scope_id, name):
    """Return cell id for a name."""
    current = scope_id
    while current is not None:
        row = db.execute(
            "SELECT id FROM cells WHERE parent_id = ? AND name = ?",
            (current, name)
        ).fetchone()
        if row:
            return row[0]
        parent = db.execute(
            "SELECT parent_id FROM cells WHERE id = ?",
            (current,)
        ).fetchone()
        current = parent[0] if parent else None
    return None


def resolve_cell_ref(db, cell_id):
    if cell_id is None:
        return None
    row = db.execute(
        "SELECT value, value_type FROM cells WHERE id = ?",
        (cell_id,)
    ).fetchone()
    if row and row[1] == "cell" and row[0] is not None:
        try:
            return int(row[0])
        except (ValueError, TypeError):
            pass
    return cell_id


def is_falsy(db, value):
    if value == [] or value is None:
        return True
    if isinstance(value, str) and value.startswith("<cell:"):
        cell_id = int(value.split(":")[1].rstrip(">"))
        count = db.execute(
            "SELECT COUNT(*) FROM cells WHERE parent_id = ?",
            (cell_id,)
        ).fetchone()[0]
        return count == 0
    return False


def assign_field(db, scope_id, name, value):
    """Reassign field."""
    current = scope_id
    while current is not None:
        row = db.execute(
            "SELECT id FROM cells WHERE parent_id = ? AND name = ?",
            (current, name)
        ).fetchone()
        if row:
            value_str, value_type = encode_value(value)
            db.execute(
                "UPDATE cells SET value = ?, value_type = ? WHERE id = ?",
                (value_str, value_type, row[0])
            )
            db.commit()
            print(f"  [assign] {name} = {value} (id={row[0]})")
            return value
        parent = db.execute(
            "SELECT parent_id FROM cells WHERE id = ?",
            (current,)
        ).fetchone()
        current = parent[0] if parent else None
    raise RuntimeError(f"Lookup failed: '{name}' not found")


# — Value encoding —

def encode_value(value):
    if value is None or value == []:
        return (None, "empty")
    elif isinstance(value, (int, float)):
        return (str(value), "number")
    elif isinstance(value, str):
        return (value, "string")
    else:
        return (str(value), "unknown")


def decode_value(cell_id, value_str, value_type):
    if value_type == "empty":
        return []
    elif value_type == "number":
        n = float(value_str)
        return int(n) if n == int(n) else n
    elif value_type == "string":
        return value_str
    elif value_type == "cell":
        return f"<cell:{cell_id}>"
    elif value_type == "block":
        return f"<block:{cell_id}>"
    return value_str


# — Debug dump —

def dump(db):
    print("\n— cells —")
    rows = db.execute(
        "SELECT id, parent_id, name, value, value_type FROM cells ORDER BY id"
    ).fetchall()
    for r in rows:
        print(f"id={r[0]} parent={r[1]} name={r[2]} value={r[3]} type={r[4]}")
    print("—\n")


# — Minimal evaluator placeholder —

def evaluate(db, source, scope_id=ROOT_ID):
    print("Source loaded:")
    print(source)


# — Run —

if __name__ == "__main__":
    db = init_db()

    source = """
.todo :
.title : ""
.done : []

.todo-list :
.todos : []
.add : [title |
.new-todo : todo
new-todo.title : title
todos : todos + new-todo
]

.my-list : todo-list
my-list.add "buy groceries"
my-list.add "learn sprout"
my-list.todos.length!
.empty-check : my-list.todos = [] true? "empty" false? "has items"
"""

    print(f"Running:\n{source}")
    evaluate(db, source)
    dump(db)
Running:
.todo :
.title : ""
.done : []
.todo-list :
.todos : []
.add : [title |
.new-todo : todo
new-todo.title : title
todos : todos + new-todo
]
.my-list : todo-list
my-list.add "buy groceries"
my-list.add "learn sprout"
my-list.todos.length!
.empty-check : my-list.todos = [] true? "empty" false? "has items"
Source loaded:
.todo :
.title : ""
.done : []
.todo-list :
.todos : []
.add : [title |
.new-todo : todo
new-todo.title : title
todos : todos + new-todo
]
.my-list : todo-list
my-list.add "buy groceries"
my-list.add "learn sprout"
my-list.todos.length!
.empty-check : my-list.todos = [] true? "empty" false? "has items"
— cells —
id=1 parent=None name=root value=None type=cell