HS test generator: property access, locals, me-val, nested opts (515→517/831)

- Handle result["foo"] and result.foo property access after eval-hs
- Handle { locals: { x: 5, y: 5 } } opts with nested braces
- Handle { me: N } opts via eval-hs-with-me helper
- Add eval-hs-with-me to test framework for "I am between" tests
- Use host-get for property access on host handles (JSON.parse results)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 14:58:00 +00:00
parent fc76a42403
commit 87fdb1db71
2 changed files with 63 additions and 19 deletions

View File

@@ -717,12 +717,27 @@ def generate_eval_only_test(test, idx):
# Pattern 1: Inline — expect(run("expr", opts)).toBe(val) or run("expr", opts).toBe(val)
for m in re.finditer(
r'(?:expect\()?' + _RUN_OPEN + _RUN_ARGS + r'\)\)?\.toBe\(([^)]+)\)',
r'(?:expect\()?' + _RUN_OPEN + r'(\s*,\s*\{[^}]*(?:\{[^}]*\}[^}]*)?\})?' + r'\)\)?\.toBe\(([^)]+)\)',
body, re.DOTALL
):
hs_expr = extract_hs_expr(m.group(2))
expected_sx = js_val_to_sx(m.group(3))
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
opts_str = m.group(3) or ''
expected_sx = js_val_to_sx(m.group(4))
# Check for { me: X } or { locals: { x: X, y: Y } } in opts
me_match = re.search(r'\bme:\s*(\d+)', opts_str)
locals_match = re.search(r'locals:\s*\{([^}]+)\}', opts_str)
if locals_match:
local_bindings = []
for lm in re.finditer(r'(\w+)\s*:\s*([^,}]+)', locals_match.group(1)):
lname = lm.group(1)
lval = js_val_to_sx(lm.group(2).strip())
local_bindings.append(f'({lname} {lval})')
assertions.append(f' (let ({" ".join(local_bindings)}) (assert= (eval-hs "{hs_expr}") {expected_sx}))')
elif me_match:
me_val = me_match.group(1)
assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_val}) {expected_sx})')
else:
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
# Pattern 1b: Inline — run("expr", opts).toEqual([...])
for m in re.finditer(
@@ -752,12 +767,25 @@ def generate_eval_only_test(test, idx):
)
if run_match:
hs_expr = extract_hs_expr(run_match.group(2))
for m in re.finditer(r'\.toBe\(([^)]+)\)', body):
expected_sx = js_val_to_sx(m.group(1))
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
for m in re.finditer(r'\.toEqual\((\[.*?\])\)', body, re.DOTALL):
var_name = re.search(r'(?:var|let|const)\s+(\w+)', body).group(1)
for m in re.finditer(r'expect\((' + re.escape(var_name) + r'(?:\["[^"]+"\]|\.\w+)?)\)\.toBe\(([^)]+)\)', body):
accessor = m.group(1)
expected_sx = js_val_to_sx(m.group(2))
# Check for property access: result["foo"] or result.foo
prop_m = re.search(r'\["([^"]+)"\]|\.(\w+)', accessor[len(var_name):])
if prop_m:
prop = prop_m.group(1) or prop_m.group(2)
assertions.append(f' (assert= (host-get (eval-hs "{hs_expr}") "{prop}") {expected_sx})')
else:
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
for m in re.finditer(r'expect\(' + re.escape(var_name) + r'(?:\.\w+)?\)\.toEqual\((\[.*?\])\)', body, re.DOTALL):
expected_sx = js_val_to_sx(m.group(1))
assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})')
# Handle .map(x => x.prop) before toEqual
for m in re.finditer(r'expect\(' + re.escape(var_name) + r'\.map\(\w+\s*=>\s*\w+\.(\w+)\)\)\.toEqual\((\[.*?\])\)', body, re.DOTALL):
prop = m.group(1)
expected_sx = js_val_to_sx(m.group(2))
assertions.append(f' (assert= (map (fn (x) (get x "{prop}")) (eval-hs "{hs_expr}")) {expected_sx})')
# Pattern 2b: run() with locals + evaluate(window.X) + expect().toBe/toEqual
# e.g.: await run(`expr`, {locals: {arr: [1,2,3]}});
@@ -883,6 +911,14 @@ output.append(' (let ((handler (eval-expr-cek')
output.append(' (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))')
output.append(' (handler nil)))))')
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)')
output.append(' (let ((sx (hs-to-sx (hs-compile src))))')
output.append(' (let ((handler (eval-expr-cek')
output.append(' (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))')
output.append(' (handler me-val)))))')
output.append('')
# Group by category
categories = OrderedDict()