Bootstrap CEK as default evaluator on both JS and Python sides
SPEC_MODULES + SPEC_MODULE_ORDER for frames/cek in platform_js.py, PLATFORM_CEK_JS + CEK_FIXUPS_JS constants, auto-inclusion in run_js_sx.py, 70+ RENAMES in js.sx. Python: CEK always-include in bootstrap_py.py, eval_expr/trampoline overridden to cek_run in platform_py.py with _tree_walk_* preserved for test runners. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -659,51 +659,6 @@ def escape_string(s):
|
||||
.replace("</script", "<\\\\/script"))
|
||||
|
||||
|
||||
def serialize(val):
|
||||
"""Serialize an SX value to SX source text.
|
||||
|
||||
Note: parser.sx defines sx-serialize with a serialize alias, but parser.sx
|
||||
is only included in JS builds (for client-side parsing). Python builds
|
||||
provide this as a platform function.
|
||||
"""
|
||||
t = type_of(val)
|
||||
if t == "sx-expr":
|
||||
return val.source
|
||||
if t == "nil":
|
||||
return "nil"
|
||||
if t == "boolean":
|
||||
return "true" if val else "false"
|
||||
if t == "number":
|
||||
return str(val)
|
||||
if t == "string":
|
||||
return '"' + escape_string(val) + '"'
|
||||
if t == "symbol":
|
||||
return symbol_name(val)
|
||||
if t == "keyword":
|
||||
return ":" + keyword_name(val)
|
||||
if t == "raw-html":
|
||||
escaped = escape_string(raw_html_content(val))
|
||||
return '(raw! "' + escaped + '")'
|
||||
if t == "list":
|
||||
if not val:
|
||||
return "()"
|
||||
items = [serialize(x) for x in val]
|
||||
return "(" + " ".join(items) + ")"
|
||||
if t == "dict":
|
||||
items = []
|
||||
for k, v in val.items():
|
||||
items.append(":" + str(k))
|
||||
items.append(serialize(v))
|
||||
return "{" + " ".join(items) + "}"
|
||||
if callable(val):
|
||||
return "nil"
|
||||
return str(val)
|
||||
|
||||
# Aliases for transpiled code — parser.sx defines sx-serialize/sx-serialize-dict
|
||||
# but parser.sx is JS-only. Provide aliases so transpiled render.sx works.
|
||||
sx_serialize = serialize
|
||||
sx_serialize_dict = lambda d: serialize(d)
|
||||
|
||||
_SPECIAL_FORM_NAMES = frozenset() # Placeholder — overridden by transpiled adapter-sx.sx
|
||||
_HO_FORM_NAMES = frozenset()
|
||||
|
||||
@@ -1066,6 +1021,55 @@ has_key_p = PRIMITIVES["has-key?"]
|
||||
dict_p = PRIMITIVES["dict?"]
|
||||
dissoc = PRIMITIVES["dissoc"]
|
||||
index_of = PRIMITIVES["index-of"]
|
||||
lower = PRIMITIVES["lower"]
|
||||
char_from_code = PRIMITIVES["char-from-code"]
|
||||
'''
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Platform: parser module — character classification, number parsing,
|
||||
# reader macro registry
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
PLATFORM_PARSER_PY = '''
|
||||
# =========================================================================
|
||||
# Platform interface — Parser
|
||||
# =========================================================================
|
||||
|
||||
import re as _re_parser
|
||||
|
||||
_IDENT_START_RE = _re_parser.compile(r"[a-zA-Z_~*+\\-><=/!?&]")
|
||||
_IDENT_CHAR_RE = _re_parser.compile(r"[a-zA-Z0-9_~*+\\-><=/!?.:&/\\[\\]#,]")
|
||||
|
||||
|
||||
def ident_start_p(ch):
|
||||
return bool(_IDENT_START_RE.match(ch))
|
||||
|
||||
|
||||
def ident_char_p(ch):
|
||||
return bool(_IDENT_CHAR_RE.match(ch))
|
||||
|
||||
|
||||
def parse_number(s):
|
||||
"""Parse a numeric string to int or float."""
|
||||
try:
|
||||
if "." in s or "e" in s or "E" in s:
|
||||
return float(s)
|
||||
return int(s)
|
||||
except (ValueError, TypeError):
|
||||
return float(s)
|
||||
|
||||
|
||||
# Reader macro registry
|
||||
_reader_macros = {}
|
||||
|
||||
|
||||
def reader_macro_get(name):
|
||||
return _reader_macros.get(name, NIL)
|
||||
|
||||
|
||||
def reader_macro_set_b(name, handler):
|
||||
_reader_macros[name] = handler
|
||||
return NIL
|
||||
'''
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1159,6 +1163,23 @@ def cek_run(state):
|
||||
while not cek_terminal_p(state):
|
||||
state = cek_step(state)
|
||||
return cek_value(state)
|
||||
|
||||
# CEK is the canonical evaluator — override eval_expr to use it.
|
||||
# The tree-walk evaluator (eval_expr from eval.sx) is superseded.
|
||||
_tree_walk_eval_expr = eval_expr
|
||||
|
||||
def eval_expr(expr, env):
|
||||
"""Evaluate expr using the CEK machine."""
|
||||
return cek_run(make_cek_state(expr, env, []))
|
||||
|
||||
# CEK never produces thunks — trampoline becomes identity
|
||||
_tree_walk_trampoline = trampoline
|
||||
|
||||
def trampoline(val):
|
||||
"""In CEK mode, values are immediate — resolve any legacy thunks."""
|
||||
if is_thunk(val):
|
||||
return eval_expr(thunk_expr(val), thunk_env(val))
|
||||
return val
|
||||
'''
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1258,11 +1279,6 @@ def number_p(x):
|
||||
return isinstance(x, (int, float)) and not isinstance(x, bool)
|
||||
|
||||
|
||||
def sx_parse(src):
|
||||
from shared.sx.parser import parse_all
|
||||
return parse_all(src)
|
||||
|
||||
|
||||
def is_async_coroutine(x):
|
||||
return _inspect.iscoroutine(x)
|
||||
|
||||
@@ -1590,9 +1606,10 @@ def public_api_py(has_html: bool, has_sx: bool, has_deps: bool = False,
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
ADAPTER_FILES = {
|
||||
"html": ("adapter-html.sx", "adapter-html"),
|
||||
"sx": ("adapter-sx.sx", "adapter-sx"),
|
||||
"async": ("adapter-async.sx", "adapter-async"),
|
||||
"parser": ("parser.sx", "parser"),
|
||||
"html": ("adapter-html.sx", "adapter-html"),
|
||||
"sx": ("adapter-sx.sx", "adapter-sx"),
|
||||
"async": ("adapter-async.sx", "adapter-async"),
|
||||
}
|
||||
|
||||
SPEC_MODULES = {
|
||||
|
||||
Reference in New Issue
Block a user