Bootstrap parser.sx to Python, add reactive runtime plan

Replace hand-written serialize/sx_serialize/sx_parse in Python with
spec-derived versions from parser.sx. Add parser as a Python adapter
alongside html/sx/async — all 48 parser spec tests pass.

Add reactive runtime plan to sx-docs: 7 feature layers (ref, foreign
FFI, state machines, commands with undo/redo, render loops, keyed
lists, client-first app shell) — zero new platform primitives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 01:45:17 +00:00
parent f96506024e
commit 4b746e4c8b
4 changed files with 281 additions and 65 deletions

View File

@@ -85,7 +85,12 @@ class PyEmitter:
if name == "define-async":
return self._emit_define_async(expr, indent)
if name == "set!":
return f"{pad}{self._mangle(expr[1].name)} = {self.emit(expr[2])}"
varname = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1])
py_var = self._mangle(varname)
cell_vars = getattr(self, '_current_cell_vars', set())
if py_var in cell_vars:
return f"{pad}_cells[{self._py_string(py_var)}] = {self.emit(expr[2])}"
return f"{pad}{py_var} = {self.emit(expr[2])}"
if name == "when":
return self._emit_when_stmt(expr, indent)
if name == "do" or name == "begin":
@@ -747,12 +752,12 @@ class PyEmitter:
nested_set_vars = self._find_nested_set_vars(body)
def_kw = "async def" if is_async else "def"
lines = [f"{pad}{def_kw} {py_name}({params_str}):"]
if nested_set_vars:
lines.append(f"{pad} _cells = {{}}")
# Emit body with cell var tracking (and async context if needed)
old_cells = getattr(self, '_current_cell_vars', set())
if nested_set_vars and not old_cells:
lines.append(f"{pad} _cells = {{}}")
old_async = self._in_async
self._current_cell_vars = nested_set_vars
self._current_cell_vars = old_cells | nested_set_vars
if is_async:
self._in_async = True
self._emit_body_stmts(body, lines, indent + 1)

View File

@@ -2179,18 +2179,18 @@ def sx_parse(source):
_cells['pos'] = 0
len_src = len(source)
def skip_comment():
if sx_truthy(((pos < len_src) if not sx_truthy((pos < len_src)) else (not sx_truthy((nth(source, pos) == '\n'))))):
pos = (pos + 1)
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (not sx_truthy((nth(source, _cells['pos']) == '\n'))))):
_cells['pos'] = (_cells['pos'] + 1)
return skip_comment()
return NIL
def skip_ws():
if sx_truthy((pos < len_src)):
ch = nth(source, pos)
if sx_truthy((_cells['pos'] < len_src)):
ch = nth(source, _cells['pos'])
if sx_truthy(((ch == ' ') if sx_truthy((ch == ' ')) else ((ch == '\t') if sx_truthy((ch == '\t')) else ((ch == '\n') if sx_truthy((ch == '\n')) else (ch == '\r'))))):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return skip_ws()
elif sx_truthy((ch == ';')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
skip_comment()
return skip_ws()
else:
@@ -2203,35 +2203,35 @@ def sx_parse(source):
_cells['pos'] = (_cells['pos'] + 1)
_cells['buf'] = ''
def read_str_loop():
if sx_truthy((pos >= len_src)):
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unterminated string')
else:
ch = nth(source, pos)
ch = nth(source, _cells['pos'])
if sx_truthy((ch == '"')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return NIL
elif sx_truthy((ch == '\\')):
pos = (pos + 1)
esc = nth(source, pos)
_cells['pos'] = (_cells['pos'] + 1)
esc = nth(source, _cells['pos'])
if sx_truthy((esc == 'u')):
pos = (pos + 1)
d0 = hex_digit_value(nth(source, pos))
_ = _sx_cell_set(_cells, 'pos', (pos + 1))
d1 = hex_digit_value(nth(source, pos))
_ = _sx_cell_set(_cells, 'pos', (pos + 1))
d2 = hex_digit_value(nth(source, pos))
_ = _sx_cell_set(_cells, 'pos', (pos + 1))
d3 = hex_digit_value(nth(source, pos))
_ = _sx_cell_set(_cells, 'pos', (pos + 1))
buf = sx_str(buf, char_from_code(((d0 * 4096) + (d1 * 256))))
_cells['pos'] = (_cells['pos'] + 1)
d0 = hex_digit_value(nth(source, _cells['pos']))
_ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
d1 = hex_digit_value(nth(source, _cells['pos']))
_ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
d2 = hex_digit_value(nth(source, _cells['pos']))
_ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
d3 = hex_digit_value(nth(source, _cells['pos']))
_ = _sx_cell_set(_cells, 'pos', (_cells['pos'] + 1))
_cells['buf'] = sx_str(_cells['buf'], char_from_code(((d0 * 4096) + (d1 * 256))))
return read_str_loop()
else:
buf = sx_str(buf, ('\n' if sx_truthy((esc == 'n')) else ('\t' if sx_truthy((esc == 't')) else ('\r' if sx_truthy((esc == 'r')) else esc))))
pos = (pos + 1)
_cells['buf'] = sx_str(_cells['buf'], ('\n' if sx_truthy((esc == 'n')) else ('\t' if sx_truthy((esc == 't')) else ('\r' if sx_truthy((esc == 'r')) else esc))))
_cells['pos'] = (_cells['pos'] + 1)
return read_str_loop()
else:
buf = sx_str(buf, ch)
pos = (pos + 1)
_cells['buf'] = sx_str(_cells['buf'], ch)
_cells['pos'] = (_cells['pos'] + 1)
return read_str_loop()
read_str_loop()
return _cells['buf']
@@ -2239,14 +2239,14 @@ def sx_parse(source):
_cells = {}
start = _cells['pos']
def read_ident_loop():
if sx_truthy(((pos < len_src) if not sx_truthy((pos < len_src)) else ident_char_p(nth(source, pos)))):
pos = (pos + 1)
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else ident_char_p(nth(source, _cells['pos'])))):
_cells['pos'] = (_cells['pos'] + 1)
return read_ident_loop()
return NIL
read_ident_loop()
return slice(source, start, _cells['pos'])
def read_keyword():
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return make_keyword(read_ident())
def read_number():
_cells = {}
@@ -2254,8 +2254,8 @@ def sx_parse(source):
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (nth(source, _cells['pos']) == '-'))):
_cells['pos'] = (_cells['pos'] + 1)
def read_digits():
if sx_truthy(((pos < len_src) if not sx_truthy((pos < len_src)) else (lambda c: ((c >= '0') if not sx_truthy((c >= '0')) else (c <= '9')))(nth(source, pos)))):
pos = (pos + 1)
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (lambda c: ((c >= '0') if not sx_truthy((c >= '0')) else (c <= '9')))(nth(source, _cells['pos'])))):
_cells['pos'] = (_cells['pos'] + 1)
return read_digits()
return NIL
read_digits()
@@ -2283,11 +2283,11 @@ def sx_parse(source):
items = []
def read_list_loop():
skip_ws()
if sx_truthy((pos >= len_src)):
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unterminated list')
else:
if sx_truthy((nth(source, pos) == close_ch)):
pos = (pos + 1)
if sx_truthy((nth(source, _cells['pos']) == close_ch)):
_cells['pos'] = (_cells['pos'] + 1)
return NIL
else:
items.append(read_expr())
@@ -2299,11 +2299,11 @@ def sx_parse(source):
result = {}
def read_map_loop():
skip_ws()
if sx_truthy((pos >= len_src)):
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unterminated map')
else:
if sx_truthy((nth(source, pos) == '}')):
pos = (pos + 1)
if sx_truthy((nth(source, _cells['pos']) == '}')):
_cells['pos'] = (_cells['pos'] + 1)
return NIL
else:
key_expr = read_expr()
@@ -2317,63 +2317,63 @@ def sx_parse(source):
_cells = {}
_cells['buf'] = ''
def raw_loop():
if sx_truthy((pos >= len_src)):
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unterminated raw string')
else:
ch = nth(source, pos)
ch = nth(source, _cells['pos'])
if sx_truthy((ch == '|')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return NIL
else:
buf = sx_str(buf, ch)
pos = (pos + 1)
_cells['buf'] = sx_str(_cells['buf'], ch)
_cells['pos'] = (_cells['pos'] + 1)
return raw_loop()
raw_loop()
return _cells['buf']
def read_expr():
skip_ws()
if sx_truthy((pos >= len_src)):
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unexpected end of input')
else:
ch = nth(source, pos)
ch = nth(source, _cells['pos'])
if sx_truthy((ch == '(')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return read_list(')')
elif sx_truthy((ch == '[')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return read_list(']')
elif sx_truthy((ch == '{')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return read_map()
elif sx_truthy((ch == '"')):
return read_string()
elif sx_truthy((ch == ':')):
return read_keyword()
elif sx_truthy((ch == '`')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return [make_symbol('quasiquote'), read_expr()]
elif sx_truthy((ch == ',')):
pos = (pos + 1)
if sx_truthy(((pos < len_src) if not sx_truthy((pos < len_src)) else (nth(source, pos) == '@'))):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else (nth(source, _cells['pos']) == '@'))):
_cells['pos'] = (_cells['pos'] + 1)
return [make_symbol('splice-unquote'), read_expr()]
else:
return [make_symbol('unquote'), read_expr()]
elif sx_truthy((ch == '#')):
pos = (pos + 1)
if sx_truthy((pos >= len_src)):
_cells['pos'] = (_cells['pos'] + 1)
if sx_truthy((_cells['pos'] >= len_src)):
return error('Unexpected end of input after #')
else:
dispatch_ch = nth(source, pos)
dispatch_ch = nth(source, _cells['pos'])
if sx_truthy((dispatch_ch == ';')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
read_expr()
return read_expr()
elif sx_truthy((dispatch_ch == '|')):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return read_raw_string()
elif sx_truthy((dispatch_ch == "'")):
pos = (pos + 1)
_cells['pos'] = (_cells['pos'] + 1)
return [make_symbol('quote'), read_expr()]
elif sx_truthy(ident_start_p(dispatch_ch)):
macro_name = read_ident()
@@ -2384,10 +2384,10 @@ def sx_parse(source):
return error(sx_str('Unknown reader macro: #', macro_name))
else:
return error(sx_str('Unknown reader macro: #', dispatch_ch))
elif sx_truthy((((ch >= '0') if not sx_truthy((ch >= '0')) else (ch <= '9')) if sx_truthy(((ch >= '0') if not sx_truthy((ch >= '0')) else (ch <= '9'))) else ((ch == '-') if not sx_truthy((ch == '-')) else (((pos + 1) < len_src) if not sx_truthy(((pos + 1) < len_src)) else (lambda next_ch: ((next_ch >= '0') if not sx_truthy((next_ch >= '0')) else (next_ch <= '9')))(nth(source, (pos + 1))))))):
elif sx_truthy((((ch >= '0') if not sx_truthy((ch >= '0')) else (ch <= '9')) if sx_truthy(((ch >= '0') if not sx_truthy((ch >= '0')) else (ch <= '9'))) else ((ch == '-') if not sx_truthy((ch == '-')) else (((_cells['pos'] + 1) < len_src) if not sx_truthy(((_cells['pos'] + 1) < len_src)) else (lambda next_ch: ((next_ch >= '0') if not sx_truthy((next_ch >= '0')) else (next_ch <= '9')))(nth(source, (_cells['pos'] + 1))))))):
return read_number()
elif sx_truthy(((ch == '.') if not sx_truthy((ch == '.')) else (((pos + 2) < len_src) if not sx_truthy(((pos + 2) < len_src)) else ((nth(source, (pos + 1)) == '.') if not sx_truthy((nth(source, (pos + 1)) == '.')) else (nth(source, (pos + 2)) == '.'))))):
pos = (pos + 3)
elif sx_truthy(((ch == '.') if not sx_truthy((ch == '.')) else (((_cells['pos'] + 2) < len_src) if not sx_truthy(((_cells['pos'] + 2) < len_src)) else ((nth(source, (_cells['pos'] + 1)) == '.') if not sx_truthy((nth(source, (_cells['pos'] + 1)) == '.')) else (nth(source, (_cells['pos'] + 2)) == '.'))))):
_cells['pos'] = (_cells['pos'] + 3)
return make_symbol('...')
elif sx_truthy(ident_start_p(ch)):
return read_symbol()
@@ -2396,7 +2396,7 @@ def sx_parse(source):
exprs = []
def parse_loop():
skip_ws()
if sx_truthy((pos < len_src)):
if sx_truthy((_cells['pos'] < len_src)):
exprs.append(read_expr())
return parse_loop()
return NIL