HS test generator: add eval-hs-locals for run(...) tests with locals
Tests using `run("expr", {locals: {x}})` were being translated to SX like
(let ((x val)) (eval-hs "expr") (assert= it EXPECTED))
That never worked: `it` is bound inside eval-hs's handler closure, not in
the outer SX scope, so the assertion errored "Undefined symbol: it".
Meanwhile `x` (bound by the outer let) wasn't reachable from the
eval-expr-cek'd handler either, so any script referencing `x` resolved
via global lookup — silently yielding stale values from earlier tests.
New `eval-hs-locals` helper injects locals as fn parameters of the
handler wrapper:
(fn (me arr str ...) (let ((it nil) (event nil)) <compiled-hs> it))
It's applied with the caller's values, returning the final `it`. The
generator now emits `(assert= (eval-hs-locals "..." (list ...)) EXP)`
for all four expect() patterns when locals are present.
New baseline: 1,055 / 1,496 pass (70.5%, up from 1,022 / 1,496 = 68.3%).
29 additional tests now pass — mostly `pick` (where locals are the
vehicle for passing arr/str test fixtures) plus cascades in
comparisonOperator, asExpression, mathOperator, etc.
Note: the remaining `pick` wins in this batch also depend on local
edits to lib/hyperscript/parser.sx and compiler.sx (not included here;
they're intertwined with pre-existing in-flight HS runtime work).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1119,36 +1119,40 @@ def generate_eval_only_test(test, idx):
|
||||
if run_match:
|
||||
hs_expr = extract_hs_expr(run_match.group(2))
|
||||
locals_str = run_match.group(3).strip()
|
||||
# Parse locals: {key: val, ...}
|
||||
local_bindings = []
|
||||
# Parse locals: {key: val, ...}. Collect (name, value-sx) pairs.
|
||||
local_pairs = []
|
||||
for lm in re.finditer(r'(\w+)\s*:\s*(.+?)(?:,\s*(?=\w+\s*:)|$)', locals_str):
|
||||
lname = lm.group(1)
|
||||
lval = js_val_to_sx(lm.group(2).strip().rstrip(','))
|
||||
local_bindings.append(f'({lname} {lval})')
|
||||
local_pairs.append((lname, lval))
|
||||
# SX list of (symbol value) pairs for eval-hs-locals
|
||||
locals_sx = '(list ' + ' '.join(f'(list (quote {n}) {v})' for n, v in local_pairs) + ')' if local_pairs else '(list)'
|
||||
|
||||
# Find expect().toBe() or .toEqual()
|
||||
# Find expect().toBe() or .toEqual(). eval-hs/eval-hs-locals return
|
||||
# the final value of `it` after the script runs, so assert on the
|
||||
# return value directly — `it` is not in the outer SX scope.
|
||||
for m in re.finditer(r'expect\([^)]*\)\.toBe\(([^)]+)\)', body):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
if local_bindings:
|
||||
assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= it {expected_sx}))')
|
||||
if local_pairs:
|
||||
assertions.append(f' (assert= (eval-hs-locals "{hs_expr}" {locals_sx}) {expected_sx})')
|
||||
else:
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
for m in re.finditer(r'expect\([^)]*\)\.toEqual\((\[.*?\])\)', body, re.DOTALL):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
if local_bindings:
|
||||
assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= it {expected_sx}))')
|
||||
if local_pairs:
|
||||
assertions.append(f' (assert= (eval-hs-locals "{hs_expr}" {locals_sx}) {expected_sx})')
|
||||
else:
|
||||
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
|
||||
for m in re.finditer(r'expect\([^)]*\)\.toContain\(([^)]+)\)', body):
|
||||
expected_sx = js_val_to_sx(m.group(1))
|
||||
if local_bindings:
|
||||
assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert (not (nil? it))))')
|
||||
if local_pairs:
|
||||
assertions.append(f' (assert (not (nil? (eval-hs-locals "{hs_expr}" {locals_sx}))))')
|
||||
else:
|
||||
assertions.append(f' (assert (not (nil? (eval-hs "{hs_expr}"))))')
|
||||
for m in re.finditer(r'expect\([^)]*\)\.toHaveLength\((\d+)\)', body):
|
||||
length = m.group(1)
|
||||
if local_bindings:
|
||||
assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= (len it) {length}))')
|
||||
if local_pairs:
|
||||
assertions.append(f' (assert= (len (eval-hs-locals "{hs_expr}" {locals_sx})) {length})')
|
||||
else:
|
||||
assertions.append(f' (assert= (len (eval-hs "{hs_expr}")) {length})')
|
||||
|
||||
@@ -1461,7 +1465,7 @@ output.append('(define hs-cleanup!')
|
||||
output.append(' (fn ()')
|
||||
output.append(' (dom-set-inner-html (dom-body) "")))')
|
||||
output.append('')
|
||||
output.append(';; Evaluate a hyperscript expression and return its result.')
|
||||
output.append(';; Evaluate a hyperscript expression and return the last-expression value.')
|
||||
output.append(';; Compiles the expression, wraps in a thunk, evaluates, returns result.')
|
||||
output.append('(define eval-hs')
|
||||
output.append(' (fn (src)')
|
||||
@@ -1477,6 +1481,28 @@ output.append(' (nth _e 1)')
|
||||
output.append(' (raise _e))))')
|
||||
output.append(' (handler nil))))))')
|
||||
output.append('')
|
||||
output.append(';; Evaluate a hyperscript expression with locals. bindings = list of (symbol value).')
|
||||
output.append(';; The locals are injected as fn params so they resolve in the handler body.')
|
||||
output.append('(define eval-hs-locals')
|
||||
output.append(' (fn (src bindings)')
|
||||
output.append(' (let ((sx (hs-to-sx (hs-compile src))))')
|
||||
output.append(' (let ((names (map (fn (b) (first b)) bindings))')
|
||||
output.append(' (vals (map (fn (b) (nth b 1)) bindings)))')
|
||||
output.append(' (let ((param-list (cons (quote me) names)))')
|
||||
output.append(' (let ((wrapper (list (quote fn) param-list')
|
||||
output.append(' (list (quote let)')
|
||||
output.append(' (list (list (quote it) nil) (list (quote event) nil))')
|
||||
output.append(' sx (quote it)))))')
|
||||
output.append(' (let ((handler (eval-expr-cek wrapper)))')
|
||||
output.append(' (guard')
|
||||
output.append(' (_e')
|
||||
output.append(' (true')
|
||||
output.append(' (if')
|
||||
output.append(' (and (list? _e) (= (first _e) "hs-return"))')
|
||||
output.append(' (nth _e 1)')
|
||||
output.append(' (raise _e))))')
|
||||
output.append(' (apply handler (cons nil vals))))))))))')
|
||||
output.append('')
|
||||
output.append(';; Evaluate with a specific me value (for "I am between" etc.)')
|
||||
output.append('(define eval-hs-with-me')
|
||||
output.append(' (fn (src me-val)')
|
||||
|
||||
Reference in New Issue
Block a user