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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user