# !/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) Click Run or press shift + ENTER to run code