# F8 — evalStatically (+3) **Suite:** `hs-upstream-core/evalStatically` **Target:** 3 failing (untranslated) out of 8. 5 already pass. ## 1. Current state 5 passing tests use `(eval-hs expr)` and check the return value for literals: booleans, null, numbers, plain strings, time expressions. These call `_hyperscript.evaluate(src)` and return the result. 3 failing tests are named: - `throws on math expressions` - `throws on symbol references` - `throws on template strings` All are `SKIP (untranslated)` — no test body has been generated. ## 2. What upstream checks From `test/core/evalStatically.js`, the `throwErrors` mode: ```js expect(() => _hyperscript.evaluate("1 + 2")).toThrow(); expect(() => _hyperscript.evaluate("x")).toThrow(); expect(() => _hyperscript.evaluate(`"hello ${name}"`)).toThrow(); ``` `_hyperscript.evaluate(src)` in strict static mode throws when the expression is not a pure literal — math operators, symbol references, and template string interpolation all involve runtime evaluation that can't be statically resolved. The "static" constraint: only literals that can be evaluated without any runtime context or side effects are allowed. `1 + 2` is not static (it's a math op). `x` is not static (symbol lookup). `"hello ${name}"` is not static (interpolation). ## 3. What `eval-hs` currently does `eval-hs` in our harness calls `(hs-compile-and-run src)` or equivalent. It does NOT currently have a "static mode" — it runs everything with the full runtime. We need a new harness helper `eval-hs-static-error` that: 1. Calls `(hs-compile src)` with a flag that makes it throw on non-literal expressions 2. Returns the caught error message, or raises if no error was thrown ## 4. Implementation options ### Option A — Static analysis pass (accurate) Before evaluation, walk the AST and reject any node that isn't a literal: - Number literal ✓ - String literal (no interpolation) ✓ - Boolean literal ✓ - Null literal ✓ - Time expression (`200ms`, `2s`) ✓ - Everything else → throw `"expression is not static"` This is a pre-eval AST check, not a runtime change. Lives in `lib/hyperscript/compiler.sx` as `hs-check-static`. ### Option B — Generator translation (simpler) The 3 tests are untranslated. All three just verify that `_hyperscript.evaluate(expr)` throws. In our SX harness we can test this with a `guard` form: ```lisp (deftest "throws on math expressions" (let ((result (guard (e (true true)) (eval-hs "1 + 2") false))) (assert result))) ``` But this only works if `eval-hs` actually throws on math expressions. Currently it doesn't — `eval-hs "1 + 2"` returns `3`. So we'd need the static analysis anyway to make the test pass. ### Chosen approach: Option A Add `hs-static-check` to the compiler: a fast AST walker that throws on any non-literal node. Wire it as an optional mode. The test harness calls `eval-hs-static` which runs with static-check enabled. Actually, reading the upstream more carefully: `_hyperscript.evaluate` already throws in static mode without additional flags — the "evaluate" API is documented as static-only. Our `eval-hs` in the passing tests works because booleans/numbers/strings/time ARE static. `1 + 2`, `x`, and template strings are NOT static and should throw. So the fix is: make `hs-compile-and-run` (or whatever backs `eval-hs`) reject non-literal AST nodes. The 5 passing tests will continue to pass (they use literals). The 3 failing tests will get translated using `eval-hs-error` or a guard pattern. ## 5. Non-literal AST node types to reject | Expression | AST node type | Reject? | |-----------|--------------|---------| | `1`, `3.14` | number literal | ✓ allow | | `"hello"`, `'world'` | string literal (no interpolation) | ✓ allow | | `true`, `false` | boolean literal | ✓ allow | | `null` | null literal | ✓ allow | | `200ms`, `2s` | time literal | ✓ allow | | `1 + 2` | math operator | ✗ throw | | `x` | symbol reference | ✗ throw | | `"hello ${name}"` | template string | ✗ throw | ## 6. Implementation checklist 1. In `lib/hyperscript/compiler.sx`, add `hs-static?` predicate: returns true only for literal AST node types. 2. In the `eval-hs` path (wherever `hs-compile-and-run` is called for the evaluate API), call `hs-static?` on the parsed AST and throw `"expression is not statically evaluable"` if false. 3. Replace 3 `SKIP` bodies in `spec/tests/test-hyperscript-behavioral.sx`: ```lisp (deftest "throws on math expressions" (assert (string? (eval-hs-error "1 + 2")))) (deftest "throws on symbol references" (assert (string? (eval-hs-error "x")))) (deftest "throws on template strings" (assert (string? (eval-hs-error "\"hello ${name}\"")))) ``` 4. Run `hs_test_run suite="hs-upstream-core/evalStatically"` — expect 8/8. 5. Run smoke 0–195 — verify the 5 passing tests still pass. 6. Commit: `HS: evalStatically — static literal check, 3 tests (+3)` ## 7. Risk Low-medium. The main risk is that `eval-hs` is used in many tests for non-static expressions and adding a static check to the shared path would break them. The fix must be gated — either a separate `eval-hs-static` helper or a flag parameter. The passing tests must not be affected.