HS test generator: bind run() me: and balanced-brace locals — +2 comparisonOperator

Two related Pattern 1 bugs:

1. The locals capture used `\\{([^}]+)\\}` (greedy non-`}` chars), so
   `locals: { that: [1, 2, 3] }` truncated at the first `,` inside `[...]`
   and bound `that` to `"[1"`. Switched to balanced-brace extraction +
   `split_top_level` so nested arrays/objects survive.

2. `{ me: <X> }` was only forwarded to the SX runtime when X was a single
   integer (eval-hs-with-me only accepts numbers). For `me: [1, 2, 3]`
   or `me: 1` alongside other locals, `me` was silently dropped, so
   `I contain that` couldn't see its receiver. Now any non-numeric `me`
   value is bound as a local (`(list (quote me) <val>)`); a numeric
   `me` alongside other locals/setups is also bound, so the HS expr
   always sees its `me`.

comparisonOperator 79/83 → 81/83 (+2: contains/includes works with arrays).
bind unchanged (43/44).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 11:41:47 +00:00
parent e4773ec336
commit 19f5bf7d72
2 changed files with 44 additions and 12 deletions

View File

@@ -4341,8 +4341,8 @@
(assert= (eval-hs "'Hello World' contains 'missing' ignoring case") false) (assert= (eval-hs "'Hello World' contains 'missing' ignoring case") false)
) )
(deftest "contains works with arrays" (deftest "contains works with arrays"
(assert= (eval-hs-locals "I contain that" (list (list (quote that) 1))) true) (assert= (eval-hs-locals "I contain that" (list (list (quote that) 1) (list (quote me) (list 1 2 3)))) true)
(assert= (eval-hs-locals "that contains me" (list (list (quote that) "[1"))) true) (assert= (eval-hs-locals "that contains me" (list (list (quote that) (list 1 2 3)) (list (quote me) 1))) true)
) )
(deftest "contains works with css literals" (deftest "contains works with css literals"
(hs-cleanup!) (hs-cleanup!)
@@ -4501,8 +4501,8 @@
(assert= (eval-hs-locals "foobar does not include foo" (list (list (quote foo) "foo") (list (quote foobar) "foobar"))) false) (assert= (eval-hs-locals "foobar does not include foo" (list (list (quote foo) "foo") (list (quote foobar) "foobar"))) false)
) )
(deftest "includes works with arrays" (deftest "includes works with arrays"
(assert= (eval-hs-locals "I include that" (list (list (quote that) 1))) true) (assert= (eval-hs-locals "I include that" (list (list (quote that) 1) (list (quote me) (list 1 2 3)))) true)
(assert= (eval-hs-locals "that includes me" (list (list (quote that) "[1"))) true) (assert= (eval-hs-locals "that includes me" (list (list (quote that) (list 1 2 3)) (list (quote me) 1))) true)
) )
(deftest "includes works with css literals" (deftest "includes works with css literals"
(hs-cleanup!) (hs-cleanup!)

View File

@@ -1281,16 +1281,48 @@ def generate_eval_only_test(test, idx):
hs_expr = extract_hs_expr(m.group(2)) hs_expr = extract_hs_expr(m.group(2))
opts_str = m.group(3) or '' opts_str = m.group(3) or ''
expected_sx = js_val_to_sx(m.group(4)) expected_sx = js_val_to_sx(m.group(4))
# Check for { me: X } or { locals: { x: X, y: Y } } in opts # Check for { me: X } or { locals: { x: X, y: Y } } in opts.
me_match = re.search(r'\bme:\s*(\d+)', opts_str) # Numeric me uses eval-hs-with-me; other me values get bound as a local.
locals_match = re.search(r'locals:\s*\{([^}]+)\}', opts_str) me_num_match = re.search(r'\bme:\s*(\d+)\b', opts_str)
me_val_match = re.search(r'\bme:\s*(\[[^\]]*\]|\{[^}]*\}|"[^"]*"|\'[^\']*\')', opts_str)
# Locals: balanced-brace extraction so nested arrays/objects don't truncate.
locals_idx = opts_str.find('locals:')
extra = [] extra = []
if locals_match: if locals_idx >= 0:
for lm in re.finditer(r'(\w+)\s*:\s*([^,}]+)', locals_match.group(1)): open_idx = opts_str.find('{', locals_idx)
extra.append((lm.group(1), js_val_to_sx(lm.group(2).strip()))) if open_idx >= 0:
if me_match and not (window_setups or extra): depth, in_str, end_idx = 1, None, -1
assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_match.group(1)}) {expected_sx})') for i in range(open_idx + 1, len(opts_str)):
ch = opts_str[i]
if in_str:
if ch == in_str and opts_str[i - 1] != '\\':
in_str = None
continue
if ch in ('"', "'", '`'):
in_str = ch
continue
if ch == '{':
depth += 1
elif ch == '}':
depth -= 1
if depth == 0:
end_idx = i
break
if end_idx > open_idx:
for kv in split_top_level(opts_str[open_idx + 1:end_idx]):
kv = kv.strip()
m2 = re.match(r'^(\w+)\s*:\s*(.+)$', kv, re.DOTALL)
if m2:
extra.append((m2.group(1), js_val_to_sx(m2.group(2).strip())))
if me_val_match:
extra.append(('me', js_val_to_sx(me_val_match.group(1))))
if me_num_match and not (window_setups or extra):
assertions.append(f' (assert= (eval-hs-with-me "{hs_expr}" {me_num_match.group(1)}) {expected_sx})')
else: else:
# If there are other locals/setups but `me: <num>` is present too,
# bind it as a local so the HS expression can see it.
if me_num_match and not me_val_match:
extra.append(('me', me_num_match.group(1)))
assertions.append(emit_eval(hs_expr, expected_sx, extra)) assertions.append(emit_eval(hs_expr, expected_sx, extra))
# Pattern 1b: Inline — run("expr", opts).toEqual([...]) # Pattern 1b: Inline — run("expr", opts).toEqual([...])