Hyperscript compiler/runtime:
- query target support in set/fire/put commands
- hs-set-prolog-hook! / hs-prolog-hook / hs-prolog in runtime
- runtime log-capture cleanup
Scripts: sx-loops-up/down, sx-hs-e-up/down, sx-primitives-down
Plans: datalog, elixir, elm, go, koka, minikanren, ocaml, hs-bucket-f,
designs (breakpoint, null-safety, step-limit, tell, cookies, eval,
plugin-system)
lib/prolog/hs-bridge.sx: initial hook-based bridge draft
lib/common-lisp/tests/runtime.sx: CL runtime tests
WASM: regenerate sx_browser.bc.js from updated hs sources
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.2 KiB
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 expressionsthrows on symbol referencesthrows 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:
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:
- Calls
(hs-compile src)with a flag that makes it throw on non-literal expressions - 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:
(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
- In
lib/hyperscript/compiler.sx, addhs-static?predicate: returns true only for literal AST node types. - In the
eval-hspath (whereverhs-compile-and-runis called for the evaluate API), callhs-static?on the parsed AST and throw"expression is not statically evaluable"if false. - Replace 3
SKIPbodies inspec/tests/test-hyperscript-behavioral.sx:(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}\"")))) - Run
hs_test_run suite="hs-upstream-core/evalStatically"— expect 8/8. - Run smoke 0–195 — verify the 5 passing tests still pass.
- 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.