- Both bootstrappers (JS + Python) now gate shift/reset behind --extensions continuations flag. Without it, using reset/shift errors at runtime. - JS bootstrapper: extracted Continuation/ShiftSignal types, sfReset/sfShift, continuation? primitive, and typeOf handling into CONTINUATIONS_JS constant. Extension wraps evalList, aserSpecial, and typeOf post-transpilation. - Python bootstrapper: added special-forms.sx validation cross-check against eval.sx dispatch, warns on mismatches. - Added shared/sx/ref/special-forms.sx: 36 declarative form specs with syntax, docs, tail-position, and examples. Used by bootstrappers for validation. - Added ellipsis (...) support to both parser.py and parser.sx spec. - Updated continuations essay to reflect optional extension architecture. - Updated specs page and nav with special-forms.sx entry. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1238 lines
64 KiB
Python
1238 lines
64 KiB
Python
"""
|
|
sx_ref.py -- Generated from reference SX evaluator specification.
|
|
|
|
Bootstrap-compiled from shared/sx/ref/{eval,render,adapter-html,adapter-sx}.sx
|
|
Compare against hand-written evaluator.py / html.py for correctness verification.
|
|
|
|
DO NOT EDIT -- regenerate with: python bootstrap_py.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from typing import Any
|
|
|
|
|
|
# =========================================================================
|
|
# Types (reuse existing types)
|
|
# =========================================================================
|
|
|
|
from shared.sx.types import (
|
|
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro, StyleValue,
|
|
HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
|
|
)
|
|
from shared.sx.parser import SxExpr
|
|
|
|
|
|
# =========================================================================
|
|
# Platform interface -- Python implementation
|
|
# =========================================================================
|
|
|
|
class _Thunk:
|
|
"""Deferred evaluation for TCO."""
|
|
__slots__ = ("expr", "env")
|
|
def __init__(self, expr, env):
|
|
self.expr = expr
|
|
self.env = env
|
|
|
|
|
|
class _RawHTML:
|
|
"""Marker for pre-rendered HTML that should not be escaped."""
|
|
__slots__ = ("html",)
|
|
def __init__(self, html: str):
|
|
self.html = html
|
|
|
|
|
|
def sx_truthy(x):
|
|
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
|
if x is False:
|
|
return False
|
|
if x is None or x is NIL:
|
|
return False
|
|
return True
|
|
|
|
|
|
def sx_str(*args):
|
|
"""SX str: concatenate string representations, skipping nil."""
|
|
parts = []
|
|
for a in args:
|
|
if a is None or a is NIL:
|
|
continue
|
|
parts.append(str(a))
|
|
return "".join(parts)
|
|
|
|
|
|
def sx_and(*args):
|
|
"""SX and: return last truthy value or first falsy."""
|
|
result = True
|
|
for a in args:
|
|
if not sx_truthy(a):
|
|
return a
|
|
result = a
|
|
return result
|
|
|
|
|
|
def sx_or(*args):
|
|
"""SX or: return first truthy value or last value."""
|
|
for a in args:
|
|
if sx_truthy(a):
|
|
return a
|
|
return args[-1] if args else False
|
|
|
|
|
|
def _sx_begin(*args):
|
|
"""Evaluate all args (for side effects), return last."""
|
|
return args[-1] if args else NIL
|
|
|
|
|
|
|
|
def _sx_case(match_val, pairs):
|
|
"""Case dispatch: pairs is [(test_val, body_fn), ...]. None test = else."""
|
|
for test, body_fn in pairs:
|
|
if test is None: # :else clause
|
|
return body_fn()
|
|
if match_val == test:
|
|
return body_fn()
|
|
return NIL
|
|
|
|
|
|
def _sx_fn(f):
|
|
"""Identity wrapper for multi-expression lambda bodies."""
|
|
return f
|
|
|
|
|
|
def type_of(x):
|
|
if x is None or x is NIL:
|
|
return "nil"
|
|
if isinstance(x, bool):
|
|
return "boolean"
|
|
if isinstance(x, (int, float)):
|
|
return "number"
|
|
if isinstance(x, SxExpr):
|
|
return "sx-expr"
|
|
if isinstance(x, str):
|
|
return "string"
|
|
if isinstance(x, Symbol):
|
|
return "symbol"
|
|
if isinstance(x, Keyword):
|
|
return "keyword"
|
|
if isinstance(x, _Thunk):
|
|
return "thunk"
|
|
if isinstance(x, Lambda):
|
|
return "lambda"
|
|
if isinstance(x, Component):
|
|
return "component"
|
|
if isinstance(x, Macro):
|
|
return "macro"
|
|
if isinstance(x, _RawHTML):
|
|
return "raw-html"
|
|
if isinstance(x, StyleValue):
|
|
return "style-value"
|
|
if isinstance(x, Continuation):
|
|
return "continuation"
|
|
if isinstance(x, list):
|
|
return "list"
|
|
if isinstance(x, dict):
|
|
return "dict"
|
|
return "unknown"
|
|
|
|
|
|
def symbol_name(s):
|
|
return s.name
|
|
|
|
|
|
def keyword_name(k):
|
|
return k.name
|
|
|
|
|
|
def make_symbol(n):
|
|
return Symbol(n)
|
|
|
|
|
|
def make_keyword(n):
|
|
return Keyword(n)
|
|
|
|
|
|
def make_lambda(params, body, env):
|
|
return Lambda(params=list(params), body=body, closure=dict(env))
|
|
|
|
|
|
def make_component(name, params, has_children, body, env):
|
|
return Component(name=name, params=list(params), has_children=has_children,
|
|
body=body, closure=dict(env))
|
|
|
|
|
|
def make_macro(params, rest_param, body, env, name=None):
|
|
return Macro(params=list(params), rest_param=rest_param, body=body,
|
|
closure=dict(env), name=name)
|
|
|
|
|
|
def make_handler_def(name, params, body, env):
|
|
return HandlerDef(name=name, params=list(params), body=body, closure=dict(env))
|
|
|
|
|
|
def make_query_def(name, params, doc, body, env):
|
|
return QueryDef(name=name, params=list(params), doc=doc, body=body, closure=dict(env))
|
|
|
|
|
|
def make_action_def(name, params, doc, body, env):
|
|
return ActionDef(name=name, params=list(params), doc=doc, body=body, closure=dict(env))
|
|
|
|
|
|
def make_page_def(name, slots, env):
|
|
path = slots.get("path", "")
|
|
auth_val = slots.get("auth", "public")
|
|
if isinstance(auth_val, Keyword):
|
|
auth = auth_val.name
|
|
elif isinstance(auth_val, list):
|
|
auth = [item.name if isinstance(item, Keyword) else str(item) for item in auth_val]
|
|
else:
|
|
auth = str(auth_val) if auth_val else "public"
|
|
layout = slots.get("layout")
|
|
if isinstance(layout, Keyword):
|
|
layout = layout.name
|
|
cache = None
|
|
return PageDef(
|
|
name=name, path=path, auth=auth, layout=layout, cache=cache,
|
|
data_expr=slots.get("data"), content_expr=slots.get("content"),
|
|
filter_expr=slots.get("filter"), aside_expr=slots.get("aside"),
|
|
menu_expr=slots.get("menu"), closure=dict(env),
|
|
)
|
|
|
|
|
|
def make_thunk(expr, env):
|
|
return _Thunk(expr, env)
|
|
|
|
|
|
def lambda_params(f):
|
|
return f.params
|
|
|
|
|
|
def lambda_body(f):
|
|
return f.body
|
|
|
|
|
|
def lambda_closure(f):
|
|
return f.closure
|
|
|
|
|
|
def lambda_name(f):
|
|
return f.name
|
|
|
|
|
|
def set_lambda_name(f, n):
|
|
f.name = n
|
|
|
|
|
|
def component_params(c):
|
|
return c.params
|
|
|
|
|
|
def component_body(c):
|
|
return c.body
|
|
|
|
|
|
def component_closure(c):
|
|
return c.closure
|
|
|
|
|
|
def component_has_children(c):
|
|
return c.has_children
|
|
|
|
|
|
def component_name(c):
|
|
return c.name
|
|
|
|
|
|
def macro_params(m):
|
|
return m.params
|
|
|
|
|
|
def macro_rest_param(m):
|
|
return m.rest_param
|
|
|
|
|
|
def macro_body(m):
|
|
return m.body
|
|
|
|
|
|
def macro_closure(m):
|
|
return m.closure
|
|
|
|
|
|
def is_thunk(x):
|
|
return isinstance(x, _Thunk)
|
|
|
|
|
|
def thunk_expr(t):
|
|
return t.expr
|
|
|
|
|
|
def thunk_env(t):
|
|
return t.env
|
|
|
|
|
|
def is_callable(x):
|
|
return callable(x) or isinstance(x, Lambda)
|
|
|
|
|
|
def is_lambda(x):
|
|
return isinstance(x, Lambda)
|
|
|
|
|
|
def is_component(x):
|
|
return isinstance(x, Component)
|
|
|
|
|
|
def is_macro(x):
|
|
return isinstance(x, Macro)
|
|
|
|
|
|
def is_style_value(x):
|
|
return isinstance(x, StyleValue)
|
|
|
|
|
|
def style_value_class(x):
|
|
return x.class_name
|
|
|
|
|
|
def env_has(env, name):
|
|
return name in env
|
|
|
|
|
|
def env_get(env, name):
|
|
return env.get(name, NIL)
|
|
|
|
|
|
def env_set(env, name, val):
|
|
env[name] = val
|
|
|
|
|
|
def env_extend(env):
|
|
return dict(env)
|
|
|
|
|
|
def env_merge(base, overlay):
|
|
result = dict(base)
|
|
result.update(overlay)
|
|
return result
|
|
|
|
|
|
def dict_set(d, k, v):
|
|
d[k] = v
|
|
|
|
|
|
def dict_get(d, k):
|
|
v = d.get(k)
|
|
return v if v is not None else NIL
|
|
|
|
|
|
def dict_has(d, k):
|
|
return k in d
|
|
|
|
|
|
def dict_delete(d, k):
|
|
d.pop(k, None)
|
|
|
|
|
|
def is_render_expr(expr):
|
|
"""Check if expression is an HTML element, component, or fragment."""
|
|
if not isinstance(expr, list) or not expr:
|
|
return False
|
|
h = expr[0]
|
|
if not isinstance(h, Symbol):
|
|
return False
|
|
n = h.name
|
|
return (n == "<>" or n == "raw!" or
|
|
n.startswith("~") or n.startswith("html:") or
|
|
n in HTML_TAGS or
|
|
("-" in n and len(expr) > 1 and isinstance(expr[1], Keyword)))
|
|
|
|
|
|
# Render dispatch -- set by adapter
|
|
_render_expr_fn = None
|
|
|
|
|
|
def render_expr(expr, env):
|
|
if _render_expr_fn:
|
|
return _render_expr_fn(expr, env)
|
|
return expr
|
|
|
|
|
|
def strip_prefix(s, prefix):
|
|
return s[len(prefix):] if s.startswith(prefix) else s
|
|
|
|
|
|
def error(msg):
|
|
raise EvalError(msg)
|
|
|
|
|
|
def inspect(x):
|
|
return repr(x)
|
|
|
|
|
|
def escape_html(s):
|
|
s = str(s)
|
|
return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
|
|
|
|
|
def escape_attr(s):
|
|
return escape_html(s)
|
|
|
|
|
|
def raw_html_content(x):
|
|
return x.html
|
|
|
|
|
|
def make_raw_html(s):
|
|
return _RawHTML(s)
|
|
|
|
|
|
class EvalError(Exception):
|
|
pass
|
|
|
|
|
|
def _sx_append(lst, item):
|
|
"""Append item to list, return the item (for expression context)."""
|
|
lst.append(item)
|
|
return item
|
|
|
|
|
|
def _sx_dict_set(d, k, v):
|
|
"""Set key in dict, return the value (for expression context)."""
|
|
d[k] = v
|
|
return v
|
|
|
|
|
|
def _sx_set_attr(obj, attr, val):
|
|
"""Set attribute on object, return the value."""
|
|
setattr(obj, attr, val)
|
|
return val
|
|
|
|
|
|
def _sx_cell_set(cells, name, val):
|
|
"""Set a mutable cell value. Returns the value."""
|
|
cells[name] = val
|
|
return val
|
|
|
|
|
|
def escape_string(s):
|
|
"""Escape a string for SX serialization."""
|
|
return (str(s)
|
|
.replace("\\", "\\\\")
|
|
.replace('"', '\\"')
|
|
.replace("\n", "\\n")
|
|
.replace("\t", "\\t")
|
|
.replace("</script", "<\\/script"))
|
|
|
|
|
|
def serialize(val):
|
|
"""Serialize an SX value to SX source text."""
|
|
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 == "style-value":
|
|
return '"' + style_value_class(val) + '"'
|
|
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)
|
|
|
|
|
|
_SPECIAL_FORM_NAMES = frozenset([
|
|
"if", "when", "cond", "case", "and", "or",
|
|
"let", "let*", "lambda", "fn",
|
|
"define", "defcomp", "defmacro", "defstyle", "defkeyframes",
|
|
"defhandler", "defpage", "defquery", "defaction", "defrelation",
|
|
"begin", "do", "quote", "quasiquote",
|
|
"->", "set!",
|
|
])
|
|
|
|
_HO_FORM_NAMES = frozenset([
|
|
"map", "map-indexed", "filter", "reduce",
|
|
"some", "every?", "for-each",
|
|
])
|
|
|
|
def is_special_form(name):
|
|
return name in _SPECIAL_FORM_NAMES
|
|
|
|
def is_ho_form(name):
|
|
return name in _HO_FORM_NAMES
|
|
|
|
|
|
def aser_special(name, expr, env):
|
|
"""Evaluate a special/HO form in aser mode.
|
|
|
|
Control flow forms evaluate conditions normally but render branches
|
|
through aser (serializing tags/components instead of rendering HTML).
|
|
Definition forms evaluate for side effects and return nil.
|
|
"""
|
|
# Control flow — evaluate conditions, aser branches
|
|
args = expr[1:]
|
|
if name == "if":
|
|
cond_val = trampoline(eval_expr(args[0], env))
|
|
if sx_truthy(cond_val):
|
|
return aser(args[1], env)
|
|
return aser(args[2], env) if _b_len(args) > 2 else NIL
|
|
if name == "when":
|
|
cond_val = trampoline(eval_expr(args[0], env))
|
|
if sx_truthy(cond_val):
|
|
result = NIL
|
|
for body in args[1:]:
|
|
result = aser(body, env)
|
|
return result
|
|
return NIL
|
|
if name == "cond":
|
|
clauses = args
|
|
if clauses and isinstance(clauses[0], _b_list) and _b_len(clauses[0]) == 2:
|
|
for clause in clauses:
|
|
test = clause[0]
|
|
if isinstance(test, Symbol) and test.name in ("else", ":else"):
|
|
return aser(clause[1], env)
|
|
if isinstance(test, Keyword) and test.name == "else":
|
|
return aser(clause[1], env)
|
|
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
return aser(clause[1], env)
|
|
else:
|
|
i = 0
|
|
while i < _b_len(clauses) - 1:
|
|
test = clauses[i]
|
|
result = clauses[i + 1]
|
|
if isinstance(test, Keyword) and test.name == "else":
|
|
return aser(result, env)
|
|
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
|
return aser(result, env)
|
|
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
return aser(result, env)
|
|
i += 2
|
|
return NIL
|
|
if name == "case":
|
|
match_val = trampoline(eval_expr(args[0], env))
|
|
clauses = args[1:]
|
|
i = 0
|
|
while i < _b_len(clauses) - 1:
|
|
test = clauses[i]
|
|
result = clauses[i + 1]
|
|
if isinstance(test, Keyword) and test.name == "else":
|
|
return aser(result, env)
|
|
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
|
return aser(result, env)
|
|
if match_val == trampoline(eval_expr(test, env)):
|
|
return aser(result, env)
|
|
i += 2
|
|
return NIL
|
|
if name in ("let", "let*"):
|
|
bindings = args[0]
|
|
local = _b_dict(env)
|
|
if isinstance(bindings, _b_list):
|
|
if bindings and isinstance(bindings[0], _b_list):
|
|
for b in bindings:
|
|
var = b[0]
|
|
vname = var.name if isinstance(var, Symbol) else var
|
|
local[vname] = trampoline(eval_expr(b[1], local))
|
|
else:
|
|
for i in _b_range(0, _b_len(bindings), 2):
|
|
var = bindings[i]
|
|
vname = var.name if isinstance(var, Symbol) else var
|
|
local[vname] = trampoline(eval_expr(bindings[i + 1], local))
|
|
result = NIL
|
|
for body in args[1:]:
|
|
result = aser(body, local)
|
|
return result
|
|
if name in ("begin", "do"):
|
|
result = NIL
|
|
for body in args:
|
|
result = aser(body, env)
|
|
return result
|
|
if name == "and":
|
|
result = True
|
|
for arg in args:
|
|
result = trampoline(eval_expr(arg, env))
|
|
if not sx_truthy(result):
|
|
return result
|
|
return result
|
|
if name == "or":
|
|
result = False
|
|
for arg in args:
|
|
result = trampoline(eval_expr(arg, env))
|
|
if sx_truthy(result):
|
|
return result
|
|
return result
|
|
# HO forms in aser mode — map/for-each render through aser
|
|
if name == "map":
|
|
fn = trampoline(eval_expr(args[0], env))
|
|
coll = trampoline(eval_expr(args[1], env))
|
|
results = []
|
|
for item in coll:
|
|
if isinstance(fn, Lambda):
|
|
local = _b_dict(fn.closure)
|
|
local.update(env)
|
|
local[fn.params[0]] = item
|
|
results.append(aser(fn.body, local))
|
|
elif callable(fn):
|
|
results.append(fn(item))
|
|
else:
|
|
raise EvalError("map requires callable")
|
|
return results
|
|
if name == "map-indexed":
|
|
fn = trampoline(eval_expr(args[0], env))
|
|
coll = trampoline(eval_expr(args[1], env))
|
|
results = []
|
|
for i, item in enumerate(coll):
|
|
if isinstance(fn, Lambda):
|
|
local = _b_dict(fn.closure)
|
|
local.update(env)
|
|
local[fn.params[0]] = i
|
|
local[fn.params[1]] = item
|
|
results.append(aser(fn.body, local))
|
|
elif callable(fn):
|
|
results.append(fn(i, item))
|
|
else:
|
|
raise EvalError("map-indexed requires callable")
|
|
return results
|
|
if name == "for-each":
|
|
fn = trampoline(eval_expr(args[0], env))
|
|
coll = trampoline(eval_expr(args[1], env))
|
|
results = []
|
|
for item in coll:
|
|
if isinstance(fn, Lambda):
|
|
local = _b_dict(fn.closure)
|
|
local.update(env)
|
|
local[fn.params[0]] = item
|
|
results.append(aser(fn.body, local))
|
|
elif callable(fn):
|
|
fn(item)
|
|
return results if results else NIL
|
|
# Definition forms — evaluate for side effects
|
|
if name in ("define", "defcomp", "defmacro", "defstyle", "defkeyframes",
|
|
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
|
|
trampoline(eval_expr(expr, env))
|
|
return NIL
|
|
# Lambda/fn, quote, quasiquote, set!, -> : evaluate normally
|
|
result = eval_expr(expr, env)
|
|
return trampoline(result)
|
|
|
|
|
|
# =========================================================================
|
|
# Primitives
|
|
# =========================================================================
|
|
|
|
# Save builtins before shadowing
|
|
_b_len = len
|
|
_b_map = map
|
|
_b_filter = filter
|
|
_b_range = range
|
|
_b_list = list
|
|
_b_dict = dict
|
|
_b_max = max
|
|
_b_min = min
|
|
_b_round = round
|
|
_b_abs = abs
|
|
_b_sum = sum
|
|
_b_zip = zip
|
|
_b_int = int
|
|
|
|
PRIMITIVES = {}
|
|
|
|
# Arithmetic
|
|
PRIMITIVES["+"] = lambda *args: _b_sum(args)
|
|
PRIMITIVES["-"] = lambda a, b=None: -a if b is None else a - b
|
|
PRIMITIVES["*"] = lambda *args: _sx_mul(*args)
|
|
PRIMITIVES["/"] = lambda a, b: a / b
|
|
PRIMITIVES["mod"] = lambda a, b: a % b
|
|
PRIMITIVES["inc"] = lambda n: n + 1
|
|
PRIMITIVES["dec"] = lambda n: n - 1
|
|
PRIMITIVES["abs"] = _b_abs
|
|
PRIMITIVES["floor"] = math.floor
|
|
PRIMITIVES["ceil"] = math.ceil
|
|
PRIMITIVES["round"] = _b_round
|
|
PRIMITIVES["min"] = _b_min
|
|
PRIMITIVES["max"] = _b_max
|
|
PRIMITIVES["sqrt"] = math.sqrt
|
|
PRIMITIVES["pow"] = lambda x, n: x ** n
|
|
PRIMITIVES["clamp"] = lambda x, lo, hi: _b_max(lo, _b_min(hi, x))
|
|
|
|
def _sx_mul(*args):
|
|
r = 1
|
|
for a in args:
|
|
r *= a
|
|
return r
|
|
|
|
# Comparison
|
|
PRIMITIVES["="] = lambda a, b: a == b
|
|
PRIMITIVES["!="] = lambda a, b: a != b
|
|
PRIMITIVES["<"] = lambda a, b: a < b
|
|
PRIMITIVES[">"] = lambda a, b: a > b
|
|
PRIMITIVES["<="] = lambda a, b: a <= b
|
|
PRIMITIVES[">="] = lambda a, b: a >= b
|
|
|
|
# Logic
|
|
PRIMITIVES["not"] = lambda x: not sx_truthy(x)
|
|
|
|
# String
|
|
PRIMITIVES["str"] = sx_str
|
|
PRIMITIVES["upper"] = lambda s: str(s).upper()
|
|
PRIMITIVES["lower"] = lambda s: str(s).lower()
|
|
PRIMITIVES["trim"] = lambda s: str(s).strip()
|
|
PRIMITIVES["split"] = lambda s, sep=" ": str(s).split(sep)
|
|
PRIMITIVES["join"] = lambda sep, coll: sep.join(coll)
|
|
PRIMITIVES["replace"] = lambda s, old, new: s.replace(old, new)
|
|
PRIMITIVES["starts-with?"] = lambda s, p: str(s).startswith(p)
|
|
PRIMITIVES["ends-with?"] = lambda s, p: str(s).endswith(p)
|
|
PRIMITIVES["slice"] = lambda c, a, b=None: c[a:b] if b is not None else c[a:]
|
|
PRIMITIVES["concat"] = lambda *args: _b_sum((a for a in args if a), [])
|
|
PRIMITIVES["strip-tags"] = lambda s: _strip_tags(str(s))
|
|
|
|
import re as _re
|
|
def _strip_tags(s):
|
|
return _re.sub(r"<[^>]+>", "", s)
|
|
|
|
# Predicates
|
|
PRIMITIVES["nil?"] = lambda x: x is None or x is NIL
|
|
PRIMITIVES["number?"] = lambda x: isinstance(x, (int, float)) and not isinstance(x, bool)
|
|
PRIMITIVES["string?"] = lambda x: isinstance(x, str)
|
|
PRIMITIVES["list?"] = lambda x: isinstance(x, _b_list)
|
|
PRIMITIVES["dict?"] = lambda x: isinstance(x, _b_dict)
|
|
PRIMITIVES["continuation?"] = lambda x: isinstance(x, Continuation)
|
|
PRIMITIVES["empty?"] = lambda c: (
|
|
c is None or c is NIL or
|
|
(isinstance(c, (_b_list, str, _b_dict)) and _b_len(c) == 0)
|
|
)
|
|
PRIMITIVES["contains?"] = lambda c, k: (
|
|
str(k) in c if isinstance(c, str) else
|
|
k in c
|
|
)
|
|
PRIMITIVES["odd?"] = lambda n: n % 2 != 0
|
|
PRIMITIVES["even?"] = lambda n: n % 2 == 0
|
|
PRIMITIVES["zero?"] = lambda n: n == 0
|
|
|
|
# Collections
|
|
PRIMITIVES["list"] = lambda *args: _b_list(args)
|
|
PRIMITIVES["dict"] = lambda *args: {args[i]: args[i+1] for i in _b_range(0, _b_len(args)-1, 2)}
|
|
PRIMITIVES["range"] = lambda a, b, step=1: _b_list(_b_range(_b_int(a), _b_int(b), _b_int(step)))
|
|
PRIMITIVES["get"] = lambda c, k, default=NIL: c.get(k, default) if isinstance(c, _b_dict) else (c[k] if isinstance(c, (_b_list, str)) and isinstance(k, _b_int) and 0 <= k < _b_len(c) else default)
|
|
PRIMITIVES["len"] = lambda c: _b_len(c) if c is not None and c is not NIL else 0
|
|
PRIMITIVES["first"] = lambda c: c[0] if c and _b_len(c) > 0 else NIL
|
|
PRIMITIVES["last"] = lambda c: c[-1] if c and _b_len(c) > 0 else NIL
|
|
PRIMITIVES["rest"] = lambda c: c[1:] if c else []
|
|
PRIMITIVES["nth"] = lambda c, n: c[n] if c and 0 <= n < _b_len(c) else NIL
|
|
PRIMITIVES["cons"] = lambda x, c: [x] + (c or [])
|
|
PRIMITIVES["append"] = lambda c, x: (c or []) + [x]
|
|
PRIMITIVES["keys"] = lambda d: _b_list((d or {}).keys())
|
|
PRIMITIVES["vals"] = lambda d: _b_list((d or {}).values())
|
|
PRIMITIVES["merge"] = lambda *args: _sx_merge_dicts(*args)
|
|
PRIMITIVES["assoc"] = lambda d, *kvs: _sx_assoc(d, *kvs)
|
|
PRIMITIVES["dissoc"] = lambda d, *ks: {k: v for k, v in d.items() if k not in ks}
|
|
PRIMITIVES["chunk-every"] = lambda c, n: [c[i:i+n] for i in _b_range(0, _b_len(c), n)]
|
|
PRIMITIVES["zip-pairs"] = lambda c: [[c[i], c[i+1]] for i in _b_range(_b_len(c)-1)]
|
|
PRIMITIVES["into"] = lambda target, coll: (_b_list(coll) if isinstance(target, _b_list) else {p[0]: p[1] for p in coll if isinstance(p, _b_list) and _b_len(p) >= 2})
|
|
PRIMITIVES["zip"] = lambda *colls: [_b_list(t) for t in _b_zip(*colls)]
|
|
|
|
# Format
|
|
PRIMITIVES["format-decimal"] = lambda v, p=2: f"{float(v):.{p}f}"
|
|
PRIMITIVES["parse-int"] = lambda v, d=0: _sx_parse_int(v, d)
|
|
PRIMITIVES["pluralize"] = lambda n, s="", p="s": s if n == 1 else p
|
|
PRIMITIVES["escape"] = escape_html
|
|
|
|
def _sx_merge_dicts(*args):
|
|
out = {}
|
|
for d in args:
|
|
if d and d is not NIL and isinstance(d, _b_dict):
|
|
out.update(d)
|
|
return out
|
|
|
|
def _sx_assoc(d, *kvs):
|
|
out = _b_dict(d) if d and d is not NIL else {}
|
|
for i in _b_range(0, _b_len(kvs) - 1, 2):
|
|
out[kvs[i]] = kvs[i + 1]
|
|
return out
|
|
|
|
def _sx_parse_int(v, default=0):
|
|
try:
|
|
return _b_int(v)
|
|
except (ValueError, TypeError):
|
|
return default
|
|
|
|
def is_primitive(name):
|
|
if name in PRIMITIVES:
|
|
return True
|
|
from shared.sx.primitives import get_primitive as _ext_get
|
|
return _ext_get(name) is not None
|
|
|
|
def get_primitive(name):
|
|
p = PRIMITIVES.get(name)
|
|
if p is not None:
|
|
return p
|
|
from shared.sx.primitives import get_primitive as _ext_get
|
|
return _ext_get(name)
|
|
|
|
# Higher-order helpers used by transpiled code
|
|
def map(fn, coll):
|
|
return [fn(x) for x in coll]
|
|
|
|
def map_indexed(fn, coll):
|
|
return [fn(i, item) for i, item in enumerate(coll)]
|
|
|
|
def filter(fn, coll):
|
|
return [x for x in coll if sx_truthy(fn(x))]
|
|
|
|
def reduce(fn, init, coll):
|
|
acc = init
|
|
for item in coll:
|
|
acc = fn(acc, item)
|
|
return acc
|
|
|
|
def some(fn, coll):
|
|
for item in coll:
|
|
r = fn(item)
|
|
if sx_truthy(r):
|
|
return r
|
|
return NIL
|
|
|
|
def every_p(fn, coll):
|
|
for item in coll:
|
|
if not sx_truthy(fn(item)):
|
|
return False
|
|
return True
|
|
|
|
def for_each(fn, coll):
|
|
for item in coll:
|
|
fn(item)
|
|
return NIL
|
|
|
|
def for_each_indexed(fn, coll):
|
|
for i, item in enumerate(coll):
|
|
fn(i, item)
|
|
return NIL
|
|
|
|
def map_dict(fn, d):
|
|
return {k: fn(k, v) for k, v in d.items()}
|
|
|
|
# Aliases used directly by transpiled code
|
|
first = PRIMITIVES["first"]
|
|
last = PRIMITIVES["last"]
|
|
rest = PRIMITIVES["rest"]
|
|
nth = PRIMITIVES["nth"]
|
|
len = PRIMITIVES["len"]
|
|
is_nil = PRIMITIVES["nil?"]
|
|
empty_p = PRIMITIVES["empty?"]
|
|
contains_p = PRIMITIVES["contains?"]
|
|
starts_with_p = PRIMITIVES["starts-with?"]
|
|
ends_with_p = PRIMITIVES["ends-with?"]
|
|
slice = PRIMITIVES["slice"]
|
|
get = PRIMITIVES["get"]
|
|
append = PRIMITIVES["append"]
|
|
cons = PRIMITIVES["cons"]
|
|
keys = PRIMITIVES["keys"]
|
|
join = PRIMITIVES["join"]
|
|
range = PRIMITIVES["range"]
|
|
apply = lambda f, args: f(*args)
|
|
assoc = PRIMITIVES["assoc"]
|
|
concat = PRIMITIVES["concat"]
|
|
|
|
|
|
# === Transpiled from eval ===
|
|
|
|
# trampoline
|
|
trampoline = lambda val: (lambda result: (trampoline(eval_expr(thunk_expr(result), thunk_env(result))) if sx_truthy(is_thunk(result)) else result))(val)
|
|
|
|
# eval-expr
|
|
eval_expr = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('dict', lambda: map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else eval_list(expr, env))), (None, lambda: expr)])
|
|
|
|
# eval-list
|
|
eval_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: trampoline(eval_expr(x, env)), expr) if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))) else ((lambda name: (sf_if(args, env) if sx_truthy((name == 'if')) else (sf_when(args, env) if sx_truthy((name == 'when')) else (sf_cond(args, env) if sx_truthy((name == 'cond')) else (sf_case(args, env) if sx_truthy((name == 'case')) else (sf_and(args, env) if sx_truthy((name == 'and')) else (sf_or(args, env) if sx_truthy((name == 'or')) else (sf_let(args, env) if sx_truthy((name == 'let')) else (sf_let(args, env) if sx_truthy((name == 'let*')) else (sf_letrec(args, env) if sx_truthy((name == 'letrec')) else (sf_lambda(args, env) if sx_truthy((name == 'lambda')) else (sf_lambda(args, env) if sx_truthy((name == 'fn')) else (sf_define(args, env) if sx_truthy((name == 'define')) else (sf_defcomp(args, env) if sx_truthy((name == 'defcomp')) else (sf_defmacro(args, env) if sx_truthy((name == 'defmacro')) else (sf_defstyle(args, env) if sx_truthy((name == 'defstyle')) else (sf_defkeyframes(args, env) if sx_truthy((name == 'defkeyframes')) else (sf_defhandler(args, env) if sx_truthy((name == 'defhandler')) else (sf_defpage(args, env) if sx_truthy((name == 'defpage')) else (sf_defquery(args, env) if sx_truthy((name == 'defquery')) else (sf_defaction(args, env) if sx_truthy((name == 'defaction')) else (sf_begin(args, env) if sx_truthy((name == 'begin')) else (sf_begin(args, env) if sx_truthy((name == 'do')) else (sf_quote(args, env) if sx_truthy((name == 'quote')) else (sf_quasiquote(args, env) if sx_truthy((name == 'quasiquote')) else (sf_thread_first(args, env) if sx_truthy((name == '->')) else (sf_set_bang(args, env) if sx_truthy((name == 'set!')) else (sf_dynamic_wind(args, env) if sx_truthy((name == 'dynamic-wind')) else (ho_map(args, env) if sx_truthy((name == 'map')) else (ho_map_indexed(args, env) if sx_truthy((name == 'map-indexed')) else (ho_filter(args, env) if sx_truthy((name == 'filter')) else (ho_reduce(args, env) if sx_truthy((name == 'reduce')) else (ho_some(args, env) if sx_truthy((name == 'some')) else (ho_every(args, env) if sx_truthy((name == 'every?')) else (ho_for_each(args, env) if sx_truthy((name == 'for-each')) else ((lambda mac: make_thunk(expand_macro(mac, args, env), env))(env_get(env, name)) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (render_expr(expr, env) if sx_truthy(is_render_expr(expr)) else eval_call(head, args, env))))))))))))))))))))))))))))))))))))))(symbol_name(head)) if sx_truthy((type_of(head) == 'symbol')) else eval_call(head, args, env))))(rest(expr)))(first(expr))
|
|
|
|
# eval-call
|
|
eval_call = lambda head, args, env: (lambda f: (lambda evaluated_args: (apply(f, evaluated_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else (not sx_truthy(is_component(f)))))) else (call_lambda(f, evaluated_args, env) if sx_truthy(is_lambda(f)) else (call_component(f, args, env) if sx_truthy(is_component(f)) else error(sx_str('Not callable: ', inspect(f)))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))
|
|
|
|
# call-lambda
|
|
call_lambda = lambda f, args, caller_env: (lambda params: (lambda local: (error(sx_str((lambda_name(f) if sx_truthy(lambda_name(f)) else 'lambda'), ' expects ', len(params), ' args, got ', len(args))) if sx_truthy((len(args) != len(params))) else _sx_begin(for_each(lambda pair: _sx_dict_set(local, first(pair), nth(pair, 1)), zip(params, args)), make_thunk(lambda_body(f), local))))(env_merge(lambda_closure(f), caller_env)))(lambda_params(f))
|
|
|
|
# call-component
|
|
call_component = lambda comp, raw_args, env: (lambda parsed: (lambda kwargs: (lambda children: (lambda local: _sx_begin(for_each(lambda p: _sx_dict_set(local, p, (dict_get(kwargs, p) if sx_truthy(dict_get(kwargs, p)) else NIL)), component_params(comp)), (_sx_dict_set(local, 'children', children) if sx_truthy(component_has_children(comp)) else NIL), make_thunk(component_body(comp), local)))(env_merge(component_closure(comp), env)))(nth(parsed, 1)))(first(parsed)))(parse_keyword_args(raw_args, env))
|
|
|
|
# parse-keyword-args
|
|
parse_keyword_args = lambda raw_args, env: (lambda kwargs: (lambda children: (lambda i: _sx_begin(reduce(lambda state, arg: (lambda idx: (lambda skip: (assoc(state, 'skip', False, 'i', (idx + 1)) if sx_truthy(skip) else (_sx_begin(_sx_dict_set(kwargs, keyword_name(arg), trampoline(eval_expr(nth(raw_args, (idx + 1)), env))), assoc(state, 'skip', True, 'i', (idx + 1))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((idx + 1) < len(raw_args)))) else _sx_begin(_sx_append(children, trampoline(eval_expr(arg, env))), assoc(state, 'i', (idx + 1))))))(get(state, 'skip')))(get(state, 'i')), {'i': 0, 'skip': False}, raw_args), [kwargs, children]))(0))([]))({})
|
|
|
|
# sf-if
|
|
sf_if = lambda args, env: (lambda condition: (make_thunk(nth(args, 1), env) if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))) else (make_thunk(nth(args, 2), env) if sx_truthy((len(args) > 2)) else NIL)))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# sf-when
|
|
sf_when = lambda args, env: (lambda condition: (_sx_begin(for_each(lambda e: trampoline(eval_expr(e, env)), slice(args, 1, (len(args) - 1))), make_thunk(last(args), env)) if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))) else NIL))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# sf-cond
|
|
sf_cond = lambda args, env: (sf_cond_scheme(args, env) if sx_truthy(((type_of(first(args)) == 'list') if not sx_truthy((type_of(first(args)) == 'list')) else (len(first(args)) == 2))) else sf_cond_clojure(args, env))
|
|
|
|
# sf-cond-scheme
|
|
sf_cond_scheme = lambda clauses, env: (NIL if sx_truthy(empty_p(clauses)) else (lambda clause: (lambda test: (lambda body: (make_thunk(body, env) if sx_truthy((((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))) if sx_truthy(((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else')))) else ((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')))) else (make_thunk(body, env) if sx_truthy(trampoline(eval_expr(test, env))) else sf_cond_scheme(rest(clauses), env))))(nth(clause, 1)))(first(clause)))(first(clauses)))
|
|
|
|
# sf-cond-clojure
|
|
sf_cond_clojure = lambda clauses, env: (NIL if sx_truthy((len(clauses) < 2)) else (lambda test: (lambda body: (make_thunk(body, env) if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))) else (make_thunk(body, env) if sx_truthy(trampoline(eval_expr(test, env))) else sf_cond_clojure(slice(clauses, 2), env))))(nth(clauses, 1)))(first(clauses)))
|
|
|
|
# sf-case
|
|
sf_case = lambda args, env: (lambda match_val: (lambda clauses: sf_case_loop(match_val, clauses, env))(rest(args)))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# sf-case-loop
|
|
sf_case_loop = lambda match_val, clauses, env: (NIL if sx_truthy((len(clauses) < 2)) else (lambda test: (lambda body: (make_thunk(body, env) if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == 'else') if sx_truthy((symbol_name(test) == 'else')) else (symbol_name(test) == ':else'))))) else (make_thunk(body, env) if sx_truthy((match_val == trampoline(eval_expr(test, env)))) else sf_case_loop(match_val, slice(clauses, 2), env))))(nth(clauses, 1)))(first(clauses)))
|
|
|
|
# sf-and
|
|
sf_and = lambda args, env: (True if sx_truthy(empty_p(args)) else (lambda val: (val if sx_truthy((not sx_truthy(val))) else (val if sx_truthy((len(args) == 1)) else sf_and(rest(args), env))))(trampoline(eval_expr(first(args), env))))
|
|
|
|
# sf-or
|
|
sf_or = lambda args, env: (False if sx_truthy(empty_p(args)) else (lambda val: (val if sx_truthy(val) else sf_or(rest(args), env)))(trampoline(eval_expr(first(args), env))))
|
|
|
|
# sf-let
|
|
sf_let = lambda args, env: (sf_named_let(args, env) if sx_truthy((type_of(first(args)) == 'symbol')) else (lambda bindings: (lambda body: (lambda local: _sx_begin((for_each(lambda binding: (lambda vname: _sx_dict_set(local, vname, trampoline(eval_expr(nth(binding, 1), local))))((symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))), bindings) if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))) else (lambda i: reduce(lambda acc, pair_idx: (lambda vname: (lambda val_expr: _sx_dict_set(local, vname, trampoline(eval_expr(val_expr, local))))(nth(bindings, ((pair_idx * 2) + 1))))((symbol_name(nth(bindings, (pair_idx * 2))) if sx_truthy((type_of(nth(bindings, (pair_idx * 2))) == 'symbol')) else nth(bindings, (pair_idx * 2)))), NIL, range(0, (len(bindings) / 2))))(0)), for_each(lambda e: trampoline(eval_expr(e, local)), slice(body, 0, (len(body) - 1))), make_thunk(last(body), local)))(env_extend(env)))(rest(args)))(first(args)))
|
|
|
|
# sf-named-let
|
|
sf_named_let = lambda args, env: (lambda loop_name: (lambda bindings: (lambda body: (lambda params: (lambda inits: _sx_begin((for_each(_sx_fn(lambda binding: (
|
|
_sx_append(params, (symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))),
|
|
_sx_append(inits, nth(binding, 1))
|
|
)[-1]), bindings) if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))) else reduce(lambda acc, pair_idx: _sx_begin(_sx_append(params, (symbol_name(nth(bindings, (pair_idx * 2))) if sx_truthy((type_of(nth(bindings, (pair_idx * 2))) == 'symbol')) else nth(bindings, (pair_idx * 2)))), _sx_append(inits, nth(bindings, ((pair_idx * 2) + 1)))), NIL, range(0, (len(bindings) / 2)))), (lambda loop_body: (lambda loop_fn: _sx_begin(_sx_set_attr(loop_fn, 'name', loop_name), _sx_dict_set(lambda_closure(loop_fn), loop_name, loop_fn), (lambda init_vals: call_lambda(loop_fn, init_vals, env))(map(lambda e: trampoline(eval_expr(e, env)), inits))))(make_lambda(params, loop_body, env)))((first(body) if sx_truthy((len(body) == 1)) else cons(make_symbol('begin'), body)))))([]))([]))(slice(args, 2)))(nth(args, 1)))(symbol_name(first(args)))
|
|
|
|
# sf-lambda
|
|
sf_lambda = lambda args, env: (lambda params_expr: (lambda body: (lambda param_names: make_lambda(param_names, body, env))(map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else p), params_expr)))(nth(args, 1)))(first(args))
|
|
|
|
# sf-define
|
|
sf_define = lambda args, env: (lambda name_sym: (lambda value: _sx_begin((_sx_set_attr(value, 'name', symbol_name(name_sym)) if sx_truthy((is_lambda(value) if not sx_truthy(is_lambda(value)) else is_nil(lambda_name(value)))) else NIL), _sx_dict_set(env, symbol_name(name_sym), value), value))(trampoline(eval_expr(nth(args, 1), env))))(first(args))
|
|
|
|
# sf-defcomp
|
|
sf_defcomp = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda body: (lambda comp_name: (lambda parsed: (lambda params: (lambda has_children: (lambda comp: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), comp), comp))(make_component(comp_name, params, has_children, body, env)))(nth(parsed, 1)))(first(parsed)))(parse_comp_params(params_raw)))(strip_prefix(symbol_name(name_sym), '~')))(nth(args, 2)))(nth(args, 1)))(first(args))
|
|
|
|
# parse-comp-params
|
|
def parse_comp_params(params_expr):
|
|
_cells = {}
|
|
params = []
|
|
_cells['has_children'] = False
|
|
_cells['in_key'] = False
|
|
for p in params_expr:
|
|
if sx_truthy((type_of(p) == 'symbol')):
|
|
name = symbol_name(p)
|
|
if sx_truthy((name == '&key')):
|
|
_cells['in_key'] = True
|
|
elif sx_truthy((name == '&rest')):
|
|
_cells['has_children'] = True
|
|
elif sx_truthy((name == '&children')):
|
|
_cells['has_children'] = True
|
|
elif sx_truthy(_cells['has_children']):
|
|
NIL
|
|
elif sx_truthy(_cells['in_key']):
|
|
params.append(name)
|
|
else:
|
|
params.append(name)
|
|
return [params, _cells['has_children']]
|
|
|
|
# sf-defmacro
|
|
sf_defmacro = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda body: (lambda parsed: (lambda params: (lambda rest_param: (lambda mac: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), mac), mac))(make_macro(params, rest_param, body, env, symbol_name(name_sym))))(nth(parsed, 1)))(first(parsed)))(parse_macro_params(params_raw)))(nth(args, 2)))(nth(args, 1)))(first(args))
|
|
|
|
# parse-macro-params
|
|
def parse_macro_params(params_expr):
|
|
_cells = {}
|
|
params = []
|
|
_cells['rest_param'] = NIL
|
|
reduce(lambda state, p: (assoc(state, 'in-rest', True) if sx_truthy(((type_of(p) == 'symbol') if not sx_truthy((type_of(p) == 'symbol')) else (symbol_name(p) == '&rest'))) else (_sx_begin(_sx_cell_set(_cells, 'rest_param', (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else p)), state) if sx_truthy(get(state, 'in-rest')) else _sx_begin(_sx_append(params, (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else p)), state))), {'in-rest': False}, params_expr)
|
|
return [params, _cells['rest_param']]
|
|
|
|
# sf-defstyle
|
|
sf_defstyle = lambda args, env: (lambda name_sym: (lambda value: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), value), value))(trampoline(eval_expr(nth(args, 1), env))))(first(args))
|
|
|
|
# sf-defkeyframes
|
|
sf_defkeyframes = lambda args, env: (lambda kf_name: (lambda steps: build_keyframes(kf_name, steps, env))(rest(args)))(symbol_name(first(args)))
|
|
|
|
# sf-begin
|
|
sf_begin = lambda args, env: (NIL if sx_truthy(empty_p(args)) else _sx_begin(for_each(lambda e: trampoline(eval_expr(e, env)), slice(args, 0, (len(args) - 1))), make_thunk(last(args), env)))
|
|
|
|
# sf-quote
|
|
sf_quote = lambda args, env: (NIL if sx_truthy(empty_p(args)) else first(args))
|
|
|
|
# sf-quasiquote
|
|
sf_quasiquote = lambda args, env: qq_expand(first(args), env)
|
|
|
|
# qq-expand
|
|
qq_expand = lambda template, env: (template if sx_truthy((not sx_truthy((type_of(template) == 'list')))) else ([] if sx_truthy(empty_p(template)) else (lambda head: (trampoline(eval_expr(nth(template, 1), env)) if sx_truthy(((type_of(head) == 'symbol') if not sx_truthy((type_of(head) == 'symbol')) else (symbol_name(head) == 'unquote'))) else reduce(lambda result, item: ((lambda spliced: (concat(result, spliced) if sx_truthy((type_of(spliced) == 'list')) else (result if sx_truthy(is_nil(spliced)) else append(result, spliced))))(trampoline(eval_expr(nth(item, 1), env))) if sx_truthy(((type_of(item) == 'list') if not sx_truthy((type_of(item) == 'list')) else ((len(item) == 2) if not sx_truthy((len(item) == 2)) else ((type_of(first(item)) == 'symbol') if not sx_truthy((type_of(first(item)) == 'symbol')) else (symbol_name(first(item)) == 'splice-unquote'))))) else append(result, qq_expand(item, env))), [], template)))(first(template))))
|
|
|
|
# sf-thread-first
|
|
sf_thread_first = lambda args, env: (lambda val: reduce(lambda result, form: ((lambda f: (lambda rest_args: (lambda all_args: (apply(f, all_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, all_args, env)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(cons(result, rest_args)))(map(lambda a: trampoline(eval_expr(a, env)), rest(form))))(trampoline(eval_expr(first(form), env))) if sx_truthy((type_of(form) == 'list')) else (lambda f: (f(result) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else (not sx_truthy(is_lambda(f))))) else (trampoline(call_lambda(f, [result], env)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(trampoline(eval_expr(form, env)))), val, rest(args)))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# sf-set!
|
|
sf_set_bang = lambda args, env: (lambda name: (lambda value: _sx_begin(_sx_dict_set(env, name, value), value))(trampoline(eval_expr(nth(args, 1), env))))(symbol_name(first(args)))
|
|
|
|
# sf-letrec
|
|
sf_letrec = lambda args, env: (lambda bindings: (lambda body: (lambda local: (lambda names: (lambda val_exprs: _sx_begin((for_each(lambda binding: (lambda vname: _sx_begin(_sx_append(names, vname), _sx_append(val_exprs, nth(binding, 1)), _sx_dict_set(local, vname, NIL)))((symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))), bindings) if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))) else reduce(lambda acc, pair_idx: (lambda vname: (lambda val_expr: _sx_begin(_sx_append(names, vname), _sx_append(val_exprs, val_expr), _sx_dict_set(local, vname, NIL)))(nth(bindings, ((pair_idx * 2) + 1))))((symbol_name(nth(bindings, (pair_idx * 2))) if sx_truthy((type_of(nth(bindings, (pair_idx * 2))) == 'symbol')) else nth(bindings, (pair_idx * 2)))), NIL, range(0, (len(bindings) / 2)))), (lambda values: _sx_begin(for_each(lambda pair: _sx_dict_set(local, first(pair), nth(pair, 1)), zip(names, values)), for_each(lambda val: (for_each(lambda n: _sx_dict_set(lambda_closure(val), n, env_get(local, n)), names) if sx_truthy(is_lambda(val)) else NIL), values)))(map(lambda e: trampoline(eval_expr(e, local)), val_exprs)), for_each(lambda e: trampoline(eval_expr(e, local)), slice(body, 0, (len(body) - 1))), make_thunk(last(body), local)))([]))([]))(env_extend(env)))(rest(args)))(first(args))
|
|
|
|
# sf-dynamic-wind
|
|
sf_dynamic_wind = lambda args, env: (lambda before: (lambda body: (lambda after: _sx_begin(call_thunk(before, env), push_wind_b(before, after), (lambda result: _sx_begin(pop_wind_b(), call_thunk(after, env), result))(call_thunk(body, env))))(trampoline(eval_expr(nth(args, 2), env))))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# expand-macro
|
|
expand_macro = lambda mac, raw_args, env: (lambda local: _sx_begin(for_each(lambda pair: _sx_dict_set(local, first(pair), (nth(raw_args, nth(pair, 1)) if sx_truthy((nth(pair, 1) < len(raw_args))) else NIL)), map_indexed(lambda i, p: [p, i], macro_params(mac))), (_sx_dict_set(local, macro_rest_param(mac), slice(raw_args, len(macro_params(mac)))) if sx_truthy(macro_rest_param(mac)) else NIL), trampoline(eval_expr(macro_body(mac), local))))(env_merge(macro_closure(mac), env))
|
|
|
|
# call-fn
|
|
call_fn = lambda f, args, env: (trampoline(call_lambda(f, args, env)) if sx_truthy(is_lambda(f)) else (apply(f, args) if sx_truthy(is_callable(f)) else error(sx_str('Not callable in HO form: ', inspect(f)))))
|
|
|
|
# ho-map
|
|
ho_map = lambda args, env: (lambda f: (lambda coll: map(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# ho-map-indexed
|
|
ho_map_indexed = lambda args, env: (lambda f: (lambda coll: map_indexed(lambda i, item: call_fn(f, [i, item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# ho-filter
|
|
ho_filter = lambda args, env: (lambda f: (lambda coll: filter(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# ho-reduce
|
|
ho_reduce = lambda args, env: (lambda f: (lambda init: (lambda coll: reduce(lambda acc, item: call_fn(f, [acc, item], env), init, coll))(trampoline(eval_expr(nth(args, 2), env))))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# ho-some
|
|
ho_some = lambda args, env: (lambda f: (lambda coll: some(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# ho-every
|
|
ho_every = lambda args, env: (lambda f: (lambda coll: every_p(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
|
|
|
# ho-for-each
|
|
ho_for_each = lambda args, env: (lambda f: (lambda coll: for_each(lambda item: call_fn(f, [item], env), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env)))
|
|
|
|
|
|
# === Transpiled from forms (server definition forms) ===
|
|
|
|
# parse-key-params
|
|
def parse_key_params(params_expr):
|
|
_cells = {}
|
|
params = []
|
|
_cells['in_key'] = False
|
|
for p in params_expr:
|
|
if sx_truthy((type_of(p) == 'symbol')):
|
|
name = symbol_name(p)
|
|
if sx_truthy((name == '&key')):
|
|
_cells['in_key'] = True
|
|
elif sx_truthy(_cells['in_key']):
|
|
params.append(name)
|
|
else:
|
|
params.append(name)
|
|
return params
|
|
|
|
# sf-defhandler
|
|
sf_defhandler = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda body: (lambda name: (lambda params: (lambda hdef: _sx_begin(_sx_dict_set(env, sx_str('handler:', name), hdef), hdef))(make_handler_def(name, params, body, env)))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 2)))(nth(args, 1)))(first(args))
|
|
|
|
# sf-defquery
|
|
sf_defquery = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda name: (lambda params: (lambda has_doc: (lambda doc: (lambda body: (lambda qdef: _sx_begin(_sx_dict_set(env, sx_str('query:', name), qdef), qdef))(make_query_def(name, params, doc, body, env)))((nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))))((nth(args, 2) if sx_truthy(has_doc) else '')))(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 1)))(first(args))
|
|
|
|
# sf-defaction
|
|
sf_defaction = lambda args, env: (lambda name_sym: (lambda params_raw: (lambda name: (lambda params: (lambda has_doc: (lambda doc: (lambda body: (lambda adef: _sx_begin(_sx_dict_set(env, sx_str('action:', name), adef), adef))(make_action_def(name, params, doc, body, env)))((nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))))((nth(args, 2) if sx_truthy(has_doc) else '')))(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))))(parse_key_params(params_raw)))(symbol_name(name_sym)))(nth(args, 1)))(first(args))
|
|
|
|
# sf-defpage
|
|
sf_defpage = lambda args, env: (lambda name_sym: (lambda name: (lambda slots: _sx_begin((lambda i: (lambda max_i: for_each(lambda idx: ((_sx_dict_set(slots, keyword_name(nth(args, idx)), nth(args, (idx + 1))) if sx_truthy(((idx + 1) < max_i)) else NIL) if sx_truthy(((idx < max_i) if not sx_truthy((idx < max_i)) else (type_of(nth(args, idx)) == 'keyword'))) else NIL), range(1, max_i, 2)))(len(args)))(1), (lambda pdef: _sx_begin(_sx_dict_set(env, sx_str('page:', name), pdef), pdef))(make_page_def(name, slots, env))))({}))(symbol_name(name_sym)))(first(args))
|
|
|
|
|
|
# === Transpiled from render (core) ===
|
|
|
|
# HTML_TAGS
|
|
HTML_TAGS = ['html', 'head', 'body', 'title', 'meta', 'link', 'script', 'style', 'noscript', 'header', 'nav', 'main', 'section', 'article', 'aside', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hgroup', 'div', 'p', 'blockquote', 'pre', 'figure', 'figcaption', 'address', 'details', 'summary', 'a', 'span', 'em', 'strong', 'small', 'b', 'i', 'u', 's', 'mark', 'sub', 'sup', 'abbr', 'cite', 'code', 'time', 'br', 'wbr', 'hr', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption', 'colgroup', 'col', 'form', 'input', 'textarea', 'select', 'option', 'optgroup', 'button', 'label', 'fieldset', 'legend', 'output', 'datalist', 'img', 'video', 'audio', 'source', 'picture', 'canvas', 'iframe', 'svg', 'math', 'path', 'circle', 'ellipse', 'rect', 'line', 'polyline', 'polygon', 'text', 'tspan', 'g', 'defs', 'use', 'clipPath', 'mask', 'pattern', 'linearGradient', 'radialGradient', 'stop', 'filter', 'feGaussianBlur', 'feOffset', 'feBlend', 'feColorMatrix', 'feComposite', 'feMerge', 'feMergeNode', 'feTurbulence', 'feComponentTransfer', 'feFuncR', 'feFuncG', 'feFuncB', 'feFuncA', 'feDisplacementMap', 'feFlood', 'feImage', 'feMorphology', 'feSpecularLighting', 'feDiffuseLighting', 'fePointLight', 'feSpotLight', 'feDistantLight', 'animate', 'animateTransform', 'foreignObject', 'template', 'slot', 'dialog', 'menu']
|
|
|
|
# VOID_ELEMENTS
|
|
VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']
|
|
|
|
# BOOLEAN_ATTRS
|
|
BOOLEAN_ATTRS = ['async', 'autofocus', 'autoplay', 'checked', 'controls', 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'inert', 'ismap', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'playsinline', 'readonly', 'required', 'reversed', 'selected']
|
|
|
|
# definition-form?
|
|
is_definition_form = lambda name: ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defkeyframes') if sx_truthy((name == 'defkeyframes')) else (name == 'defhandler'))))))
|
|
|
|
# parse-element-args
|
|
parse_element_args = lambda args, env: (lambda attrs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(attrs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), [attrs, children]))([]))({})
|
|
|
|
# render-attrs
|
|
render_attrs = lambda attrs: join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else (sx_str(' class="', style_value_class(val), '"') if sx_truthy(((key == 'style') if not sx_truthy((key == 'style')) else is_style_value(val))) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"'))))))(dict_get(attrs, key)), keys(attrs)))
|
|
|
|
|
|
# === Transpiled from adapter-html ===
|
|
|
|
# render-to-html
|
|
render_to_html = lambda expr, env: _sx_case(type_of(expr), [('nil', lambda: ''), ('string', lambda: escape_html(expr)), ('number', lambda: sx_str(expr)), ('boolean', lambda: ('true' if sx_truthy(expr) else 'false')), ('list', lambda: ('' if sx_truthy(empty_p(expr)) else render_list_to_html(expr, env))), ('symbol', lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env)), ('keyword', lambda: escape_html(keyword_name(expr))), ('raw-html', lambda: raw_html_content(expr)), (None, lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env))])
|
|
|
|
# render-value-to-html
|
|
render_value_to_html = lambda val, env: _sx_case(type_of(val), [('nil', lambda: ''), ('string', lambda: escape_html(val)), ('number', lambda: sx_str(val)), ('boolean', lambda: ('true' if sx_truthy(val) else 'false')), ('list', lambda: render_list_to_html(val, env)), ('raw-html', lambda: raw_html_content(val)), ('style-value', lambda: style_value_class(val)), (None, lambda: escape_html(sx_str(val)))])
|
|
|
|
# RENDER_HTML_FORMS
|
|
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defmacro', 'defstyle', 'defkeyframes', 'defhandler', 'map', 'map-indexed', 'filter', 'for-each']
|
|
|
|
# render-html-form?
|
|
is_render_html_form = lambda name: contains_p(RENDER_HTML_FORMS, name)
|
|
|
|
# render-list-to-html
|
|
render_list_to_html = lambda expr, env: ('' if sx_truthy(empty_p(expr)) else (lambda head: (join('', map(lambda x: render_value_to_html(x, env), expr)) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (lambda args: (join('', map(lambda x: render_to_html(x, env), args)) if sx_truthy((name == '<>')) else (join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args)) if sx_truthy((name == 'raw!')) else (render_html_element(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else ((lambda val: (render_html_component(val, args, env) if sx_truthy(is_component(val)) else (render_to_html(expand_macro(val, args, env), env) if sx_truthy(is_macro(val)) else error(sx_str('Unknown component: ', name)))))(env_get(env, name)) if sx_truthy(starts_with_p(name, '~')) else (dispatch_html_form(name, expr, env) if sx_truthy(is_render_html_form(name)) else (render_to_html(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else render_value_to_html(trampoline(eval_expr(expr, env)), env))))))))(rest(expr)))(symbol_name(head))))(first(expr)))
|
|
|
|
# dispatch-html-form
|
|
dispatch_html_form = lambda name, expr, env: ((lambda cond_val: (render_to_html(nth(expr, 2), env) if sx_truthy(cond_val) else (render_to_html(nth(expr, 3), env) if sx_truthy((len(expr) > 3)) else '')))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'if')) else (('' if sx_truthy((not sx_truthy(trampoline(eval_expr(nth(expr, 1), env))))) else join('', map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr))))) if sx_truthy((name == 'when')) else ((lambda branch: (render_to_html(branch, env) if sx_truthy(branch) else ''))(eval_cond(rest(expr), env)) if sx_truthy((name == 'cond')) else (render_to_html(trampoline(eval_expr(expr, env)), env) if sx_truthy((name == 'case')) else ((lambda local: join('', map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr)))))(process_bindings(nth(expr, 1), env)) if sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))) else (join('', map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr)))) if sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))) else (_sx_begin(trampoline(eval_expr(expr, env)), '') if sx_truthy(is_definition_form(name)) else ((lambda f: (lambda coll: join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'map')) else ((lambda f: (lambda coll: join('', map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'map-indexed')) else (render_to_html(trampoline(eval_expr(expr, env)), env) if sx_truthy((name == 'filter')) else ((lambda f: (lambda coll: join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'for-each')) else render_value_to_html(trampoline(eval_expr(expr, env)), env))))))))))))
|
|
|
|
# render-lambda-html
|
|
render_lambda_html = lambda f, args, env: (lambda local: _sx_begin(for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f)), render_to_html(lambda_body(f), local)))(env_merge(lambda_closure(f), env))
|
|
|
|
# render-html-component
|
|
render_html_component = lambda comp, args, env: (lambda kwargs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(kwargs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), (lambda local: _sx_begin(for_each(lambda p: _sx_dict_set(local, p, (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)), component_params(comp)), (_sx_dict_set(local, 'children', make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))) if sx_truthy(component_has_children(comp)) else NIL), render_to_html(component_body(comp), local)))(env_merge(component_closure(comp), env))))([]))({})
|
|
|
|
# render-html-element
|
|
render_html_element = lambda tag, args, env: (lambda parsed: (lambda attrs: (lambda children: (lambda is_void: sx_str('<', tag, render_attrs(attrs), (' />' if sx_truthy(is_void) else sx_str('>', join('', map(lambda c: render_to_html(c, env), children)), '</', tag, '>'))))(contains_p(VOID_ELEMENTS, tag)))(nth(parsed, 1)))(first(parsed)))(parse_element_args(args, env))
|
|
|
|
|
|
# === Transpiled from adapter-sx ===
|
|
|
|
# render-to-sx
|
|
render_to_sx = lambda expr, env: (lambda result: (result if sx_truthy((type_of(result) == 'string')) else serialize(result)))(aser(expr, env))
|
|
|
|
# aser
|
|
aser = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else aser_list(expr, env))), (None, lambda: expr)])
|
|
|
|
# aser-list
|
|
aser_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: aser(x, env), expr) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (aser_fragment(args, env) if sx_truthy((name == '<>')) else (aser_call(name, args, env) if sx_truthy(starts_with_p(name, '~')) else (aser_call(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else (aser_special(name, expr, env) if sx_truthy((is_special_form(name) if sx_truthy(is_special_form(name)) else is_ho_form(name))) else (aser(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (lambda f: (lambda evaled_args: (apply(f, evaled_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else (not sx_truthy(is_component(f)))))) else (trampoline(call_lambda(f, evaled_args, env)) if sx_truthy(is_lambda(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_component(f)) else error(sx_str('Not callable: ', inspect(f)))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))))))))(symbol_name(head))))(rest(expr)))(first(expr))
|
|
|
|
# aser-fragment
|
|
aser_fragment = lambda children, env: (lambda parts: ('' if sx_truthy(empty_p(parts)) else sx_str('(<> ', join(' ', map(serialize, parts)), ')')))(filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda c: aser(c, env), children)))
|
|
|
|
# aser-call
|
|
aser_call = lambda name, args, env: (lambda parts: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin((_sx_begin(_sx_append(parts, sx_str(':', keyword_name(arg))), _sx_append(parts, serialize(val))) if sx_truthy((not sx_truthy(is_nil(val)))) else NIL), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(aser(nth(args, (get(state, 'i') + 1)), env)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else (lambda val: _sx_begin((_sx_append(parts, serialize(val)) if sx_truthy((not sx_truthy(is_nil(val)))) else NIL), assoc(state, 'i', (get(state, 'i') + 1))))(aser(arg, env)))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), sx_str('(', join(' ', parts), ')')))([name])
|
|
|
|
|
|
# =========================================================================
|
|
# Fixups -- wire up render adapter dispatch
|
|
# =========================================================================
|
|
|
|
def _setup_html_adapter():
|
|
global _render_expr_fn
|
|
_render_expr_fn = lambda expr, env: render_list_to_html(expr, env)
|
|
|
|
def _setup_sx_adapter():
|
|
global _render_expr_fn
|
|
_render_expr_fn = lambda expr, env: aser_list(expr, env)
|
|
|
|
|
|
# Wrap aser_call and aser_fragment to return SxExpr
|
|
# so serialize() won't double-quote them
|
|
_orig_aser_call = None
|
|
_orig_aser_fragment = None
|
|
|
|
def _wrap_aser_outputs():
|
|
global aser_call, aser_fragment, _orig_aser_call, _orig_aser_fragment
|
|
_orig_aser_call = aser_call
|
|
_orig_aser_fragment = aser_fragment
|
|
def _aser_call_wrapped(name, args, env):
|
|
result = _orig_aser_call(name, args, env)
|
|
return SxExpr(result) if isinstance(result, str) else result
|
|
def _aser_fragment_wrapped(children, env):
|
|
result = _orig_aser_fragment(children, env)
|
|
return SxExpr(result) if isinstance(result, str) else result
|
|
aser_call = _aser_call_wrapped
|
|
aser_fragment = _aser_fragment_wrapped
|
|
|
|
|
|
# =========================================================================
|
|
# Extension: delimited continuations (shift/reset)
|
|
# =========================================================================
|
|
|
|
_RESET_RESUME = [] # stack of resume values; empty = not resuming
|
|
|
|
_SPECIAL_FORM_NAMES = _SPECIAL_FORM_NAMES | frozenset(["reset", "shift"])
|
|
|
|
def sf_reset(args, env):
|
|
"""(reset body) -- establish a continuation delimiter."""
|
|
body = first(args)
|
|
try:
|
|
return trampoline(eval_expr(body, env))
|
|
except _ShiftSignal as sig:
|
|
def cont_fn(value=NIL):
|
|
_RESET_RESUME.append(value)
|
|
try:
|
|
return trampoline(eval_expr(body, env))
|
|
finally:
|
|
_RESET_RESUME.pop()
|
|
k = Continuation(cont_fn)
|
|
sig_env = dict(sig.env)
|
|
sig_env[sig.k_name] = k
|
|
return trampoline(eval_expr(sig.body, sig_env))
|
|
|
|
def sf_shift(args, env):
|
|
"""(shift k body) -- capture continuation to nearest reset."""
|
|
if _RESET_RESUME:
|
|
return _RESET_RESUME[-1]
|
|
k_name = symbol_name(first(args))
|
|
body = nth(args, 1)
|
|
raise _ShiftSignal(k_name, body, env)
|
|
|
|
# Wrap eval_list to inject shift/reset dispatch
|
|
_base_eval_list = eval_list
|
|
def _eval_list_with_continuations(expr, env):
|
|
head = first(expr)
|
|
if type_of(head) == "symbol":
|
|
name = symbol_name(head)
|
|
args = rest(expr)
|
|
if name == "reset":
|
|
return sf_reset(args, env)
|
|
if name == "shift":
|
|
return sf_shift(args, env)
|
|
return _base_eval_list(expr, env)
|
|
eval_list = _eval_list_with_continuations
|
|
|
|
# Inject into aser_special
|
|
_base_aser_special = aser_special
|
|
def _aser_special_with_continuations(name, expr, env):
|
|
if name == "reset":
|
|
return sf_reset(expr[1:], env)
|
|
if name == "shift":
|
|
return sf_shift(expr[1:], env)
|
|
return _base_aser_special(name, expr, env)
|
|
aser_special = _aser_special_with_continuations
|
|
|
|
|
|
# =========================================================================
|
|
# Public API
|
|
# =========================================================================
|
|
|
|
# Wrap aser outputs to return SxExpr
|
|
_wrap_aser_outputs()
|
|
|
|
# Set HTML as default adapter
|
|
_setup_html_adapter()
|
|
|
|
def evaluate(expr, env=None):
|
|
"""Evaluate expr in env and return the result."""
|
|
if env is None:
|
|
env = {}
|
|
result = eval_expr(expr, env)
|
|
while is_thunk(result):
|
|
result = eval_expr(thunk_expr(result), thunk_env(result))
|
|
return result
|
|
|
|
|
|
def render(expr, env=None):
|
|
"""Render expr to HTML string."""
|
|
if env is None:
|
|
env = {}
|
|
return render_to_html(expr, env)
|
|
|
|
|
|
def make_env(**kwargs):
|
|
"""Create an environment dict with initial bindings."""
|
|
return dict(kwargs)
|