Add TCO trampolining to async evaluator and sx.js client
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Both evaluators now use thunk-based trampolining to eliminate stack
overflow on deep tail recursion (verified at 50K+ depth). Mirrors
the sync evaluator TCO added in 5069072.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,11 @@
|
|||||||
}
|
}
|
||||||
Macro.prototype._macro = true;
|
Macro.prototype._macro = true;
|
||||||
|
|
||||||
|
/** Thunk — deferred evaluation for tail-call optimization. */
|
||||||
|
function _Thunk(expr, env) { this.expr = expr; this.env = env; }
|
||||||
|
_Thunk.prototype._thunk = true;
|
||||||
|
function isThunk(x) { return x && x._thunk; }
|
||||||
|
|
||||||
/** Marker for pre-rendered HTML that bypasses escaping. */
|
/** Marker for pre-rendered HTML that bypasses escaping. */
|
||||||
function RawHTML(html) { this.html = html; }
|
function RawHTML(html) { this.html = html; }
|
||||||
RawHTML.prototype._raw = true;
|
RawHTML.prototype._raw = true;
|
||||||
@@ -338,7 +343,17 @@
|
|||||||
|
|
||||||
// --- Evaluator ---
|
// --- Evaluator ---
|
||||||
|
|
||||||
function sxEval(expr, env) {
|
/** Unwrap thunks by re-entering the evaluator until we get an actual value. */
|
||||||
|
function trampoline(val) {
|
||||||
|
while (isThunk(val)) val = _sxEval(val.expr, val.env);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Public evaluator — trampolines thunks from tail positions. */
|
||||||
|
function sxEval(expr, env) { return trampoline(_sxEval(expr, env)); }
|
||||||
|
|
||||||
|
/** Internal evaluator — may return _Thunk for tail positions. */
|
||||||
|
function _sxEval(expr, env) {
|
||||||
// Literals
|
// Literals
|
||||||
if (typeof expr === "number" || typeof expr === "string" || typeof expr === "boolean") return expr;
|
if (typeof expr === "number" || typeof expr === "string" || typeof expr === "boolean") return expr;
|
||||||
if (isNil(expr)) return NIL;
|
if (isNil(expr)) return NIL;
|
||||||
@@ -387,7 +402,7 @@
|
|||||||
var macroVal = env[head.name];
|
var macroVal = env[head.name];
|
||||||
if (isMacro(macroVal)) {
|
if (isMacro(macroVal)) {
|
||||||
var expanded = expandMacro(macroVal, expr.slice(1), env);
|
var expanded = expandMacro(macroVal, expr.slice(1), env);
|
||||||
return sxEval(expanded, env);
|
return new _Thunk(expanded, env);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,7 +424,7 @@
|
|||||||
}
|
}
|
||||||
var local = merge({}, fn.closure, callerEnv);
|
var local = merge({}, fn.closure, callerEnv);
|
||||||
for (var i = 0; i < fn.params.length; i++) local[fn.params[i]] = args[i];
|
for (var i = 0; i < fn.params.length; i++) local[fn.params[i]] = args[i];
|
||||||
return sxEval(fn.body, local);
|
return new _Thunk(fn.body, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
function callComponent(comp, rawArgs, env) {
|
function callComponent(comp, rawArgs, env) {
|
||||||
@@ -430,7 +445,7 @@
|
|||||||
local[p] = (p in kwargs) ? kwargs[p] : NIL;
|
local[p] = (p in kwargs) ? kwargs[p] : NIL;
|
||||||
}
|
}
|
||||||
if (comp.hasChildren) local["children"] = children;
|
if (comp.hasChildren) local["children"] = children;
|
||||||
return sxEval(comp.body, local);
|
return new _Thunk(comp.body, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Shared helpers for special/render forms ---
|
// --- Shared helpers for special/render forms ---
|
||||||
@@ -501,20 +516,19 @@
|
|||||||
|
|
||||||
SPECIAL_FORMS["if"] = function (expr, env) {
|
SPECIAL_FORMS["if"] = function (expr, env) {
|
||||||
var cond = sxEval(expr[1], env);
|
var cond = sxEval(expr[1], env);
|
||||||
if (isSxTruthy(cond)) return sxEval(expr[2], env);
|
if (isSxTruthy(cond)) return new _Thunk(expr[2], env);
|
||||||
return expr.length > 3 ? sxEval(expr[3], env) : NIL;
|
return expr.length > 3 ? new _Thunk(expr[3], env) : NIL;
|
||||||
};
|
};
|
||||||
|
|
||||||
SPECIAL_FORMS["when"] = function (expr, env) {
|
SPECIAL_FORMS["when"] = function (expr, env) {
|
||||||
if (!isSxTruthy(sxEval(expr[1], env))) return NIL;
|
if (!isSxTruthy(sxEval(expr[1], env))) return NIL;
|
||||||
var result = NIL;
|
for (var i = 2; i < expr.length - 1; i++) sxEval(expr[i], env);
|
||||||
for (var i = 2; i < expr.length; i++) result = sxEval(expr[i], env);
|
return new _Thunk(expr[expr.length - 1], env);
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SPECIAL_FORMS["cond"] = function (expr, env) {
|
SPECIAL_FORMS["cond"] = function (expr, env) {
|
||||||
var branch = _evalCond(expr.slice(1), env);
|
var branch = _evalCond(expr.slice(1), env);
|
||||||
return branch ? sxEval(branch, env) : NIL;
|
return branch ? new _Thunk(branch, env) : NIL;
|
||||||
};
|
};
|
||||||
|
|
||||||
SPECIAL_FORMS["case"] = function (expr, env) {
|
SPECIAL_FORMS["case"] = function (expr, env) {
|
||||||
@@ -522,8 +536,8 @@
|
|||||||
for (var i = 2; i < expr.length - 1; i += 2) {
|
for (var i = 2; i < expr.length - 1; i += 2) {
|
||||||
var t = expr[i];
|
var t = expr[i];
|
||||||
if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
|
if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
|
||||||
return sxEval(expr[i + 1], env);
|
return new _Thunk(expr[i + 1], env);
|
||||||
if (val == sxEval(t, env)) return sxEval(expr[i + 1], env);
|
if (val == sxEval(t, env)) return new _Thunk(expr[i + 1], env);
|
||||||
}
|
}
|
||||||
return NIL;
|
return NIL;
|
||||||
};
|
};
|
||||||
@@ -548,9 +562,8 @@
|
|||||||
|
|
||||||
SPECIAL_FORMS["let"] = SPECIAL_FORMS["let*"] = function (expr, env) {
|
SPECIAL_FORMS["let"] = SPECIAL_FORMS["let*"] = function (expr, env) {
|
||||||
var local = _processBindings(expr[1], env);
|
var local = _processBindings(expr[1], env);
|
||||||
var result = NIL;
|
for (var k = 2; k < expr.length - 1; k++) sxEval(expr[k], local);
|
||||||
for (var k = 2; k < expr.length; k++) result = sxEval(expr[k], local);
|
return expr.length > 2 ? new _Thunk(expr[expr.length - 1], local) : NIL;
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SPECIAL_FORMS["lambda"] = SPECIAL_FORMS["fn"] = function (expr, env) {
|
SPECIAL_FORMS["lambda"] = SPECIAL_FORMS["fn"] = function (expr, env) {
|
||||||
@@ -590,9 +603,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
SPECIAL_FORMS["begin"] = SPECIAL_FORMS["do"] = function (expr, env) {
|
SPECIAL_FORMS["begin"] = SPECIAL_FORMS["do"] = function (expr, env) {
|
||||||
var result = NIL;
|
for (var i = 1; i < expr.length - 1; i++) sxEval(expr[i], env);
|
||||||
for (var i = 1; i < expr.length; i++) result = sxEval(expr[i], env);
|
return expr.length > 1 ? new _Thunk(expr[expr.length - 1], env) : NIL;
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SPECIAL_FORMS["quote"] = function (expr) { return expr[1]; };
|
SPECIAL_FORMS["quote"] = function (expr) { return expr[1]; };
|
||||||
@@ -617,7 +629,7 @@
|
|||||||
args = [result];
|
args = [result];
|
||||||
}
|
}
|
||||||
if (typeof fn === "function") result = fn.apply(null, args);
|
if (typeof fn === "function") result = fn.apply(null, args);
|
||||||
else if (isLambda(fn)) result = callLambda(fn, args, env);
|
else if (isLambda(fn)) result = trampoline(callLambda(fn, args, env));
|
||||||
else throw new Error("-> form not callable: " + fn);
|
else throw new Error("-> form not callable: " + fn);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -687,32 +699,32 @@
|
|||||||
|
|
||||||
HO_FORMS["map"] = function (expr, env) {
|
HO_FORMS["map"] = function (expr, env) {
|
||||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||||
return coll.map(function (item) { return isLambda(fn) ? callLambda(fn, [item], env) : fn(item); });
|
return coll.map(function (item) { return isLambda(fn) ? trampoline(callLambda(fn, [item], env)) : fn(item); });
|
||||||
};
|
};
|
||||||
|
|
||||||
HO_FORMS["map-indexed"] = function (expr, env) {
|
HO_FORMS["map-indexed"] = function (expr, env) {
|
||||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||||
return coll.map(function (item, i) { return isLambda(fn) ? callLambda(fn, [i, item], env) : fn(i, item); });
|
return coll.map(function (item, i) { return isLambda(fn) ? trampoline(callLambda(fn, [i, item], env)) : fn(i, item); });
|
||||||
};
|
};
|
||||||
|
|
||||||
HO_FORMS["filter"] = function (expr, env) {
|
HO_FORMS["filter"] = function (expr, env) {
|
||||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||||
return coll.filter(function (item) {
|
return coll.filter(function (item) {
|
||||||
var r = isLambda(fn) ? callLambda(fn, [item], env) : fn(item);
|
var r = isLambda(fn) ? trampoline(callLambda(fn, [item], env)) : fn(item);
|
||||||
return isSxTruthy(r);
|
return isSxTruthy(r);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
HO_FORMS["reduce"] = function (expr, env) {
|
HO_FORMS["reduce"] = function (expr, env) {
|
||||||
var fn = sxEval(expr[1], env), acc = sxEval(expr[2], env), coll = sxEval(expr[3], env);
|
var fn = sxEval(expr[1], env), acc = sxEval(expr[2], env), coll = sxEval(expr[3], env);
|
||||||
for (var i = 0; i < coll.length; i++) acc = isLambda(fn) ? callLambda(fn, [acc, coll[i]], env) : fn(acc, coll[i]);
|
for (var i = 0; i < coll.length; i++) acc = isLambda(fn) ? trampoline(callLambda(fn, [acc, coll[i]], env)) : fn(acc, coll[i]);
|
||||||
return acc;
|
return acc;
|
||||||
};
|
};
|
||||||
|
|
||||||
HO_FORMS["some"] = function (expr, env) {
|
HO_FORMS["some"] = function (expr, env) {
|
||||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||||
for (var i = 0; i < coll.length; i++) {
|
for (var i = 0; i < coll.length; i++) {
|
||||||
var r = isLambda(fn) ? callLambda(fn, [coll[i]], env) : fn(coll[i]);
|
var r = isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]);
|
||||||
if (isSxTruthy(r)) return r;
|
if (isSxTruthy(r)) return r;
|
||||||
}
|
}
|
||||||
return NIL;
|
return NIL;
|
||||||
@@ -721,14 +733,14 @@
|
|||||||
HO_FORMS["every?"] = function (expr, env) {
|
HO_FORMS["every?"] = function (expr, env) {
|
||||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||||
for (var i = 0; i < coll.length; i++) {
|
for (var i = 0; i < coll.length; i++) {
|
||||||
if (!isSxTruthy(isLambda(fn) ? callLambda(fn, [coll[i]], env) : fn(coll[i]))) return false;
|
if (!isSxTruthy(isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]))) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
HO_FORMS["for-each"] = function (expr, env) {
|
HO_FORMS["for-each"] = function (expr, env) {
|
||||||
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
|
||||||
for (var i = 0; i < coll.length; i++) isLambda(fn) ? callLambda(fn, [coll[i]], env) : fn(coll[i]);
|
for (var i = 0; i < coll.length; i++) isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]);
|
||||||
return NIL;
|
return NIL;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1352,7 +1364,7 @@
|
|||||||
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML },
|
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML },
|
||||||
_eval: sxEval,
|
_eval: sxEval,
|
||||||
_expandMacro: expandMacro,
|
_expandMacro: expandMacro,
|
||||||
_callLambda: callLambda,
|
_callLambda: function (fn, args, env) { return trampoline(callLambda(fn, args, env)); },
|
||||||
_renderDOM: renderDOM,
|
_renderDOM: renderDOM,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,40 @@ from .html import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Async TCO — thunk + trampoline
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class _AsyncThunk:
|
||||||
|
"""Deferred (expr, env, ctx) for tail-call optimization."""
|
||||||
|
__slots__ = ("expr", "env", "ctx")
|
||||||
|
def __init__(self, expr: Any, env: dict[str, Any], ctx: RequestContext) -> None:
|
||||||
|
self.expr = expr
|
||||||
|
self.env = env
|
||||||
|
self.ctx = ctx
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_trampoline(val: Any) -> Any:
|
||||||
|
"""Iteratively resolve thunks from tail positions."""
|
||||||
|
while isinstance(val, _AsyncThunk):
|
||||||
|
val = await _async_eval(val.expr, val.env, val.ctx)
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Async evaluate
|
# Async evaluate
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async def async_eval(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any:
|
async def async_eval(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any:
|
||||||
"""Evaluate *expr* in *env*, awaiting I/O primitives inline."""
|
"""Public entry — evaluates and trampolines thunks."""
|
||||||
|
result = await _async_eval(expr, env, ctx)
|
||||||
|
while isinstance(result, _AsyncThunk):
|
||||||
|
result = await _async_eval(result.expr, result.env, result.ctx)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_eval(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any:
|
||||||
|
"""Internal evaluator — may return _AsyncThunk for tail positions."""
|
||||||
# --- literals ---
|
# --- literals ---
|
||||||
if isinstance(expr, (int, float, str, bool)):
|
if isinstance(expr, (int, float, str, bool)):
|
||||||
return expr
|
return expr
|
||||||
@@ -66,7 +94,7 @@ async def async_eval(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any
|
|||||||
|
|
||||||
# --- dict literal ---
|
# --- dict literal ---
|
||||||
if isinstance(expr, dict):
|
if isinstance(expr, dict):
|
||||||
return {k: await async_eval(v, env, ctx) for k, v in expr.items()}
|
return {k: await _async_trampoline(await _async_eval(v, env, ctx)) for k, v in expr.items()}
|
||||||
|
|
||||||
# --- list ---
|
# --- list ---
|
||||||
if not isinstance(expr, list):
|
if not isinstance(expr, list):
|
||||||
@@ -77,7 +105,7 @@ async def async_eval(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any
|
|||||||
head = expr[0]
|
head = expr[0]
|
||||||
|
|
||||||
if not isinstance(head, (Symbol, Lambda, list)):
|
if not isinstance(head, (Symbol, Lambda, list)):
|
||||||
return [await async_eval(x, env, ctx) for x in expr]
|
return [await _async_trampoline(await _async_eval(x, env, ctx)) for x in expr]
|
||||||
|
|
||||||
if isinstance(head, Symbol):
|
if isinstance(head, Symbol):
|
||||||
name = head.name
|
name = head.name
|
||||||
@@ -96,12 +124,12 @@ async def async_eval(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any
|
|||||||
if ho is not None:
|
if ho is not None:
|
||||||
return await ho(expr, env, ctx)
|
return await ho(expr, env, ctx)
|
||||||
|
|
||||||
# Macro expansion
|
# Macro expansion — tail position
|
||||||
if name in env:
|
if name in env:
|
||||||
val = env[name]
|
val = env[name]
|
||||||
if isinstance(val, Macro):
|
if isinstance(val, Macro):
|
||||||
expanded = _expand_macro(val, expr[1:], env)
|
expanded = _expand_macro(val, expr[1:], env)
|
||||||
return await async_eval(expanded, env, ctx)
|
return _AsyncThunk(expanded, env, ctx)
|
||||||
|
|
||||||
# Render forms in eval position — delegate to renderer and return
|
# Render forms in eval position — delegate to renderer and return
|
||||||
# as _RawHTML so it won't be double-escaped when used in render
|
# as _RawHTML so it won't be double-escaped when used in render
|
||||||
@@ -111,8 +139,8 @@ async def async_eval(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any
|
|||||||
return _RawHTML(html)
|
return _RawHTML(html)
|
||||||
|
|
||||||
# --- function / lambda call ---
|
# --- function / lambda call ---
|
||||||
fn = await async_eval(head, env, ctx)
|
fn = await _async_trampoline(await _async_eval(head, env, ctx))
|
||||||
args = [await async_eval(a, env, ctx) for a in expr[1:]]
|
args = [await _async_trampoline(await _async_eval(a, env, ctx)) for a in expr[1:]]
|
||||||
|
|
||||||
if callable(fn) and not isinstance(fn, (Lambda, Component)):
|
if callable(fn) and not isinstance(fn, (Lambda, Component)):
|
||||||
result = fn(*args)
|
result = fn(*args)
|
||||||
@@ -153,7 +181,7 @@ async def _async_call_lambda(
|
|||||||
local.update(caller_env)
|
local.update(caller_env)
|
||||||
for p, v in zip(fn.params, args):
|
for p, v in zip(fn.params, args):
|
||||||
local[p] = v
|
local[p] = v
|
||||||
return await async_eval(fn.body, local, ctx)
|
return _AsyncThunk(fn.body, local, ctx)
|
||||||
|
|
||||||
|
|
||||||
async def _async_call_component(
|
async def _async_call_component(
|
||||||
@@ -176,7 +204,7 @@ async def _async_call_component(
|
|||||||
local[p] = kwargs.get(p, NIL)
|
local[p] = kwargs.get(p, NIL)
|
||||||
if comp.has_children:
|
if comp.has_children:
|
||||||
local["children"] = children
|
local["children"] = children
|
||||||
return await async_eval(comp.body, local, ctx)
|
return _AsyncThunk(comp.body, local, ctx)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -184,28 +212,28 @@ async def _async_call_component(
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async def _asf_if(expr, env, ctx):
|
async def _asf_if(expr, env, ctx):
|
||||||
cond = await async_eval(expr[1], env, ctx)
|
cond = await _async_trampoline(await _async_eval(expr[1], env, ctx))
|
||||||
if cond and cond is not NIL:
|
if cond and cond is not NIL:
|
||||||
return await async_eval(expr[2], env, ctx)
|
return _AsyncThunk(expr[2], env, ctx)
|
||||||
if len(expr) > 3:
|
if len(expr) > 3:
|
||||||
return await async_eval(expr[3], env, ctx)
|
return _AsyncThunk(expr[3], env, ctx)
|
||||||
return NIL
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
async def _asf_when(expr, env, ctx):
|
async def _asf_when(expr, env, ctx):
|
||||||
cond = await async_eval(expr[1], env, ctx)
|
cond = await _async_trampoline(await _async_eval(expr[1], env, ctx))
|
||||||
if cond and cond is not NIL:
|
if cond and cond is not NIL:
|
||||||
result = NIL
|
for body_expr in expr[2:-1]:
|
||||||
for body_expr in expr[2:]:
|
await _async_trampoline(await _async_eval(body_expr, env, ctx))
|
||||||
result = await async_eval(body_expr, env, ctx)
|
if len(expr) > 2:
|
||||||
return result
|
return _AsyncThunk(expr[-1], env, ctx)
|
||||||
return NIL
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
async def _asf_and(expr, env, ctx):
|
async def _asf_and(expr, env, ctx):
|
||||||
result: Any = True
|
result: Any = True
|
||||||
for arg in expr[1:]:
|
for arg in expr[1:]:
|
||||||
result = await async_eval(arg, env, ctx)
|
result = await _async_trampoline(await _async_eval(arg, env, ctx))
|
||||||
if not result:
|
if not result:
|
||||||
return result
|
return result
|
||||||
return result
|
return result
|
||||||
@@ -214,7 +242,7 @@ async def _asf_and(expr, env, ctx):
|
|||||||
async def _asf_or(expr, env, ctx):
|
async def _asf_or(expr, env, ctx):
|
||||||
result: Any = False
|
result: Any = False
|
||||||
for arg in expr[1:]:
|
for arg in expr[1:]:
|
||||||
result = await async_eval(arg, env, ctx)
|
result = await _async_trampoline(await _async_eval(arg, env, ctx))
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
return result
|
return result
|
||||||
@@ -228,16 +256,17 @@ async def _asf_let(expr, env, ctx):
|
|||||||
for binding in bindings:
|
for binding in bindings:
|
||||||
var = binding[0]
|
var = binding[0]
|
||||||
vname = var.name if isinstance(var, Symbol) else var
|
vname = var.name if isinstance(var, Symbol) else var
|
||||||
local[vname] = await async_eval(binding[1], local, ctx)
|
local[vname] = await _async_trampoline(await _async_eval(binding[1], local, ctx))
|
||||||
elif len(bindings) % 2 == 0:
|
elif len(bindings) % 2 == 0:
|
||||||
for i in range(0, len(bindings), 2):
|
for i in range(0, len(bindings), 2):
|
||||||
var = bindings[i]
|
var = bindings[i]
|
||||||
vname = var.name if isinstance(var, Symbol) else var
|
vname = var.name if isinstance(var, Symbol) else var
|
||||||
local[vname] = await async_eval(bindings[i + 1], local, ctx)
|
local[vname] = await _async_trampoline(await _async_eval(bindings[i + 1], local, ctx))
|
||||||
result: Any = NIL
|
for body_expr in expr[2:-1]:
|
||||||
for body_expr in expr[2:]:
|
await _async_trampoline(await _async_eval(body_expr, local, ctx))
|
||||||
result = await async_eval(body_expr, local, ctx)
|
if len(expr) > 2:
|
||||||
return result
|
return _AsyncThunk(expr[-1], local, ctx)
|
||||||
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
async def _asf_lambda(expr, env, ctx):
|
async def _asf_lambda(expr, env, ctx):
|
||||||
@@ -253,7 +282,7 @@ async def _asf_lambda(expr, env, ctx):
|
|||||||
|
|
||||||
async def _asf_define(expr, env, ctx):
|
async def _asf_define(expr, env, ctx):
|
||||||
name_sym = expr[1]
|
name_sym = expr[1]
|
||||||
value = await async_eval(expr[2], env, ctx)
|
value = await _async_trampoline(await _async_eval(expr[2], env, ctx))
|
||||||
if isinstance(value, Lambda) and value.name is None:
|
if isinstance(value, Lambda) and value.name is None:
|
||||||
value.name = name_sym.name
|
value.name = name_sym.name
|
||||||
env[name_sym.name] = value
|
env[name_sym.name] = value
|
||||||
@@ -276,10 +305,11 @@ async def _asf_defhandler(expr, env, ctx):
|
|||||||
|
|
||||||
|
|
||||||
async def _asf_begin(expr, env, ctx):
|
async def _asf_begin(expr, env, ctx):
|
||||||
result: Any = NIL
|
for sub in expr[1:-1]:
|
||||||
for sub in expr[1:]:
|
await _async_trampoline(await _async_eval(sub, env, ctx))
|
||||||
result = await async_eval(sub, env, ctx)
|
if len(expr) > 1:
|
||||||
return result
|
return _AsyncThunk(expr[-1], env, ctx)
|
||||||
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
async def _asf_quote(expr, env, ctx):
|
async def _asf_quote(expr, env, ctx):
|
||||||
@@ -325,65 +355,65 @@ async def _asf_cond(expr, env, ctx):
|
|||||||
for clause in clauses:
|
for clause in clauses:
|
||||||
test = clause[0]
|
test = clause[0]
|
||||||
if isinstance(test, Symbol) and test.name in ("else", ":else"):
|
if isinstance(test, Symbol) and test.name in ("else", ":else"):
|
||||||
return await async_eval(clause[1], env, ctx)
|
return _AsyncThunk(clause[1], env, ctx)
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
if isinstance(test, Keyword) and test.name == "else":
|
||||||
return await async_eval(clause[1], env, ctx)
|
return _AsyncThunk(clause[1], env, ctx)
|
||||||
if await async_eval(test, env, ctx):
|
if await _async_trampoline(await _async_eval(test, env, ctx)):
|
||||||
return await async_eval(clause[1], env, ctx)
|
return _AsyncThunk(clause[1], env, ctx)
|
||||||
else:
|
else:
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(clauses) - 1:
|
while i < len(clauses) - 1:
|
||||||
test = clauses[i]
|
test = clauses[i]
|
||||||
result = clauses[i + 1]
|
result = clauses[i + 1]
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
if isinstance(test, Keyword) and test.name == "else":
|
||||||
return await async_eval(result, env, ctx)
|
return _AsyncThunk(result, env, ctx)
|
||||||
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
||||||
return await async_eval(result, env, ctx)
|
return _AsyncThunk(result, env, ctx)
|
||||||
if await async_eval(test, env, ctx):
|
if await _async_trampoline(await _async_eval(test, env, ctx)):
|
||||||
return await async_eval(result, env, ctx)
|
return _AsyncThunk(result, env, ctx)
|
||||||
i += 2
|
i += 2
|
||||||
return NIL
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
async def _asf_case(expr, env, ctx):
|
async def _asf_case(expr, env, ctx):
|
||||||
match_val = await async_eval(expr[1], env, ctx)
|
match_val = await _async_trampoline(await _async_eval(expr[1], env, ctx))
|
||||||
clauses = expr[2:]
|
clauses = expr[2:]
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(clauses) - 1:
|
while i < len(clauses) - 1:
|
||||||
test = clauses[i]
|
test = clauses[i]
|
||||||
result = clauses[i + 1]
|
result = clauses[i + 1]
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
if isinstance(test, Keyword) and test.name == "else":
|
||||||
return await async_eval(result, env, ctx)
|
return _AsyncThunk(result, env, ctx)
|
||||||
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
||||||
return await async_eval(result, env, ctx)
|
return _AsyncThunk(result, env, ctx)
|
||||||
if match_val == await async_eval(test, env, ctx):
|
if match_val == await _async_trampoline(await _async_eval(test, env, ctx)):
|
||||||
return await async_eval(result, env, ctx)
|
return _AsyncThunk(result, env, ctx)
|
||||||
i += 2
|
i += 2
|
||||||
return NIL
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
async def _asf_thread_first(expr, env, ctx):
|
async def _asf_thread_first(expr, env, ctx):
|
||||||
result = await async_eval(expr[1], env, ctx)
|
result = await _async_trampoline(await _async_eval(expr[1], env, ctx))
|
||||||
for form in expr[2:]:
|
for form in expr[2:]:
|
||||||
if isinstance(form, list):
|
if isinstance(form, list):
|
||||||
fn = await async_eval(form[0], env, ctx)
|
fn = await _async_trampoline(await _async_eval(form[0], env, ctx))
|
||||||
args = [result] + [await async_eval(a, env, ctx) for a in form[1:]]
|
args = [result] + [await _async_trampoline(await _async_eval(a, env, ctx)) for a in form[1:]]
|
||||||
else:
|
else:
|
||||||
fn = await async_eval(form, env, ctx)
|
fn = await _async_trampoline(await _async_eval(form, env, ctx))
|
||||||
args = [result]
|
args = [result]
|
||||||
if callable(fn) and not isinstance(fn, (Lambda, Component)):
|
if callable(fn) and not isinstance(fn, (Lambda, Component)):
|
||||||
result = fn(*args)
|
result = fn(*args)
|
||||||
if inspect.iscoroutine(result):
|
if inspect.iscoroutine(result):
|
||||||
result = await result
|
result = await result
|
||||||
elif isinstance(fn, Lambda):
|
elif isinstance(fn, Lambda):
|
||||||
result = await _async_call_lambda(fn, args, env, ctx)
|
result = await _async_trampoline(await _async_call_lambda(fn, args, env, ctx))
|
||||||
else:
|
else:
|
||||||
raise EvalError(f"-> form not callable: {fn!r}")
|
raise EvalError(f"-> form not callable: {fn!r}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def _asf_set_bang(expr, env, ctx):
|
async def _asf_set_bang(expr, env, ctx):
|
||||||
value = await async_eval(expr[2], env, ctx)
|
value = await _async_trampoline(await _async_eval(expr[2], env, ctx))
|
||||||
env[expr[1].name] = value
|
env[expr[1].name] = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -422,7 +452,7 @@ async def _aho_map(expr, env, ctx):
|
|||||||
results = []
|
results = []
|
||||||
for item in coll:
|
for item in coll:
|
||||||
if isinstance(fn, Lambda):
|
if isinstance(fn, Lambda):
|
||||||
results.append(await _async_call_lambda(fn, [item], env, ctx))
|
results.append(await _async_trampoline(await _async_call_lambda(fn, [item], env, ctx)))
|
||||||
elif callable(fn):
|
elif callable(fn):
|
||||||
r = fn(item)
|
r = fn(item)
|
||||||
results.append(await r if inspect.iscoroutine(r) else r)
|
results.append(await r if inspect.iscoroutine(r) else r)
|
||||||
@@ -437,7 +467,7 @@ async def _aho_map_indexed(expr, env, ctx):
|
|||||||
results = []
|
results = []
|
||||||
for i, item in enumerate(coll):
|
for i, item in enumerate(coll):
|
||||||
if isinstance(fn, Lambda):
|
if isinstance(fn, Lambda):
|
||||||
results.append(await _async_call_lambda(fn, [i, item], env, ctx))
|
results.append(await _async_trampoline(await _async_call_lambda(fn, [i, item], env, ctx)))
|
||||||
elif callable(fn):
|
elif callable(fn):
|
||||||
r = fn(i, item)
|
r = fn(i, item)
|
||||||
results.append(await r if inspect.iscoroutine(r) else r)
|
results.append(await r if inspect.iscoroutine(r) else r)
|
||||||
@@ -452,7 +482,7 @@ async def _aho_filter(expr, env, ctx):
|
|||||||
results = []
|
results = []
|
||||||
for item in coll:
|
for item in coll:
|
||||||
if isinstance(fn, Lambda):
|
if isinstance(fn, Lambda):
|
||||||
val = await _async_call_lambda(fn, [item], env, ctx)
|
val = await _async_trampoline(await _async_call_lambda(fn, [item], env, ctx))
|
||||||
elif callable(fn):
|
elif callable(fn):
|
||||||
val = fn(item)
|
val = fn(item)
|
||||||
if inspect.iscoroutine(val):
|
if inspect.iscoroutine(val):
|
||||||
@@ -470,7 +500,7 @@ async def _aho_reduce(expr, env, ctx):
|
|||||||
coll = await async_eval(expr[3], env, ctx)
|
coll = await async_eval(expr[3], env, ctx)
|
||||||
for item in coll:
|
for item in coll:
|
||||||
if isinstance(fn, Lambda):
|
if isinstance(fn, Lambda):
|
||||||
acc = await _async_call_lambda(fn, [acc, item], env, ctx)
|
acc = await _async_trampoline(await _async_call_lambda(fn, [acc, item], env, ctx))
|
||||||
else:
|
else:
|
||||||
acc = fn(acc, item)
|
acc = fn(acc, item)
|
||||||
if inspect.iscoroutine(acc):
|
if inspect.iscoroutine(acc):
|
||||||
@@ -483,7 +513,7 @@ async def _aho_some(expr, env, ctx):
|
|||||||
coll = await async_eval(expr[2], env, ctx)
|
coll = await async_eval(expr[2], env, ctx)
|
||||||
for item in coll:
|
for item in coll:
|
||||||
if isinstance(fn, Lambda):
|
if isinstance(fn, Lambda):
|
||||||
result = await _async_call_lambda(fn, [item], env, ctx)
|
result = await _async_trampoline(await _async_call_lambda(fn, [item], env, ctx))
|
||||||
else:
|
else:
|
||||||
result = fn(item)
|
result = fn(item)
|
||||||
if inspect.iscoroutine(result):
|
if inspect.iscoroutine(result):
|
||||||
@@ -498,7 +528,7 @@ async def _aho_every(expr, env, ctx):
|
|||||||
coll = await async_eval(expr[2], env, ctx)
|
coll = await async_eval(expr[2], env, ctx)
|
||||||
for item in coll:
|
for item in coll:
|
||||||
if isinstance(fn, Lambda):
|
if isinstance(fn, Lambda):
|
||||||
val = await _async_call_lambda(fn, [item], env, ctx)
|
val = await _async_trampoline(await _async_call_lambda(fn, [item], env, ctx))
|
||||||
else:
|
else:
|
||||||
val = fn(item)
|
val = fn(item)
|
||||||
if inspect.iscoroutine(val):
|
if inspect.iscoroutine(val):
|
||||||
@@ -513,7 +543,7 @@ async def _aho_for_each(expr, env, ctx):
|
|||||||
coll = await async_eval(expr[2], env, ctx)
|
coll = await async_eval(expr[2], env, ctx)
|
||||||
for item in coll:
|
for item in coll:
|
||||||
if isinstance(fn, Lambda):
|
if isinstance(fn, Lambda):
|
||||||
await _async_call_lambda(fn, [item], env, ctx)
|
await _async_trampoline(await _async_call_lambda(fn, [item], env, ctx))
|
||||||
elif callable(fn):
|
elif callable(fn):
|
||||||
r = fn(item)
|
r = fn(item)
|
||||||
if inspect.iscoroutine(r):
|
if inspect.iscoroutine(r):
|
||||||
@@ -1038,7 +1068,7 @@ async def _aser(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any:
|
|||||||
return await result
|
return await result
|
||||||
return result
|
return result
|
||||||
if isinstance(fn, Lambda):
|
if isinstance(fn, Lambda):
|
||||||
return await _async_call_lambda(fn, args, env, ctx)
|
return await _async_trampoline(await _async_call_lambda(fn, args, env, ctx))
|
||||||
if isinstance(fn, Component):
|
if isinstance(fn, Component):
|
||||||
# Component invoked as function — serialize the call
|
# Component invoked as function — serialize the call
|
||||||
return await _aser_call(f"~{fn.name}", expr[1:], env, ctx)
|
return await _aser_call(f"~{fn.name}", expr[1:], env, ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user