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>
108 lines
5.2 KiB
Markdown
108 lines
5.2 KiB
Markdown
# 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.
|