Replace invoke with cek-call in reactive island primitives
All signal operations (computed, effect, batch, etc.) now dispatch function calls through cek-call, which routes SX lambdas via cek-run and native callables via apply. This replaces the invoke shim. Key changes: - cek.sx: add cek-call (defined before reactive-shift-deref), replace invoke in subscriber disposal and ReactiveResetFrame handler - signals.sx: replace all 11 invoke calls with cek-call - js.sx: fix octal escape in js-quote-string (char-from-code 0) - platform_js.py: fix JS append to match Python (list concat semantics), add Continuation type guard in PLATFORM_CEK_JS, add scheduleIdle safety check, module ordering (cek before signals) - platform_py.py: fix ident-char regex (remove [ ] from valid chars), module ordering (cek before signals) - run_js_sx.py: emit PLATFORM_CEK_JS before transpiled spec files - page-functions.sx: add cek and provide page functions for SX URLs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -760,7 +760,16 @@ class PyEmitter:
|
||||
self._current_cell_vars = old_cells | nested_set_vars
|
||||
if is_async:
|
||||
self._in_async = True
|
||||
self._emit_body_stmts(body, lines, indent + 1)
|
||||
# Self-tail-recursive 0-param functions: wrap body in while True
|
||||
if (not param_names and not is_async
|
||||
and self._has_self_tail_call(body, name)):
|
||||
lines.append(f"{pad} while True:")
|
||||
old_loop = getattr(self, '_current_loop_name', None)
|
||||
self._current_loop_name = name
|
||||
self._emit_body_stmts(body, lines, indent + 2)
|
||||
self._current_loop_name = old_loop
|
||||
else:
|
||||
self._emit_body_stmts(body, lines, indent + 1)
|
||||
self._current_cell_vars = old_cells
|
||||
self._in_async = old_async
|
||||
return "\n".join(lines)
|
||||
@@ -799,14 +808,20 @@ class PyEmitter:
|
||||
Handles let as local variable declarations, and returns the last
|
||||
expression. Control flow in tail position (if, cond, case, when)
|
||||
is flattened to if/elif statements with returns in each branch.
|
||||
|
||||
Detects self-tail-recursive (define name (fn () ...)) followed by
|
||||
(name) and emits as while True loop instead of recursive def.
|
||||
"""
|
||||
pad = " " * indent
|
||||
for i, expr in enumerate(body):
|
||||
is_last = (i == len(body) - 1)
|
||||
idx = 0
|
||||
while idx < len(body):
|
||||
expr = body[idx]
|
||||
is_last = (idx == len(body) - 1)
|
||||
if isinstance(expr, list) and expr and isinstance(expr[0], Symbol):
|
||||
name = expr[0].name
|
||||
if name in ("let", "let*"):
|
||||
self._emit_let_as_stmts(expr, lines, indent, is_last)
|
||||
idx += 1
|
||||
continue
|
||||
if name in ("do", "begin"):
|
||||
sub_body = expr[1:]
|
||||
@@ -815,15 +830,172 @@ class PyEmitter:
|
||||
else:
|
||||
for sub in sub_body:
|
||||
lines.append(self.emit_statement(sub, indent))
|
||||
idx += 1
|
||||
continue
|
||||
# Detect self-tail-recursive loop pattern:
|
||||
# (define loop-name (fn () body...))
|
||||
# (loop-name)
|
||||
# Emit as: while True: <body with self-calls as continue>
|
||||
if (name == "define" and not is_last
|
||||
and idx + 1 < len(body)):
|
||||
loop_info = self._detect_tail_loop(expr, body[idx + 1])
|
||||
if loop_info:
|
||||
loop_name, fn_body = loop_info
|
||||
remaining = body[idx + 2:]
|
||||
# Only optimize if the function isn't called again later
|
||||
if not self._name_in_exprs(loop_name, remaining):
|
||||
self._emit_while_loop(loop_name, fn_body, lines, indent)
|
||||
# Skip the invocation; emit remaining body
|
||||
for j, rem in enumerate(remaining):
|
||||
if j == len(remaining) - 1:
|
||||
self._emit_return_expr(rem, lines, indent)
|
||||
else:
|
||||
self._emit_stmt_recursive(rem, lines, indent)
|
||||
return
|
||||
if is_last:
|
||||
self._emit_return_expr(expr, lines, indent)
|
||||
else:
|
||||
self._emit_stmt_recursive(expr, lines, indent)
|
||||
idx += 1
|
||||
|
||||
def _detect_tail_loop(self, define_expr, next_expr):
|
||||
"""Detect pattern: (define name (fn () body...)) followed by (name).
|
||||
|
||||
Returns (loop_name, fn_body) if tail-recursive, else None.
|
||||
The function must have 0 params and body must end with self-call
|
||||
in all tail positions.
|
||||
"""
|
||||
# Extract name and fn from define
|
||||
dname = define_expr[1].name if isinstance(define_expr[1], Symbol) else None
|
||||
if not dname:
|
||||
return None
|
||||
# Skip :effects annotation
|
||||
if (len(define_expr) >= 5 and isinstance(define_expr[2], Keyword)
|
||||
and define_expr[2].name == "effects"):
|
||||
val_expr = define_expr[4]
|
||||
else:
|
||||
val_expr = define_expr[2] if len(define_expr) > 2 else None
|
||||
if not (isinstance(val_expr, list) and val_expr
|
||||
and isinstance(val_expr[0], Symbol)
|
||||
and val_expr[0].name in ("fn", "lambda")):
|
||||
return None
|
||||
params = val_expr[1]
|
||||
if not isinstance(params, list) or len(params) != 0:
|
||||
return None # Must be 0-param function
|
||||
fn_body = val_expr[2:]
|
||||
# Check next expression is (name) — invocation
|
||||
if not (isinstance(next_expr, list) and len(next_expr) == 1
|
||||
and isinstance(next_expr[0], Symbol)
|
||||
and next_expr[0].name == dname):
|
||||
return None
|
||||
# Check that fn_body has self-call in tail position(s)
|
||||
if not self._has_self_tail_call(fn_body, dname):
|
||||
return None
|
||||
return (dname, fn_body)
|
||||
|
||||
def _has_self_tail_call(self, body, name):
|
||||
"""Check if body is safe for while-loop optimization.
|
||||
|
||||
Returns True only when ALL tail positions are either:
|
||||
- self-calls (name) → will become continue
|
||||
- nil/void returns → will become break
|
||||
- error() calls → raise, don't return
|
||||
- when blocks → implicit nil else is fine
|
||||
No tail position may return a computed value, since while-loop
|
||||
break discards return values.
|
||||
"""
|
||||
if not body:
|
||||
return False
|
||||
last = body[-1]
|
||||
# Non-list terminal: nil is ok, anything else is a value return
|
||||
if not isinstance(last, list) or not last:
|
||||
return (last is None or last is SX_NIL
|
||||
or (isinstance(last, Symbol) and last.name == "nil"))
|
||||
head = last[0] if isinstance(last[0], Symbol) else None
|
||||
if not head:
|
||||
return False
|
||||
# Direct self-call in tail position
|
||||
if head.name == name and len(last) == 1:
|
||||
return True
|
||||
# error() — raises, safe
|
||||
if head.name == "error":
|
||||
return True
|
||||
# if — ALL branches must be safe
|
||||
if head.name == "if":
|
||||
then_ok = self._has_self_tail_call(
|
||||
[last[2]] if len(last) > 2 else [None], name)
|
||||
else_ok = self._has_self_tail_call(
|
||||
[last[3]] if len(last) > 3 else [None], name)
|
||||
return then_ok and else_ok
|
||||
# do/begin — check last expression
|
||||
if head.name in ("do", "begin"):
|
||||
return self._has_self_tail_call(last[1:], name)
|
||||
# when — body must be safe (implicit nil else is ok)
|
||||
if head.name == "when":
|
||||
return self._has_self_tail_call(last[2:], name)
|
||||
# let/let* — check body (skip bindings)
|
||||
if head.name in ("let", "let*"):
|
||||
return self._has_self_tail_call(last[2:], name)
|
||||
# cond — ALL branches must be safe
|
||||
if head.name == "cond":
|
||||
clauses = last[1:]
|
||||
is_scheme = (
|
||||
all(isinstance(c, list) and len(c) == 2 for c in clauses)
|
||||
and not any(isinstance(c, Keyword) for c in clauses)
|
||||
)
|
||||
if is_scheme:
|
||||
for clause in clauses:
|
||||
if not self._has_self_tail_call([clause[1]], name):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
i = 0
|
||||
while i < len(clauses) - 1:
|
||||
if not self._has_self_tail_call([clauses[i + 1]], name):
|
||||
return False
|
||||
i += 2
|
||||
return True
|
||||
return False
|
||||
|
||||
def _name_in_exprs(self, name, exprs):
|
||||
"""Check if a symbol name appears anywhere in a list of expressions."""
|
||||
for expr in exprs:
|
||||
if isinstance(expr, Symbol) and expr.name == name:
|
||||
return True
|
||||
if isinstance(expr, list):
|
||||
if self._name_in_exprs(name, expr):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _emit_while_loop(self, loop_name, fn_body, lines, indent):
|
||||
"""Emit a self-tail-recursive function body as a while True loop."""
|
||||
pad = " " * indent
|
||||
lines.append(f"{pad}while True:")
|
||||
# Track the loop name so _emit_return_expr can emit 'continue'
|
||||
old_loop = getattr(self, '_current_loop_name', None)
|
||||
self._current_loop_name = loop_name
|
||||
self._emit_body_stmts(fn_body, lines, indent + 1)
|
||||
self._current_loop_name = old_loop
|
||||
|
||||
def _emit_nil_return(self, lines: list, indent: int) -> None:
|
||||
"""Emit 'return NIL' or 'break' depending on while-loop context."""
|
||||
pad = " " * indent
|
||||
if getattr(self, '_current_loop_name', None):
|
||||
lines.append(f"{pad}break")
|
||||
else:
|
||||
lines.append(f"{pad}return NIL")
|
||||
|
||||
def _emit_return_expr(self, expr, lines: list, indent: int) -> None:
|
||||
"""Emit an expression in return position, flattening control flow."""
|
||||
pad = " " * indent
|
||||
# Inside a while loop (self-tail-recursive define optimization):
|
||||
# self-call → continue
|
||||
loop_name = getattr(self, '_current_loop_name', None)
|
||||
if loop_name:
|
||||
if (isinstance(expr, list) and len(expr) == 1
|
||||
and isinstance(expr[0], Symbol) and expr[0].name == loop_name):
|
||||
lines.append(f"{pad}continue")
|
||||
return
|
||||
if isinstance(expr, list) and expr and isinstance(expr[0], Symbol):
|
||||
name = expr[0].name
|
||||
if name == "if":
|
||||
@@ -845,11 +1017,17 @@ class PyEmitter:
|
||||
self._emit_body_stmts(expr[1:], lines, indent)
|
||||
return
|
||||
if name == "for-each":
|
||||
# for-each in return position: emit as statement, return NIL
|
||||
# for-each in return position: emit as statement, then return/break
|
||||
lines.append(self._emit_for_each_stmt(expr, indent))
|
||||
lines.append(f"{pad}return NIL")
|
||||
self._emit_nil_return(lines, indent)
|
||||
return
|
||||
lines.append(f"{pad}return {self.emit(expr)}")
|
||||
if loop_name:
|
||||
emitted = self.emit(expr)
|
||||
if emitted != "NIL":
|
||||
lines.append(f"{pad}{emitted}")
|
||||
lines.append(f"{pad}break")
|
||||
else:
|
||||
lines.append(f"{pad}return {self.emit(expr)}")
|
||||
|
||||
def _emit_if_return(self, expr, lines: list, indent: int) -> None:
|
||||
"""Emit if as statement with returns in each branch."""
|
||||
@@ -860,7 +1038,7 @@ class PyEmitter:
|
||||
lines.append(f"{pad}else:")
|
||||
self._emit_return_expr(expr[3], lines, indent + 1)
|
||||
else:
|
||||
lines.append(f"{pad}return NIL")
|
||||
self._emit_nil_return(lines, indent)
|
||||
|
||||
def _emit_when_return(self, expr, lines: list, indent: int) -> None:
|
||||
"""Emit when as statement with return in body, else return NIL."""
|
||||
@@ -873,7 +1051,7 @@ class PyEmitter:
|
||||
for b in body_parts[:-1]:
|
||||
lines.append(self.emit_statement(b, indent + 1))
|
||||
self._emit_return_expr(body_parts[-1], lines, indent + 1)
|
||||
lines.append(f"{pad}return NIL")
|
||||
self._emit_nil_return(lines, indent)
|
||||
|
||||
def _emit_cond_return(self, expr, lines: list, indent: int) -> None:
|
||||
"""Emit cond as if/elif/else with returns in each branch."""
|
||||
@@ -915,7 +1093,7 @@ class PyEmitter:
|
||||
self._emit_return_expr(body, lines, indent + 1)
|
||||
i += 2
|
||||
if not has_else:
|
||||
lines.append(f"{pad}return NIL")
|
||||
self._emit_nil_return(lines, indent)
|
||||
|
||||
def _emit_case_return(self, expr, lines: list, indent: int) -> None:
|
||||
"""Emit case as if/elif/else with returns in each branch."""
|
||||
@@ -940,7 +1118,7 @@ class PyEmitter:
|
||||
self._emit_return_expr(body, lines, indent + 1)
|
||||
i += 2
|
||||
if not has_else:
|
||||
lines.append(f"{pad}return NIL")
|
||||
self._emit_nil_return(lines, indent)
|
||||
|
||||
def _emit_let_as_stmts(self, expr, lines: list, indent: int, is_last: bool) -> None:
|
||||
"""Emit a let expression as local variable declarations."""
|
||||
|
||||
Reference in New Issue
Block a user