Completes the invoke→cek-call migration across all spec .sx files: - adapter-sx.sx: map/filter/for-each in aser wire format - adapter-dom.sx: island render update-fn - engine.sx: fetch transform callback - test-cek-reactive.sx: disposal test Only async-invoke (adapter-async.sx) remains — separate async pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5770 lines
240 KiB
Python
5770 lines
240 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 run_py_sx.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, Island, Continuation, Macro,
|
|
HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
|
|
)
|
|
from shared.sx.parser import SxExpr
|
|
from shared.sx.env import Env as _Env, MergedEnv as _MergedEnv
|
|
|
|
|
|
# =========================================================================
|
|
# 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
|
|
|
|
|
|
class _Spread:
|
|
"""Attribute injection value — merges attrs onto parent element."""
|
|
__slots__ = ("attrs",)
|
|
def __init__(self, attrs: dict):
|
|
self.attrs = dict(attrs) if attrs else {}
|
|
|
|
|
|
# Unified scope stacks — backing store for provide/context/emit!/collect!
|
|
# Each entry: {"value": v, "emitted": [], "dedup": bool}
|
|
_scope_stacks: dict[str, list[dict]] = {}
|
|
|
|
|
|
def _collect_reset():
|
|
"""Reset all scope stacks (call at start of each render pass)."""
|
|
global _scope_stacks
|
|
_scope_stacks = {}
|
|
|
|
|
|
def scope_push(name, value=None):
|
|
"""Push a scope with name, value, and empty accumulator."""
|
|
_scope_stacks.setdefault(name, []).append({"value": value, "emitted": [], "dedup": False})
|
|
|
|
|
|
def scope_pop(name):
|
|
"""Pop the most recent scope for name."""
|
|
if name in _scope_stacks and _scope_stacks[name]:
|
|
_scope_stacks[name].pop()
|
|
|
|
|
|
# Aliases — provide-push!/provide-pop! map to scope-push!/scope-pop!
|
|
provide_push = scope_push
|
|
provide_pop = scope_pop
|
|
|
|
|
|
def sx_context(name, *default):
|
|
"""Read value from nearest enclosing scope. Error if no scope and no default."""
|
|
if name in _scope_stacks and _scope_stacks[name]:
|
|
return _scope_stacks[name][-1]["value"]
|
|
if default:
|
|
return default[0]
|
|
raise RuntimeError(f"No provider for: {name}")
|
|
|
|
|
|
def sx_emit(name, value):
|
|
"""Append value to nearest enclosing scope's accumulator. Respects dedup flag."""
|
|
if name in _scope_stacks and _scope_stacks[name]:
|
|
entry = _scope_stacks[name][-1]
|
|
if entry["dedup"] and value in entry["emitted"]:
|
|
return NIL
|
|
entry["emitted"].append(value)
|
|
return NIL
|
|
|
|
|
|
def sx_emitted(name):
|
|
"""Return list of values emitted into nearest matching scope."""
|
|
if name in _scope_stacks and _scope_stacks[name]:
|
|
return list(_scope_stacks[name][-1]["emitted"])
|
|
return []
|
|
|
|
|
|
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, Island):
|
|
return "island"
|
|
if isinstance(x, _Spread):
|
|
return "spread"
|
|
if isinstance(x, Macro):
|
|
return "macro"
|
|
if isinstance(x, _RawHTML):
|
|
return "raw-html"
|
|
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 _ensure_env(env):
|
|
"""Wrap plain dict in Env if needed."""
|
|
if isinstance(env, _Env):
|
|
return env
|
|
return _Env(env if isinstance(env, dict) else {})
|
|
|
|
|
|
def make_lambda(params, body, env):
|
|
return Lambda(params=list(params), body=body, closure=_ensure_env(env))
|
|
|
|
|
|
def make_component(name, params, has_children, body, env, affinity="auto"):
|
|
return Component(name=name, params=list(params), has_children=has_children,
|
|
body=body, closure=dict(env), affinity=str(affinity) if affinity else "auto")
|
|
|
|
|
|
def make_island(name, params, has_children, body, env):
|
|
return Island(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, opts=None):
|
|
path = opts.get('path') if opts else None
|
|
method = str(opts.get('method', 'get')) if opts else 'get'
|
|
csrf = opts.get('csrf', True) if opts else True
|
|
returns = str(opts.get('returns', 'element')) if opts else 'element'
|
|
if isinstance(csrf, str):
|
|
csrf = csrf.lower() not in ('false', 'nil', 'no')
|
|
return HandlerDef(name=name, params=list(params), body=body, closure=dict(env),
|
|
path=path, method=method.lower(), csrf=csrf, returns=returns)
|
|
|
|
|
|
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
|
|
stream_val = slots.get("stream")
|
|
stream = bool(trampoline(eval_expr(stream_val, env))) if stream_val is not None else False
|
|
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"), stream=stream,
|
|
fallback_expr=slots.get("fallback"), shell_expr=slots.get("shell"),
|
|
closure=dict(env),
|
|
)
|
|
|
|
|
|
def make_thunk(expr, env):
|
|
return _Thunk(expr, env)
|
|
|
|
|
|
def make_spread(attrs):
|
|
return _Spread(attrs if isinstance(attrs, dict) else {})
|
|
|
|
|
|
def is_spread(x):
|
|
return isinstance(x, _Spread)
|
|
|
|
|
|
def spread_attrs(s):
|
|
return s.attrs if isinstance(s, _Spread) else {}
|
|
|
|
|
|
def sx_collect(bucket, value):
|
|
"""Add value to named scope accumulator (deduplicated). Lazily creates root scope."""
|
|
if bucket not in _scope_stacks or not _scope_stacks[bucket]:
|
|
_scope_stacks.setdefault(bucket, []).append({"value": None, "emitted": [], "dedup": True})
|
|
entry = _scope_stacks[bucket][-1]
|
|
if value not in entry["emitted"]:
|
|
entry["emitted"].append(value)
|
|
|
|
|
|
def sx_collected(bucket):
|
|
"""Return all values collected in named scope accumulator."""
|
|
return sx_emitted(bucket)
|
|
|
|
|
|
def sx_clear_collected(bucket):
|
|
"""Clear nearest scope's accumulator for name."""
|
|
if bucket in _scope_stacks and _scope_stacks[bucket]:
|
|
_scope_stacks[bucket][-1]["emitted"] = []
|
|
|
|
|
|
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 component_affinity(c):
|
|
return getattr(c, 'affinity', 'auto')
|
|
|
|
|
|
def component_param_types(c):
|
|
return getattr(c, 'param_types', None)
|
|
|
|
|
|
def component_set_param_types(c, d):
|
|
c.param_types = d
|
|
|
|
|
|
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_island(x):
|
|
return isinstance(x, Island)
|
|
|
|
|
|
def is_identical(a, b):
|
|
return a is b
|
|
|
|
|
|
|
|
def invoke(f, *args):
|
|
"""Call f with args — handles both native callables and SX lambdas.
|
|
|
|
In Python, all transpiled lambdas are natively callable, so this is
|
|
just a direct call. The JS host needs dispatch logic here because
|
|
SX lambdas from runtime-evaluated code are objects, not functions.
|
|
"""
|
|
return f(*args)
|
|
|
|
|
|
def json_serialize(obj):
|
|
import json
|
|
return json.dumps(obj)
|
|
|
|
|
|
def is_empty_dict(d):
|
|
if not isinstance(d, dict):
|
|
return True
|
|
return len(d) == 0
|
|
|
|
|
|
# DOM event primitives — no-ops on server (browser-only).
|
|
def dom_listen(el, name, handler):
|
|
return lambda: None
|
|
|
|
def dom_dispatch(el, name, detail=None):
|
|
return False
|
|
|
|
def event_detail(e):
|
|
return None
|
|
|
|
|
|
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 _ensure_env(env).extend()
|
|
|
|
|
|
def env_merge(base, overlay):
|
|
base = _ensure_env(base)
|
|
overlay = _ensure_env(overlay)
|
|
if base is overlay:
|
|
# Same env — just extend with empty local scope for params
|
|
return base.extend()
|
|
# Check if base is an ancestor of overlay — if so, no need to merge
|
|
# (common for self-recursive calls where closure == caller's ancestor)
|
|
p = overlay
|
|
depth = 0
|
|
while p is not None and depth < 100:
|
|
if p is base:
|
|
return base.extend()
|
|
p = getattr(p, '_parent', None)
|
|
depth += 1
|
|
# MergedEnv: reads walk base then overlay; set! walks base only
|
|
return _MergedEnv({}, primary=base, secondary=overlay)
|
|
|
|
|
|
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):
|
|
"""Placeholder — overridden by transpiled version from render.sx."""
|
|
return False
|
|
|
|
|
|
# Render dispatch -- set by adapter
|
|
_render_expr_fn = None
|
|
|
|
# Render mode flag -- set by render-to-html/aser, checked by eval-list
|
|
# When false, render expressions (HTML tags, components) fall through to eval-call
|
|
_render_mode = False
|
|
|
|
|
|
def render_active_p():
|
|
return _render_mode
|
|
|
|
|
|
def set_render_active_b(val):
|
|
global _render_mode
|
|
_render_mode = bool(val)
|
|
|
|
|
|
def render_expr(expr, env):
|
|
if _render_expr_fn:
|
|
return _render_expr_fn(expr, env)
|
|
# No adapter — fall through to eval_call so components still evaluate
|
|
return eval_call(first(expr), rest(expr), env)
|
|
|
|
|
|
def strip_prefix(s, prefix):
|
|
return s[len(prefix):] if s.startswith(prefix) else s
|
|
|
|
|
|
def debug_log(*args):
|
|
import sys
|
|
print(*args, file=sys.stderr)
|
|
|
|
|
|
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)
|
|
|
|
|
|
def sx_expr_source(x):
|
|
return x.source if isinstance(x, SxExpr) else str(x)
|
|
|
|
|
|
try:
|
|
from shared.sx.types import EvalError
|
|
except ImportError:
|
|
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"))
|
|
|
|
|
|
_SPECIAL_FORM_NAMES = frozenset() # Placeholder — overridden by transpiled adapter-sx.sx
|
|
_HO_FORM_NAMES = frozenset()
|
|
|
|
def is_special_form(name):
|
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
|
return False
|
|
|
|
def is_ho_form(name):
|
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
|
return False
|
|
|
|
|
|
def aser_special(name, expr, env):
|
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
|
return trampoline(eval_expr(expr, env))
|
|
|
|
|
|
# =========================================================================
|
|
# 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 = {}
|
|
|
|
|
|
# core.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
|
|
|
|
|
|
# core.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
|
|
|
|
|
|
# core.logic
|
|
PRIMITIVES["not"] = lambda x: not sx_truthy(x)
|
|
|
|
|
|
# core.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
|
|
|
|
|
|
# core.strings
|
|
PRIMITIVES["str"] = sx_str
|
|
PRIMITIVES["char-from-code"] = lambda n: chr(_b_int(n))
|
|
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(str(x) for x in coll)
|
|
PRIMITIVES["replace"] = lambda s, old, new: s.replace(old, new)
|
|
PRIMITIVES["index-of"] = lambda s, needle, start=0: str(s).find(needle, start)
|
|
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[int(a):] if (b is None or b is NIL) else c[int(a):int(b)]
|
|
PRIMITIVES["concat"] = lambda *args: _b_sum((a for a in args if a), [])
|
|
|
|
|
|
# core.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 (c.get(k, default) if hasattr(c, 'get') 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 if isinstance(x, list) else [x])
|
|
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)]
|
|
|
|
|
|
# core.dict
|
|
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["has-key?"] = lambda d, k: isinstance(d, _b_dict) and k in d
|
|
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["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)]
|
|
|
|
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
|
|
|
|
|
|
# stdlib.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["parse-datetime"] = lambda s: str(s) if s else NIL
|
|
|
|
def _sx_parse_int(v, default=0):
|
|
if v is None or v is NIL:
|
|
return default
|
|
s = str(v).strip()
|
|
# Match JS parseInt: extract leading integer portion
|
|
import re as _re
|
|
m = _re.match(r'^[+-]?\d+', s)
|
|
if m:
|
|
return _b_int(m.group())
|
|
return default
|
|
|
|
|
|
# stdlib.text
|
|
PRIMITIVES["pluralize"] = lambda n, s="", p="s": s if n == 1 else p
|
|
PRIMITIVES["escape"] = escape_html
|
|
PRIMITIVES["strip-tags"] = lambda s: _strip_tags(str(s))
|
|
|
|
import re as _re
|
|
def _strip_tags(s):
|
|
return _re.sub(r"<[^>]+>", "", s)
|
|
|
|
|
|
# stdlib.style — stubs (CSSX needs full runtime)
|
|
|
|
|
|
# stdlib.debug
|
|
PRIMITIVES["assert"] = lambda cond, msg="Assertion failed": (_ for _ in ()).throw(RuntimeError(f"Assertion error: {msg}")) if not sx_truthy(cond) else True
|
|
|
|
|
|
# stdlib.spread — spread + collect + scope primitives
|
|
PRIMITIVES["make-spread"] = make_spread
|
|
PRIMITIVES["spread?"] = is_spread
|
|
PRIMITIVES["spread-attrs"] = spread_attrs
|
|
PRIMITIVES["collect!"] = sx_collect
|
|
PRIMITIVES["collected"] = sx_collected
|
|
PRIMITIVES["clear-collected!"] = sx_clear_collected
|
|
# scope — unified render-time dynamic scope
|
|
PRIMITIVES["scope-push!"] = scope_push
|
|
PRIMITIVES["scope-pop!"] = scope_pop
|
|
# provide-push!/provide-pop! — aliases for scope-push!/scope-pop!
|
|
PRIMITIVES["provide-push!"] = provide_push
|
|
PRIMITIVES["provide-pop!"] = provide_pop
|
|
PRIMITIVES["context"] = sx_context
|
|
PRIMITIVES["emit!"] = sx_emit
|
|
PRIMITIVES["emitted"] = sx_emitted
|
|
|
|
|
|
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()}
|
|
|
|
# Dynamic wind support (used by sf-dynamic-wind in eval.sx)
|
|
_wind_stack = []
|
|
|
|
def push_wind_b(before, after):
|
|
_wind_stack.append((before, after))
|
|
return NIL
|
|
|
|
def pop_wind_b():
|
|
if _wind_stack:
|
|
_wind_stack.pop()
|
|
return NIL
|
|
|
|
def call_thunk(f, env):
|
|
"""Call a zero-arg function/lambda."""
|
|
if is_callable(f) and not is_lambda(f):
|
|
return f()
|
|
if is_lambda(f):
|
|
return trampoline(call_lambda(f, [], env))
|
|
return trampoline(eval_expr([f], env))
|
|
|
|
def dynamic_wind_call(before, body, after, env):
|
|
"""Execute dynamic-wind with try/finally for error safety."""
|
|
call_thunk(before, env)
|
|
push_wind_b(before, after)
|
|
try:
|
|
result = call_thunk(body, env)
|
|
finally:
|
|
pop_wind_b()
|
|
call_thunk(after, env)
|
|
return result
|
|
|
|
# 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"]
|
|
split = PRIMITIVES["split"]
|
|
length = PRIMITIVES["len"]
|
|
merge = PRIMITIVES["merge"]
|
|
trim = PRIMITIVES["trim"]
|
|
replace = PRIMITIVES["replace"]
|
|
parse_int = PRIMITIVES["parse-int"]
|
|
upper = PRIMITIVES["upper"]
|
|
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 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
|
|
|
|
|
|
# =========================================================================
|
|
# Platform: deps module — component dependency analysis
|
|
# =========================================================================
|
|
|
|
import re as _re
|
|
|
|
def component_deps(c):
|
|
"""Return cached deps list for a component (may be empty)."""
|
|
return list(c.deps) if hasattr(c, "deps") and c.deps else []
|
|
|
|
def component_set_deps(c, deps):
|
|
"""Cache deps on a component."""
|
|
c.deps = set(deps) if not isinstance(deps, set) else deps
|
|
|
|
def component_css_classes(c):
|
|
"""Return pre-scanned CSS class list for a component."""
|
|
return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else []
|
|
|
|
def env_components(env):
|
|
"""Placeholder — overridden by transpiled version from deps.sx."""
|
|
return [k for k, v in env.items()
|
|
if isinstance(v, (Component, Macro))]
|
|
|
|
def regex_find_all(pattern, source):
|
|
"""Return list of capture group 1 matches."""
|
|
return [m.group(1) for m in _re.finditer(pattern, source)]
|
|
|
|
def scan_css_classes(source):
|
|
"""Extract CSS class strings from SX source."""
|
|
classes = set()
|
|
for m in _re.finditer(r':class\s+"([^"]*)"', source):
|
|
classes.update(m.group(1).split())
|
|
for m in _re.finditer(r':class\s+\(str\s+((?:"[^"]*"\s*)+)\)', source):
|
|
for s in _re.findall(r'"([^"]*)"', m.group(1)):
|
|
classes.update(s.split())
|
|
for m in _re.finditer(r';;\s*@css\s+(.+)', source):
|
|
classes.update(m.group(1).split())
|
|
return list(classes)
|
|
|
|
def component_io_refs(c):
|
|
"""Return cached IO refs list, or NIL if not yet computed."""
|
|
if not hasattr(c, "io_refs") or c.io_refs is None:
|
|
return NIL
|
|
return list(c.io_refs)
|
|
|
|
def component_set_io_refs(c, refs):
|
|
"""Cache IO refs on a component."""
|
|
c.io_refs = set(refs) if not isinstance(refs, set) else refs
|
|
|
|
|
|
# =========================================================================
|
|
# Platform: CEK module — explicit CEK machine
|
|
# =========================================================================
|
|
|
|
# Standalone aliases for primitives used by cek.sx / frames.sx
|
|
inc = PRIMITIVES["inc"]
|
|
dec = PRIMITIVES["dec"]
|
|
zip_pairs = PRIMITIVES["zip-pairs"]
|
|
|
|
continuation_p = PRIMITIVES["continuation?"]
|
|
|
|
def make_cek_continuation(captured, rest_kont):
|
|
"""Create a Continuation storing captured CEK frames as data."""
|
|
c = Continuation(lambda v=NIL: v)
|
|
c._cek_data = {"captured": captured, "rest-kont": rest_kont}
|
|
return c
|
|
|
|
def continuation_data(c):
|
|
"""Return the _cek_data dict from a CEK continuation."""
|
|
return getattr(c, '_cek_data', {}) or {}
|
|
|
|
|
|
# =========================================================================
|
|
# Platform interface -- Async adapter
|
|
# =========================================================================
|
|
|
|
import contextvars
|
|
import inspect as _inspect
|
|
|
|
from shared.sx.primitives_io import (
|
|
IO_PRIMITIVES, RequestContext, execute_io,
|
|
)
|
|
|
|
# Lazy imports to avoid circular dependency (html.py imports sx_ref.py)
|
|
_css_class_collector_cv = None
|
|
_svg_context_cv = None
|
|
|
|
def _ensure_html_imports():
|
|
global _css_class_collector_cv, _svg_context_cv
|
|
if _css_class_collector_cv is None:
|
|
from shared.sx.html import css_class_collector, _svg_context
|
|
_css_class_collector_cv = css_class_collector
|
|
_svg_context_cv = _svg_context
|
|
|
|
# When True, async_aser expands known components server-side
|
|
_expand_components_cv: contextvars.ContextVar[bool] = contextvars.ContextVar(
|
|
"_expand_components_ref", default=False
|
|
)
|
|
|
|
|
|
class _AsyncThunk:
|
|
__slots__ = ("expr", "env", "ctx")
|
|
def __init__(self, expr, env, ctx):
|
|
self.expr = expr
|
|
self.env = env
|
|
self.ctx = ctx
|
|
|
|
|
|
def io_primitive_p(name):
|
|
return name in IO_PRIMITIVES
|
|
|
|
|
|
def expand_components_p():
|
|
return _expand_components_cv.get()
|
|
|
|
|
|
def svg_context_p():
|
|
_ensure_html_imports()
|
|
return _svg_context_cv.get(False)
|
|
|
|
|
|
def svg_context_set(val):
|
|
_ensure_html_imports()
|
|
return _svg_context_cv.set(val)
|
|
|
|
|
|
def svg_context_reset(token):
|
|
_ensure_html_imports()
|
|
_svg_context_cv.reset(token)
|
|
|
|
|
|
def css_class_collect(val):
|
|
_ensure_html_imports()
|
|
collector = _css_class_collector_cv.get(None)
|
|
if collector is not None:
|
|
collector.update(str(val).split())
|
|
|
|
|
|
def is_raw_html(x):
|
|
return isinstance(x, _RawHTML)
|
|
|
|
|
|
def make_sx_expr(s):
|
|
return SxExpr(s)
|
|
|
|
|
|
def is_sx_expr(x):
|
|
return isinstance(x, SxExpr)
|
|
|
|
|
|
# Predicate helpers used by adapter-async (these are in PRIMITIVES but
|
|
# the bootstrapped code calls them as plain functions)
|
|
def string_p(x):
|
|
return isinstance(x, str)
|
|
|
|
|
|
def list_p(x):
|
|
return isinstance(x, _b_list)
|
|
|
|
|
|
def number_p(x):
|
|
return isinstance(x, (int, float)) and not isinstance(x, bool)
|
|
|
|
|
|
def is_async_coroutine(x):
|
|
return _inspect.iscoroutine(x)
|
|
|
|
|
|
async def async_await(x):
|
|
return await x
|
|
|
|
|
|
async def _async_trampoline(val):
|
|
while isinstance(val, _AsyncThunk):
|
|
val = await async_eval(val.expr, val.env, val.ctx)
|
|
return val
|
|
|
|
|
|
async def async_eval(expr, env, ctx=None):
|
|
"""Evaluate with I/O primitives. Entry point for async evaluation."""
|
|
if ctx is None:
|
|
ctx = RequestContext()
|
|
result = await _async_eval_inner(expr, env, ctx)
|
|
while isinstance(result, _AsyncThunk):
|
|
result = await _async_eval_inner(result.expr, result.env, result.ctx)
|
|
return result
|
|
|
|
|
|
async def _async_eval_inner(expr, env, ctx):
|
|
"""Intercept I/O primitives, delegate everything else to sync eval."""
|
|
if isinstance(expr, list) and expr:
|
|
head = expr[0]
|
|
if isinstance(head, Symbol) and head.name in IO_PRIMITIVES:
|
|
args_list, kwargs = await _parse_io_args(expr[1:], env, ctx)
|
|
return await execute_io(head.name, args_list, kwargs, ctx)
|
|
is_render = isinstance(expr, list) and is_render_expr(expr)
|
|
result = eval_expr(expr, env)
|
|
result = trampoline(result)
|
|
if is_render and isinstance(result, str):
|
|
return _RawHTML(result)
|
|
return result
|
|
|
|
|
|
async def _parse_io_args(exprs, env, ctx):
|
|
"""Parse and evaluate I/O node args (keyword + positional)."""
|
|
from shared.sx.types import Keyword as _Kw
|
|
args_list = []
|
|
kwargs = {}
|
|
i = 0
|
|
while i < len(exprs):
|
|
item = exprs[i]
|
|
if isinstance(item, _Kw) and i + 1 < len(exprs):
|
|
kwargs[item.name] = await async_eval(exprs[i + 1], env, ctx)
|
|
i += 2
|
|
else:
|
|
args_list.append(await async_eval(item, env, ctx))
|
|
i += 1
|
|
return args_list, kwargs
|
|
|
|
|
|
async def async_eval_to_sx(expr, env, ctx=None):
|
|
"""Evaluate and produce SX source string (wire format)."""
|
|
if ctx is None:
|
|
ctx = RequestContext()
|
|
result = await async_aser(expr, env, ctx)
|
|
if isinstance(result, SxExpr):
|
|
return result
|
|
if result is None or result is NIL:
|
|
return SxExpr("")
|
|
if isinstance(result, str):
|
|
return SxExpr(result)
|
|
return SxExpr(sx_serialize(result))
|
|
|
|
|
|
async def async_eval_slot_to_sx(expr, env, ctx=None):
|
|
"""Like async_eval_to_sx but expands component calls server-side."""
|
|
if ctx is None:
|
|
ctx = RequestContext()
|
|
token = _expand_components_cv.set(True)
|
|
try:
|
|
result = await async_eval_slot_inner(expr, env, ctx)
|
|
if isinstance(result, SxExpr):
|
|
return result
|
|
if result is None or result is NIL:
|
|
return SxExpr("")
|
|
if isinstance(result, str):
|
|
return SxExpr(result)
|
|
return SxExpr(sx_serialize(result))
|
|
finally:
|
|
_expand_components_cv.reset(token)
|
|
|
|
|
|
# === Transpiled from eval ===
|
|
|
|
# trampoline
|
|
def trampoline(val):
|
|
result = val
|
|
if sx_truthy(is_thunk(result)):
|
|
return trampoline(eval_expr(thunk_expr(result), thunk_env(result)))
|
|
else:
|
|
return result
|
|
|
|
# eval-expr
|
|
def eval_expr(expr, env):
|
|
_match = type_of(expr)
|
|
if _match == 'number':
|
|
return expr
|
|
elif _match == 'string':
|
|
return expr
|
|
elif _match == 'boolean':
|
|
return expr
|
|
elif _match == 'nil':
|
|
return NIL
|
|
elif _match == 'symbol':
|
|
name = symbol_name(expr)
|
|
if sx_truthy(env_has(env, name)):
|
|
return env_get(env, name)
|
|
elif sx_truthy(is_primitive(name)):
|
|
return get_primitive(name)
|
|
elif sx_truthy((name == 'true')):
|
|
return True
|
|
elif sx_truthy((name == 'false')):
|
|
return False
|
|
elif sx_truthy((name == 'nil')):
|
|
return NIL
|
|
else:
|
|
debug_log('Undefined symbol:', name, 'primitive?:', is_primitive(name))
|
|
return error(sx_str('Undefined symbol: ', name))
|
|
elif _match == 'keyword':
|
|
return keyword_name(expr)
|
|
elif _match == 'dict':
|
|
return map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr)
|
|
elif _match == 'list':
|
|
if sx_truthy(empty_p(expr)):
|
|
return []
|
|
else:
|
|
return eval_list(expr, env)
|
|
else:
|
|
return expr
|
|
|
|
# eval-list
|
|
def eval_list(expr, env):
|
|
head = first(expr)
|
|
args = rest(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')))))):
|
|
return map(lambda x: trampoline(eval_expr(x, env)), expr)
|
|
else:
|
|
if sx_truthy((type_of(head) == 'symbol')):
|
|
name = symbol_name(head)
|
|
if sx_truthy((name == 'if')):
|
|
return sf_if(args, env)
|
|
elif sx_truthy((name == 'when')):
|
|
return sf_when(args, env)
|
|
elif sx_truthy((name == 'cond')):
|
|
return sf_cond(args, env)
|
|
elif sx_truthy((name == 'case')):
|
|
return sf_case(args, env)
|
|
elif sx_truthy((name == 'and')):
|
|
return sf_and(args, env)
|
|
elif sx_truthy((name == 'or')):
|
|
return sf_or(args, env)
|
|
elif sx_truthy((name == 'let')):
|
|
return sf_let(args, env)
|
|
elif sx_truthy((name == 'let*')):
|
|
return sf_let(args, env)
|
|
elif sx_truthy((name == 'letrec')):
|
|
return sf_letrec(args, env)
|
|
elif sx_truthy((name == 'lambda')):
|
|
return sf_lambda(args, env)
|
|
elif sx_truthy((name == 'fn')):
|
|
return sf_lambda(args, env)
|
|
elif sx_truthy((name == 'define')):
|
|
return sf_define(args, env)
|
|
elif sx_truthy((name == 'defcomp')):
|
|
return sf_defcomp(args, env)
|
|
elif sx_truthy((name == 'defisland')):
|
|
return sf_defisland(args, env)
|
|
elif sx_truthy((name == 'defmacro')):
|
|
return sf_defmacro(args, env)
|
|
elif sx_truthy((name == 'defstyle')):
|
|
return sf_defstyle(args, env)
|
|
elif sx_truthy((name == 'defhandler')):
|
|
return sf_defhandler(args, env)
|
|
elif sx_truthy((name == 'defpage')):
|
|
return sf_defpage(args, env)
|
|
elif sx_truthy((name == 'defquery')):
|
|
return sf_defquery(args, env)
|
|
elif sx_truthy((name == 'defaction')):
|
|
return sf_defaction(args, env)
|
|
elif sx_truthy((name == 'deftype')):
|
|
return sf_deftype(args, env)
|
|
elif sx_truthy((name == 'defeffect')):
|
|
return sf_defeffect(args, env)
|
|
elif sx_truthy((name == 'begin')):
|
|
return sf_begin(args, env)
|
|
elif sx_truthy((name == 'do')):
|
|
return sf_begin(args, env)
|
|
elif sx_truthy((name == 'quote')):
|
|
return sf_quote(args, env)
|
|
elif sx_truthy((name == 'quasiquote')):
|
|
return sf_quasiquote(args, env)
|
|
elif sx_truthy((name == '->')):
|
|
return sf_thread_first(args, env)
|
|
elif sx_truthy((name == 'set!')):
|
|
return sf_set_bang(args, env)
|
|
elif sx_truthy((name == 'reset')):
|
|
return sf_reset(args, env)
|
|
elif sx_truthy((name == 'shift')):
|
|
return sf_shift(args, env)
|
|
elif sx_truthy((name == 'dynamic-wind')):
|
|
return sf_dynamic_wind(args, env)
|
|
elif sx_truthy((name == 'scope')):
|
|
return sf_scope(args, env)
|
|
elif sx_truthy((name == 'provide')):
|
|
return sf_provide(args, env)
|
|
elif sx_truthy((name == 'map')):
|
|
return ho_map(args, env)
|
|
elif sx_truthy((name == 'map-indexed')):
|
|
return ho_map_indexed(args, env)
|
|
elif sx_truthy((name == 'filter')):
|
|
return ho_filter(args, env)
|
|
elif sx_truthy((name == 'reduce')):
|
|
return ho_reduce(args, env)
|
|
elif sx_truthy((name == 'some')):
|
|
return ho_some(args, env)
|
|
elif sx_truthy((name == 'every?')):
|
|
return ho_every(args, env)
|
|
elif sx_truthy((name == 'for-each')):
|
|
return ho_for_each(args, env)
|
|
elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
|
|
mac = env_get(env, name)
|
|
return make_thunk(expand_macro(mac, args, env), env)
|
|
elif sx_truthy((render_active_p() if not sx_truthy(render_active_p()) else is_render_expr(expr))):
|
|
return render_expr(expr, env)
|
|
else:
|
|
return eval_call(head, args, env)
|
|
else:
|
|
return eval_call(head, args, env)
|
|
|
|
# eval-call
|
|
def eval_call(head, args, env):
|
|
f = trampoline(eval_expr(head, env))
|
|
evaluated_args = map(lambda a: trampoline(eval_expr(a, env)), 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))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))):
|
|
return apply(f, evaluated_args)
|
|
elif sx_truthy(is_lambda(f)):
|
|
return call_lambda(f, evaluated_args, env)
|
|
elif sx_truthy(is_component(f)):
|
|
return call_component(f, args, env)
|
|
elif sx_truthy(is_island(f)):
|
|
return call_component(f, args, env)
|
|
else:
|
|
return error(sx_str('Not callable: ', inspect(f)))
|
|
|
|
# call-lambda
|
|
def call_lambda(f, args, caller_env):
|
|
params = lambda_params(f)
|
|
local = env_merge(lambda_closure(f), caller_env)
|
|
if sx_truthy((len(args) > len(params))):
|
|
return error(sx_str((lambda_name(f) if sx_truthy(lambda_name(f)) else 'lambda'), ' expects ', len(params), ' args, got ', len(args)))
|
|
else:
|
|
for pair in zip(params, args):
|
|
local[first(pair)] = nth(pair, 1)
|
|
for p in slice(params, len(args)):
|
|
local[p] = NIL
|
|
return make_thunk(lambda_body(f), local)
|
|
|
|
# call-component
|
|
def call_component(comp, raw_args, env):
|
|
parsed = parse_keyword_args(raw_args, env)
|
|
kwargs = first(parsed)
|
|
children = nth(parsed, 1)
|
|
local = env_merge(component_closure(comp), env)
|
|
for p in component_params(comp):
|
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_get(kwargs, p)) else NIL)
|
|
if sx_truthy(component_has_children(comp)):
|
|
local['children'] = children
|
|
return make_thunk(component_body(comp), local)
|
|
|
|
# parse-keyword-args
|
|
def parse_keyword_args(raw_args, env):
|
|
kwargs = {}
|
|
children = []
|
|
i = 0
|
|
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)
|
|
return [kwargs, children]
|
|
|
|
# sf-if
|
|
def sf_if(args, env):
|
|
condition = trampoline(eval_expr(first(args), env))
|
|
if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))):
|
|
return make_thunk(nth(args, 1), env)
|
|
else:
|
|
if sx_truthy((len(args) > 2)):
|
|
return make_thunk(nth(args, 2), env)
|
|
else:
|
|
return NIL
|
|
|
|
# sf-when
|
|
def sf_when(args, env):
|
|
condition = trampoline(eval_expr(first(args), env))
|
|
if sx_truthy((condition if not sx_truthy(condition) else (not sx_truthy(is_nil(condition))))):
|
|
for e in slice(args, 1, (len(args) - 1)):
|
|
trampoline(eval_expr(e, env))
|
|
return make_thunk(last(args), env)
|
|
else:
|
|
return NIL
|
|
|
|
# cond-scheme?
|
|
def cond_scheme_p(clauses):
|
|
return every_p(lambda c: ((type_of(c) == 'list') if not sx_truthy((type_of(c) == 'list')) else (len(c) == 2)), clauses)
|
|
|
|
# sf-cond
|
|
def sf_cond(args, env):
|
|
if sx_truthy(cond_scheme_p(args)):
|
|
return sf_cond_scheme(args, env)
|
|
else:
|
|
return sf_cond_clojure(args, env)
|
|
|
|
# sf-cond-scheme
|
|
def sf_cond_scheme(clauses, env):
|
|
if sx_truthy(empty_p(clauses)):
|
|
return NIL
|
|
else:
|
|
clause = first(clauses)
|
|
test = first(clause)
|
|
body = nth(clause, 1)
|
|
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')))):
|
|
return make_thunk(body, env)
|
|
else:
|
|
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
return make_thunk(body, env)
|
|
else:
|
|
return sf_cond_scheme(rest(clauses), env)
|
|
|
|
# sf-cond-clojure
|
|
def sf_cond_clojure(clauses, env):
|
|
if sx_truthy((len(clauses) < 2)):
|
|
return NIL
|
|
else:
|
|
test = first(clauses)
|
|
body = nth(clauses, 1)
|
|
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'))))):
|
|
return make_thunk(body, env)
|
|
else:
|
|
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
return make_thunk(body, env)
|
|
else:
|
|
return sf_cond_clojure(slice(clauses, 2), env)
|
|
|
|
# sf-case
|
|
def sf_case(args, env):
|
|
match_val = trampoline(eval_expr(first(args), env))
|
|
clauses = rest(args)
|
|
return sf_case_loop(match_val, clauses, env)
|
|
|
|
# sf-case-loop
|
|
def sf_case_loop(match_val, clauses, env):
|
|
if sx_truthy((len(clauses) < 2)):
|
|
return NIL
|
|
else:
|
|
test = first(clauses)
|
|
body = nth(clauses, 1)
|
|
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'))))):
|
|
return make_thunk(body, env)
|
|
else:
|
|
if sx_truthy((match_val == trampoline(eval_expr(test, env)))):
|
|
return make_thunk(body, env)
|
|
else:
|
|
return sf_case_loop(match_val, slice(clauses, 2), env)
|
|
|
|
# sf-and
|
|
def sf_and(args, env):
|
|
if sx_truthy(empty_p(args)):
|
|
return True
|
|
else:
|
|
val = trampoline(eval_expr(first(args), env))
|
|
if sx_truthy((not sx_truthy(val))):
|
|
return val
|
|
else:
|
|
if sx_truthy((len(args) == 1)):
|
|
return val
|
|
else:
|
|
return sf_and(rest(args), env)
|
|
|
|
# sf-or
|
|
def sf_or(args, env):
|
|
if sx_truthy(empty_p(args)):
|
|
return False
|
|
else:
|
|
val = trampoline(eval_expr(first(args), env))
|
|
if sx_truthy(val):
|
|
return val
|
|
else:
|
|
return sf_or(rest(args), env)
|
|
|
|
# sf-let
|
|
def sf_let(args, env):
|
|
if sx_truthy((type_of(first(args)) == 'symbol')):
|
|
return sf_named_let(args, env)
|
|
else:
|
|
bindings = first(args)
|
|
body = rest(args)
|
|
local = env_extend(env)
|
|
if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))):
|
|
for binding in bindings:
|
|
vname = (symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))
|
|
local[vname] = trampoline(eval_expr(nth(binding, 1), local))
|
|
else:
|
|
i = 0
|
|
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)))
|
|
for e in slice(body, 0, (len(body) - 1)):
|
|
trampoline(eval_expr(e, local))
|
|
return make_thunk(last(body), local)
|
|
|
|
# sf-named-let
|
|
def sf_named_let(args, env):
|
|
loop_name = symbol_name(first(args))
|
|
bindings = nth(args, 1)
|
|
body = slice(args, 2)
|
|
params = []
|
|
inits = []
|
|
if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))):
|
|
for binding in bindings:
|
|
params.append((symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding)))
|
|
inits.append(nth(binding, 1))
|
|
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)))
|
|
loop_body = (first(body) if sx_truthy((len(body) == 1)) else cons(make_symbol('begin'), body))
|
|
loop_fn = make_lambda(params, loop_body, env)
|
|
loop_fn.name = loop_name
|
|
lambda_closure(loop_fn)[loop_name] = loop_fn
|
|
init_vals = map(lambda e: trampoline(eval_expr(e, env)), inits)
|
|
return call_lambda(loop_fn, init_vals, env)
|
|
|
|
# sf-lambda
|
|
def sf_lambda(args, env):
|
|
params_expr = first(args)
|
|
body_exprs = rest(args)
|
|
body = (first(body_exprs) if sx_truthy((len(body_exprs) == 1)) else cons(make_symbol('begin'), body_exprs))
|
|
param_names = map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else (symbol_name(first(p)) if sx_truthy(((type_of(p) == 'list') if not sx_truthy((type_of(p) == 'list')) else ((len(p) == 3) if not sx_truthy((len(p) == 3)) else ((type_of(nth(p, 1)) == 'keyword') if not sx_truthy((type_of(nth(p, 1)) == 'keyword')) else (keyword_name(nth(p, 1)) == 'as'))))) else p)), params_expr)
|
|
return make_lambda(param_names, body, env)
|
|
|
|
# sf-define
|
|
def sf_define(args, env):
|
|
name_sym = first(args)
|
|
has_effects = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))
|
|
val_idx = (3 if sx_truthy(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))) else 1)
|
|
value = trampoline(eval_expr(nth(args, val_idx), env))
|
|
if sx_truthy((is_lambda(value) if not sx_truthy(is_lambda(value)) else is_nil(lambda_name(value)))):
|
|
value.name = symbol_name(name_sym)
|
|
env[symbol_name(name_sym)] = value
|
|
if sx_truthy(has_effects):
|
|
effects_raw = nth(args, 2)
|
|
effect_list = (map(lambda e: (symbol_name(e) if sx_truthy((type_of(e) == 'symbol')) else sx_str(e)), effects_raw) if sx_truthy((type_of(effects_raw) == 'list')) else [sx_str(effects_raw)])
|
|
effect_anns = (env_get(env, '*effect-annotations*') if sx_truthy(env_has(env, '*effect-annotations*')) else {})
|
|
effect_anns[symbol_name(name_sym)] = effect_list
|
|
env['*effect-annotations*'] = effect_anns
|
|
return value
|
|
|
|
# sf-defcomp
|
|
def sf_defcomp(args, env):
|
|
name_sym = first(args)
|
|
params_raw = nth(args, 1)
|
|
body = last(args)
|
|
comp_name = strip_prefix(symbol_name(name_sym), '~')
|
|
parsed = parse_comp_params(params_raw)
|
|
params = first(parsed)
|
|
has_children = nth(parsed, 1)
|
|
param_types = nth(parsed, 2)
|
|
affinity = defcomp_kwarg(args, 'affinity', 'auto')
|
|
comp = make_component(comp_name, params, has_children, body, env, affinity)
|
|
effects = defcomp_kwarg(args, 'effects', NIL)
|
|
if sx_truthy(((not sx_truthy(is_nil(param_types))) if not sx_truthy((not sx_truthy(is_nil(param_types)))) else (not sx_truthy(empty_p(keys(param_types)))))):
|
|
component_set_param_types(comp, param_types)
|
|
if sx_truthy((not sx_truthy(is_nil(effects)))):
|
|
effect_list = (map(lambda e: (symbol_name(e) if sx_truthy((type_of(e) == 'symbol')) else sx_str(e)), effects) if sx_truthy((type_of(effects) == 'list')) else [sx_str(effects)])
|
|
effect_anns = (env_get(env, '*effect-annotations*') if sx_truthy(env_has(env, '*effect-annotations*')) else {})
|
|
effect_anns[symbol_name(name_sym)] = effect_list
|
|
env['*effect-annotations*'] = effect_anns
|
|
env[symbol_name(name_sym)] = comp
|
|
return comp
|
|
|
|
# defcomp-kwarg
|
|
def defcomp_kwarg(args, key, default_):
|
|
_cells = {}
|
|
end = (len(args) - 1)
|
|
_cells['result'] = default_
|
|
for i in range(2, end, 1):
|
|
if sx_truthy(((type_of(nth(args, i)) == 'keyword') if not sx_truthy((type_of(nth(args, i)) == 'keyword')) else ((keyword_name(nth(args, i)) == key) if not sx_truthy((keyword_name(nth(args, i)) == key)) else ((i + 1) < end)))):
|
|
val = nth(args, (i + 1))
|
|
_cells['result'] = (keyword_name(val) if sx_truthy((type_of(val) == 'keyword')) else val)
|
|
return _cells['result']
|
|
|
|
# parse-comp-params
|
|
def parse_comp_params(params_expr):
|
|
_cells = {}
|
|
params = []
|
|
param_types = {}
|
|
_cells['has_children'] = False
|
|
_cells['in_key'] = False
|
|
for p in params_expr:
|
|
if sx_truthy(((type_of(p) == 'list') if not sx_truthy((type_of(p) == 'list')) else ((len(p) == 3) if not sx_truthy((len(p) == 3)) else ((type_of(first(p)) == 'symbol') if not sx_truthy((type_of(first(p)) == 'symbol')) else ((type_of(nth(p, 1)) == 'keyword') if not sx_truthy((type_of(nth(p, 1)) == 'keyword')) else (keyword_name(nth(p, 1)) == 'as')))))):
|
|
name = symbol_name(first(p))
|
|
ptype = nth(p, 2)
|
|
type_val = (symbol_name(ptype) if sx_truthy((type_of(ptype) == 'symbol')) else ptype)
|
|
if sx_truthy((not sx_truthy(_cells['has_children']))):
|
|
params.append(name)
|
|
param_types[name] = type_val
|
|
else:
|
|
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'], param_types]
|
|
|
|
# sf-defisland
|
|
def sf_defisland(args, env):
|
|
name_sym = first(args)
|
|
params_raw = nth(args, 1)
|
|
body = last(args)
|
|
comp_name = strip_prefix(symbol_name(name_sym), '~')
|
|
parsed = parse_comp_params(params_raw)
|
|
params = first(parsed)
|
|
has_children = nth(parsed, 1)
|
|
island = make_island(comp_name, params, has_children, body, env)
|
|
env[symbol_name(name_sym)] = island
|
|
return island
|
|
|
|
# sf-defmacro
|
|
def sf_defmacro(args, env):
|
|
name_sym = first(args)
|
|
params_raw = nth(args, 1)
|
|
body = nth(args, 2)
|
|
parsed = parse_macro_params(params_raw)
|
|
params = first(parsed)
|
|
rest_param = nth(parsed, 1)
|
|
mac = make_macro(params, rest_param, body, env, symbol_name(name_sym))
|
|
env[symbol_name(name_sym)] = mac
|
|
return mac
|
|
|
|
# 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
|
|
def sf_defstyle(args, env):
|
|
name_sym = first(args)
|
|
value = trampoline(eval_expr(nth(args, 1), env))
|
|
env[symbol_name(name_sym)] = value
|
|
return value
|
|
|
|
# make-type-def
|
|
def make_type_def(name, params, body):
|
|
return {'name': name, 'params': params, 'body': body}
|
|
|
|
# normalize-type-body
|
|
def normalize_type_body(body):
|
|
if sx_truthy(is_nil(body)):
|
|
return 'nil'
|
|
elif sx_truthy((type_of(body) == 'symbol')):
|
|
return symbol_name(body)
|
|
elif sx_truthy((type_of(body) == 'string')):
|
|
return body
|
|
elif sx_truthy((type_of(body) == 'keyword')):
|
|
return keyword_name(body)
|
|
elif sx_truthy((type_of(body) == 'dict')):
|
|
return map_dict(lambda k, v: normalize_type_body(v), body)
|
|
elif sx_truthy((type_of(body) == 'list')):
|
|
if sx_truthy(empty_p(body)):
|
|
return 'any'
|
|
else:
|
|
head = first(body)
|
|
head_name = (symbol_name(head) if sx_truthy((type_of(head) == 'symbol')) else sx_str(head))
|
|
if sx_truthy((head_name == 'union')):
|
|
return cons('or', map(normalize_type_body, rest(body)))
|
|
else:
|
|
return cons(head_name, map(normalize_type_body, rest(body)))
|
|
else:
|
|
return sx_str(body)
|
|
|
|
# sf-deftype
|
|
def sf_deftype(args, env):
|
|
name_or_form = first(args)
|
|
body_expr = nth(args, 1)
|
|
type_name = NIL
|
|
type_params = []
|
|
if sx_truthy((type_of(name_or_form) == 'symbol')):
|
|
type_name = symbol_name(name_or_form)
|
|
else:
|
|
if sx_truthy((type_of(name_or_form) == 'list')):
|
|
type_name = symbol_name(first(name_or_form))
|
|
type_params = map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else sx_str(p)), rest(name_or_form))
|
|
body = normalize_type_body(body_expr)
|
|
registry = (env_get(env, '*type-registry*') if sx_truthy(env_has(env, '*type-registry*')) else {})
|
|
registry[type_name] = make_type_def(type_name, type_params, body)
|
|
env['*type-registry*'] = registry
|
|
return NIL
|
|
|
|
# sf-defeffect
|
|
def sf_defeffect(args, env):
|
|
effect_name = (symbol_name(first(args)) if sx_truthy((type_of(first(args)) == 'symbol')) else sx_str(first(args)))
|
|
registry = (env_get(env, '*effect-registry*') if sx_truthy(env_has(env, '*effect-registry*')) else [])
|
|
if sx_truthy((not sx_truthy(contains_p(registry, effect_name)))):
|
|
registry.append(effect_name)
|
|
env['*effect-registry*'] = registry
|
|
return NIL
|
|
|
|
# sf-begin
|
|
def sf_begin(args, env):
|
|
if sx_truthy(empty_p(args)):
|
|
return NIL
|
|
else:
|
|
for e in slice(args, 0, (len(args) - 1)):
|
|
trampoline(eval_expr(e, env))
|
|
return make_thunk(last(args), env)
|
|
|
|
# sf-quote
|
|
def sf_quote(args, env):
|
|
if sx_truthy(empty_p(args)):
|
|
return NIL
|
|
else:
|
|
return first(args)
|
|
|
|
# sf-quasiquote
|
|
def sf_quasiquote(args, env):
|
|
return qq_expand(first(args), env)
|
|
|
|
# qq-expand
|
|
def qq_expand(template, env):
|
|
if sx_truthy((not sx_truthy((type_of(template) == 'list')))):
|
|
return template
|
|
else:
|
|
if sx_truthy(empty_p(template)):
|
|
return []
|
|
else:
|
|
head = first(template)
|
|
if sx_truthy(((type_of(head) == 'symbol') if not sx_truthy((type_of(head) == 'symbol')) else (symbol_name(head) == 'unquote'))):
|
|
return trampoline(eval_expr(nth(template, 1), env))
|
|
else:
|
|
return 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 concat(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 concat(result, [qq_expand(item, env)])), [], template)
|
|
|
|
# sf-thread-first
|
|
def sf_thread_first(args, env):
|
|
val = trampoline(eval_expr(first(args), env))
|
|
return 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))
|
|
|
|
# sf-set!
|
|
def sf_set_bang(args, env):
|
|
name = symbol_name(first(args))
|
|
value = trampoline(eval_expr(nth(args, 1), env))
|
|
env[name] = value
|
|
return value
|
|
|
|
# sf-letrec
|
|
def sf_letrec(args, env):
|
|
bindings = first(args)
|
|
body = rest(args)
|
|
local = env_extend(env)
|
|
names = []
|
|
val_exprs = []
|
|
if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))):
|
|
for binding in bindings:
|
|
vname = (symbol_name(first(binding)) if sx_truthy((type_of(first(binding)) == 'symbol')) else first(binding))
|
|
names.append(vname)
|
|
val_exprs.append(nth(binding, 1))
|
|
local[vname] = NIL
|
|
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)))
|
|
values = map(lambda e: trampoline(eval_expr(e, local)), val_exprs)
|
|
for pair in zip(names, values):
|
|
local[first(pair)] = nth(pair, 1)
|
|
for val in values:
|
|
if sx_truthy(is_lambda(val)):
|
|
for n in names:
|
|
lambda_closure(val)[n] = env_get(local, n)
|
|
for e in slice(body, 0, (len(body) - 1)):
|
|
trampoline(eval_expr(e, local))
|
|
return make_thunk(last(body), local)
|
|
|
|
# sf-dynamic-wind
|
|
def sf_dynamic_wind(args, env):
|
|
before = trampoline(eval_expr(first(args), env))
|
|
body = trampoline(eval_expr(nth(args, 1), env))
|
|
after = trampoline(eval_expr(nth(args, 2), env))
|
|
return dynamic_wind_call(before, body, after, env)
|
|
|
|
# sf-scope
|
|
def sf_scope(args, env):
|
|
_cells = {}
|
|
name = trampoline(eval_expr(first(args), env))
|
|
rest = slice(args, 1)
|
|
val = NIL
|
|
body_exprs = NIL
|
|
if sx_truthy(((len(rest) >= 2) if not sx_truthy((len(rest) >= 2)) else ((type_of(first(rest)) == 'keyword') if not sx_truthy((type_of(first(rest)) == 'keyword')) else (keyword_name(first(rest)) == 'value')))):
|
|
val = trampoline(eval_expr(nth(rest, 1), env))
|
|
body_exprs = slice(rest, 2)
|
|
else:
|
|
body_exprs = rest
|
|
scope_push(name, val)
|
|
_cells['result'] = NIL
|
|
for e in body_exprs:
|
|
_cells['result'] = trampoline(eval_expr(e, env))
|
|
scope_pop(name)
|
|
return _cells['result']
|
|
|
|
# sf-provide
|
|
def sf_provide(args, env):
|
|
_cells = {}
|
|
name = trampoline(eval_expr(first(args), env))
|
|
val = trampoline(eval_expr(nth(args, 1), env))
|
|
body_exprs = slice(args, 2)
|
|
_cells['result'] = NIL
|
|
scope_push(name, val)
|
|
for e in body_exprs:
|
|
_cells['result'] = trampoline(eval_expr(e, env))
|
|
scope_pop(name)
|
|
return _cells['result']
|
|
|
|
# expand-macro
|
|
def expand_macro(mac, raw_args, env):
|
|
local = env_merge(macro_closure(mac), env)
|
|
for pair in map_indexed(lambda i, p: [p, i], macro_params(mac)):
|
|
local[first(pair)] = (nth(raw_args, nth(pair, 1)) if sx_truthy((nth(pair, 1) < len(raw_args))) else NIL)
|
|
if sx_truthy(macro_rest_param(mac)):
|
|
local[macro_rest_param(mac)] = slice(raw_args, len(macro_params(mac)))
|
|
return trampoline(eval_expr(macro_body(mac), local))
|
|
|
|
# call-fn
|
|
def call_fn(f, args, env):
|
|
if sx_truthy(is_lambda(f)):
|
|
return trampoline(call_lambda(f, args, env))
|
|
elif sx_truthy(is_callable(f)):
|
|
return apply(f, args)
|
|
else:
|
|
return error(sx_str('Not callable in HO form: ', inspect(f)))
|
|
|
|
# ho-map
|
|
def ho_map(args, env):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
return map(lambda item: call_fn(f, [item], env), coll)
|
|
|
|
# ho-map-indexed
|
|
def ho_map_indexed(args, env):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
return map_indexed(lambda i, item: call_fn(f, [i, item], env), coll)
|
|
|
|
# ho-filter
|
|
def ho_filter(args, env):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
return filter(lambda item: call_fn(f, [item], env), coll)
|
|
|
|
# ho-reduce
|
|
def ho_reduce(args, env):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
init = trampoline(eval_expr(nth(args, 1), env))
|
|
coll = trampoline(eval_expr(nth(args, 2), env))
|
|
return reduce(lambda acc, item: call_fn(f, [acc, item], env), init, coll)
|
|
|
|
# ho-some
|
|
def ho_some(args, env):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
return some(lambda item: call_fn(f, [item], env), coll)
|
|
|
|
# ho-every
|
|
def ho_every(args, env):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
return every_p(lambda item: call_fn(f, [item], env), coll)
|
|
|
|
# ho-for-each
|
|
def ho_for_each(args, env):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
for item in coll:
|
|
call_fn(f, [item], env)
|
|
return NIL
|
|
|
|
|
|
# === 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
|
|
|
|
# parse-handler-args
|
|
def parse_handler_args(args):
|
|
_cells = {}
|
|
'Parse defhandler args after the name symbol.\n Scans for :keyword value option pairs, then a list (params), then body.\n Returns dict with keys: opts, params, body.'
|
|
opts = {}
|
|
_cells['params'] = []
|
|
_cells['body'] = NIL
|
|
_cells['i'] = 0
|
|
n = len(args)
|
|
_cells['done'] = False
|
|
for idx in range(0, n):
|
|
if sx_truthy(((not sx_truthy(_cells['done'])) if not sx_truthy((not sx_truthy(_cells['done']))) else (idx == _cells['i']))):
|
|
arg = nth(args, idx)
|
|
if sx_truthy((type_of(arg) == 'keyword')):
|
|
if sx_truthy(((idx + 1) < n)):
|
|
val = nth(args, (idx + 1))
|
|
opts[keyword_name(arg)] = (keyword_name(val) if sx_truthy((type_of(val) == 'keyword')) else val)
|
|
_cells['i'] = (idx + 2)
|
|
elif sx_truthy((type_of(arg) == 'list')):
|
|
_cells['params'] = parse_key_params(arg)
|
|
if sx_truthy(((idx + 1) < n)):
|
|
_cells['body'] = nth(args, (idx + 1))
|
|
_cells['done'] = True
|
|
else:
|
|
_cells['body'] = arg
|
|
_cells['done'] = True
|
|
return {'opts': opts, 'params': _cells['params'], 'body': _cells['body']}
|
|
|
|
# sf-defhandler
|
|
def sf_defhandler(args, env):
|
|
name_sym = first(args)
|
|
name = symbol_name(name_sym)
|
|
parsed = parse_handler_args(rest(args))
|
|
opts = get(parsed, 'opts')
|
|
params = get(parsed, 'params')
|
|
body = get(parsed, 'body')
|
|
hdef = make_handler_def(name, params, body, env, opts)
|
|
env[sx_str('handler:', name)] = hdef
|
|
return hdef
|
|
|
|
# sf-defquery
|
|
def sf_defquery(args, env):
|
|
name_sym = first(args)
|
|
params_raw = nth(args, 1)
|
|
name = symbol_name(name_sym)
|
|
params = parse_key_params(params_raw)
|
|
has_doc = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))
|
|
doc = (nth(args, 2) if sx_truthy(has_doc) else '')
|
|
body = (nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))
|
|
qdef = make_query_def(name, params, doc, body, env)
|
|
env[sx_str('query:', name)] = qdef
|
|
return qdef
|
|
|
|
# sf-defaction
|
|
def sf_defaction(args, env):
|
|
name_sym = first(args)
|
|
params_raw = nth(args, 1)
|
|
name = symbol_name(name_sym)
|
|
params = parse_key_params(params_raw)
|
|
has_doc = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else (type_of(nth(args, 2)) == 'string'))
|
|
doc = (nth(args, 2) if sx_truthy(has_doc) else '')
|
|
body = (nth(args, 3) if sx_truthy(has_doc) else nth(args, 2))
|
|
adef = make_action_def(name, params, doc, body, env)
|
|
env[sx_str('action:', name)] = adef
|
|
return adef
|
|
|
|
# sf-defpage
|
|
def sf_defpage(args, env):
|
|
name_sym = first(args)
|
|
name = symbol_name(name_sym)
|
|
slots = {}
|
|
i = 1
|
|
max_i = len(args)
|
|
for idx in range(1, max_i, 2):
|
|
if sx_truthy(((idx < max_i) if not sx_truthy((idx < max_i)) else (type_of(nth(args, idx)) == 'keyword'))):
|
|
if sx_truthy(((idx + 1) < max_i)):
|
|
slots[keyword_name(nth(args, idx))] = nth(args, (idx + 1))
|
|
pdef = make_page_def(name, slots, env)
|
|
env[sx_str('page:', name)] = pdef
|
|
return pdef
|
|
|
|
# stream-chunk-id
|
|
def stream_chunk_id(chunk):
|
|
if sx_truthy(has_key_p(chunk, 'stream-id')):
|
|
return get(chunk, 'stream-id')
|
|
else:
|
|
return 'stream-content'
|
|
|
|
# stream-chunk-bindings
|
|
def stream_chunk_bindings(chunk):
|
|
return dissoc(chunk, 'stream-id')
|
|
|
|
# normalize-binding-key
|
|
def normalize_binding_key(key):
|
|
return replace(key, '_', '-')
|
|
|
|
# bind-stream-chunk
|
|
def bind_stream_chunk(chunk, base_env):
|
|
env = merge({}, base_env)
|
|
bindings = stream_chunk_bindings(chunk)
|
|
for key in keys(bindings):
|
|
env[normalize_binding_key(key)] = get(bindings, key)
|
|
return env
|
|
|
|
# validate-stream-data
|
|
def validate_stream_data(data):
|
|
return ((type_of(data) == 'list') if not sx_truthy((type_of(data) == 'list')) else every_p(lambda item: (type_of(item) == 'dict'), data))
|
|
|
|
|
|
# === 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?
|
|
def is_definition_form(name):
|
|
return ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defisland') if sx_truthy((name == 'defisland')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))
|
|
|
|
# parse-element-args
|
|
def parse_element_args(args, env):
|
|
attrs = {}
|
|
children = []
|
|
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)
|
|
return [attrs, children]
|
|
|
|
# render-attrs
|
|
def render_attrs(attrs):
|
|
return 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(' ', key, '="', escape_attr(sx_str(val)), '"')))))(dict_get(attrs, key)), keys(attrs)))
|
|
|
|
# eval-cond
|
|
def eval_cond(clauses, env):
|
|
if sx_truthy(cond_scheme_p(clauses)):
|
|
return eval_cond_scheme(clauses, env)
|
|
else:
|
|
return eval_cond_clojure(clauses, env)
|
|
|
|
# eval-cond-scheme
|
|
def eval_cond_scheme(clauses, env):
|
|
if sx_truthy(empty_p(clauses)):
|
|
return NIL
|
|
else:
|
|
clause = first(clauses)
|
|
test = first(clause)
|
|
body = nth(clause, 1)
|
|
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')))):
|
|
return body
|
|
else:
|
|
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
return body
|
|
else:
|
|
return eval_cond_scheme(rest(clauses), env)
|
|
|
|
# eval-cond-clojure
|
|
def eval_cond_clojure(clauses, env):
|
|
if sx_truthy((len(clauses) < 2)):
|
|
return NIL
|
|
else:
|
|
test = first(clauses)
|
|
body = nth(clauses, 1)
|
|
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'))))):
|
|
return body
|
|
else:
|
|
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
return body
|
|
else:
|
|
return eval_cond_clojure(slice(clauses, 2), env)
|
|
|
|
# process-bindings
|
|
def process_bindings(bindings, env):
|
|
local = env_extend(env)
|
|
for pair in bindings:
|
|
if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))):
|
|
name = (symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair)))
|
|
local[name] = trampoline(eval_expr(nth(pair, 1), local))
|
|
return local
|
|
|
|
# is-render-expr?
|
|
def is_render_expr(expr):
|
|
if sx_truthy(((not sx_truthy((type_of(expr) == 'list'))) if sx_truthy((not sx_truthy((type_of(expr) == 'list')))) else empty_p(expr))):
|
|
return False
|
|
else:
|
|
h = first(expr)
|
|
if sx_truthy((not sx_truthy((type_of(h) == 'symbol')))):
|
|
return False
|
|
else:
|
|
n = symbol_name(h)
|
|
return ((n == '<>') if sx_truthy((n == '<>')) else ((n == 'raw!') if sx_truthy((n == 'raw!')) else (starts_with_p(n, '~') if sx_truthy(starts_with_p(n, '~')) else (starts_with_p(n, 'html:') if sx_truthy(starts_with_p(n, 'html:')) else (contains_p(HTML_TAGS, n) if sx_truthy(contains_p(HTML_TAGS, n)) else ((index_of(n, '-') > 0) if not sx_truthy((index_of(n, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword'))))))))
|
|
|
|
# merge-spread-attrs
|
|
def merge_spread_attrs(target, spread_dict):
|
|
for key in keys(spread_dict):
|
|
val = dict_get(spread_dict, key)
|
|
if sx_truthy((key == 'class')):
|
|
existing = dict_get(target, 'class')
|
|
target['class'] = (sx_str(existing, ' ', val) if sx_truthy((existing if not sx_truthy(existing) else (not sx_truthy((existing == ''))))) else val)
|
|
else:
|
|
if sx_truthy((key == 'style')):
|
|
existing = dict_get(target, 'style')
|
|
target['style'] = (sx_str(existing, ';', val) if sx_truthy((existing if not sx_truthy(existing) else (not sx_truthy((existing == ''))))) else val)
|
|
else:
|
|
target[key] = val
|
|
return NIL
|
|
|
|
|
|
# === Transpiled from parser ===
|
|
|
|
# sx-parse
|
|
def sx_parse(source):
|
|
_cells = {}
|
|
_cells['pos'] = 0
|
|
len_src = len(source)
|
|
def skip_comment():
|
|
while True:
|
|
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)
|
|
continue
|
|
break
|
|
def skip_ws():
|
|
while True:
|
|
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'))))):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
continue
|
|
elif sx_truthy((ch == ';')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
skip_comment()
|
|
continue
|
|
else:
|
|
break
|
|
break
|
|
def hex_digit_value(ch):
|
|
return index_of('0123456789abcdef', lower(ch))
|
|
def read_string():
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
_cells['buf'] = ''
|
|
while True:
|
|
if sx_truthy((_cells['pos'] >= len_src)):
|
|
error('Unterminated string')
|
|
break
|
|
else:
|
|
ch = nth(source, _cells['pos'])
|
|
if sx_truthy((ch == '"')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
break
|
|
elif sx_truthy((ch == '\\')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
esc = nth(source, _cells['pos'])
|
|
if sx_truthy((esc == 'u')):
|
|
_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))))
|
|
continue
|
|
else:
|
|
_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)
|
|
continue
|
|
else:
|
|
_cells['buf'] = sx_str(_cells['buf'], ch)
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
continue
|
|
return _cells['buf']
|
|
def read_ident():
|
|
start = _cells['pos']
|
|
while True:
|
|
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)
|
|
continue
|
|
break
|
|
return slice(source, start, _cells['pos'])
|
|
def read_keyword():
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
return make_keyword(read_ident())
|
|
def read_number():
|
|
start = _cells['pos']
|
|
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():
|
|
while True:
|
|
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)
|
|
continue
|
|
break
|
|
read_digits()
|
|
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)
|
|
read_digits()
|
|
if sx_truthy(((_cells['pos'] < len_src) if not sx_truthy((_cells['pos'] < len_src)) else ((nth(source, _cells['pos']) == 'e') if sx_truthy((nth(source, _cells['pos']) == 'e')) else (nth(source, _cells['pos']) == 'E')))):
|
|
_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']) == '+') if sx_truthy((nth(source, _cells['pos']) == '+')) else (nth(source, _cells['pos']) == '-')))):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
read_digits()
|
|
return parse_number(slice(source, start, _cells['pos']))
|
|
def read_symbol():
|
|
name = read_ident()
|
|
if sx_truthy((name == 'true')):
|
|
return True
|
|
elif sx_truthy((name == 'false')):
|
|
return False
|
|
elif sx_truthy((name == 'nil')):
|
|
return NIL
|
|
else:
|
|
return make_symbol(name)
|
|
def read_list(close_ch):
|
|
items = []
|
|
while True:
|
|
skip_ws()
|
|
if sx_truthy((_cells['pos'] >= len_src)):
|
|
error('Unterminated list')
|
|
break
|
|
else:
|
|
if sx_truthy((nth(source, _cells['pos']) == close_ch)):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
break
|
|
else:
|
|
items.append(read_expr())
|
|
continue
|
|
return items
|
|
def read_map():
|
|
result = {}
|
|
while True:
|
|
skip_ws()
|
|
if sx_truthy((_cells['pos'] >= len_src)):
|
|
error('Unterminated map')
|
|
break
|
|
else:
|
|
if sx_truthy((nth(source, _cells['pos']) == '}')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
break
|
|
else:
|
|
key_expr = read_expr()
|
|
key_str = (keyword_name(key_expr) if sx_truthy((type_of(key_expr) == 'keyword')) else sx_str(key_expr))
|
|
val_expr = read_expr()
|
|
result[key_str] = val_expr
|
|
continue
|
|
return result
|
|
def read_raw_string():
|
|
_cells['buf'] = ''
|
|
while True:
|
|
if sx_truthy((_cells['pos'] >= len_src)):
|
|
error('Unterminated raw string')
|
|
break
|
|
else:
|
|
ch = nth(source, _cells['pos'])
|
|
if sx_truthy((ch == '|')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
break
|
|
else:
|
|
_cells['buf'] = sx_str(_cells['buf'], ch)
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
continue
|
|
return _cells['buf']
|
|
def read_expr():
|
|
skip_ws()
|
|
if sx_truthy((_cells['pos'] >= len_src)):
|
|
return error('Unexpected end of input')
|
|
else:
|
|
ch = nth(source, _cells['pos'])
|
|
if sx_truthy((ch == '(')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
return read_list(')')
|
|
elif sx_truthy((ch == '[')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
return read_list(']')
|
|
elif sx_truthy((ch == '{')):
|
|
_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 == "'")):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
return [make_symbol('quote'), read_expr()]
|
|
elif sx_truthy((ch == '`')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
return [make_symbol('quasiquote'), read_expr()]
|
|
elif sx_truthy((ch == ',')):
|
|
_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 == '#')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
if sx_truthy((_cells['pos'] >= len_src)):
|
|
return error('Unexpected end of input after #')
|
|
else:
|
|
dispatch_ch = nth(source, _cells['pos'])
|
|
if sx_truthy((dispatch_ch == ';')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
read_expr()
|
|
return read_expr()
|
|
elif sx_truthy((dispatch_ch == '|')):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
return read_raw_string()
|
|
elif sx_truthy((dispatch_ch == "'")):
|
|
_cells['pos'] = (_cells['pos'] + 1)
|
|
return [make_symbol('quote'), read_expr()]
|
|
elif sx_truthy(ident_start_p(dispatch_ch)):
|
|
macro_name = read_ident()
|
|
handler = reader_macro_get(macro_name)
|
|
if sx_truthy(handler):
|
|
return handler(read_expr())
|
|
else:
|
|
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 (((_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 (((_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()
|
|
else:
|
|
return error(sx_str('Unexpected character: ', ch))
|
|
exprs = []
|
|
while True:
|
|
skip_ws()
|
|
if sx_truthy((_cells['pos'] < len_src)):
|
|
exprs.append(read_expr())
|
|
continue
|
|
break
|
|
return exprs
|
|
|
|
# sx-serialize
|
|
def sx_serialize(val):
|
|
_match = type_of(val)
|
|
if _match == 'nil':
|
|
return 'nil'
|
|
elif _match == 'boolean':
|
|
if sx_truthy(val):
|
|
return 'true'
|
|
else:
|
|
return 'false'
|
|
elif _match == 'number':
|
|
return sx_str(val)
|
|
elif _match == 'string':
|
|
return sx_str('"', escape_string(val), '"')
|
|
elif _match == 'symbol':
|
|
return symbol_name(val)
|
|
elif _match == 'keyword':
|
|
return sx_str(':', keyword_name(val))
|
|
elif _match == 'list':
|
|
return sx_str('(', join(' ', map(sx_serialize, val)), ')')
|
|
elif _match == 'dict':
|
|
return sx_serialize_dict(val)
|
|
elif _match == 'sx-expr':
|
|
return sx_expr_source(val)
|
|
elif _match == 'spread':
|
|
return sx_str('(make-spread ', sx_serialize_dict(spread_attrs(val)), ')')
|
|
else:
|
|
return sx_str(val)
|
|
|
|
# sx-serialize-dict
|
|
def sx_serialize_dict(d):
|
|
return sx_str('{', join(' ', reduce(lambda acc, key: concat(acc, [sx_str(':', key), sx_serialize(dict_get(d, key))]), [], keys(d))), '}')
|
|
|
|
# serialize
|
|
serialize = sx_serialize
|
|
|
|
|
|
# === Transpiled from adapter-html ===
|
|
|
|
# render-to-html
|
|
def render_to_html(expr, env):
|
|
set_render_active_b(True)
|
|
_match = type_of(expr)
|
|
if _match == 'nil':
|
|
return ''
|
|
elif _match == 'string':
|
|
return escape_html(expr)
|
|
elif _match == 'number':
|
|
return sx_str(expr)
|
|
elif _match == 'boolean':
|
|
if sx_truthy(expr):
|
|
return 'true'
|
|
else:
|
|
return 'false'
|
|
elif _match == 'list':
|
|
if sx_truthy(empty_p(expr)):
|
|
return ''
|
|
else:
|
|
return render_list_to_html(expr, env)
|
|
elif _match == 'symbol':
|
|
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
|
elif _match == 'keyword':
|
|
return escape_html(keyword_name(expr))
|
|
elif _match == 'raw-html':
|
|
return raw_html_content(expr)
|
|
elif _match == 'spread':
|
|
sx_emit('element-attrs', spread_attrs(expr))
|
|
return ''
|
|
else:
|
|
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
|
|
|
# render-value-to-html
|
|
def render_value_to_html(val, env):
|
|
_match = type_of(val)
|
|
if _match == 'nil':
|
|
return ''
|
|
elif _match == 'string':
|
|
return escape_html(val)
|
|
elif _match == 'number':
|
|
return sx_str(val)
|
|
elif _match == 'boolean':
|
|
if sx_truthy(val):
|
|
return 'true'
|
|
else:
|
|
return 'false'
|
|
elif _match == 'list':
|
|
return render_list_to_html(val, env)
|
|
elif _match == 'raw-html':
|
|
return raw_html_content(val)
|
|
elif _match == 'spread':
|
|
sx_emit('element-attrs', spread_attrs(val))
|
|
return ''
|
|
else:
|
|
return escape_html(sx_str(val))
|
|
|
|
# RENDER_HTML_FORMS
|
|
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each', 'scope', 'provide']
|
|
|
|
# render-html-form?
|
|
def is_render_html_form(name):
|
|
return contains_p(RENDER_HTML_FORMS, name)
|
|
|
|
# render-list-to-html
|
|
def render_list_to_html(expr, env):
|
|
if sx_truthy(empty_p(expr)):
|
|
return ''
|
|
else:
|
|
head = first(expr)
|
|
if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
|
|
return join('', map(lambda x: render_value_to_html(x, env), expr))
|
|
else:
|
|
name = symbol_name(head)
|
|
args = rest(expr)
|
|
if sx_truthy((name == '<>')):
|
|
return join('', map(lambda x: render_to_html(x, env), args))
|
|
elif sx_truthy((name == 'raw!')):
|
|
return join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args))
|
|
elif sx_truthy((name == 'lake')):
|
|
return render_html_lake(args, env)
|
|
elif sx_truthy((name == 'marsh')):
|
|
return render_html_marsh(args, env)
|
|
elif sx_truthy(contains_p(HTML_TAGS, name)):
|
|
return render_html_element(name, args, env)
|
|
elif sx_truthy((starts_with_p(name, '~') if not sx_truthy(starts_with_p(name, '~')) else (env_has(env, name) if not sx_truthy(env_has(env, name)) else is_island(env_get(env, name))))):
|
|
return render_html_island(env_get(env, name), args, env)
|
|
elif sx_truthy(starts_with_p(name, '~')):
|
|
val = env_get(env, name)
|
|
if sx_truthy(is_component(val)):
|
|
return render_html_component(val, args, env)
|
|
elif sx_truthy(is_macro(val)):
|
|
return render_to_html(expand_macro(val, args, env), env)
|
|
else:
|
|
return error(sx_str('Unknown component: ', name))
|
|
elif sx_truthy(is_render_html_form(name)):
|
|
return dispatch_html_form(name, expr, env)
|
|
elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
|
|
return render_to_html(expand_macro(env_get(env, name), args, env), env)
|
|
else:
|
|
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
|
|
|
# dispatch-html-form
|
|
def dispatch_html_form(name, expr, env):
|
|
if sx_truthy((name == 'if')):
|
|
cond_val = trampoline(eval_expr(nth(expr, 1), env))
|
|
if sx_truthy(cond_val):
|
|
return render_to_html(nth(expr, 2), env)
|
|
else:
|
|
if sx_truthy((len(expr) > 3)):
|
|
return render_to_html(nth(expr, 3), env)
|
|
else:
|
|
return ''
|
|
elif sx_truthy((name == 'when')):
|
|
if sx_truthy((not sx_truthy(trampoline(eval_expr(nth(expr, 1), env))))):
|
|
return ''
|
|
else:
|
|
if sx_truthy((len(expr) == 3)):
|
|
return render_to_html(nth(expr, 2), env)
|
|
else:
|
|
return join('', map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr))))
|
|
elif sx_truthy((name == 'cond')):
|
|
branch = eval_cond(rest(expr), env)
|
|
if sx_truthy(branch):
|
|
return render_to_html(branch, env)
|
|
else:
|
|
return ''
|
|
elif sx_truthy((name == 'case')):
|
|
return render_to_html(trampoline(eval_expr(expr, env)), env)
|
|
elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
|
|
local = process_bindings(nth(expr, 1), env)
|
|
if sx_truthy((len(expr) == 3)):
|
|
return render_to_html(nth(expr, 2), local)
|
|
else:
|
|
return join('', map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr))))
|
|
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
|
|
if sx_truthy((len(expr) == 2)):
|
|
return render_to_html(nth(expr, 1), env)
|
|
else:
|
|
return join('', map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr))))
|
|
elif sx_truthy(is_definition_form(name)):
|
|
trampoline(eval_expr(expr, env))
|
|
return ''
|
|
elif sx_truthy((name == 'map')):
|
|
f = trampoline(eval_expr(nth(expr, 1), env))
|
|
coll = trampoline(eval_expr(nth(expr, 2), env))
|
|
return 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))
|
|
elif sx_truthy((name == 'map-indexed')):
|
|
f = trampoline(eval_expr(nth(expr, 1), env))
|
|
coll = trampoline(eval_expr(nth(expr, 2), env))
|
|
return 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))
|
|
elif sx_truthy((name == 'filter')):
|
|
return render_to_html(trampoline(eval_expr(expr, env)), env)
|
|
elif sx_truthy((name == 'for-each')):
|
|
f = trampoline(eval_expr(nth(expr, 1), env))
|
|
coll = trampoline(eval_expr(nth(expr, 2), env))
|
|
return 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))
|
|
elif sx_truthy((name == 'scope')):
|
|
scope_name = trampoline(eval_expr(nth(expr, 1), env))
|
|
rest_args = slice(expr, 2)
|
|
scope_val = NIL
|
|
body_exprs = NIL
|
|
if sx_truthy(((len(rest_args) >= 2) if not sx_truthy((len(rest_args) >= 2)) else ((type_of(first(rest_args)) == 'keyword') if not sx_truthy((type_of(first(rest_args)) == 'keyword')) else (keyword_name(first(rest_args)) == 'value')))):
|
|
scope_val = trampoline(eval_expr(nth(rest_args, 1), env))
|
|
body_exprs = slice(rest_args, 2)
|
|
else:
|
|
body_exprs = rest_args
|
|
scope_push(scope_name, scope_val)
|
|
result = (render_to_html(first(body_exprs), env) if sx_truthy((len(body_exprs) == 1)) else join('', map(lambda e: render_to_html(e, env), body_exprs)))
|
|
scope_pop(scope_name)
|
|
return result
|
|
elif sx_truthy((name == 'provide')):
|
|
prov_name = trampoline(eval_expr(nth(expr, 1), env))
|
|
prov_val = trampoline(eval_expr(nth(expr, 2), env))
|
|
body_start = 3
|
|
body_count = (len(expr) - 3)
|
|
scope_push(prov_name, prov_val)
|
|
result = (render_to_html(nth(expr, body_start), env) if sx_truthy((body_count == 1)) else join('', map(lambda i: render_to_html(nth(expr, i), env), range(body_start, (body_start + body_count)))))
|
|
scope_pop(prov_name)
|
|
return result
|
|
else:
|
|
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
|
|
|
# render-lambda-html
|
|
def render_lambda_html(f, args, env):
|
|
local = env_merge(lambda_closure(f), env)
|
|
for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f))
|
|
return render_to_html(lambda_body(f), local)
|
|
|
|
# render-html-component
|
|
def render_html_component(comp, args, env):
|
|
kwargs = {}
|
|
children = []
|
|
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)
|
|
local = env_merge(component_closure(comp), env)
|
|
for p in component_params(comp):
|
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
|
if sx_truthy(component_has_children(comp)):
|
|
local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))
|
|
return render_to_html(component_body(comp), local)
|
|
|
|
# render-html-element
|
|
def render_html_element(tag, args, env):
|
|
parsed = parse_element_args(args, env)
|
|
attrs = first(parsed)
|
|
children = nth(parsed, 1)
|
|
is_void = contains_p(VOID_ELEMENTS, tag)
|
|
if sx_truthy(is_void):
|
|
return sx_str('<', tag, render_attrs(attrs), ' />')
|
|
else:
|
|
scope_push('element-attrs', NIL)
|
|
content = join('', map(lambda c: render_to_html(c, env), children))
|
|
for spread_dict in sx_emitted('element-attrs'):
|
|
merge_spread_attrs(attrs, spread_dict)
|
|
scope_pop('element-attrs')
|
|
return sx_str('<', tag, render_attrs(attrs), '>', content, '</', tag, '>')
|
|
|
|
# render-html-lake
|
|
def render_html_lake(args, env):
|
|
_cells = {}
|
|
_cells['lake_id'] = NIL
|
|
_cells['lake_tag'] = 'div'
|
|
children = []
|
|
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'lake_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'lake_tag', kval) if sx_truthy((kname == 'tag')) else NIL)), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) 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)
|
|
lake_attrs = {'data-sx-lake': (_cells['lake_id'] if sx_truthy(_cells['lake_id']) else '')}
|
|
scope_push('element-attrs', NIL)
|
|
content = join('', map(lambda c: render_to_html(c, env), children))
|
|
for spread_dict in sx_emitted('element-attrs'):
|
|
merge_spread_attrs(lake_attrs, spread_dict)
|
|
scope_pop('element-attrs')
|
|
return sx_str('<', _cells['lake_tag'], render_attrs(lake_attrs), '>', content, '</', _cells['lake_tag'], '>')
|
|
|
|
# render-html-marsh
|
|
def render_html_marsh(args, env):
|
|
_cells = {}
|
|
_cells['marsh_id'] = NIL
|
|
_cells['marsh_tag'] = 'div'
|
|
children = []
|
|
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'marsh_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'marsh_tag', kval) if sx_truthy((kname == 'tag')) else (NIL if sx_truthy((kname == 'transform')) else NIL))), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) 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)
|
|
marsh_attrs = {'data-sx-marsh': (_cells['marsh_id'] if sx_truthy(_cells['marsh_id']) else '')}
|
|
scope_push('element-attrs', NIL)
|
|
content = join('', map(lambda c: render_to_html(c, env), children))
|
|
for spread_dict in sx_emitted('element-attrs'):
|
|
merge_spread_attrs(marsh_attrs, spread_dict)
|
|
scope_pop('element-attrs')
|
|
return sx_str('<', _cells['marsh_tag'], render_attrs(marsh_attrs), '>', content, '</', _cells['marsh_tag'], '>')
|
|
|
|
# render-html-island
|
|
def render_html_island(island, args, env):
|
|
kwargs = {}
|
|
children = []
|
|
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)
|
|
local = env_merge(component_closure(island), env)
|
|
island_name = component_name(island)
|
|
for p in component_params(island):
|
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
|
if sx_truthy(component_has_children(island)):
|
|
local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))
|
|
body_html = render_to_html(component_body(island), local)
|
|
state_sx = serialize_island_state(kwargs)
|
|
return sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_sx), '"') if sx_truthy(state_sx) else ''), '>', body_html, '</span>')
|
|
|
|
# serialize-island-state
|
|
def serialize_island_state(kwargs):
|
|
if sx_truthy(is_empty_dict(kwargs)):
|
|
return NIL
|
|
else:
|
|
return sx_serialize(kwargs)
|
|
|
|
|
|
# === Transpiled from adapter-sx ===
|
|
|
|
# render-to-sx
|
|
def render_to_sx(expr, env):
|
|
result = aser(expr, env)
|
|
if sx_truthy((type_of(result) == 'string')):
|
|
return result
|
|
else:
|
|
return serialize(result)
|
|
|
|
# aser
|
|
def aser(expr, env):
|
|
set_render_active_b(True)
|
|
result = _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))), ('spread', lambda: _sx_begin(sx_emit('element-attrs', spread_attrs(expr)), NIL)), (None, lambda: expr)])
|
|
if sx_truthy(is_spread(result)):
|
|
sx_emit('element-attrs', spread_attrs(result))
|
|
return NIL
|
|
else:
|
|
return result
|
|
|
|
# aser-list
|
|
def aser_list(expr, env):
|
|
head = first(expr)
|
|
args = rest(expr)
|
|
if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
|
|
return map(lambda x: aser(x, env), expr)
|
|
else:
|
|
name = symbol_name(head)
|
|
if sx_truthy((name == '<>')):
|
|
return aser_fragment(args, env)
|
|
elif sx_truthy(starts_with_p(name, '~')):
|
|
return aser_call(name, args, env)
|
|
elif sx_truthy((name == 'lake')):
|
|
return aser_call(name, args, env)
|
|
elif sx_truthy((name == 'marsh')):
|
|
return aser_call(name, args, env)
|
|
elif sx_truthy(contains_p(HTML_TAGS, name)):
|
|
return aser_call(name, args, env)
|
|
elif sx_truthy((is_special_form(name) if sx_truthy(is_special_form(name)) else is_ho_form(name))):
|
|
return aser_special(name, expr, env)
|
|
elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
|
|
return aser(expand_macro(env_get(env, name), args, env), env)
|
|
else:
|
|
f = trampoline(eval_expr(head, env))
|
|
evaled_args = map(lambda a: trampoline(eval_expr(a, env)), 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))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))):
|
|
return apply(f, evaled_args)
|
|
elif sx_truthy(is_lambda(f)):
|
|
return trampoline(call_lambda(f, evaled_args, env))
|
|
elif sx_truthy(is_component(f)):
|
|
return aser_call(sx_str('~', component_name(f)), args, env)
|
|
elif sx_truthy(is_island(f)):
|
|
return aser_call(sx_str('~', component_name(f)), args, env)
|
|
else:
|
|
return error(sx_str('Not callable: ', inspect(f)))
|
|
|
|
# aser-fragment
|
|
def aser_fragment(children, env):
|
|
parts = []
|
|
for c in children:
|
|
result = aser(c, env)
|
|
if sx_truthy((type_of(result) == 'list')):
|
|
for item in result:
|
|
if sx_truthy((not sx_truthy(is_nil(item)))):
|
|
parts.append(serialize(item))
|
|
else:
|
|
if sx_truthy((not sx_truthy(is_nil(result)))):
|
|
parts.append(serialize(result))
|
|
if sx_truthy(empty_p(parts)):
|
|
return ''
|
|
else:
|
|
return sx_str('(<> ', join(' ', parts), ')')
|
|
|
|
# aser-call
|
|
def aser_call(name, args, env):
|
|
_cells = {}
|
|
attr_parts = []
|
|
child_parts = []
|
|
_cells['skip'] = False
|
|
_cells['i'] = 0
|
|
scope_push('element-attrs', NIL)
|
|
for arg in args:
|
|
if sx_truthy(_cells['skip']):
|
|
_cells['skip'] = False
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
|
|
val = aser(nth(args, (_cells['i'] + 1)), env)
|
|
if sx_truthy((not sx_truthy(is_nil(val)))):
|
|
attr_parts.append(sx_str(':', keyword_name(arg)))
|
|
attr_parts.append(serialize(val))
|
|
_cells['skip'] = True
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
val = aser(arg, env)
|
|
if sx_truthy((not sx_truthy(is_nil(val)))):
|
|
if sx_truthy((type_of(val) == 'list')):
|
|
for item in val:
|
|
if sx_truthy((not sx_truthy(is_nil(item)))):
|
|
child_parts.append(serialize(item))
|
|
else:
|
|
child_parts.append(serialize(val))
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
for spread_dict in sx_emitted('element-attrs'):
|
|
for k in keys(spread_dict):
|
|
v = dict_get(spread_dict, k)
|
|
attr_parts.append(sx_str(':', k))
|
|
attr_parts.append(serialize(v))
|
|
scope_pop('element-attrs')
|
|
parts = concat([name], attr_parts, child_parts)
|
|
return sx_str('(', join(' ', parts), ')')
|
|
|
|
# SPECIAL_FORM_NAMES
|
|
SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland', 'deftype', 'defeffect', 'scope', 'provide']
|
|
|
|
# HO_FORM_NAMES
|
|
HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each']
|
|
|
|
# special-form?
|
|
def is_special_form(name):
|
|
return contains_p(SPECIAL_FORM_NAMES, name)
|
|
|
|
# ho-form?
|
|
def is_ho_form(name):
|
|
return contains_p(HO_FORM_NAMES, name)
|
|
|
|
# aser-special
|
|
def aser_special(name, expr, env):
|
|
_cells = {}
|
|
args = rest(expr)
|
|
if sx_truthy((name == 'if')):
|
|
if sx_truthy(trampoline(eval_expr(first(args), env))):
|
|
return aser(nth(args, 1), env)
|
|
else:
|
|
if sx_truthy((len(args) > 2)):
|
|
return aser(nth(args, 2), env)
|
|
else:
|
|
return NIL
|
|
elif sx_truthy((name == 'when')):
|
|
if sx_truthy((not sx_truthy(trampoline(eval_expr(first(args), env))))):
|
|
return NIL
|
|
else:
|
|
_cells['result'] = NIL
|
|
for body in rest(args):
|
|
_cells['result'] = aser(body, env)
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'cond')):
|
|
branch = eval_cond(args, env)
|
|
if sx_truthy(branch):
|
|
return aser(branch, env)
|
|
else:
|
|
return NIL
|
|
elif sx_truthy((name == 'case')):
|
|
match_val = trampoline(eval_expr(first(args), env))
|
|
clauses = rest(args)
|
|
return eval_case_aser(match_val, clauses, env)
|
|
elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
|
|
local = process_bindings(first(args), env)
|
|
_cells['result'] = NIL
|
|
for body in rest(args):
|
|
_cells['result'] = aser(body, local)
|
|
return _cells['result']
|
|
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
|
|
_cells['result'] = NIL
|
|
for body in args:
|
|
_cells['result'] = aser(body, env)
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'and')):
|
|
_cells['result'] = True
|
|
some(_sx_fn(lambda arg: (
|
|
_sx_cell_set(_cells, 'result', trampoline(eval_expr(arg, env))),
|
|
(not sx_truthy(_cells['result']))
|
|
)[-1]), args)
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'or')):
|
|
_cells['result'] = False
|
|
some(_sx_fn(lambda arg: (
|
|
_sx_cell_set(_cells, 'result', trampoline(eval_expr(arg, env))),
|
|
_cells['result']
|
|
)[-1]), args)
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'map')):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
return map(lambda item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else cek_call(f, [item])), coll)
|
|
elif sx_truthy((name == 'map-indexed')):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
return map_indexed(lambda i, item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), i), _sx_dict_set(local, nth(lambda_params(f), 1), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else cek_call(f, [i, item])), coll)
|
|
elif sx_truthy((name == 'for-each')):
|
|
f = trampoline(eval_expr(first(args), env))
|
|
coll = trampoline(eval_expr(nth(args, 1), env))
|
|
results = []
|
|
for item in coll:
|
|
if sx_truthy(is_lambda(f)):
|
|
local = env_merge(lambda_closure(f), env)
|
|
local[first(lambda_params(f))] = item
|
|
results.append(aser(lambda_body(f), local))
|
|
else:
|
|
cek_call(f, [item])
|
|
if sx_truthy(empty_p(results)):
|
|
return NIL
|
|
else:
|
|
return results
|
|
elif sx_truthy((name == 'defisland')):
|
|
trampoline(eval_expr(expr, env))
|
|
return serialize(expr)
|
|
elif sx_truthy(((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 == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'defrelation') if sx_truthy((name == 'defrelation')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect')))))))))))):
|
|
trampoline(eval_expr(expr, env))
|
|
return NIL
|
|
elif sx_truthy((name == 'scope')):
|
|
scope_name = trampoline(eval_expr(first(args), env))
|
|
rest_args = rest(args)
|
|
scope_val = NIL
|
|
body_args = NIL
|
|
if sx_truthy(((len(rest_args) >= 2) if not sx_truthy((len(rest_args) >= 2)) else ((type_of(first(rest_args)) == 'keyword') if not sx_truthy((type_of(first(rest_args)) == 'keyword')) else (keyword_name(first(rest_args)) == 'value')))):
|
|
scope_val = trampoline(eval_expr(nth(rest_args, 1), env))
|
|
body_args = slice(rest_args, 2)
|
|
else:
|
|
body_args = rest_args
|
|
scope_push(scope_name, scope_val)
|
|
_cells['result'] = NIL
|
|
for body in body_args:
|
|
_cells['result'] = aser(body, env)
|
|
scope_pop(scope_name)
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'provide')):
|
|
prov_name = trampoline(eval_expr(first(args), env))
|
|
prov_val = trampoline(eval_expr(nth(args, 1), env))
|
|
_cells['result'] = NIL
|
|
scope_push(prov_name, prov_val)
|
|
for body in slice(args, 2):
|
|
_cells['result'] = aser(body, env)
|
|
scope_pop(prov_name)
|
|
return _cells['result']
|
|
else:
|
|
return trampoline(eval_expr(expr, env))
|
|
|
|
# eval-case-aser
|
|
def eval_case_aser(match_val, clauses, env):
|
|
if sx_truthy((len(clauses) < 2)):
|
|
return NIL
|
|
else:
|
|
test = first(clauses)
|
|
body = nth(clauses, 1)
|
|
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'))))):
|
|
return aser(body, env)
|
|
else:
|
|
if sx_truthy((match_val == trampoline(eval_expr(test, env)))):
|
|
return aser(body, env)
|
|
else:
|
|
return eval_case_aser(match_val, slice(clauses, 2), env)
|
|
|
|
|
|
# === Transpiled from deps (component dependency analysis) ===
|
|
|
|
# scan-refs
|
|
def scan_refs(node):
|
|
refs = []
|
|
scan_refs_walk(node, refs)
|
|
return refs
|
|
|
|
# scan-refs-walk
|
|
def scan_refs_walk(node, refs):
|
|
if sx_truthy((type_of(node) == 'symbol')):
|
|
name = symbol_name(node)
|
|
if sx_truthy(starts_with_p(name, '~')):
|
|
if sx_truthy((not sx_truthy(contains_p(refs, name)))):
|
|
return _sx_append(refs, name)
|
|
return NIL
|
|
return NIL
|
|
elif sx_truthy((type_of(node) == 'list')):
|
|
for item in node:
|
|
scan_refs_walk(item, refs)
|
|
return NIL
|
|
elif sx_truthy((type_of(node) == 'dict')):
|
|
for key in keys(node):
|
|
scan_refs_walk(dict_get(node, key), refs)
|
|
return NIL
|
|
else:
|
|
return NIL
|
|
|
|
# transitive-deps-walk
|
|
def transitive_deps_walk(n, seen, env):
|
|
if sx_truthy((not sx_truthy(contains_p(seen, n)))):
|
|
seen.append(n)
|
|
val = env_get(env, n)
|
|
if sx_truthy(((type_of(val) == 'component') if sx_truthy((type_of(val) == 'component')) else (type_of(val) == 'island'))):
|
|
for ref in scan_refs(component_body(val)):
|
|
transitive_deps_walk(ref, seen, env)
|
|
return NIL
|
|
elif sx_truthy((type_of(val) == 'macro')):
|
|
for ref in scan_refs(macro_body(val)):
|
|
transitive_deps_walk(ref, seen, env)
|
|
return NIL
|
|
else:
|
|
return NIL
|
|
return NIL
|
|
|
|
# transitive-deps
|
|
def transitive_deps(name, env):
|
|
seen = []
|
|
key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
|
|
transitive_deps_walk(key, seen, env)
|
|
return filter(lambda x: (not sx_truthy((x == key))), seen)
|
|
|
|
# compute-all-deps
|
|
def compute_all_deps(env):
|
|
for name in env_components(env):
|
|
val = env_get(env, name)
|
|
if sx_truthy(((type_of(val) == 'component') if sx_truthy((type_of(val) == 'component')) else (type_of(val) == 'island'))):
|
|
component_set_deps(val, transitive_deps(name, env))
|
|
return NIL
|
|
|
|
# scan-components-from-source
|
|
def scan_components_from_source(source):
|
|
matches = regex_find_all('\\(~([a-zA-Z_][a-zA-Z0-9_\\-:/]*)', source)
|
|
return map(lambda m: sx_str('~', m), matches)
|
|
|
|
# components-needed
|
|
def components_needed(page_source, env):
|
|
direct = scan_components_from_source(page_source)
|
|
all_needed = []
|
|
for name in direct:
|
|
if sx_truthy((not sx_truthy(contains_p(all_needed, name)))):
|
|
all_needed.append(name)
|
|
val = env_get(env, name)
|
|
deps = (component_deps(val) if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(empty_p(component_deps(val)))))) else transitive_deps(name, env))
|
|
for dep in deps:
|
|
if sx_truthy((not sx_truthy(contains_p(all_needed, dep)))):
|
|
all_needed.append(dep)
|
|
return all_needed
|
|
|
|
# page-component-bundle
|
|
def page_component_bundle(page_source, env):
|
|
return components_needed(page_source, env)
|
|
|
|
# page-css-classes
|
|
def page_css_classes(page_source, env):
|
|
needed = components_needed(page_source, env)
|
|
classes = []
|
|
for name in needed:
|
|
val = env_get(env, name)
|
|
if sx_truthy((type_of(val) == 'component')):
|
|
for cls in component_css_classes(val):
|
|
if sx_truthy((not sx_truthy(contains_p(classes, cls)))):
|
|
classes.append(cls)
|
|
for cls in scan_css_classes(page_source):
|
|
if sx_truthy((not sx_truthy(contains_p(classes, cls)))):
|
|
classes.append(cls)
|
|
return classes
|
|
|
|
# scan-io-refs-walk
|
|
def scan_io_refs_walk(node, io_names, refs):
|
|
if sx_truthy((type_of(node) == 'symbol')):
|
|
name = symbol_name(node)
|
|
if sx_truthy(contains_p(io_names, name)):
|
|
if sx_truthy((not sx_truthy(contains_p(refs, name)))):
|
|
return _sx_append(refs, name)
|
|
return NIL
|
|
return NIL
|
|
elif sx_truthy((type_of(node) == 'list')):
|
|
for item in node:
|
|
scan_io_refs_walk(item, io_names, refs)
|
|
return NIL
|
|
elif sx_truthy((type_of(node) == 'dict')):
|
|
for key in keys(node):
|
|
scan_io_refs_walk(dict_get(node, key), io_names, refs)
|
|
return NIL
|
|
else:
|
|
return NIL
|
|
|
|
# scan-io-refs
|
|
def scan_io_refs(node, io_names):
|
|
refs = []
|
|
scan_io_refs_walk(node, io_names, refs)
|
|
return refs
|
|
|
|
# transitive-io-refs-walk
|
|
def transitive_io_refs_walk(n, seen, all_refs, env, io_names):
|
|
if sx_truthy((not sx_truthy(contains_p(seen, n)))):
|
|
seen.append(n)
|
|
val = env_get(env, n)
|
|
if sx_truthy((type_of(val) == 'component')):
|
|
for ref in scan_io_refs(component_body(val), io_names):
|
|
if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))):
|
|
all_refs.append(ref)
|
|
for dep in scan_refs(component_body(val)):
|
|
transitive_io_refs_walk(dep, seen, all_refs, env, io_names)
|
|
return NIL
|
|
elif sx_truthy((type_of(val) == 'macro')):
|
|
for ref in scan_io_refs(macro_body(val), io_names):
|
|
if sx_truthy((not sx_truthy(contains_p(all_refs, ref)))):
|
|
all_refs.append(ref)
|
|
for dep in scan_refs(macro_body(val)):
|
|
transitive_io_refs_walk(dep, seen, all_refs, env, io_names)
|
|
return NIL
|
|
else:
|
|
return NIL
|
|
return NIL
|
|
|
|
# transitive-io-refs
|
|
def transitive_io_refs(name, env, io_names):
|
|
all_refs = []
|
|
seen = []
|
|
key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
|
|
transitive_io_refs_walk(key, seen, all_refs, env, io_names)
|
|
return all_refs
|
|
|
|
# compute-all-io-refs
|
|
def compute_all_io_refs(env, io_names):
|
|
for name in env_components(env):
|
|
val = env_get(env, name)
|
|
if sx_truthy((type_of(val) == 'component')):
|
|
component_set_io_refs(val, transitive_io_refs(name, env, io_names))
|
|
return NIL
|
|
|
|
# component-io-refs-cached
|
|
def component_io_refs_cached(name, env, io_names):
|
|
key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
|
|
val = env_get(env, key)
|
|
if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else ((not sx_truthy(is_nil(component_io_refs(val)))) if not sx_truthy((not sx_truthy(is_nil(component_io_refs(val))))) else (not sx_truthy(empty_p(component_io_refs(val))))))):
|
|
return component_io_refs(val)
|
|
else:
|
|
return transitive_io_refs(name, env, io_names)
|
|
|
|
# component-pure?
|
|
def component_pure_p(name, env, io_names):
|
|
key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
|
|
val = env_get(env, key)
|
|
if sx_truthy(((type_of(val) == 'component') if not sx_truthy((type_of(val) == 'component')) else (not sx_truthy(is_nil(component_io_refs(val)))))):
|
|
return empty_p(component_io_refs(val))
|
|
else:
|
|
return empty_p(transitive_io_refs(name, env, io_names))
|
|
|
|
# render-target
|
|
def render_target(name, env, io_names):
|
|
key = (name if sx_truthy(starts_with_p(name, '~')) else sx_str('~', name))
|
|
val = env_get(env, key)
|
|
if sx_truthy((not sx_truthy((type_of(val) == 'component')))):
|
|
return 'server'
|
|
else:
|
|
affinity = component_affinity(val)
|
|
if sx_truthy((affinity == 'server')):
|
|
return 'server'
|
|
elif sx_truthy((affinity == 'client')):
|
|
return 'client'
|
|
elif sx_truthy((not sx_truthy(component_pure_p(name, env, io_names)))):
|
|
return 'server'
|
|
else:
|
|
return 'client'
|
|
|
|
# page-render-plan
|
|
def page_render_plan(page_source, env, io_names):
|
|
needed = components_needed(page_source, env)
|
|
comp_targets = {}
|
|
server_list = []
|
|
client_list = []
|
|
io_deps = []
|
|
for name in needed:
|
|
target = render_target(name, env, io_names)
|
|
comp_targets[name] = target
|
|
if sx_truthy((target == 'server')):
|
|
server_list.append(name)
|
|
for io_ref in component_io_refs_cached(name, env, io_names):
|
|
if sx_truthy((not sx_truthy(contains_p(io_deps, io_ref)))):
|
|
io_deps.append(io_ref)
|
|
else:
|
|
client_list.append(name)
|
|
return {'components': comp_targets, 'server': server_list, 'client': client_list, 'io-deps': io_deps}
|
|
|
|
# env-components
|
|
def env_components(env):
|
|
return filter(lambda k: (lambda v: (is_component(v) if sx_truthy(is_component(v)) else is_macro(v)))(env_get(env, k)), keys(env))
|
|
|
|
|
|
# === Transpiled from frames (CEK continuation frames) ===
|
|
|
|
# make-cek-state
|
|
def make_cek_state(control, env, kont):
|
|
return {'control': control, 'env': env, 'kont': kont, 'phase': 'eval', 'value': NIL}
|
|
|
|
# make-cek-value
|
|
def make_cek_value(value, env, kont):
|
|
return {'control': NIL, 'env': env, 'kont': kont, 'phase': 'continue', 'value': value}
|
|
|
|
# cek-terminal?
|
|
def cek_terminal_p(state):
|
|
return ((get(state, 'phase') == 'continue') if not sx_truthy((get(state, 'phase') == 'continue')) else empty_p(get(state, 'kont')))
|
|
|
|
# cek-control
|
|
def cek_control(s):
|
|
return get(s, 'control')
|
|
|
|
# cek-env
|
|
def cek_env(s):
|
|
return get(s, 'env')
|
|
|
|
# cek-kont
|
|
def cek_kont(s):
|
|
return get(s, 'kont')
|
|
|
|
# cek-phase
|
|
def cek_phase(s):
|
|
return get(s, 'phase')
|
|
|
|
# cek-value
|
|
def cek_value(s):
|
|
return get(s, 'value')
|
|
|
|
# make-if-frame
|
|
def make_if_frame(then_expr, else_expr, env):
|
|
return {'type': 'if', 'then': then_expr, 'else': else_expr, 'env': env}
|
|
|
|
# make-when-frame
|
|
def make_when_frame(body_exprs, env):
|
|
return {'type': 'when', 'body': body_exprs, 'env': env}
|
|
|
|
# make-begin-frame
|
|
def make_begin_frame(remaining, env):
|
|
return {'type': 'begin', 'remaining': remaining, 'env': env}
|
|
|
|
# make-let-frame
|
|
def make_let_frame(name, remaining, body, local):
|
|
return {'type': 'let', 'name': name, 'remaining': remaining, 'body': body, 'env': local}
|
|
|
|
# make-define-frame
|
|
def make_define_frame(name, env, has_effects, effect_list):
|
|
return {'type': 'define', 'name': name, 'env': env, 'has-effects': has_effects, 'effect-list': effect_list}
|
|
|
|
# make-set-frame
|
|
def make_set_frame(name, env):
|
|
return {'type': 'set', 'name': name, 'env': env}
|
|
|
|
# make-arg-frame
|
|
def make_arg_frame(f, evaled, remaining, env, raw_args):
|
|
return {'type': 'arg', 'f': f, 'evaled': evaled, 'remaining': remaining, 'env': env, 'raw-args': raw_args}
|
|
|
|
# make-call-frame
|
|
def make_call_frame(f, args, env):
|
|
return {'type': 'call', 'f': f, 'args': args, 'env': env}
|
|
|
|
# make-cond-frame
|
|
def make_cond_frame(remaining, env, scheme_p):
|
|
return {'type': 'cond', 'remaining': remaining, 'env': env, 'scheme': scheme_p}
|
|
|
|
# make-case-frame
|
|
def make_case_frame(match_val, remaining, env):
|
|
return {'type': 'case', 'match-val': match_val, 'remaining': remaining, 'env': env}
|
|
|
|
# make-thread-frame
|
|
def make_thread_frame(remaining, env):
|
|
return {'type': 'thread', 'remaining': remaining, 'env': env}
|
|
|
|
# make-map-frame
|
|
def make_map_frame(f, remaining, results, env):
|
|
return {'type': 'map', 'f': f, 'remaining': remaining, 'results': results, 'env': env}
|
|
|
|
# make-filter-frame
|
|
def make_filter_frame(f, remaining, results, current_item, env):
|
|
return {'type': 'filter', 'f': f, 'remaining': remaining, 'results': results, 'current-item': current_item, 'env': env}
|
|
|
|
# make-reduce-frame
|
|
def make_reduce_frame(f, remaining, env):
|
|
return {'type': 'reduce', 'f': f, 'remaining': remaining, 'env': env}
|
|
|
|
# make-for-each-frame
|
|
def make_for_each_frame(f, remaining, env):
|
|
return {'type': 'for-each', 'f': f, 'remaining': remaining, 'env': env}
|
|
|
|
# make-scope-frame
|
|
def make_scope_frame(name, remaining, env):
|
|
return {'type': 'scope', 'name': name, 'remaining': remaining, 'env': env}
|
|
|
|
# make-reset-frame
|
|
def make_reset_frame(env):
|
|
return {'type': 'reset', 'env': env}
|
|
|
|
# make-dict-frame
|
|
def make_dict_frame(remaining, results, env):
|
|
return {'type': 'dict', 'remaining': remaining, 'results': results, 'env': env}
|
|
|
|
# make-and-frame
|
|
def make_and_frame(remaining, env):
|
|
return {'type': 'and', 'remaining': remaining, 'env': env}
|
|
|
|
# make-or-frame
|
|
def make_or_frame(remaining, env):
|
|
return {'type': 'or', 'remaining': remaining, 'env': env}
|
|
|
|
# make-dynamic-wind-frame
|
|
def make_dynamic_wind_frame(phase, body_thunk, after_thunk, env):
|
|
return {'type': 'dynamic-wind', 'phase': phase, 'body-thunk': body_thunk, 'after-thunk': after_thunk, 'env': env}
|
|
|
|
# make-reactive-reset-frame
|
|
def make_reactive_reset_frame(env, update_fn, first_render_p):
|
|
return {'type': 'reactive-reset', 'env': env, 'update-fn': update_fn, 'first-render': first_render_p}
|
|
|
|
# make-deref-frame
|
|
def make_deref_frame(env):
|
|
return {'type': 'deref', 'env': env}
|
|
|
|
# frame-type
|
|
def frame_type(f):
|
|
return get(f, 'type')
|
|
|
|
# kont-push
|
|
def kont_push(frame, kont):
|
|
return cons(frame, kont)
|
|
|
|
# kont-top
|
|
def kont_top(kont):
|
|
return first(kont)
|
|
|
|
# kont-pop
|
|
def kont_pop(kont):
|
|
return rest(kont)
|
|
|
|
# kont-empty?
|
|
def kont_empty_p(kont):
|
|
return empty_p(kont)
|
|
|
|
# kont-capture-to-reset
|
|
def kont_capture_to_reset(kont):
|
|
def scan(k, captured):
|
|
if sx_truthy(empty_p(k)):
|
|
return error('shift without enclosing reset')
|
|
else:
|
|
frame = first(k)
|
|
if sx_truthy(((frame_type(frame) == 'reset') if sx_truthy((frame_type(frame) == 'reset')) else (frame_type(frame) == 'reactive-reset'))):
|
|
return [captured, rest(k)]
|
|
else:
|
|
return scan(rest(k), append(captured, [frame]))
|
|
return scan(kont, [])
|
|
|
|
# has-reactive-reset-frame?
|
|
def has_reactive_reset_frame_p(kont):
|
|
if sx_truthy(empty_p(kont)):
|
|
return False
|
|
else:
|
|
if sx_truthy((frame_type(first(kont)) == 'reactive-reset')):
|
|
return True
|
|
else:
|
|
return has_reactive_reset_frame_p(rest(kont))
|
|
|
|
# kont-capture-to-reactive-reset
|
|
def kont_capture_to_reactive_reset(kont):
|
|
def scan(k, captured):
|
|
if sx_truthy(empty_p(k)):
|
|
return error('reactive deref without enclosing reactive-reset')
|
|
else:
|
|
frame = first(k)
|
|
if sx_truthy((frame_type(frame) == 'reactive-reset')):
|
|
return [captured, frame, rest(k)]
|
|
else:
|
|
return scan(rest(k), append(captured, [frame]))
|
|
return scan(kont, [])
|
|
|
|
|
|
# === Transpiled from page-helpers (pure data transformation helpers) ===
|
|
|
|
# special-form-category-map
|
|
special_form_category_map = {'if': 'Control Flow', 'when': 'Control Flow', 'cond': 'Control Flow', 'case': 'Control Flow', 'and': 'Control Flow', 'or': 'Control Flow', 'let': 'Binding', 'let*': 'Binding', 'letrec': 'Binding', 'define': 'Binding', 'set!': 'Binding', 'lambda': 'Functions & Components', 'fn': 'Functions & Components', 'defcomp': 'Functions & Components', 'defmacro': 'Functions & Components', 'begin': 'Sequencing & Threading', 'do': 'Sequencing & Threading', '->': 'Sequencing & Threading', 'quote': 'Quoting', 'quasiquote': 'Quoting', 'reset': 'Continuations', 'shift': 'Continuations', 'dynamic-wind': 'Guards', 'map': 'Higher-Order Forms', 'map-indexed': 'Higher-Order Forms', 'filter': 'Higher-Order Forms', 'reduce': 'Higher-Order Forms', 'some': 'Higher-Order Forms', 'every?': 'Higher-Order Forms', 'for-each': 'Higher-Order Forms', 'defstyle': 'Domain Definitions', 'defhandler': 'Domain Definitions', 'defpage': 'Domain Definitions', 'defquery': 'Domain Definitions', 'defaction': 'Domain Definitions'}
|
|
|
|
# extract-define-kwargs
|
|
def extract_define_kwargs(expr):
|
|
result = {}
|
|
items = slice(expr, 2)
|
|
n = len(items)
|
|
for idx in range(0, n):
|
|
if sx_truthy((((idx + 1) < n) if not sx_truthy(((idx + 1) < n)) else (type_of(nth(items, idx)) == 'keyword'))):
|
|
key = keyword_name(nth(items, idx))
|
|
val = nth(items, (idx + 1))
|
|
result[key] = (sx_str('(', join(' ', map(serialize, val)), ')') if sx_truthy((type_of(val) == 'list')) else sx_str(val))
|
|
return result
|
|
|
|
# categorize-special-forms
|
|
def categorize_special_forms(parsed_exprs):
|
|
categories = {}
|
|
for expr in parsed_exprs:
|
|
if sx_truthy(((type_of(expr) == 'list') if not sx_truthy((type_of(expr) == 'list')) else ((len(expr) >= 2) if not sx_truthy((len(expr) >= 2)) else ((type_of(first(expr)) == 'symbol') if not sx_truthy((type_of(first(expr)) == 'symbol')) else (symbol_name(first(expr)) == 'define-special-form'))))):
|
|
name = nth(expr, 1)
|
|
kwargs = extract_define_kwargs(expr)
|
|
category = (get(special_form_category_map, name) if sx_truthy(get(special_form_category_map, name)) else 'Other')
|
|
if sx_truthy((not sx_truthy(has_key_p(categories, category)))):
|
|
categories[category] = []
|
|
get(categories, category).append({'name': name, 'syntax': (get(kwargs, 'syntax') if sx_truthy(get(kwargs, 'syntax')) else ''), 'doc': (get(kwargs, 'doc') if sx_truthy(get(kwargs, 'doc')) else ''), 'tail-position': (get(kwargs, 'tail-position') if sx_truthy(get(kwargs, 'tail-position')) else ''), 'example': (get(kwargs, 'example') if sx_truthy(get(kwargs, 'example')) else '')})
|
|
return categories
|
|
|
|
# build-ref-items-with-href
|
|
def build_ref_items_with_href(items, base_path, detail_keys, n_fields):
|
|
return map(lambda item: ((lambda name: (lambda field2: (lambda field3: {'name': name, 'desc': field2, 'exists': field3, 'href': (sx_str(base_path, name) if sx_truthy((field3 if not sx_truthy(field3) else some(lambda k: (k == name), detail_keys))) else NIL)})(nth(item, 2)))(nth(item, 1)))(nth(item, 0)) if sx_truthy((n_fields == 3)) else (lambda name: (lambda desc: {'name': name, 'desc': desc, 'href': (sx_str(base_path, name) if sx_truthy(some(lambda k: (k == name), detail_keys)) else NIL)})(nth(item, 1)))(nth(item, 0))), items)
|
|
|
|
# build-reference-data
|
|
def build_reference_data(slug, raw_data, detail_keys):
|
|
_match = slug
|
|
if _match == 'attributes':
|
|
return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3)}
|
|
elif _match == 'headers':
|
|
return {'req-headers': build_ref_items_with_href(get(raw_data, 'req-headers'), '/geography/hypermedia/reference/headers/', detail_keys, 3), 'resp-headers': build_ref_items_with_href(get(raw_data, 'resp-headers'), '/geography/hypermedia/reference/headers/', detail_keys, 3)}
|
|
elif _match == 'events':
|
|
return {'events-list': build_ref_items_with_href(get(raw_data, 'events-list'), '/geography/hypermedia/reference/events/', detail_keys, 2)}
|
|
elif _match == 'js-api':
|
|
return {'js-api-list': map(lambda item: {'name': nth(item, 0), 'desc': nth(item, 1)}, get(raw_data, 'js-api-list'))}
|
|
else:
|
|
return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3)}
|
|
|
|
# build-attr-detail
|
|
def build_attr_detail(slug, detail):
|
|
if sx_truthy(is_nil(detail)):
|
|
return {'attr-not-found': True}
|
|
else:
|
|
return {'attr-not-found': NIL, 'attr-title': slug, 'attr-description': get(detail, 'description'), 'attr-example': get(detail, 'example'), 'attr-handler': get(detail, 'handler'), 'attr-demo': get(detail, 'demo'), 'attr-wire-id': (sx_str('ref-wire-', replace(replace(slug, ':', '-'), '*', 'star')) if sx_truthy(has_key_p(detail, 'handler')) else NIL)}
|
|
|
|
# build-header-detail
|
|
def build_header_detail(slug, detail):
|
|
if sx_truthy(is_nil(detail)):
|
|
return {'header-not-found': True}
|
|
else:
|
|
return {'header-not-found': NIL, 'header-title': slug, 'header-direction': get(detail, 'direction'), 'header-description': get(detail, 'description'), 'header-example': get(detail, 'example'), 'header-demo': get(detail, 'demo')}
|
|
|
|
# build-event-detail
|
|
def build_event_detail(slug, detail):
|
|
if sx_truthy(is_nil(detail)):
|
|
return {'event-not-found': True}
|
|
else:
|
|
return {'event-not-found': NIL, 'event-title': slug, 'event-description': get(detail, 'description'), 'event-example': get(detail, 'example'), 'event-demo': get(detail, 'demo')}
|
|
|
|
# build-component-source
|
|
def build_component_source(comp_data):
|
|
comp_type = get(comp_data, 'type')
|
|
name = get(comp_data, 'name')
|
|
params = get(comp_data, 'params')
|
|
has_children = get(comp_data, 'has-children')
|
|
body_sx = get(comp_data, 'body-sx')
|
|
affinity = get(comp_data, 'affinity')
|
|
if sx_truthy((comp_type == 'not-found')):
|
|
return sx_str(';; component ', name, ' not found')
|
|
else:
|
|
param_strs = ((['&rest', 'children'] if sx_truthy(has_children) else []) if sx_truthy(empty_p(params)) else (append(cons('&key', params), ['&rest', 'children']) if sx_truthy(has_children) else cons('&key', params)))
|
|
params_sx = sx_str('(', join(' ', param_strs), ')')
|
|
form_name = ('defisland' if sx_truthy((comp_type == 'island')) else 'defcomp')
|
|
affinity_str = (sx_str(' :affinity ', affinity) if sx_truthy(((comp_type == 'component') if not sx_truthy((comp_type == 'component')) else ((not sx_truthy(is_nil(affinity))) if not sx_truthy((not sx_truthy(is_nil(affinity)))) else (not sx_truthy((affinity == 'auto')))))) else '')
|
|
return sx_str('(', form_name, ' ', name, ' ', params_sx, affinity_str, '\n ', body_sx, ')')
|
|
|
|
# build-bundle-analysis
|
|
def build_bundle_analysis(pages_raw, components_raw, total_components, total_macros, pure_count, io_count):
|
|
_cells = {}
|
|
pages_data = []
|
|
for page in pages_raw:
|
|
needed_names = get(page, 'needed-names')
|
|
n = len(needed_names)
|
|
pct = (round(((n / total_components) * 100)) if sx_truthy((total_components > 0)) else 0)
|
|
savings = (100 - pct)
|
|
_cells['pure_in_page'] = 0
|
|
_cells['io_in_page'] = 0
|
|
page_io_refs = []
|
|
comp_details = []
|
|
for comp_name in needed_names:
|
|
info = get(components_raw, comp_name)
|
|
if sx_truthy((not sx_truthy(is_nil(info)))):
|
|
if sx_truthy(get(info, 'is-pure')):
|
|
_cells['pure_in_page'] = (_cells['pure_in_page'] + 1)
|
|
else:
|
|
_cells['io_in_page'] = (_cells['io_in_page'] + 1)
|
|
for ref in (get(info, 'io-refs') if sx_truthy(get(info, 'io-refs')) else []):
|
|
if sx_truthy((not sx_truthy(some(lambda r: (r == ref), page_io_refs)))):
|
|
page_io_refs.append(ref)
|
|
comp_details.append({'name': comp_name, 'is-pure': get(info, 'is-pure'), 'affinity': get(info, 'affinity'), 'render-target': get(info, 'render-target'), 'io-refs': (get(info, 'io-refs') if sx_truthy(get(info, 'io-refs')) else []), 'deps': (get(info, 'deps') if sx_truthy(get(info, 'deps')) else []), 'source': get(info, 'source')})
|
|
pages_data.append({'name': get(page, 'name'), 'path': get(page, 'path'), 'direct': get(page, 'direct'), 'needed': n, 'pct': pct, 'savings': savings, 'io-refs': len(page_io_refs), 'pure-in-page': _cells['pure_in_page'], 'io-in-page': _cells['io_in_page'], 'components': comp_details})
|
|
return {'pages': pages_data, 'total-components': total_components, 'total-macros': total_macros, 'pure-count': pure_count, 'io-count': io_count}
|
|
|
|
# build-routing-analysis
|
|
def build_routing_analysis(pages_raw):
|
|
_cells = {}
|
|
pages_data = []
|
|
_cells['client_count'] = 0
|
|
_cells['server_count'] = 0
|
|
for page in pages_raw:
|
|
has_data = get(page, 'has-data')
|
|
content_src = (get(page, 'content-src') if sx_truthy(get(page, 'content-src')) else '')
|
|
_cells['mode'] = NIL
|
|
_cells['reason'] = ''
|
|
if sx_truthy(has_data):
|
|
_cells['mode'] = 'server'
|
|
_cells['reason'] = 'Has :data expression — needs server IO'
|
|
_cells['server_count'] = (_cells['server_count'] + 1)
|
|
elif sx_truthy(empty_p(content_src)):
|
|
_cells['mode'] = 'server'
|
|
_cells['reason'] = 'No content expression'
|
|
_cells['server_count'] = (_cells['server_count'] + 1)
|
|
else:
|
|
_cells['mode'] = 'client'
|
|
_cells['client_count'] = (_cells['client_count'] + 1)
|
|
pages_data.append({'name': get(page, 'name'), 'path': get(page, 'path'), 'mode': _cells['mode'], 'has-data': has_data, 'content-expr': (sx_str(slice(content_src, 0, 80), '...') if sx_truthy((len(content_src) > 80)) else content_src), 'reason': _cells['reason']})
|
|
return {'pages': pages_data, 'total-pages': (_cells['client_count'] + _cells['server_count']), 'client-count': _cells['client_count'], 'server-count': _cells['server_count']}
|
|
|
|
# build-affinity-analysis
|
|
def build_affinity_analysis(demo_components, page_plans):
|
|
return {'components': demo_components, 'page-plans': page_plans}
|
|
|
|
|
|
# === Transpiled from router (client-side route matching) ===
|
|
|
|
# split-path-segments
|
|
def split_path_segments(path):
|
|
trimmed = (slice(path, 1) if sx_truthy(starts_with_p(path, '/')) else path)
|
|
trimmed2 = (slice(trimmed, 0, (len(trimmed) - 1)) if sx_truthy(((not sx_truthy(empty_p(trimmed))) if not sx_truthy((not sx_truthy(empty_p(trimmed)))) else ends_with_p(trimmed, '/'))) else trimmed)
|
|
if sx_truthy(empty_p(trimmed2)):
|
|
return []
|
|
else:
|
|
return split(trimmed2, '/')
|
|
|
|
# make-route-segment
|
|
def make_route_segment(seg):
|
|
if sx_truthy((starts_with_p(seg, '<') if not sx_truthy(starts_with_p(seg, '<')) else ends_with_p(seg, '>'))):
|
|
param_name = slice(seg, 1, (len(seg) - 1))
|
|
d = {}
|
|
d['type'] = 'param'
|
|
d['value'] = param_name
|
|
return d
|
|
else:
|
|
d = {}
|
|
d['type'] = 'literal'
|
|
d['value'] = seg
|
|
return d
|
|
|
|
# parse-route-pattern
|
|
def parse_route_pattern(pattern):
|
|
segments = split_path_segments(pattern)
|
|
return map(make_route_segment, segments)
|
|
|
|
# match-route-segments
|
|
def match_route_segments(path_segs, parsed_segs):
|
|
_cells = {}
|
|
if sx_truthy((not sx_truthy((len(path_segs) == len(parsed_segs))))):
|
|
return NIL
|
|
else:
|
|
params = {}
|
|
_cells['matched'] = True
|
|
for_each_indexed(lambda i, parsed_seg: ((lambda path_seg: (lambda seg_type: ((_sx_cell_set(_cells, 'matched', False) if sx_truthy((not sx_truthy((path_seg == get(parsed_seg, 'value'))))) else NIL) if sx_truthy((seg_type == 'literal')) else (_sx_dict_set(params, get(parsed_seg, 'value'), path_seg) if sx_truthy((seg_type == 'param')) else _sx_cell_set(_cells, 'matched', False))))(get(parsed_seg, 'type')))(nth(path_segs, i)) if sx_truthy(_cells['matched']) else NIL), parsed_segs)
|
|
if sx_truthy(_cells['matched']):
|
|
return params
|
|
else:
|
|
return NIL
|
|
|
|
# match-route
|
|
def match_route(path, pattern):
|
|
path_segs = split_path_segments(path)
|
|
parsed_segs = parse_route_pattern(pattern)
|
|
return match_route_segments(path_segs, parsed_segs)
|
|
|
|
# find-matching-route
|
|
def find_matching_route(path, routes):
|
|
_cells = {}
|
|
match_path = ((sx_url_to_path(path) if sx_truthy(sx_url_to_path(path)) else path) if sx_truthy(starts_with_p(path, '/(')) else path)
|
|
path_segs = split_path_segments(match_path)
|
|
_cells['result'] = NIL
|
|
for route in routes:
|
|
if sx_truthy(is_nil(_cells['result'])):
|
|
params = match_route_segments(path_segs, get(route, 'parsed'))
|
|
if sx_truthy((not sx_truthy(is_nil(params)))):
|
|
matched = merge(route, {})
|
|
matched['params'] = params
|
|
_cells['result'] = matched
|
|
return _cells['result']
|
|
|
|
# _fn-to-segment
|
|
def _fn_to_segment(name):
|
|
_match = name
|
|
if _match == 'doc':
|
|
return 'docs'
|
|
elif _match == 'spec':
|
|
return 'specs'
|
|
elif _match == 'bootstrapper':
|
|
return 'bootstrappers'
|
|
elif _match == 'test':
|
|
return 'testing'
|
|
elif _match == 'example':
|
|
return 'examples'
|
|
elif _match == 'protocol':
|
|
return 'protocols'
|
|
elif _match == 'essay':
|
|
return 'essays'
|
|
elif _match == 'plan':
|
|
return 'plans'
|
|
elif _match == 'reference-detail':
|
|
return 'reference'
|
|
else:
|
|
return name
|
|
|
|
# sx-url-to-path
|
|
def sx_url_to_path(url):
|
|
if sx_truthy((not sx_truthy((starts_with_p(url, '/(') if not sx_truthy(starts_with_p(url, '/(')) else ends_with_p(url, ')'))))):
|
|
return NIL
|
|
else:
|
|
inner = slice(url, 2, (len(url) - 1))
|
|
s = replace(replace(replace(inner, '.', '/'), '(', ''), ')', '')
|
|
segs = filter(lambda s: (not sx_truthy(empty_p(s))), split(s, '/'))
|
|
return sx_str('/', join('/', map(_fn_to_segment, segs)))
|
|
|
|
# _count-leading-dots
|
|
def _count_leading_dots(s):
|
|
if sx_truthy(empty_p(s)):
|
|
return 0
|
|
else:
|
|
if sx_truthy(starts_with_p(s, '.')):
|
|
return (1 + _count_leading_dots(slice(s, 1)))
|
|
else:
|
|
return 0
|
|
|
|
# _strip-trailing-close
|
|
def _strip_trailing_close(s):
|
|
if sx_truthy(ends_with_p(s, ')')):
|
|
return _strip_trailing_close(slice(s, 0, (len(s) - 1)))
|
|
else:
|
|
return s
|
|
|
|
# _index-of-safe
|
|
def _index_of_safe(s, needle):
|
|
idx = index_of(s, needle)
|
|
if sx_truthy((is_nil(idx) if sx_truthy(is_nil(idx)) else (idx < 0))):
|
|
return NIL
|
|
else:
|
|
return idx
|
|
|
|
# _last-index-of
|
|
def _last_index_of(s, needle):
|
|
idx = _index_of_safe(s, needle)
|
|
if sx_truthy(is_nil(idx)):
|
|
return NIL
|
|
else:
|
|
rest_idx = _last_index_of(slice(s, (idx + 1)), needle)
|
|
if sx_truthy(is_nil(rest_idx)):
|
|
return idx
|
|
else:
|
|
return ((idx + 1) + rest_idx)
|
|
|
|
# _pop-sx-url-level
|
|
def _pop_sx_url_level(url):
|
|
stripped = _strip_trailing_close(url)
|
|
close_count = (len(url) - len(_strip_trailing_close(url)))
|
|
if sx_truthy((close_count <= 1)):
|
|
return '/'
|
|
else:
|
|
last_dp = _last_index_of(stripped, '.(')
|
|
if sx_truthy(is_nil(last_dp)):
|
|
return '/'
|
|
else:
|
|
return sx_str(slice(stripped, 0, last_dp), slice(url, (len(url) - (close_count - 1))))
|
|
|
|
# _pop-sx-url-levels
|
|
def _pop_sx_url_levels(url, n):
|
|
if sx_truthy((n <= 0)):
|
|
return url
|
|
else:
|
|
return _pop_sx_url_levels(_pop_sx_url_level(url), (n - 1))
|
|
|
|
# _split-pos-kw
|
|
def _split_pos_kw(tokens, i, pos, kw):
|
|
if sx_truthy((i >= len(tokens))):
|
|
return {'positional': join('.', pos), 'keywords': kw}
|
|
else:
|
|
tok = nth(tokens, i)
|
|
if sx_truthy(starts_with_p(tok, ':')):
|
|
val = (nth(tokens, (i + 1)) if sx_truthy(((i + 1) < len(tokens))) else '')
|
|
return _split_pos_kw(tokens, (i + 2), pos, append(kw, [[tok, val]]))
|
|
else:
|
|
return _split_pos_kw(tokens, (i + 1), append(pos, [tok]), kw)
|
|
|
|
# _parse-relative-body
|
|
def _parse_relative_body(body):
|
|
if sx_truthy(empty_p(body)):
|
|
return {'positional': '', 'keywords': []}
|
|
else:
|
|
return _split_pos_kw(split(body, '.'), 0, [], [])
|
|
|
|
# _extract-innermost
|
|
def _extract_innermost(url):
|
|
stripped = _strip_trailing_close(url)
|
|
suffix = slice(url, len(_strip_trailing_close(url)))
|
|
last_dp = _last_index_of(stripped, '.(')
|
|
if sx_truthy(is_nil(last_dp)):
|
|
return {'before': '/(', 'content': slice(stripped, 2), 'suffix': suffix}
|
|
else:
|
|
return {'before': slice(stripped, 0, (last_dp + 2)), 'content': slice(stripped, (last_dp + 2)), 'suffix': suffix}
|
|
|
|
# _find-kw-in-tokens
|
|
def _find_kw_in_tokens(tokens, i, kw):
|
|
if sx_truthy((i >= len(tokens))):
|
|
return NIL
|
|
else:
|
|
if sx_truthy(((nth(tokens, i) == kw) if not sx_truthy((nth(tokens, i) == kw)) else ((i + 1) < len(tokens)))):
|
|
return nth(tokens, (i + 1))
|
|
else:
|
|
return _find_kw_in_tokens(tokens, (i + 1), kw)
|
|
|
|
# _find-keyword-value
|
|
def _find_keyword_value(content, kw):
|
|
return _find_kw_in_tokens(split(content, '.'), 0, kw)
|
|
|
|
# _replace-kw-in-tokens
|
|
def _replace_kw_in_tokens(tokens, i, kw, value):
|
|
if sx_truthy((i >= len(tokens))):
|
|
return []
|
|
else:
|
|
if sx_truthy(((nth(tokens, i) == kw) if not sx_truthy((nth(tokens, i) == kw)) else ((i + 1) < len(tokens)))):
|
|
return append([kw, value], _replace_kw_in_tokens(tokens, (i + 2), kw, value))
|
|
else:
|
|
return cons(nth(tokens, i), _replace_kw_in_tokens(tokens, (i + 1), kw, value))
|
|
|
|
# _set-keyword-in-content
|
|
def _set_keyword_in_content(content, kw, value):
|
|
current = _find_keyword_value(content, kw)
|
|
if sx_truthy(is_nil(current)):
|
|
return sx_str(content, '.', kw, '.', value)
|
|
else:
|
|
return join('.', _replace_kw_in_tokens(split(content, '.'), 0, kw, value))
|
|
|
|
# _is-delta-value?
|
|
def _is_delta_value_p(s):
|
|
return ((not sx_truthy(empty_p(s))) if not sx_truthy((not sx_truthy(empty_p(s)))) else ((len(s) > 1) if not sx_truthy((len(s) > 1)) else (starts_with_p(s, '+') if sx_truthy(starts_with_p(s, '+')) else starts_with_p(s, '-'))))
|
|
|
|
# _apply-delta
|
|
def _apply_delta(current_str, delta_str):
|
|
cur = parse_int(current_str, NIL)
|
|
delta = parse_int(delta_str, NIL)
|
|
if sx_truthy((is_nil(cur) if sx_truthy(is_nil(cur)) else is_nil(delta))):
|
|
return delta_str
|
|
else:
|
|
return sx_str((cur + delta))
|
|
|
|
# _apply-kw-pairs
|
|
def _apply_kw_pairs(content, kw_pairs):
|
|
if sx_truthy(empty_p(kw_pairs)):
|
|
return content
|
|
else:
|
|
pair = first(kw_pairs)
|
|
kw = first(pair)
|
|
raw_val = nth(pair, 1)
|
|
actual_val = ((lambda current: (raw_val if sx_truthy(is_nil(current)) else _apply_delta(current, raw_val)))(_find_keyword_value(content, kw)) if sx_truthy(_is_delta_value_p(raw_val)) else raw_val)
|
|
return _apply_kw_pairs(_set_keyword_in_content(content, kw, actual_val), rest(kw_pairs))
|
|
|
|
# _apply-keywords-to-url
|
|
def _apply_keywords_to_url(url, kw_pairs):
|
|
if sx_truthy(empty_p(kw_pairs)):
|
|
return url
|
|
else:
|
|
parts = _extract_innermost(url)
|
|
new_content = _apply_kw_pairs(get(parts, 'content'), kw_pairs)
|
|
return sx_str(get(parts, 'before'), new_content, get(parts, 'suffix'))
|
|
|
|
# _normalize-relative
|
|
def _normalize_relative(url):
|
|
if sx_truthy(starts_with_p(url, '(')):
|
|
return url
|
|
else:
|
|
return sx_str('(', url, ')')
|
|
|
|
# resolve-relative-url
|
|
def resolve_relative_url(current, relative):
|
|
canonical = _normalize_relative(relative)
|
|
rel_inner = slice(canonical, 1, (len(canonical) - 1))
|
|
dots = _count_leading_dots(rel_inner)
|
|
body = slice(rel_inner, _count_leading_dots(rel_inner))
|
|
if sx_truthy((dots == 0)):
|
|
return current
|
|
else:
|
|
parsed = _parse_relative_body(body)
|
|
pos_body = get(parsed, 'positional')
|
|
kw_pairs = get(parsed, 'keywords')
|
|
after_nav = ((current if sx_truthy(empty_p(pos_body)) else (lambda stripped: (lambda suffix: sx_str(stripped, '.', pos_body, suffix))(slice(current, len(_strip_trailing_close(current)))))(_strip_trailing_close(current))) if sx_truthy((dots == 1)) else (lambda base: (base if sx_truthy(empty_p(pos_body)) else (sx_str('/(', pos_body, ')') if sx_truthy((base == '/')) else (lambda stripped: (lambda suffix: sx_str(stripped, '.(', pos_body, ')', suffix))(slice(base, len(_strip_trailing_close(base)))))(_strip_trailing_close(base)))))(_pop_sx_url_levels(current, (dots - 1))))
|
|
return _apply_keywords_to_url(after_nav, kw_pairs)
|
|
|
|
# relative-sx-url?
|
|
def relative_sx_url_p(url):
|
|
return ((starts_with_p(url, '(') if not sx_truthy(starts_with_p(url, '(')) else (not sx_truthy(starts_with_p(url, '/(')))) if sx_truthy((starts_with_p(url, '(') if not sx_truthy(starts_with_p(url, '(')) else (not sx_truthy(starts_with_p(url, '/('))))) else starts_with_p(url, '.'))
|
|
|
|
# _url-special-forms
|
|
def _url_special_forms():
|
|
return ['!source', '!inspect', '!diff', '!search', '!raw', '!json']
|
|
|
|
# url-special-form?
|
|
def url_special_form_p(name):
|
|
return (starts_with_p(name, '!') if not sx_truthy(starts_with_p(name, '!')) else contains_p(_url_special_forms(), name))
|
|
|
|
# parse-sx-url
|
|
def parse_sx_url(url):
|
|
if sx_truthy((url == '/')):
|
|
return {'type': 'home', 'raw': url}
|
|
elif sx_truthy(relative_sx_url_p(url)):
|
|
return {'type': 'relative', 'raw': url}
|
|
elif sx_truthy((starts_with_p(url, '/(!') if not sx_truthy(starts_with_p(url, '/(!')) else ends_with_p(url, ')'))):
|
|
inner = slice(url, 2, (len(url) - 1))
|
|
dot_pos = _index_of_safe(inner, '.')
|
|
paren_pos = _index_of_safe(inner, '(')
|
|
end_pos = (len(inner) if sx_truthy((is_nil(dot_pos) if not sx_truthy(is_nil(dot_pos)) else is_nil(paren_pos))) else (paren_pos if sx_truthy(is_nil(dot_pos)) else (dot_pos if sx_truthy(is_nil(paren_pos)) else min(dot_pos, paren_pos))))
|
|
form_name = slice(inner, 0, end_pos)
|
|
rest_part = slice(inner, end_pos)
|
|
inner_expr = (slice(rest_part, 1) if sx_truthy(starts_with_p(rest_part, '.')) else rest_part)
|
|
return {'type': 'special-form', 'form': form_name, 'inner': inner_expr, 'raw': url}
|
|
elif sx_truthy((starts_with_p(url, '/(~') if not sx_truthy(starts_with_p(url, '/(~')) else ends_with_p(url, ')'))):
|
|
name = slice(url, 2, (len(url) - 1))
|
|
return {'type': 'direct-component', 'name': name, 'raw': url}
|
|
elif sx_truthy((starts_with_p(url, '/(') if not sx_truthy(starts_with_p(url, '/(')) else ends_with_p(url, ')'))):
|
|
return {'type': 'absolute', 'raw': url}
|
|
else:
|
|
return {'type': 'path', 'raw': url}
|
|
|
|
# url-special-form-name
|
|
def url_special_form_name(url):
|
|
parsed = parse_sx_url(url)
|
|
if sx_truthy((get(parsed, 'type') == 'special-form')):
|
|
return get(parsed, 'form')
|
|
else:
|
|
return NIL
|
|
|
|
# url-special-form-inner
|
|
def url_special_form_inner(url):
|
|
parsed = parse_sx_url(url)
|
|
if sx_truthy((get(parsed, 'type') == 'special-form')):
|
|
return get(parsed, 'inner')
|
|
else:
|
|
return NIL
|
|
|
|
# url-to-expr
|
|
def url_to_expr(url_path):
|
|
if sx_truthy(((url_path == '/') if sx_truthy((url_path == '/')) else empty_p(url_path))):
|
|
return []
|
|
else:
|
|
trimmed = (slice(url_path, 1) if sx_truthy(starts_with_p(url_path, '/')) else url_path)
|
|
sx_source = replace(trimmed, '.', ' ')
|
|
exprs = sx_parse(sx_source)
|
|
if sx_truthy(empty_p(exprs)):
|
|
return []
|
|
else:
|
|
return first(exprs)
|
|
|
|
# auto-quote-unknowns
|
|
def auto_quote_unknowns(expr, env):
|
|
if sx_truthy((not sx_truthy(list_p(expr)))):
|
|
return expr
|
|
else:
|
|
if sx_truthy(empty_p(expr)):
|
|
return expr
|
|
else:
|
|
return cons(first(expr), map(lambda child: (auto_quote_unknowns(child, env) if sx_truthy(list_p(child)) else ((lambda name: (child if sx_truthy((env_has(env, name) if sx_truthy(env_has(env, name)) else (starts_with_p(name, ':') if sx_truthy(starts_with_p(name, ':')) else (starts_with_p(name, '~') if sx_truthy(starts_with_p(name, '~')) else starts_with_p(name, '!'))))) else name))(symbol_name(child)) if sx_truthy((type_of(child) == 'symbol')) else child)), rest(expr)))
|
|
|
|
# prepare-url-expr
|
|
def prepare_url_expr(url_path, env):
|
|
expr = url_to_expr(url_path)
|
|
if sx_truthy(empty_p(expr)):
|
|
return expr
|
|
else:
|
|
return auto_quote_unknowns(expr, env)
|
|
|
|
|
|
# === Transpiled from cek (explicit CEK machine evaluator) ===
|
|
|
|
# cek-run
|
|
def cek_run(state):
|
|
if sx_truthy(cek_terminal_p(state)):
|
|
return cek_value(state)
|
|
else:
|
|
return cek_run(cek_step(state))
|
|
|
|
# cek-step
|
|
def cek_step(state):
|
|
if sx_truthy((cek_phase(state) == 'eval')):
|
|
return step_eval(state)
|
|
else:
|
|
return step_continue(state)
|
|
|
|
# step-eval
|
|
def step_eval(state):
|
|
expr = cek_control(state)
|
|
env = cek_env(state)
|
|
kont = cek_kont(state)
|
|
_match = type_of(expr)
|
|
if _match == 'number':
|
|
return make_cek_value(expr, env, kont)
|
|
elif _match == 'string':
|
|
return make_cek_value(expr, env, kont)
|
|
elif _match == 'boolean':
|
|
return make_cek_value(expr, env, kont)
|
|
elif _match == 'nil':
|
|
return make_cek_value(NIL, env, kont)
|
|
elif _match == 'symbol':
|
|
name = symbol_name(expr)
|
|
val = (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)))))))
|
|
return make_cek_value(val, env, kont)
|
|
elif _match == 'keyword':
|
|
return make_cek_value(keyword_name(expr), env, kont)
|
|
elif _match == 'dict':
|
|
ks = keys(expr)
|
|
if sx_truthy(empty_p(ks)):
|
|
return make_cek_value({}, env, kont)
|
|
else:
|
|
first_key = first(ks)
|
|
remaining_entries = []
|
|
for k in rest(ks):
|
|
remaining_entries.append([k, get(expr, k)])
|
|
return make_cek_state(get(expr, first_key), env, kont_push(make_dict_frame(remaining_entries, [[first_key]], env), kont))
|
|
elif _match == 'list':
|
|
if sx_truthy(empty_p(expr)):
|
|
return make_cek_value([], env, kont)
|
|
else:
|
|
return step_eval_list(expr, env, kont)
|
|
else:
|
|
return make_cek_value(expr, env, kont)
|
|
|
|
# step-eval-list
|
|
def step_eval_list(expr, env, kont):
|
|
head = first(expr)
|
|
args = rest(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')))))):
|
|
if sx_truthy(empty_p(expr)):
|
|
return make_cek_value([], env, kont)
|
|
else:
|
|
return make_cek_state(first(expr), env, kont_push(make_map_frame(NIL, rest(expr), [], env), kont))
|
|
else:
|
|
if sx_truthy((type_of(head) == 'symbol')):
|
|
name = symbol_name(head)
|
|
if sx_truthy((name == 'if')):
|
|
return step_sf_if(args, env, kont)
|
|
elif sx_truthy((name == 'when')):
|
|
return step_sf_when(args, env, kont)
|
|
elif sx_truthy((name == 'cond')):
|
|
return step_sf_cond(args, env, kont)
|
|
elif sx_truthy((name == 'case')):
|
|
return step_sf_case(args, env, kont)
|
|
elif sx_truthy((name == 'and')):
|
|
return step_sf_and(args, env, kont)
|
|
elif sx_truthy((name == 'or')):
|
|
return step_sf_or(args, env, kont)
|
|
elif sx_truthy((name == 'let')):
|
|
return step_sf_let(args, env, kont)
|
|
elif sx_truthy((name == 'let*')):
|
|
return step_sf_let(args, env, kont)
|
|
elif sx_truthy((name == 'lambda')):
|
|
return step_sf_lambda(args, env, kont)
|
|
elif sx_truthy((name == 'fn')):
|
|
return step_sf_lambda(args, env, kont)
|
|
elif sx_truthy((name == 'define')):
|
|
return step_sf_define(args, env, kont)
|
|
elif sx_truthy((name == 'defcomp')):
|
|
return make_cek_value(sf_defcomp(args, env), env, kont)
|
|
elif sx_truthy((name == 'defisland')):
|
|
return make_cek_value(sf_defisland(args, env), env, kont)
|
|
elif sx_truthy((name == 'defmacro')):
|
|
return make_cek_value(sf_defmacro(args, env), env, kont)
|
|
elif sx_truthy((name == 'defstyle')):
|
|
return make_cek_value(sf_defstyle(args, env), env, kont)
|
|
elif sx_truthy((name == 'defhandler')):
|
|
return make_cek_value(sf_defhandler(args, env), env, kont)
|
|
elif sx_truthy((name == 'defpage')):
|
|
return make_cek_value(sf_defpage(args, env), env, kont)
|
|
elif sx_truthy((name == 'defquery')):
|
|
return make_cek_value(sf_defquery(args, env), env, kont)
|
|
elif sx_truthy((name == 'defaction')):
|
|
return make_cek_value(sf_defaction(args, env), env, kont)
|
|
elif sx_truthy((name == 'deftype')):
|
|
return make_cek_value(sf_deftype(args, env), env, kont)
|
|
elif sx_truthy((name == 'defeffect')):
|
|
return make_cek_value(sf_defeffect(args, env), env, kont)
|
|
elif sx_truthy((name == 'begin')):
|
|
return step_sf_begin(args, env, kont)
|
|
elif sx_truthy((name == 'do')):
|
|
return step_sf_begin(args, env, kont)
|
|
elif sx_truthy((name == 'quote')):
|
|
return make_cek_value((NIL if sx_truthy(empty_p(args)) else first(args)), env, kont)
|
|
elif sx_truthy((name == 'quasiquote')):
|
|
return make_cek_value(qq_expand(first(args), env), env, kont)
|
|
elif sx_truthy((name == '->')):
|
|
return step_sf_thread_first(args, env, kont)
|
|
elif sx_truthy((name == 'set!')):
|
|
return step_sf_set_b(args, env, kont)
|
|
elif sx_truthy((name == 'letrec')):
|
|
return make_cek_value(sf_letrec(args, env), env, kont)
|
|
elif sx_truthy((name == 'reset')):
|
|
return step_sf_reset(args, env, kont)
|
|
elif sx_truthy((name == 'shift')):
|
|
return step_sf_shift(args, env, kont)
|
|
elif sx_truthy((name == 'deref')):
|
|
return step_sf_deref(args, env, kont)
|
|
elif sx_truthy((name == 'scope')):
|
|
return step_sf_scope(args, env, kont)
|
|
elif sx_truthy((name == 'provide')):
|
|
return step_sf_provide(args, env, kont)
|
|
elif sx_truthy((name == 'dynamic-wind')):
|
|
return make_cek_value(sf_dynamic_wind(args, env), env, kont)
|
|
elif sx_truthy((name == 'map')):
|
|
return step_ho_map(args, env, kont)
|
|
elif sx_truthy((name == 'map-indexed')):
|
|
return make_cek_value(ho_map_indexed(args, env), env, kont)
|
|
elif sx_truthy((name == 'filter')):
|
|
return step_ho_filter(args, env, kont)
|
|
elif sx_truthy((name == 'reduce')):
|
|
return step_ho_reduce(args, env, kont)
|
|
elif sx_truthy((name == 'some')):
|
|
return make_cek_value(ho_some(args, env), env, kont)
|
|
elif sx_truthy((name == 'every?')):
|
|
return make_cek_value(ho_every(args, env), env, kont)
|
|
elif sx_truthy((name == 'for-each')):
|
|
return step_ho_for_each(args, env, kont)
|
|
elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
|
|
mac = env_get(env, name)
|
|
return make_cek_state(expand_macro(mac, args, env), env, kont)
|
|
elif sx_truthy((render_active_p() if not sx_truthy(render_active_p()) else is_render_expr(expr))):
|
|
return make_cek_value(render_expr(expr, env), env, kont)
|
|
else:
|
|
return step_eval_call(head, args, env, kont)
|
|
else:
|
|
return step_eval_call(head, args, env, kont)
|
|
|
|
# step-sf-if
|
|
def step_sf_if(args, env, kont):
|
|
return make_cek_state(first(args), env, kont_push(make_if_frame(nth(args, 1), (nth(args, 2) if sx_truthy((len(args) > 2)) else NIL), env), kont))
|
|
|
|
# step-sf-when
|
|
def step_sf_when(args, env, kont):
|
|
return make_cek_state(first(args), env, kont_push(make_when_frame(rest(args), env), kont))
|
|
|
|
# step-sf-begin
|
|
def step_sf_begin(args, env, kont):
|
|
if sx_truthy(empty_p(args)):
|
|
return make_cek_value(NIL, env, kont)
|
|
else:
|
|
if sx_truthy((len(args) == 1)):
|
|
return make_cek_state(first(args), env, kont)
|
|
else:
|
|
return make_cek_state(first(args), env, kont_push(make_begin_frame(rest(args), env), kont))
|
|
|
|
# step-sf-let
|
|
def step_sf_let(args, env, kont):
|
|
if sx_truthy((type_of(first(args)) == 'symbol')):
|
|
return make_cek_value(sf_named_let(args, env), env, kont)
|
|
else:
|
|
bindings = first(args)
|
|
body = rest(args)
|
|
local = env_extend(env)
|
|
if sx_truthy(empty_p(bindings)):
|
|
return step_sf_begin(body, local, kont)
|
|
else:
|
|
first_binding = (first(bindings) if sx_truthy(((type_of(first(bindings)) == 'list') if not sx_truthy((type_of(first(bindings)) == 'list')) else (len(first(bindings)) == 2))) else [first(bindings), nth(bindings, 1)])
|
|
rest_bindings = (rest(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 pairs: _sx_begin(reduce(lambda acc, i: _sx_append(pairs, [nth(bindings, (i * 2)), nth(bindings, ((i * 2) + 1))]), NIL, range(1, (len(bindings) / 2))), pairs))([]))
|
|
vname = (symbol_name(first(first_binding)) if sx_truthy((type_of(first(first_binding)) == 'symbol')) else first(first_binding))
|
|
return make_cek_state(nth(first_binding, 1), local, kont_push(make_let_frame(vname, rest_bindings, body, local), kont))
|
|
|
|
# step-sf-define
|
|
def step_sf_define(args, env, kont):
|
|
name_sym = first(args)
|
|
has_effects = ((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))
|
|
val_idx = (3 if sx_truthy(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))) else 1)
|
|
effect_list = (nth(args, 2) if sx_truthy(((len(args) >= 4) if not sx_truthy((len(args) >= 4)) else ((type_of(nth(args, 1)) == 'keyword') if not sx_truthy((type_of(nth(args, 1)) == 'keyword')) else (keyword_name(nth(args, 1)) == 'effects')))) else NIL)
|
|
return make_cek_state(nth(args, val_idx), env, kont_push(make_define_frame(symbol_name(name_sym), env, has_effects, effect_list), kont))
|
|
|
|
# step-sf-set!
|
|
def step_sf_set_b(args, env, kont):
|
|
return make_cek_state(nth(args, 1), env, kont_push(make_set_frame(symbol_name(first(args)), env), kont))
|
|
|
|
# step-sf-and
|
|
def step_sf_and(args, env, kont):
|
|
if sx_truthy(empty_p(args)):
|
|
return make_cek_value(True, env, kont)
|
|
else:
|
|
return make_cek_state(first(args), env, kont_push(make_and_frame(rest(args), env), kont))
|
|
|
|
# step-sf-or
|
|
def step_sf_or(args, env, kont):
|
|
if sx_truthy(empty_p(args)):
|
|
return make_cek_value(False, env, kont)
|
|
else:
|
|
return make_cek_state(first(args), env, kont_push(make_or_frame(rest(args), env), kont))
|
|
|
|
# step-sf-cond
|
|
def step_sf_cond(args, env, kont):
|
|
scheme_p = cond_scheme_p(args)
|
|
if sx_truthy(scheme_p):
|
|
if sx_truthy(empty_p(args)):
|
|
return make_cek_value(NIL, env, kont)
|
|
else:
|
|
clause = first(args)
|
|
test = first(clause)
|
|
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')))):
|
|
return make_cek_state(nth(clause, 1), env, kont)
|
|
else:
|
|
return make_cek_state(test, env, kont_push(make_cond_frame(args, env, True), kont))
|
|
else:
|
|
if sx_truthy((len(args) < 2)):
|
|
return make_cek_value(NIL, env, kont)
|
|
else:
|
|
test = first(args)
|
|
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'))))):
|
|
return make_cek_state(nth(args, 1), env, kont)
|
|
else:
|
|
return make_cek_state(test, env, kont_push(make_cond_frame(args, env, False), kont))
|
|
|
|
# step-sf-case
|
|
def step_sf_case(args, env, kont):
|
|
return make_cek_state(first(args), env, kont_push(make_case_frame(NIL, rest(args), env), kont))
|
|
|
|
# step-sf-thread-first
|
|
def step_sf_thread_first(args, env, kont):
|
|
return make_cek_state(first(args), env, kont_push(make_thread_frame(rest(args), env), kont))
|
|
|
|
# step-sf-lambda
|
|
def step_sf_lambda(args, env, kont):
|
|
return make_cek_value(sf_lambda(args, env), env, kont)
|
|
|
|
# step-sf-scope
|
|
def step_sf_scope(args, env, kont):
|
|
return make_cek_value(sf_scope(args, env), env, kont)
|
|
|
|
# step-sf-provide
|
|
def step_sf_provide(args, env, kont):
|
|
return make_cek_value(sf_provide(args, env), env, kont)
|
|
|
|
# step-sf-reset
|
|
def step_sf_reset(args, env, kont):
|
|
return make_cek_state(first(args), env, kont_push(make_reset_frame(env), kont))
|
|
|
|
# step-sf-shift
|
|
def step_sf_shift(args, env, kont):
|
|
k_name = symbol_name(first(args))
|
|
body = nth(args, 1)
|
|
captured_result = kont_capture_to_reset(kont)
|
|
captured = first(captured_result)
|
|
rest_kont = nth(captured_result, 1)
|
|
k = make_cek_continuation(captured, rest_kont)
|
|
shift_env = env_extend(env)
|
|
shift_env[k_name] = k
|
|
return make_cek_state(body, shift_env, rest_kont)
|
|
|
|
# step-sf-deref
|
|
def step_sf_deref(args, env, kont):
|
|
return make_cek_state(first(args), env, kont_push(make_deref_frame(env), kont))
|
|
|
|
# cek-call
|
|
def cek_call(f, args):
|
|
a = ([] if sx_truthy(is_nil(args)) else args)
|
|
if sx_truthy(is_nil(f)):
|
|
return NIL
|
|
elif sx_truthy(is_lambda(f)):
|
|
return cek_run(continue_with_call(f, a, {}, a, []))
|
|
elif sx_truthy(is_callable(f)):
|
|
return apply(f, a)
|
|
else:
|
|
return NIL
|
|
|
|
# reactive-shift-deref
|
|
def reactive_shift_deref(sig, env, kont):
|
|
_cells = {}
|
|
scan_result = kont_capture_to_reactive_reset(kont)
|
|
captured_frames = first(scan_result)
|
|
reset_frame = nth(scan_result, 1)
|
|
remaining_kont = nth(scan_result, 2)
|
|
update_fn = get(reset_frame, 'update-fn')
|
|
_cells['sub_disposers'] = []
|
|
subscriber = _sx_fn(lambda : (
|
|
for_each(lambda d: cek_call(d, NIL), _cells['sub_disposers']),
|
|
_sx_cell_set(_cells, 'sub_disposers', []),
|
|
(lambda new_reset: (lambda new_kont: with_island_scope(lambda d: _sx_append(_cells['sub_disposers'], d), lambda : cek_run(make_cek_value(signal_value(sig), env, new_kont))))(concat(captured_frames, [new_reset], remaining_kont)))(make_reactive_reset_frame(env, update_fn, False))
|
|
)[-1])
|
|
signal_add_sub(sig, subscriber)
|
|
register_in_scope(_sx_fn(lambda : (
|
|
signal_remove_sub(sig, subscriber),
|
|
for_each(lambda d: cek_call(d, NIL), _cells['sub_disposers'])
|
|
)[-1]))
|
|
initial_kont = concat(captured_frames, [reset_frame], remaining_kont)
|
|
return make_cek_value(signal_value(sig), env, initial_kont)
|
|
|
|
# step-eval-call
|
|
def step_eval_call(head, args, env, kont):
|
|
return make_cek_state(head, env, kont_push(make_arg_frame(NIL, [], args, env, args), kont))
|
|
|
|
# step-ho-map
|
|
def step_ho_map(args, env, kont):
|
|
return make_cek_value(ho_map(args, env), env, kont)
|
|
|
|
# step-ho-filter
|
|
def step_ho_filter(args, env, kont):
|
|
return make_cek_value(ho_filter(args, env), env, kont)
|
|
|
|
# step-ho-reduce
|
|
def step_ho_reduce(args, env, kont):
|
|
return make_cek_value(ho_reduce(args, env), env, kont)
|
|
|
|
# step-ho-for-each
|
|
def step_ho_for_each(args, env, kont):
|
|
return make_cek_value(ho_for_each(args, env), env, kont)
|
|
|
|
# step-continue
|
|
def step_continue(state):
|
|
value = cek_value(state)
|
|
env = cek_env(state)
|
|
kont = cek_kont(state)
|
|
if sx_truthy(kont_empty_p(kont)):
|
|
return state
|
|
else:
|
|
frame = kont_top(kont)
|
|
rest_k = kont_pop(kont)
|
|
ft = frame_type(frame)
|
|
if sx_truthy((ft == 'if')):
|
|
if sx_truthy((value if not sx_truthy(value) else (not sx_truthy(is_nil(value))))):
|
|
return make_cek_state(get(frame, 'then'), get(frame, 'env'), rest_k)
|
|
else:
|
|
if sx_truthy(is_nil(get(frame, 'else'))):
|
|
return make_cek_value(NIL, env, rest_k)
|
|
else:
|
|
return make_cek_state(get(frame, 'else'), get(frame, 'env'), rest_k)
|
|
elif sx_truthy((ft == 'when')):
|
|
if sx_truthy((value if not sx_truthy(value) else (not sx_truthy(is_nil(value))))):
|
|
body = get(frame, 'body')
|
|
fenv = get(frame, 'env')
|
|
if sx_truthy(empty_p(body)):
|
|
return make_cek_value(NIL, fenv, rest_k)
|
|
else:
|
|
if sx_truthy((len(body) == 1)):
|
|
return make_cek_state(first(body), fenv, rest_k)
|
|
else:
|
|
return make_cek_state(first(body), fenv, kont_push(make_begin_frame(rest(body), fenv), rest_k))
|
|
else:
|
|
return make_cek_value(NIL, env, rest_k)
|
|
elif sx_truthy((ft == 'begin')):
|
|
remaining = get(frame, 'remaining')
|
|
fenv = get(frame, 'env')
|
|
if sx_truthy(empty_p(remaining)):
|
|
return make_cek_value(value, fenv, rest_k)
|
|
else:
|
|
if sx_truthy((len(remaining) == 1)):
|
|
return make_cek_state(first(remaining), fenv, rest_k)
|
|
else:
|
|
return make_cek_state(first(remaining), fenv, kont_push(make_begin_frame(rest(remaining), fenv), rest_k))
|
|
elif sx_truthy((ft == 'let')):
|
|
name = get(frame, 'name')
|
|
remaining = get(frame, 'remaining')
|
|
body = get(frame, 'body')
|
|
local = get(frame, 'env')
|
|
local[name] = value
|
|
if sx_truthy(empty_p(remaining)):
|
|
return step_sf_begin(body, local, rest_k)
|
|
else:
|
|
next_binding = first(remaining)
|
|
vname = (symbol_name(first(next_binding)) if sx_truthy((type_of(first(next_binding)) == 'symbol')) else first(next_binding))
|
|
return make_cek_state(nth(next_binding, 1), local, kont_push(make_let_frame(vname, rest(remaining), body, local), rest_k))
|
|
elif sx_truthy((ft == 'define')):
|
|
name = get(frame, 'name')
|
|
fenv = get(frame, 'env')
|
|
has_effects = get(frame, 'has-effects')
|
|
effect_list = get(frame, 'effect-list')
|
|
if sx_truthy((is_lambda(value) if not sx_truthy(is_lambda(value)) else is_nil(lambda_name(value)))):
|
|
value.name = name
|
|
fenv[name] = value
|
|
if sx_truthy(has_effects):
|
|
effect_names = (map(lambda e: (symbol_name(e) if sx_truthy((type_of(e) == 'symbol')) else sx_str(e)), effect_list) if sx_truthy((type_of(effect_list) == 'list')) else [sx_str(effect_list)])
|
|
effect_anns = (env_get(fenv, '*effect-annotations*') if sx_truthy(env_has(fenv, '*effect-annotations*')) else {})
|
|
effect_anns[name] = effect_names
|
|
fenv['*effect-annotations*'] = effect_anns
|
|
return make_cek_value(value, fenv, rest_k)
|
|
elif sx_truthy((ft == 'set')):
|
|
name = get(frame, 'name')
|
|
fenv = get(frame, 'env')
|
|
fenv[name] = value
|
|
return make_cek_value(value, env, rest_k)
|
|
elif sx_truthy((ft == 'and')):
|
|
if sx_truthy((not sx_truthy(value))):
|
|
return make_cek_value(value, env, rest_k)
|
|
else:
|
|
remaining = get(frame, 'remaining')
|
|
if sx_truthy(empty_p(remaining)):
|
|
return make_cek_value(value, env, rest_k)
|
|
else:
|
|
return make_cek_state(first(remaining), get(frame, 'env'), (rest_k if sx_truthy((len(remaining) == 1)) else kont_push(make_and_frame(rest(remaining), get(frame, 'env')), rest_k)))
|
|
elif sx_truthy((ft == 'or')):
|
|
if sx_truthy(value):
|
|
return make_cek_value(value, env, rest_k)
|
|
else:
|
|
remaining = get(frame, 'remaining')
|
|
if sx_truthy(empty_p(remaining)):
|
|
return make_cek_value(False, env, rest_k)
|
|
else:
|
|
return make_cek_state(first(remaining), get(frame, 'env'), (rest_k if sx_truthy((len(remaining) == 1)) else kont_push(make_or_frame(rest(remaining), get(frame, 'env')), rest_k)))
|
|
elif sx_truthy((ft == 'cond')):
|
|
remaining = get(frame, 'remaining')
|
|
fenv = get(frame, 'env')
|
|
scheme_p = get(frame, 'scheme')
|
|
if sx_truthy(scheme_p):
|
|
if sx_truthy(value):
|
|
return make_cek_state(nth(first(remaining), 1), fenv, rest_k)
|
|
else:
|
|
next_clauses = rest(remaining)
|
|
if sx_truthy(empty_p(next_clauses)):
|
|
return make_cek_value(NIL, fenv, rest_k)
|
|
else:
|
|
next_clause = first(next_clauses)
|
|
next_test = first(next_clause)
|
|
if sx_truthy((((type_of(next_test) == 'symbol') if not sx_truthy((type_of(next_test) == 'symbol')) else ((symbol_name(next_test) == 'else') if sx_truthy((symbol_name(next_test) == 'else')) else (symbol_name(next_test) == ':else'))) if sx_truthy(((type_of(next_test) == 'symbol') if not sx_truthy((type_of(next_test) == 'symbol')) else ((symbol_name(next_test) == 'else') if sx_truthy((symbol_name(next_test) == 'else')) else (symbol_name(next_test) == ':else')))) else ((type_of(next_test) == 'keyword') if not sx_truthy((type_of(next_test) == 'keyword')) else (keyword_name(next_test) == 'else')))):
|
|
return make_cek_state(nth(next_clause, 1), fenv, rest_k)
|
|
else:
|
|
return make_cek_state(next_test, fenv, kont_push(make_cond_frame(next_clauses, fenv, True), rest_k))
|
|
else:
|
|
if sx_truthy(value):
|
|
return make_cek_state(nth(remaining, 1), fenv, rest_k)
|
|
else:
|
|
next = slice(remaining, 2)
|
|
if sx_truthy((len(next) < 2)):
|
|
return make_cek_value(NIL, fenv, rest_k)
|
|
else:
|
|
next_test = first(next)
|
|
if sx_truthy((((type_of(next_test) == 'keyword') if not sx_truthy((type_of(next_test) == 'keyword')) else (keyword_name(next_test) == 'else')) if sx_truthy(((type_of(next_test) == 'keyword') if not sx_truthy((type_of(next_test) == 'keyword')) else (keyword_name(next_test) == 'else'))) else ((type_of(next_test) == 'symbol') if not sx_truthy((type_of(next_test) == 'symbol')) else ((symbol_name(next_test) == 'else') if sx_truthy((symbol_name(next_test) == 'else')) else (symbol_name(next_test) == ':else'))))):
|
|
return make_cek_state(nth(next, 1), fenv, rest_k)
|
|
else:
|
|
return make_cek_state(next_test, fenv, kont_push(make_cond_frame(next, fenv, False), rest_k))
|
|
elif sx_truthy((ft == 'case')):
|
|
match_val = get(frame, 'match-val')
|
|
remaining = get(frame, 'remaining')
|
|
fenv = get(frame, 'env')
|
|
if sx_truthy(is_nil(match_val)):
|
|
return sf_case_step_loop(value, remaining, fenv, rest_k)
|
|
else:
|
|
return sf_case_step_loop(match_val, remaining, fenv, rest_k)
|
|
elif sx_truthy((ft == 'thread')):
|
|
remaining = get(frame, 'remaining')
|
|
fenv = get(frame, 'env')
|
|
if sx_truthy(empty_p(remaining)):
|
|
return make_cek_value(value, fenv, rest_k)
|
|
else:
|
|
form = first(remaining)
|
|
rest_forms = rest(remaining)
|
|
result = ((lambda f: (lambda rargs: (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, fenv)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(cons(value, rargs)))(map(lambda a: trampoline(eval_expr(a, fenv)), rest(form))))(trampoline(eval_expr(first(form), fenv))) if sx_truthy((type_of(form) == 'list')) else (lambda f: (f(value) 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, [value], fenv)) if sx_truthy(is_lambda(f)) else error(sx_str('-> form not callable: ', inspect(f))))))(trampoline(eval_expr(form, fenv))))
|
|
if sx_truthy(empty_p(rest_forms)):
|
|
return make_cek_value(result, fenv, rest_k)
|
|
else:
|
|
return make_cek_value(result, fenv, kont_push(make_thread_frame(rest_forms, fenv), rest_k))
|
|
elif sx_truthy((ft == 'arg')):
|
|
f = get(frame, 'f')
|
|
evaled = get(frame, 'evaled')
|
|
remaining = get(frame, 'remaining')
|
|
fenv = get(frame, 'env')
|
|
raw_args = get(frame, 'raw-args')
|
|
if sx_truthy(is_nil(f)):
|
|
if sx_truthy(empty_p(remaining)):
|
|
return continue_with_call(value, [], fenv, raw_args, rest_k)
|
|
else:
|
|
return make_cek_state(first(remaining), fenv, kont_push(make_arg_frame(value, [], rest(remaining), fenv, raw_args), rest_k))
|
|
else:
|
|
new_evaled = append(evaled, [value])
|
|
if sx_truthy(empty_p(remaining)):
|
|
return continue_with_call(f, new_evaled, fenv, raw_args, rest_k)
|
|
else:
|
|
return make_cek_state(first(remaining), fenv, kont_push(make_arg_frame(f, new_evaled, rest(remaining), fenv, raw_args), rest_k))
|
|
elif sx_truthy((ft == 'dict')):
|
|
remaining = get(frame, 'remaining')
|
|
results = get(frame, 'results')
|
|
fenv = get(frame, 'env')
|
|
last_result = last(results)
|
|
completed = append(slice(results, 0, (len(results) - 1)), [[first(last_result), value]])
|
|
if sx_truthy(empty_p(remaining)):
|
|
d = {}
|
|
for pair in completed:
|
|
d[first(pair)] = nth(pair, 1)
|
|
return make_cek_value(d, fenv, rest_k)
|
|
else:
|
|
next_entry = first(remaining)
|
|
return make_cek_state(nth(next_entry, 1), fenv, kont_push(make_dict_frame(rest(remaining), append(completed, [[first(next_entry)]]), fenv), rest_k))
|
|
elif sx_truthy((ft == 'reset')):
|
|
return make_cek_value(value, env, rest_k)
|
|
elif sx_truthy((ft == 'deref')):
|
|
val = value
|
|
fenv = get(frame, 'env')
|
|
if sx_truthy((not sx_truthy(is_signal(val)))):
|
|
return make_cek_value(val, fenv, rest_k)
|
|
else:
|
|
if sx_truthy(has_reactive_reset_frame_p(rest_k)):
|
|
return reactive_shift_deref(val, fenv, rest_k)
|
|
else:
|
|
ctx = sx_context('sx-reactive', NIL)
|
|
if sx_truthy(ctx):
|
|
dep_list = get(ctx, 'deps')
|
|
notify_fn = get(ctx, 'notify')
|
|
if sx_truthy((not sx_truthy(contains_p(dep_list, val)))):
|
|
dep_list.append(val)
|
|
signal_add_sub(val, notify_fn)
|
|
return make_cek_value(signal_value(val), fenv, rest_k)
|
|
elif sx_truthy((ft == 'reactive-reset')):
|
|
update_fn = get(frame, 'update-fn')
|
|
first_p = get(frame, 'first-render')
|
|
if sx_truthy((update_fn if not sx_truthy(update_fn) else (not sx_truthy(first_p)))):
|
|
cek_call(update_fn, [value])
|
|
return make_cek_value(value, env, rest_k)
|
|
elif sx_truthy((ft == 'scope')):
|
|
name = get(frame, 'name')
|
|
remaining = get(frame, 'remaining')
|
|
fenv = get(frame, 'env')
|
|
if sx_truthy(empty_p(remaining)):
|
|
scope_pop(name)
|
|
return make_cek_value(value, fenv, rest_k)
|
|
else:
|
|
return make_cek_state(first(remaining), fenv, kont_push(make_scope_frame(name, rest(remaining), fenv), rest_k))
|
|
else:
|
|
return error(sx_str('Unknown frame type: ', ft))
|
|
|
|
# continue-with-call
|
|
def continue_with_call(f, args, env, raw_args, kont):
|
|
if sx_truthy(continuation_p(f)):
|
|
arg = (NIL if sx_truthy(empty_p(args)) else first(args))
|
|
cont_data = continuation_data(f)
|
|
captured = get(cont_data, 'captured')
|
|
rest_k = get(cont_data, 'rest-kont')
|
|
return make_cek_value(arg, env, concat(captured, rest_k))
|
|
elif 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))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))):
|
|
return make_cek_value(apply(f, args), env, kont)
|
|
elif sx_truthy(is_lambda(f)):
|
|
params = lambda_params(f)
|
|
local = env_merge(lambda_closure(f), env)
|
|
if sx_truthy((len(args) > len(params))):
|
|
return error(sx_str((lambda_name(f) if sx_truthy(lambda_name(f)) else 'lambda'), ' expects ', len(params), ' args, got ', len(args)))
|
|
else:
|
|
for pair in zip(params, args):
|
|
local[first(pair)] = nth(pair, 1)
|
|
for p in slice(params, len(args)):
|
|
local[p] = NIL
|
|
return make_cek_state(lambda_body(f), local, kont)
|
|
elif sx_truthy((is_component(f) if sx_truthy(is_component(f)) else is_island(f))):
|
|
parsed = parse_keyword_args(raw_args, env)
|
|
kwargs = first(parsed)
|
|
children = nth(parsed, 1)
|
|
local = env_merge(component_closure(f), env)
|
|
for p in component_params(f):
|
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_get(kwargs, p)) else NIL)
|
|
if sx_truthy(component_has_children(f)):
|
|
local['children'] = children
|
|
return make_cek_state(component_body(f), local, kont)
|
|
else:
|
|
return error(sx_str('Not callable: ', inspect(f)))
|
|
|
|
# sf-case-step-loop
|
|
def sf_case_step_loop(match_val, clauses, env, kont):
|
|
if sx_truthy((len(clauses) < 2)):
|
|
return make_cek_value(NIL, env, kont)
|
|
else:
|
|
test = first(clauses)
|
|
body = nth(clauses, 1)
|
|
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'))))):
|
|
return make_cek_state(body, env, kont)
|
|
else:
|
|
test_val = trampoline(eval_expr(test, env))
|
|
if sx_truthy((match_val == test_val)):
|
|
return make_cek_state(body, env, kont)
|
|
else:
|
|
return sf_case_step_loop(match_val, slice(clauses, 2), env, kont)
|
|
|
|
# eval-expr-cek
|
|
def eval_expr_cek(expr, env):
|
|
return cek_run(make_cek_state(expr, env, []))
|
|
|
|
# trampoline-cek
|
|
def trampoline_cek(val):
|
|
if sx_truthy(is_thunk(val)):
|
|
return eval_expr_cek(thunk_expr(val), thunk_env(val))
|
|
else:
|
|
return val
|
|
|
|
|
|
# === Transpiled from signals (reactive signal runtime) ===
|
|
|
|
# make-signal
|
|
def make_signal(value):
|
|
return {'__signal': True, 'value': value, 'subscribers': [], 'deps': []}
|
|
|
|
# signal?
|
|
def is_signal(x):
|
|
return (dict_p(x) if not sx_truthy(dict_p(x)) else has_key_p(x, '__signal'))
|
|
|
|
# signal-value
|
|
def signal_value(s):
|
|
return get(s, 'value')
|
|
|
|
# signal-set-value!
|
|
def signal_set_value(s, v):
|
|
return _sx_dict_set(s, 'value', v)
|
|
|
|
# signal-subscribers
|
|
def signal_subscribers(s):
|
|
return get(s, 'subscribers')
|
|
|
|
# signal-add-sub!
|
|
def signal_add_sub(s, f):
|
|
if sx_truthy((not sx_truthy(contains_p(get(s, 'subscribers'), f)))):
|
|
return _sx_append(get(s, 'subscribers'), f)
|
|
return NIL
|
|
|
|
# signal-remove-sub!
|
|
def signal_remove_sub(s, f):
|
|
return _sx_dict_set(s, 'subscribers', filter(lambda sub: (not sx_truthy(is_identical(sub, f))), get(s, 'subscribers')))
|
|
|
|
# signal-deps
|
|
def signal_deps(s):
|
|
return get(s, 'deps')
|
|
|
|
# signal-set-deps!
|
|
def signal_set_deps(s, deps):
|
|
return _sx_dict_set(s, 'deps', deps)
|
|
|
|
# signal
|
|
def signal(initial_value):
|
|
return make_signal(initial_value)
|
|
|
|
# deref
|
|
def deref(s):
|
|
if sx_truthy((not sx_truthy(is_signal(s)))):
|
|
return s
|
|
else:
|
|
ctx = sx_context('sx-reactive', NIL)
|
|
if sx_truthy(ctx):
|
|
dep_list = get(ctx, 'deps')
|
|
notify_fn = get(ctx, 'notify')
|
|
if sx_truthy((not sx_truthy(contains_p(dep_list, s)))):
|
|
dep_list.append(s)
|
|
signal_add_sub(s, notify_fn)
|
|
return signal_value(s)
|
|
|
|
# reset!
|
|
def reset_b(s, value):
|
|
if sx_truthy(is_signal(s)):
|
|
old = signal_value(s)
|
|
if sx_truthy((not sx_truthy(is_identical(old, value)))):
|
|
signal_set_value(s, value)
|
|
return notify_subscribers(s)
|
|
return NIL
|
|
return NIL
|
|
|
|
# swap!
|
|
def swap_b(s, f, *args):
|
|
if sx_truthy(is_signal(s)):
|
|
old = signal_value(s)
|
|
new_val = apply(f, cons(old, args))
|
|
if sx_truthy((not sx_truthy(is_identical(old, new_val)))):
|
|
signal_set_value(s, new_val)
|
|
return notify_subscribers(s)
|
|
return NIL
|
|
return NIL
|
|
|
|
# computed
|
|
def computed(compute_fn):
|
|
s = make_signal(NIL)
|
|
deps = []
|
|
compute_ctx = NIL
|
|
recompute = _sx_fn(lambda : (
|
|
for_each(lambda dep: signal_remove_sub(dep, recompute), signal_deps(s)),
|
|
signal_set_deps(s, []),
|
|
(lambda ctx: _sx_begin(scope_push('sx-reactive', ctx), (lambda new_val: _sx_begin(scope_pop('sx-reactive'), signal_set_deps(s, get(ctx, 'deps')), (lambda old: _sx_begin(signal_set_value(s, new_val), (notify_subscribers(s) if sx_truthy((not sx_truthy(is_identical(old, new_val)))) else NIL)))(signal_value(s))))(cek_call(compute_fn, NIL))))({'deps': [], 'notify': recompute})
|
|
)[-1])
|
|
recompute()
|
|
register_in_scope(lambda : dispose_computed(s))
|
|
return s
|
|
|
|
# effect
|
|
def effect(effect_fn):
|
|
_cells = {}
|
|
_cells['deps'] = []
|
|
_cells['disposed'] = False
|
|
_cells['cleanup_fn'] = NIL
|
|
run_effect = lambda : (_sx_begin((cek_call(_cells['cleanup_fn'], NIL) if sx_truthy(_cells['cleanup_fn']) else NIL), for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']), _sx_cell_set(_cells, 'deps', []), (lambda ctx: _sx_begin(scope_push('sx-reactive', ctx), (lambda result: _sx_begin(scope_pop('sx-reactive'), _sx_cell_set(_cells, 'deps', get(ctx, 'deps')), (_sx_cell_set(_cells, 'cleanup_fn', result) if sx_truthy(is_callable(result)) else NIL)))(cek_call(effect_fn, NIL))))({'deps': [], 'notify': run_effect})) if sx_truthy((not sx_truthy(_cells['disposed']))) else NIL)
|
|
run_effect()
|
|
dispose_fn = _sx_fn(lambda : (
|
|
_sx_cell_set(_cells, 'disposed', True),
|
|
(cek_call(_cells['cleanup_fn'], NIL) if sx_truthy(_cells['cleanup_fn']) else NIL),
|
|
for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']),
|
|
_sx_cell_set(_cells, 'deps', [])
|
|
)[-1])
|
|
register_in_scope(dispose_fn)
|
|
return dispose_fn
|
|
|
|
# *batch-depth*
|
|
_batch_depth = 0
|
|
|
|
# *batch-queue*
|
|
_batch_queue = []
|
|
|
|
# batch
|
|
def batch(thunk):
|
|
_batch_depth = (_batch_depth + 1)
|
|
cek_call(thunk, NIL)
|
|
_batch_depth = (_batch_depth - 1)
|
|
if sx_truthy((_batch_depth == 0)):
|
|
queue = _batch_queue
|
|
_batch_queue = []
|
|
seen = []
|
|
pending = []
|
|
for s in queue:
|
|
for sub in signal_subscribers(s):
|
|
if sx_truthy((not sx_truthy(contains_p(seen, sub)))):
|
|
seen.append(sub)
|
|
pending.append(sub)
|
|
for sub in pending:
|
|
sub()
|
|
return NIL
|
|
return NIL
|
|
|
|
# notify-subscribers
|
|
def notify_subscribers(s):
|
|
if sx_truthy((_batch_depth > 0)):
|
|
if sx_truthy((not sx_truthy(contains_p(_batch_queue, s)))):
|
|
return _sx_append(_batch_queue, s)
|
|
return NIL
|
|
else:
|
|
return flush_subscribers(s)
|
|
|
|
# flush-subscribers
|
|
def flush_subscribers(s):
|
|
for sub in signal_subscribers(s):
|
|
sub()
|
|
return NIL
|
|
|
|
# dispose-computed
|
|
def dispose_computed(s):
|
|
if sx_truthy(is_signal(s)):
|
|
for dep in signal_deps(s):
|
|
signal_remove_sub(dep, NIL)
|
|
return signal_set_deps(s, [])
|
|
return NIL
|
|
|
|
# with-island-scope
|
|
def with_island_scope(scope_fn, body_fn):
|
|
scope_push('sx-island-scope', scope_fn)
|
|
result = body_fn()
|
|
scope_pop('sx-island-scope')
|
|
return result
|
|
|
|
# register-in-scope
|
|
def register_in_scope(disposable):
|
|
collector = sx_context('sx-island-scope', NIL)
|
|
if sx_truthy(collector):
|
|
return cek_call(collector, [disposable])
|
|
return NIL
|
|
|
|
# with-marsh-scope
|
|
def with_marsh_scope(marsh_el, body_fn):
|
|
disposers = []
|
|
with_island_scope(lambda d: _sx_append(disposers, d), body_fn)
|
|
return dom_set_data(marsh_el, 'sx-marsh-disposers', disposers)
|
|
|
|
# dispose-marsh-scope
|
|
def dispose_marsh_scope(marsh_el):
|
|
disposers = dom_get_data(marsh_el, 'sx-marsh-disposers')
|
|
if sx_truthy(disposers):
|
|
for d in disposers:
|
|
cek_call(d, NIL)
|
|
return dom_set_data(marsh_el, 'sx-marsh-disposers', NIL)
|
|
return NIL
|
|
|
|
# *store-registry*
|
|
_store_registry = {}
|
|
|
|
# def-store
|
|
def def_store(name, init_fn):
|
|
registry = _store_registry
|
|
if sx_truthy((not sx_truthy(has_key_p(registry, name)))):
|
|
_store_registry = assoc(registry, name, cek_call(init_fn, NIL))
|
|
return get(_store_registry, name)
|
|
|
|
# use-store
|
|
def use_store(name):
|
|
if sx_truthy(has_key_p(_store_registry, name)):
|
|
return get(_store_registry, name)
|
|
else:
|
|
return error(sx_str('Store not found: ', name, '. Call (def-store ...) before (use-store ...).'))
|
|
|
|
# clear-stores
|
|
def clear_stores():
|
|
return _sx_cell_set(_cells, '_store_registry', {})
|
|
|
|
# emit-event
|
|
def emit_event(el, event_name, detail):
|
|
return dom_dispatch(el, event_name, detail)
|
|
|
|
# on-event
|
|
def on_event(el, event_name, handler):
|
|
return dom_listen(el, event_name, handler)
|
|
|
|
# bridge-event
|
|
def bridge_event(el, event_name, target_signal, transform_fn):
|
|
return effect(lambda : (lambda remove: remove)(dom_listen(el, event_name, lambda e: (lambda detail: (lambda new_val: reset_b(target_signal, new_val))((cek_call(transform_fn, [detail]) if sx_truthy(transform_fn) else detail)))(event_detail(e)))))
|
|
|
|
# resource
|
|
def resource(fetch_fn):
|
|
state = signal({'loading': True, 'data': NIL, 'error': NIL})
|
|
promise_then(cek_call(fetch_fn, NIL), lambda data: reset_b(state, {'loading': False, 'data': data, 'error': NIL}), lambda err: reset_b(state, {'loading': False, 'data': NIL, 'error': err}))
|
|
return state
|
|
|
|
|
|
# === Transpiled from adapter-async ===
|
|
|
|
# async-render
|
|
async def async_render(expr, env, ctx):
|
|
_match = type_of(expr)
|
|
if _match == 'nil':
|
|
return ''
|
|
elif _match == 'boolean':
|
|
return ''
|
|
elif _match == 'string':
|
|
return escape_html(expr)
|
|
elif _match == 'number':
|
|
return escape_html(sx_str(expr))
|
|
elif _match == 'raw-html':
|
|
return raw_html_content(expr)
|
|
elif _match == 'spread':
|
|
sx_emit('element-attrs', spread_attrs(expr))
|
|
return ''
|
|
elif _match == 'symbol':
|
|
val = (await async_eval(expr, env, ctx))
|
|
return (await async_render(val, env, ctx))
|
|
elif _match == 'keyword':
|
|
return escape_html(keyword_name(expr))
|
|
elif _match == 'list':
|
|
if sx_truthy(empty_p(expr)):
|
|
return ''
|
|
else:
|
|
return (await async_render_list(expr, env, ctx))
|
|
elif _match == 'dict':
|
|
return ''
|
|
else:
|
|
return escape_html(sx_str(expr))
|
|
|
|
# async-render-list
|
|
async def async_render_list(expr, env, ctx):
|
|
head = first(expr)
|
|
if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
|
|
if sx_truthy((is_lambda(head) if sx_truthy(is_lambda(head)) else (type_of(head) == 'list'))):
|
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
|
else:
|
|
return join('', (await async_map_render(expr, env, ctx)))
|
|
else:
|
|
name = symbol_name(head)
|
|
args = rest(expr)
|
|
if sx_truthy(io_primitive_p(name)):
|
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
|
elif sx_truthy((name == 'raw!')):
|
|
return (await async_render_raw(args, env, ctx))
|
|
elif sx_truthy((name == '<>')):
|
|
return join('', (await async_map_render(args, env, ctx)))
|
|
elif sx_truthy(starts_with_p(name, 'html:')):
|
|
return (await async_render_element(slice(name, 5), args, env, ctx))
|
|
elif sx_truthy(async_render_form_p(name)):
|
|
if sx_truthy((contains_p(HTML_TAGS, name) if not sx_truthy(contains_p(HTML_TAGS, name)) else (((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')) if sx_truthy(((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword'))) else svg_context_p()))):
|
|
return (await async_render_element(name, args, env, ctx))
|
|
else:
|
|
return (await dispatch_async_render_form(name, expr, env, ctx))
|
|
elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
|
|
return (await async_render(trampoline(expand_macro(env_get(env, name), args, env)), env, ctx))
|
|
elif sx_truthy(contains_p(HTML_TAGS, name)):
|
|
return (await async_render_element(name, args, env, ctx))
|
|
elif sx_truthy((starts_with_p(name, '~') if not sx_truthy(starts_with_p(name, '~')) else (env_has(env, name) if not sx_truthy(env_has(env, name)) else is_island(env_get(env, name))))):
|
|
return (await async_render_island(env_get(env, name), args, env, ctx))
|
|
elif sx_truthy(starts_with_p(name, '~')):
|
|
val = (env_get(env, name) if sx_truthy(env_has(env, name)) else NIL)
|
|
if sx_truthy(is_component(val)):
|
|
return (await async_render_component(val, args, env, ctx))
|
|
elif sx_truthy(is_macro(val)):
|
|
return (await async_render(trampoline(expand_macro(val, args, env)), env, ctx))
|
|
else:
|
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
|
elif sx_truthy(((index_of(name, '-') > 0) if not sx_truthy((index_of(name, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')))):
|
|
return (await async_render_element(name, args, env, ctx))
|
|
elif sx_truthy(svg_context_p()):
|
|
return (await async_render_element(name, args, env, ctx))
|
|
else:
|
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
|
|
|
# async-render-raw
|
|
async def async_render_raw(args, env, ctx):
|
|
parts = []
|
|
for arg in args:
|
|
val = (await async_eval(arg, env, ctx))
|
|
if sx_truthy(is_raw_html(val)):
|
|
parts.append(raw_html_content(val))
|
|
elif sx_truthy((type_of(val) == 'string')):
|
|
parts.append(val)
|
|
elif sx_truthy(((not sx_truthy(is_nil(val))) if not sx_truthy((not sx_truthy(is_nil(val)))) else (not sx_truthy((val == False))))):
|
|
parts.append(sx_str(val))
|
|
return join('', parts)
|
|
|
|
# async-render-element
|
|
async def async_render_element(tag, args, env, ctx):
|
|
attrs = {}
|
|
children = []
|
|
(await async_parse_element_args(args, attrs, children, env, ctx))
|
|
class_val = dict_get(attrs, 'class')
|
|
if sx_truthy(((not sx_truthy(is_nil(class_val))) if not sx_truthy((not sx_truthy(is_nil(class_val)))) else (not sx_truthy((class_val == False))))):
|
|
css_class_collect(sx_str(class_val))
|
|
if sx_truthy(contains_p(VOID_ELEMENTS, tag)):
|
|
return sx_str('<', tag, render_attrs(attrs), '>')
|
|
else:
|
|
token = (svg_context_set(True) if sx_truthy(((tag == 'svg') if sx_truthy((tag == 'svg')) else (tag == 'math'))) else NIL)
|
|
content_parts = []
|
|
scope_push('element-attrs', NIL)
|
|
for c in children:
|
|
content_parts.append((await async_render(c, env, ctx)))
|
|
for spread_dict in sx_emitted('element-attrs'):
|
|
merge_spread_attrs(attrs, spread_dict)
|
|
scope_pop('element-attrs')
|
|
if sx_truthy(token):
|
|
svg_context_reset(token)
|
|
return sx_str('<', tag, render_attrs(attrs), '>', join('', content_parts), '</', tag, '>')
|
|
|
|
# async-parse-element-args
|
|
async def async_parse_element_args(args, attrs, children, env, ctx):
|
|
_cells = {}
|
|
_cells['skip'] = False
|
|
_cells['i'] = 0
|
|
for arg in args:
|
|
if sx_truthy(_cells['skip']):
|
|
_cells['skip'] = False
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
|
|
val = (await async_eval(nth(args, (_cells['i'] + 1)), env, ctx))
|
|
attrs[keyword_name(arg)] = val
|
|
_cells['skip'] = True
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
children.append(arg)
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
return NIL
|
|
|
|
# async-render-component
|
|
async def async_render_component(comp, args, env, ctx):
|
|
kwargs = {}
|
|
children = []
|
|
(await async_parse_kw_args(args, kwargs, children, env, ctx))
|
|
local = env_merge(component_closure(comp), env)
|
|
for p in component_params(comp):
|
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
|
if sx_truthy(component_has_children(comp)):
|
|
parts = []
|
|
for c in children:
|
|
parts.append((await async_render(c, env, ctx)))
|
|
local['children'] = make_raw_html(join('', parts))
|
|
return (await async_render(component_body(comp), local, ctx))
|
|
|
|
# async-render-island
|
|
async def async_render_island(island, args, env, ctx):
|
|
kwargs = {}
|
|
children = []
|
|
(await async_parse_kw_args(args, kwargs, children, env, ctx))
|
|
local = env_merge(component_closure(island), env)
|
|
island_name = component_name(island)
|
|
for p in component_params(island):
|
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
|
if sx_truthy(component_has_children(island)):
|
|
parts = []
|
|
for c in children:
|
|
parts.append((await async_render(c, env, ctx)))
|
|
local['children'] = make_raw_html(join('', parts))
|
|
body_html = (await async_render(component_body(island), local, ctx))
|
|
state_json = serialize_island_state(kwargs)
|
|
return sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_json), '"') if sx_truthy(state_json) else ''), '>', body_html, '</span>')
|
|
|
|
# async-render-lambda
|
|
async def async_render_lambda(f, args, env, ctx):
|
|
local = env_merge(lambda_closure(f), env)
|
|
for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f))
|
|
return (await async_render(lambda_body(f), local, ctx))
|
|
|
|
# async-parse-kw-args
|
|
async def async_parse_kw_args(args, kwargs, children, env, ctx):
|
|
_cells = {}
|
|
_cells['skip'] = False
|
|
_cells['i'] = 0
|
|
for arg in args:
|
|
if sx_truthy(_cells['skip']):
|
|
_cells['skip'] = False
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
|
|
val = (await async_eval(nth(args, (_cells['i'] + 1)), env, ctx))
|
|
kwargs[keyword_name(arg)] = val
|
|
_cells['skip'] = True
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
children.append(arg)
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
return NIL
|
|
|
|
# async-map-render
|
|
async def async_map_render(exprs, env, ctx):
|
|
results = []
|
|
for x in exprs:
|
|
results.append((await async_render(x, env, ctx)))
|
|
return results
|
|
|
|
# ASYNC_RENDER_FORMS
|
|
ASYNC_RENDER_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each', 'scope', 'provide']
|
|
|
|
# async-render-form?
|
|
def async_render_form_p(name):
|
|
return contains_p(ASYNC_RENDER_FORMS, name)
|
|
|
|
# dispatch-async-render-form
|
|
async def dispatch_async_render_form(name, expr, env, ctx):
|
|
if sx_truthy((name == 'if')):
|
|
cond_val = (await async_eval(nth(expr, 1), env, ctx))
|
|
if sx_truthy(cond_val):
|
|
return (await async_render(nth(expr, 2), env, ctx))
|
|
else:
|
|
if sx_truthy((len(expr) > 3)):
|
|
return (await async_render(nth(expr, 3), env, ctx))
|
|
else:
|
|
return ''
|
|
elif sx_truthy((name == 'when')):
|
|
if sx_truthy((not sx_truthy((await async_eval(nth(expr, 1), env, ctx))))):
|
|
return ''
|
|
else:
|
|
if sx_truthy((len(expr) == 3)):
|
|
return (await async_render(nth(expr, 2), env, ctx))
|
|
else:
|
|
return join('', (await async_map_render(slice(expr, 2), env, ctx)))
|
|
elif sx_truthy((name == 'cond')):
|
|
clauses = rest(expr)
|
|
if sx_truthy(cond_scheme_p(clauses)):
|
|
return (await async_render_cond_scheme(clauses, env, ctx))
|
|
else:
|
|
return (await async_render_cond_clojure(clauses, env, ctx))
|
|
elif sx_truthy((name == 'case')):
|
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
|
elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
|
|
local = (await async_process_bindings(nth(expr, 1), env, ctx))
|
|
if sx_truthy((len(expr) == 3)):
|
|
return (await async_render(nth(expr, 2), local, ctx))
|
|
else:
|
|
return join('', (await async_map_render(slice(expr, 2), local, ctx)))
|
|
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
|
|
if sx_truthy((len(expr) == 2)):
|
|
return (await async_render(nth(expr, 1), env, ctx))
|
|
else:
|
|
return join('', (await async_map_render(rest(expr), env, ctx)))
|
|
elif sx_truthy(is_definition_form(name)):
|
|
(await async_eval(expr, env, ctx))
|
|
return ''
|
|
elif sx_truthy((name == 'map')):
|
|
f = (await async_eval(nth(expr, 1), env, ctx))
|
|
coll = (await async_eval(nth(expr, 2), env, ctx))
|
|
return join('', (await async_map_fn_render(f, coll, env, ctx)))
|
|
elif sx_truthy((name == 'map-indexed')):
|
|
f = (await async_eval(nth(expr, 1), env, ctx))
|
|
coll = (await async_eval(nth(expr, 2), env, ctx))
|
|
return join('', (await async_map_indexed_fn_render(f, coll, env, ctx)))
|
|
elif sx_truthy((name == 'filter')):
|
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
|
elif sx_truthy((name == 'for-each')):
|
|
f = (await async_eval(nth(expr, 1), env, ctx))
|
|
coll = (await async_eval(nth(expr, 2), env, ctx))
|
|
return join('', (await async_map_fn_render(f, coll, env, ctx)))
|
|
elif sx_truthy((name == 'scope')):
|
|
scope_name = (await async_eval(nth(expr, 1), env, ctx))
|
|
rest_args = slice(expr, 2)
|
|
scope_val = NIL
|
|
body_exprs = NIL
|
|
if sx_truthy(((len(rest_args) >= 2) if not sx_truthy((len(rest_args) >= 2)) else ((type_of(first(rest_args)) == 'keyword') if not sx_truthy((type_of(first(rest_args)) == 'keyword')) else (keyword_name(first(rest_args)) == 'value')))):
|
|
scope_val = (await async_eval(nth(rest_args, 1), env, ctx))
|
|
body_exprs = slice(rest_args, 2)
|
|
else:
|
|
body_exprs = rest_args
|
|
scope_push(scope_name, scope_val)
|
|
result = ((await async_render(first(body_exprs), env, ctx)) if sx_truthy((len(body_exprs) == 1)) else join('', (await async_map_render(body_exprs, env, ctx))))
|
|
scope_pop(scope_name)
|
|
return result
|
|
elif sx_truthy((name == 'provide')):
|
|
prov_name = (await async_eval(nth(expr, 1), env, ctx))
|
|
prov_val = (await async_eval(nth(expr, 2), env, ctx))
|
|
body_start = 3
|
|
body_count = (len(expr) - 3)
|
|
scope_push(prov_name, prov_val)
|
|
result = ((await async_render(nth(expr, body_start), env, ctx)) if sx_truthy((body_count == 1)) else join('', (await async_map_render(slice(expr, body_start), env, ctx))))
|
|
scope_pop(prov_name)
|
|
return result
|
|
else:
|
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
|
|
|
# async-render-cond-scheme
|
|
async def async_render_cond_scheme(clauses, env, ctx):
|
|
if sx_truthy(empty_p(clauses)):
|
|
return ''
|
|
else:
|
|
clause = first(clauses)
|
|
test = first(clause)
|
|
body = nth(clause, 1)
|
|
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')))):
|
|
return (await async_render(body, env, ctx))
|
|
else:
|
|
if sx_truthy((await async_eval(test, env, ctx))):
|
|
return (await async_render(body, env, ctx))
|
|
else:
|
|
return (await async_render_cond_scheme(rest(clauses), env, ctx))
|
|
|
|
# async-render-cond-clojure
|
|
async def async_render_cond_clojure(clauses, env, ctx):
|
|
if sx_truthy((len(clauses) < 2)):
|
|
return ''
|
|
else:
|
|
test = first(clauses)
|
|
body = nth(clauses, 1)
|
|
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'))))):
|
|
return (await async_render(body, env, ctx))
|
|
else:
|
|
if sx_truthy((await async_eval(test, env, ctx))):
|
|
return (await async_render(body, env, ctx))
|
|
else:
|
|
return (await async_render_cond_clojure(slice(clauses, 2), env, ctx))
|
|
|
|
# async-process-bindings
|
|
async def async_process_bindings(bindings, env, ctx):
|
|
local = env_extend(env)
|
|
if sx_truthy(((type_of(bindings) == 'list') if not sx_truthy((type_of(bindings) == 'list')) else (not sx_truthy(empty_p(bindings))))):
|
|
if sx_truthy((type_of(first(bindings)) == 'list')):
|
|
for pair in bindings:
|
|
if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))):
|
|
name = (symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair)))
|
|
local[name] = (await async_eval(nth(pair, 1), local, ctx))
|
|
else:
|
|
(await async_process_bindings_flat(bindings, local, ctx))
|
|
return local
|
|
|
|
# async-process-bindings-flat
|
|
async def async_process_bindings_flat(bindings, local, ctx):
|
|
_cells = {}
|
|
_cells['skip'] = False
|
|
_cells['i'] = 0
|
|
for item in bindings:
|
|
if sx_truthy(_cells['skip']):
|
|
_cells['skip'] = False
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
name = (symbol_name(item) if sx_truthy((type_of(item) == 'symbol')) else sx_str(item))
|
|
if sx_truthy(((_cells['i'] + 1) < len(bindings))):
|
|
local[name] = (await async_eval(nth(bindings, (_cells['i'] + 1)), local, ctx))
|
|
_cells['skip'] = True
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
return NIL
|
|
|
|
# async-map-fn-render
|
|
async def async_map_fn_render(f, coll, env, ctx):
|
|
results = []
|
|
for item in coll:
|
|
if sx_truthy(is_lambda(f)):
|
|
results.append((await async_render_lambda(f, [item], env, ctx)))
|
|
else:
|
|
r = (await async_invoke(f, item))
|
|
results.append((await async_render(r, env, ctx)))
|
|
return results
|
|
|
|
# async-map-indexed-fn-render
|
|
async def async_map_indexed_fn_render(f, coll, env, ctx):
|
|
_cells = {}
|
|
results = []
|
|
_cells['i'] = 0
|
|
for item in coll:
|
|
if sx_truthy(is_lambda(f)):
|
|
results.append((await async_render_lambda(f, [_cells['i'], item], env, ctx)))
|
|
else:
|
|
r = (await async_invoke(f, _cells['i'], item))
|
|
results.append((await async_render(r, env, ctx)))
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
return results
|
|
|
|
# async-invoke
|
|
async def async_invoke(f, *args):
|
|
r = apply(f, args)
|
|
if sx_truthy(is_async_coroutine(r)):
|
|
return (await async_await(r))
|
|
else:
|
|
return r
|
|
|
|
# async-aser
|
|
async def async_aser(expr, env, ctx):
|
|
t = type_of(expr)
|
|
result = NIL
|
|
if sx_truthy((t == 'number')):
|
|
result = expr
|
|
elif sx_truthy((t == 'string')):
|
|
result = expr
|
|
elif sx_truthy((t == 'boolean')):
|
|
result = expr
|
|
elif sx_truthy((t == 'nil')):
|
|
result = NIL
|
|
elif sx_truthy((t == 'symbol')):
|
|
name = symbol_name(expr)
|
|
result = (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)))))))
|
|
elif sx_truthy((t == 'keyword')):
|
|
result = keyword_name(expr)
|
|
elif sx_truthy((t == 'dict')):
|
|
result = (await async_aser_dict(expr, env, ctx))
|
|
elif sx_truthy((t == 'spread')):
|
|
sx_emit('element-attrs', spread_attrs(expr))
|
|
result = NIL
|
|
elif sx_truthy((t == 'list')):
|
|
result = ([] if sx_truthy(empty_p(expr)) else (await async_aser_list(expr, env, ctx)))
|
|
else:
|
|
result = expr
|
|
if sx_truthy(is_spread(result)):
|
|
sx_emit('element-attrs', spread_attrs(result))
|
|
return NIL
|
|
else:
|
|
return result
|
|
|
|
# async-aser-dict
|
|
async def async_aser_dict(expr, env, ctx):
|
|
result = {}
|
|
for key in keys(expr):
|
|
result[key] = (await async_aser(dict_get(expr, key), env, ctx))
|
|
return result
|
|
|
|
# async-aser-list
|
|
async def async_aser_list(expr, env, ctx):
|
|
head = first(expr)
|
|
args = rest(expr)
|
|
if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
|
|
if sx_truthy((is_lambda(head) if sx_truthy(is_lambda(head)) else (type_of(head) == 'list'))):
|
|
return (await async_aser_eval_call(head, args, env, ctx))
|
|
else:
|
|
return (await async_aser_map_list(expr, env, ctx))
|
|
else:
|
|
name = symbol_name(head)
|
|
if sx_truthy(io_primitive_p(name)):
|
|
return (await async_eval(expr, env, ctx))
|
|
elif sx_truthy((name == '<>')):
|
|
return (await async_aser_fragment(args, env, ctx))
|
|
elif sx_truthy((name == 'raw!')):
|
|
return (await async_aser_call('raw!', args, env, ctx))
|
|
elif sx_truthy(starts_with_p(name, 'html:')):
|
|
return (await async_aser_call(slice(name, 5), args, env, ctx))
|
|
elif sx_truthy(starts_with_p(name, '~')):
|
|
val = (env_get(env, name) if sx_truthy(env_has(env, name)) else NIL)
|
|
if sx_truthy(is_macro(val)):
|
|
return (await async_aser(trampoline(expand_macro(val, args, env)), env, ctx))
|
|
elif sx_truthy((is_component(val) if not sx_truthy(is_component(val)) else (expand_components_p() if sx_truthy(expand_components_p()) else (component_affinity(val) == 'server')))):
|
|
return (await async_aser_component(val, args, env, ctx))
|
|
else:
|
|
return (await async_aser_call(name, args, env, ctx))
|
|
elif sx_truthy(async_aser_form_p(name)):
|
|
if sx_truthy((contains_p(HTML_TAGS, name) if not sx_truthy(contains_p(HTML_TAGS, name)) else (((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')) if sx_truthy(((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword'))) else svg_context_p()))):
|
|
return (await async_aser_call(name, args, env, ctx))
|
|
else:
|
|
return (await dispatch_async_aser_form(name, expr, env, ctx))
|
|
elif sx_truthy(contains_p(HTML_TAGS, name)):
|
|
return (await async_aser_call(name, args, env, ctx))
|
|
elif sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))):
|
|
return (await async_aser(trampoline(expand_macro(env_get(env, name), args, env)), env, ctx))
|
|
elif sx_truthy(((index_of(name, '-') > 0) if not sx_truthy((index_of(name, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')))):
|
|
return (await async_aser_call(name, args, env, ctx))
|
|
elif sx_truthy(svg_context_p()):
|
|
return (await async_aser_call(name, args, env, ctx))
|
|
else:
|
|
return (await async_aser_eval_call(head, args, env, ctx))
|
|
|
|
# async-aser-eval-call
|
|
async def async_aser_eval_call(head, args, env, ctx):
|
|
f = (await async_eval(head, env, ctx))
|
|
evaled_args = (await async_eval_args(args, env, ctx))
|
|
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)))))):
|
|
r = apply(f, evaled_args)
|
|
if sx_truthy(is_async_coroutine(r)):
|
|
return (await async_await(r))
|
|
else:
|
|
return r
|
|
elif sx_truthy(is_lambda(f)):
|
|
local = env_merge(lambda_closure(f), env)
|
|
for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(evaled_args, i)), lambda_params(f))
|
|
return (await async_aser(lambda_body(f), local, ctx))
|
|
elif sx_truthy(is_component(f)):
|
|
return (await async_aser_call(sx_str('~', component_name(f)), args, env, ctx))
|
|
elif sx_truthy(is_island(f)):
|
|
return (await async_aser_call(sx_str('~', component_name(f)), args, env, ctx))
|
|
else:
|
|
return error(sx_str('Not callable: ', inspect(f)))
|
|
|
|
# async-eval-args
|
|
async def async_eval_args(args, env, ctx):
|
|
results = []
|
|
for a in args:
|
|
results.append((await async_eval(a, env, ctx)))
|
|
return results
|
|
|
|
# async-aser-map-list
|
|
async def async_aser_map_list(exprs, env, ctx):
|
|
results = []
|
|
for x in exprs:
|
|
results.append((await async_aser(x, env, ctx)))
|
|
return results
|
|
|
|
# async-aser-fragment
|
|
async def async_aser_fragment(children, env, ctx):
|
|
parts = []
|
|
for c in children:
|
|
result = (await async_aser(c, env, ctx))
|
|
if sx_truthy((type_of(result) == 'list')):
|
|
for item in result:
|
|
if sx_truthy((not sx_truthy(is_nil(item)))):
|
|
parts.append(serialize(item))
|
|
else:
|
|
if sx_truthy((not sx_truthy(is_nil(result)))):
|
|
parts.append(serialize(result))
|
|
if sx_truthy(empty_p(parts)):
|
|
return make_sx_expr('')
|
|
else:
|
|
return make_sx_expr(sx_str('(<> ', join(' ', parts), ')'))
|
|
|
|
# async-aser-component
|
|
async def async_aser_component(comp, args, env, ctx):
|
|
kwargs = {}
|
|
children = []
|
|
(await async_parse_aser_kw_args(args, kwargs, children, env, ctx))
|
|
local = env_merge(component_closure(comp), env)
|
|
for p in component_params(comp):
|
|
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
|
|
if sx_truthy(component_has_children(comp)):
|
|
child_parts = []
|
|
for c in children:
|
|
result = (await async_aser(c, env, ctx))
|
|
if sx_truthy(list_p(result)):
|
|
for item in result:
|
|
if sx_truthy((not sx_truthy(is_nil(item)))):
|
|
child_parts.append(serialize(item))
|
|
else:
|
|
if sx_truthy((not sx_truthy(is_nil(result)))):
|
|
child_parts.append(serialize(result))
|
|
local['children'] = make_sx_expr(sx_str('(<> ', join(' ', child_parts), ')'))
|
|
return (await async_aser(component_body(comp), local, ctx))
|
|
|
|
# async-parse-aser-kw-args
|
|
async def async_parse_aser_kw_args(args, kwargs, children, env, ctx):
|
|
_cells = {}
|
|
_cells['skip'] = False
|
|
_cells['i'] = 0
|
|
for arg in args:
|
|
if sx_truthy(_cells['skip']):
|
|
_cells['skip'] = False
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
|
|
val = (await async_aser(nth(args, (_cells['i'] + 1)), env, ctx))
|
|
kwargs[keyword_name(arg)] = val
|
|
_cells['skip'] = True
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
children.append(arg)
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
return NIL
|
|
|
|
# async-aser-call
|
|
async def async_aser_call(name, args, env, ctx):
|
|
_cells = {}
|
|
token = (svg_context_set(True) if sx_truthy(((name == 'svg') if sx_truthy((name == 'svg')) else (name == 'math'))) else NIL)
|
|
attr_parts = []
|
|
child_parts = []
|
|
_cells['skip'] = False
|
|
_cells['i'] = 0
|
|
scope_push('element-attrs', NIL)
|
|
for arg in args:
|
|
if sx_truthy(_cells['skip']):
|
|
_cells['skip'] = False
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
|
|
val = (await async_aser(nth(args, (_cells['i'] + 1)), env, ctx))
|
|
if sx_truthy((not sx_truthy(is_nil(val)))):
|
|
attr_parts.append(sx_str(':', keyword_name(arg)))
|
|
if sx_truthy((type_of(val) == 'list')):
|
|
live = filter(lambda v: (not sx_truthy(is_nil(v))), val)
|
|
if sx_truthy(empty_p(live)):
|
|
attr_parts.append('nil')
|
|
else:
|
|
items = map(serialize, live)
|
|
if sx_truthy(some(lambda v: is_sx_expr(v), live)):
|
|
attr_parts.append(sx_str('(<> ', join(' ', items), ')'))
|
|
else:
|
|
attr_parts.append(sx_str('(list ', join(' ', items), ')'))
|
|
else:
|
|
attr_parts.append(serialize(val))
|
|
_cells['skip'] = True
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
else:
|
|
result = (await async_aser(arg, env, ctx))
|
|
if sx_truthy((not sx_truthy(is_nil(result)))):
|
|
if sx_truthy((type_of(result) == 'list')):
|
|
for item in result:
|
|
if sx_truthy((not sx_truthy(is_nil(item)))):
|
|
child_parts.append(serialize(item))
|
|
else:
|
|
child_parts.append(serialize(result))
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
for spread_dict in sx_emitted('element-attrs'):
|
|
for k in keys(spread_dict):
|
|
v = dict_get(spread_dict, k)
|
|
attr_parts.append(sx_str(':', k))
|
|
attr_parts.append(serialize(v))
|
|
scope_pop('element-attrs')
|
|
if sx_truthy(token):
|
|
svg_context_reset(token)
|
|
parts = concat([name], attr_parts, child_parts)
|
|
return make_sx_expr(sx_str('(', join(' ', parts), ')'))
|
|
|
|
# ASYNC_ASER_FORM_NAMES
|
|
ASYNC_ASER_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'begin', 'do', 'quote', '->', 'set!', 'defisland', 'deftype', 'defeffect', 'scope', 'provide']
|
|
|
|
# ASYNC_ASER_HO_NAMES
|
|
ASYNC_ASER_HO_NAMES = ['map', 'map-indexed', 'filter', 'for-each']
|
|
|
|
# async-aser-form?
|
|
def async_aser_form_p(name):
|
|
return (contains_p(ASYNC_ASER_FORM_NAMES, name) if sx_truthy(contains_p(ASYNC_ASER_FORM_NAMES, name)) else contains_p(ASYNC_ASER_HO_NAMES, name))
|
|
|
|
# dispatch-async-aser-form
|
|
async def dispatch_async_aser_form(name, expr, env, ctx):
|
|
_cells = {}
|
|
args = rest(expr)
|
|
if sx_truthy((name == 'if')):
|
|
cond_val = (await async_eval(first(args), env, ctx))
|
|
if sx_truthy(cond_val):
|
|
return (await async_aser(nth(args, 1), env, ctx))
|
|
else:
|
|
if sx_truthy((len(args) > 2)):
|
|
return (await async_aser(nth(args, 2), env, ctx))
|
|
else:
|
|
return NIL
|
|
elif sx_truthy((name == 'when')):
|
|
if sx_truthy((not sx_truthy((await async_eval(first(args), env, ctx))))):
|
|
return NIL
|
|
else:
|
|
_cells['result'] = NIL
|
|
for body in rest(args):
|
|
_cells['result'] = (await async_aser(body, env, ctx))
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'cond')):
|
|
if sx_truthy(cond_scheme_p(args)):
|
|
return (await async_aser_cond_scheme(args, env, ctx))
|
|
else:
|
|
return (await async_aser_cond_clojure(args, env, ctx))
|
|
elif sx_truthy((name == 'case')):
|
|
match_val = (await async_eval(first(args), env, ctx))
|
|
return (await async_aser_case_loop(match_val, rest(args), env, ctx))
|
|
elif sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))):
|
|
local = (await async_process_bindings(first(args), env, ctx))
|
|
_cells['result'] = NIL
|
|
for body in rest(args):
|
|
_cells['result'] = (await async_aser(body, local, ctx))
|
|
return _cells['result']
|
|
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
|
|
_cells['result'] = NIL
|
|
for body in args:
|
|
_cells['result'] = (await async_aser(body, env, ctx))
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'and')):
|
|
_cells['result'] = True
|
|
_cells['stop'] = False
|
|
for arg in args:
|
|
if sx_truthy((not sx_truthy(_cells['stop']))):
|
|
_cells['result'] = (await async_eval(arg, env, ctx))
|
|
if sx_truthy((not sx_truthy(_cells['result']))):
|
|
_cells['stop'] = True
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'or')):
|
|
_cells['result'] = False
|
|
_cells['stop'] = False
|
|
for arg in args:
|
|
if sx_truthy((not sx_truthy(_cells['stop']))):
|
|
_cells['result'] = (await async_eval(arg, env, ctx))
|
|
if sx_truthy(_cells['result']):
|
|
_cells['stop'] = True
|
|
return _cells['result']
|
|
elif sx_truthy(((name == 'lambda') if sx_truthy((name == 'lambda')) else (name == 'fn'))):
|
|
return sf_lambda(args, env)
|
|
elif sx_truthy((name == 'quote')):
|
|
if sx_truthy(empty_p(args)):
|
|
return NIL
|
|
else:
|
|
return first(args)
|
|
elif sx_truthy((name == '->')):
|
|
return (await async_aser_thread_first(args, env, ctx))
|
|
elif sx_truthy((name == 'set!')):
|
|
value = (await async_eval(nth(args, 1), env, ctx))
|
|
env[symbol_name(first(args))] = value
|
|
return value
|
|
elif sx_truthy((name == 'map')):
|
|
return (await async_aser_ho_map(args, env, ctx))
|
|
elif sx_truthy((name == 'map-indexed')):
|
|
return (await async_aser_ho_map_indexed(args, env, ctx))
|
|
elif sx_truthy((name == 'filter')):
|
|
return (await async_eval(expr, env, ctx))
|
|
elif sx_truthy((name == 'for-each')):
|
|
return (await async_aser_ho_for_each(args, env, ctx))
|
|
elif sx_truthy((name == 'defisland')):
|
|
(await async_eval(expr, env, ctx))
|
|
return serialize(expr)
|
|
elif sx_truthy(((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 == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))))):
|
|
(await async_eval(expr, env, ctx))
|
|
return NIL
|
|
elif sx_truthy((name == 'scope')):
|
|
scope_name = (await async_eval(first(args), env, ctx))
|
|
rest_args = rest(args)
|
|
scope_val = NIL
|
|
body_args = NIL
|
|
if sx_truthy(((len(rest_args) >= 2) if not sx_truthy((len(rest_args) >= 2)) else ((type_of(first(rest_args)) == 'keyword') if not sx_truthy((type_of(first(rest_args)) == 'keyword')) else (keyword_name(first(rest_args)) == 'value')))):
|
|
scope_val = (await async_eval(nth(rest_args, 1), env, ctx))
|
|
body_args = slice(rest_args, 2)
|
|
else:
|
|
body_args = rest_args
|
|
scope_push(scope_name, scope_val)
|
|
_cells['result'] = NIL
|
|
for body in body_args:
|
|
_cells['result'] = (await async_aser(body, env, ctx))
|
|
scope_pop(scope_name)
|
|
return _cells['result']
|
|
elif sx_truthy((name == 'provide')):
|
|
prov_name = (await async_eval(first(args), env, ctx))
|
|
prov_val = (await async_eval(nth(args, 1), env, ctx))
|
|
_cells['result'] = NIL
|
|
scope_push(prov_name, prov_val)
|
|
for body in slice(args, 2):
|
|
_cells['result'] = (await async_aser(body, env, ctx))
|
|
scope_pop(prov_name)
|
|
return _cells['result']
|
|
else:
|
|
return (await async_eval(expr, env, ctx))
|
|
|
|
# async-aser-cond-scheme
|
|
async def async_aser_cond_scheme(clauses, env, ctx):
|
|
if sx_truthy(empty_p(clauses)):
|
|
return NIL
|
|
else:
|
|
clause = first(clauses)
|
|
test = first(clause)
|
|
body = nth(clause, 1)
|
|
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')))):
|
|
return (await async_aser(body, env, ctx))
|
|
else:
|
|
if sx_truthy((await async_eval(test, env, ctx))):
|
|
return (await async_aser(body, env, ctx))
|
|
else:
|
|
return (await async_aser_cond_scheme(rest(clauses), env, ctx))
|
|
|
|
# async-aser-cond-clojure
|
|
async def async_aser_cond_clojure(clauses, env, ctx):
|
|
if sx_truthy((len(clauses) < 2)):
|
|
return NIL
|
|
else:
|
|
test = first(clauses)
|
|
body = nth(clauses, 1)
|
|
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'))))):
|
|
return (await async_aser(body, env, ctx))
|
|
else:
|
|
if sx_truthy((await async_eval(test, env, ctx))):
|
|
return (await async_aser(body, env, ctx))
|
|
else:
|
|
return (await async_aser_cond_clojure(slice(clauses, 2), env, ctx))
|
|
|
|
# async-aser-case-loop
|
|
async def async_aser_case_loop(match_val, clauses, env, ctx):
|
|
if sx_truthy((len(clauses) < 2)):
|
|
return NIL
|
|
else:
|
|
test = first(clauses)
|
|
body = nth(clauses, 1)
|
|
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'))))):
|
|
return (await async_aser(body, env, ctx))
|
|
else:
|
|
if sx_truthy((match_val == (await async_eval(test, env, ctx)))):
|
|
return (await async_aser(body, env, ctx))
|
|
else:
|
|
return (await async_aser_case_loop(match_val, slice(clauses, 2), env, ctx))
|
|
|
|
# async-aser-thread-first
|
|
async def async_aser_thread_first(args, env, ctx):
|
|
_cells = {}
|
|
_cells['result'] = (await async_eval(first(args), env, ctx))
|
|
for form in rest(args):
|
|
if sx_truthy((type_of(form) == 'list')):
|
|
f = (await async_eval(first(form), env, ctx))
|
|
fn_args = cons(_cells['result'], (await async_eval_args(rest(form), env, ctx)))
|
|
_cells['result'] = (await async_invoke_or_lambda(f, fn_args, env, ctx))
|
|
else:
|
|
f = (await async_eval(form, env, ctx))
|
|
_cells['result'] = (await async_invoke_or_lambda(f, [_cells['result']], env, ctx))
|
|
return _cells['result']
|
|
|
|
# async-invoke-or-lambda
|
|
async def async_invoke_or_lambda(f, args, env, ctx):
|
|
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)))))):
|
|
r = apply(f, args)
|
|
if sx_truthy(is_async_coroutine(r)):
|
|
return (await async_await(r))
|
|
else:
|
|
return r
|
|
elif sx_truthy(is_lambda(f)):
|
|
local = env_merge(lambda_closure(f), env)
|
|
for_each_indexed(lambda i, p: _sx_dict_set(local, p, nth(args, i)), lambda_params(f))
|
|
return (await async_eval(lambda_body(f), local, ctx))
|
|
else:
|
|
return error(sx_str('-> form not callable: ', inspect(f)))
|
|
|
|
# async-aser-ho-map
|
|
async def async_aser_ho_map(args, env, ctx):
|
|
f = (await async_eval(first(args), env, ctx))
|
|
coll = (await async_eval(nth(args, 1), env, ctx))
|
|
results = []
|
|
for item in coll:
|
|
if sx_truthy(is_lambda(f)):
|
|
local = env_merge(lambda_closure(f), env)
|
|
local[first(lambda_params(f))] = item
|
|
results.append((await async_aser(lambda_body(f), local, ctx)))
|
|
else:
|
|
results.append((await async_invoke(f, item)))
|
|
return results
|
|
|
|
# async-aser-ho-map-indexed
|
|
async def async_aser_ho_map_indexed(args, env, ctx):
|
|
_cells = {}
|
|
f = (await async_eval(first(args), env, ctx))
|
|
coll = (await async_eval(nth(args, 1), env, ctx))
|
|
results = []
|
|
_cells['i'] = 0
|
|
for item in coll:
|
|
if sx_truthy(is_lambda(f)):
|
|
local = env_merge(lambda_closure(f), env)
|
|
local[first(lambda_params(f))] = _cells['i']
|
|
local[nth(lambda_params(f), 1)] = item
|
|
results.append((await async_aser(lambda_body(f), local, ctx)))
|
|
else:
|
|
results.append((await async_invoke(f, _cells['i'], item)))
|
|
_cells['i'] = (_cells['i'] + 1)
|
|
return results
|
|
|
|
# async-aser-ho-for-each
|
|
async def async_aser_ho_for_each(args, env, ctx):
|
|
f = (await async_eval(first(args), env, ctx))
|
|
coll = (await async_eval(nth(args, 1), env, ctx))
|
|
results = []
|
|
for item in coll:
|
|
if sx_truthy(is_lambda(f)):
|
|
local = env_merge(lambda_closure(f), env)
|
|
local[first(lambda_params(f))] = item
|
|
results.append((await async_aser(lambda_body(f), local, ctx)))
|
|
else:
|
|
results.append((await async_invoke(f, item)))
|
|
return results
|
|
|
|
# async-eval-slot-inner
|
|
async def async_eval_slot_inner(expr, env, ctx):
|
|
result = NIL
|
|
if sx_truthy((list_p(expr) if not sx_truthy(list_p(expr)) else (not sx_truthy(empty_p(expr))))):
|
|
head = first(expr)
|
|
if sx_truthy(((type_of(head) == 'symbol') if not sx_truthy((type_of(head) == 'symbol')) else starts_with_p(symbol_name(head), '~'))):
|
|
name = symbol_name(head)
|
|
val = (env_get(env, name) if sx_truthy(env_has(env, name)) else NIL)
|
|
if sx_truthy(is_component(val)):
|
|
result = (await async_aser_component(val, rest(expr), env, ctx))
|
|
else:
|
|
result = (await async_maybe_expand_result((await async_aser(expr, env, ctx)), env, ctx))
|
|
else:
|
|
result = (await async_maybe_expand_result((await async_aser(expr, env, ctx)), env, ctx))
|
|
else:
|
|
result = (await async_maybe_expand_result((await async_aser(expr, env, ctx)), env, ctx))
|
|
if sx_truthy(is_sx_expr(result)):
|
|
return result
|
|
else:
|
|
if sx_truthy(is_nil(result)):
|
|
return make_sx_expr('')
|
|
else:
|
|
if sx_truthy(string_p(result)):
|
|
return make_sx_expr(result)
|
|
else:
|
|
return make_sx_expr(serialize(result))
|
|
|
|
# async-maybe-expand-result
|
|
async def async_maybe_expand_result(result, env, ctx):
|
|
raw = (trim(sx_str(result)) if sx_truthy(is_sx_expr(result)) else (trim(result) if sx_truthy(string_p(result)) else NIL))
|
|
if sx_truthy((raw if not sx_truthy(raw) else starts_with_p(raw, '(~'))):
|
|
parsed = sx_parse(raw)
|
|
if sx_truthy((parsed if not sx_truthy(parsed) else (not sx_truthy(empty_p(parsed))))):
|
|
return (await async_eval_slot_inner(first(parsed), env, ctx))
|
|
else:
|
|
return result
|
|
else:
|
|
return result
|
|
|
|
|
|
# =========================================================================
|
|
# 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
|
|
|
|
|
|
# Override sf_set_bang to walk the Env scope chain so that (set! var val)
|
|
# updates the variable in its defining scope, not just the local copy.
|
|
def sf_set_bang(args, env):
|
|
name = symbol_name(first(args))
|
|
value = trampoline(eval_expr(nth(args, 1), env))
|
|
env = _ensure_env(env)
|
|
try:
|
|
env.set(name, value)
|
|
except KeyError:
|
|
# Not found in chain — define locally (matches prior behavior)
|
|
env[name] = value
|
|
return value
|
|
|
|
|
|
# Override recursive cek_run with iterative loop (avoids Python stack overflow)
|
|
def cek_run(state):
|
|
"""Drive CEK machine to completion (iterative)."""
|
|
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
|
|
|
|
|
|
# =========================================================================
|
|
# 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 = _Env()
|
|
elif isinstance(env, dict):
|
|
env = _Env(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."""
|
|
global _render_mode
|
|
if env is None:
|
|
env = {}
|
|
try:
|
|
_render_mode = True
|
|
return render_to_html(expr, env)
|
|
finally:
|
|
_render_mode = False
|
|
|
|
|
|
def make_env(**kwargs):
|
|
"""Create an environment with initial bindings."""
|
|
return _Env(dict(kwargs))
|
|
|
|
|
|
def populate_effect_annotations(env, effect_map=None):
|
|
"""Populate *effect-annotations* in env from boundary declarations.
|
|
|
|
If effect_map is provided, use it directly (dict of name -> effects list).
|
|
Otherwise, parse boundary.sx via boundary_parser.
|
|
"""
|
|
if effect_map is None:
|
|
from shared.sx.ref.boundary_parser import parse_boundary_effects
|
|
effect_map = parse_boundary_effects()
|
|
anns = env.get("*effect-annotations*", {})
|
|
if not isinstance(anns, dict):
|
|
anns = {}
|
|
anns.update(effect_map)
|
|
env["*effect-annotations*"] = anns
|
|
return anns
|
|
|
|
|
|
def check_component_effects(env, comp_name=None):
|
|
"""Check effect violations for components in env.
|
|
|
|
If comp_name is given, check only that component.
|
|
Returns list of diagnostic dicts (warnings, not errors).
|
|
"""
|
|
anns = env.get("*effect-annotations*")
|
|
if not anns:
|
|
return []
|
|
diagnostics = []
|
|
names = [comp_name] if comp_name else [k for k in env if isinstance(k, str) and k.startswith("~")]
|
|
for name in names:
|
|
val = env.get(name)
|
|
if val is not None and type_of(val) == "component":
|
|
comp_effects = anns.get(name)
|
|
if comp_effects is None:
|
|
continue # unannotated — skip
|
|
body = val.body if hasattr(val, "body") else None
|
|
if body is None:
|
|
continue
|
|
_walk_effects(body, name, comp_effects, anns, diagnostics)
|
|
return diagnostics
|
|
|
|
|
|
def _walk_effects(node, comp_name, caller_effects, anns, diagnostics):
|
|
"""Walk AST node and check effect calls."""
|
|
if not isinstance(node, list) or not node:
|
|
return
|
|
head = node[0]
|
|
if isinstance(head, Symbol):
|
|
callee = head.name
|
|
callee_effects = anns.get(callee)
|
|
if callee_effects is not None and caller_effects is not None:
|
|
for e in callee_effects:
|
|
if e not in caller_effects:
|
|
diagnostics.append({
|
|
"level": "warning",
|
|
"message": f"`{callee}` has effects {callee_effects} but `{comp_name}` only allows {caller_effects or '[pure]'}",
|
|
"component": comp_name,
|
|
})
|
|
break
|
|
for child in node[1:]:
|
|
_walk_effects(child, comp_name, caller_effects, anns, diagnostics)
|