Hyperscript conformance: 222 test fixtures from _hyperscript 0.9.14

Extract pure expression tests from the official _hyperscript test suite
and implement parser/compiler/runtime extensions to pass them.

Test infrastructure:
- 222 fixtures extracted from evalHyperScript calls (no DOM dependency)
- SX data format with eval-hs bridge and run-hs-fixture runner
- 24 suites covering expressions, comparisons, coercion, logic, etc.

Parser extensions (parser.sx):
- mod as infix arithmetic operator
- English comparison phrases (is less than, is greater than or equal to)
- is a/an Type typecheck syntax
- === / !== strict equality operators
- I as me synonym, am as is for comparisons
- does not exist/match/contain postfix
- some/every ... with quantifier expressions
- undefined keyword → nil

Compiler updates (compiler.sx):
- + emits hs-add (type-dispatching: string concat or numeric add)
- no emits hs-falsy? (HS truthiness: empty string is falsy)
- matches? emits hs-matches? (string regex in non-DOM context)
- New cases: not-in?, in?, type-check, strict-eq, some, every

Runtime additions (runtime.sx):
- hs-coerce: Int/Integer truncation via floor
- hs-add: string concat when either operand is string
- hs-falsy?: HS-compatible truthiness (nil, false, "" are falsy)
- hs-matches?: string pattern matching
- hs-type-check/hs-type-check!: lenient/strict type checking
- hs-strict-eq: type + value equality

Tokenizer (tokenizer.sx):
- Added keywords: I, am, does, some, mod, equal, equals, really,
  include, includes, contain, undefined, exist

Scorecard: 47/112 test groups passing. 0 non-HS regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 18:53:50 +00:00
parent 71d1ac9ce4
commit 2278443182
6 changed files with 1250 additions and 70 deletions

View File

@@ -202,42 +202,44 @@
(define
emit-inc
(fn
(target)
(expr tgt-override)
(let
((t (hs-to-sx target)))
((t (hs-to-sx expr)))
(if
(and (list? target) (= (first target) (quote attr)))
(list
(quote dom-set-attr)
(hs-to-sx (nth target 2))
(nth target 1)
(and (list? expr) (= (first expr) (quote attr)))
(let
((el (if tgt-override (hs-to-sx tgt-override) (hs-to-sx (nth expr 2)))))
(list
(quote +)
(quote dom-set-attr)
el
(nth expr 1)
(list
(quote dom-get-attr)
(hs-to-sx (nth target 2))
(nth target 1))
1))
(quote +)
(list
(quote parse-number)
(list (quote dom-get-attr) el (nth expr 1)))
1)))
(list (quote set!) t (list (quote +) t 1))))))
(define
emit-dec
(fn
(target)
(expr tgt-override)
(let
((t (hs-to-sx target)))
((t (hs-to-sx expr)))
(if
(and (list? target) (= (first target) (quote attr)))
(list
(quote dom-set-attr)
(hs-to-sx (nth target 2))
(nth target 1)
(and (list? expr) (= (first expr) (quote attr)))
(let
((el (if tgt-override (hs-to-sx tgt-override) (hs-to-sx (nth expr 2)))))
(list
(quote -)
(quote dom-set-attr)
el
(nth expr 1)
(list
(quote dom-get-attr)
(hs-to-sx (nth target 2))
(nth target 1))
1))
(quote -)
(list
(quote parse-number)
(list (quote dom-get-attr) el (nth expr 1)))
1)))
(list (quote set!) t (list (quote -) t 1))))))
(define
emit-behavior
@@ -288,7 +290,7 @@
((= head (quote not))
(list (quote not) (hs-to-sx (nth ast 1))))
((= head (quote no))
(list (quote not) (hs-to-sx (nth ast 1))))
(list (quote hs-falsy?) (hs-to-sx (nth ast 1))))
((= head (quote and))
(list
(quote and)
@@ -306,7 +308,7 @@
(hs-to-sx (nth ast 2))))
((= head (quote +))
(list
(quote +)
(quote hs-add)
(hs-to-sx (nth ast 1))
(hs-to-sx (nth ast 2))))
((= head (quote -))
@@ -337,7 +339,7 @@
(list (quote nil?) (hs-to-sx (nth ast 1)))))
((= head (quote matches?))
(list
(quote dom-matches?)
(quote hs-matches?)
(hs-to-sx (nth ast 1))
(hs-to-sx (nth ast 2))))
((= head (quote contains?))
@@ -508,8 +510,14 @@
(cons (quote hs-install) (map hs-to-sx (rest ast))))
((= head (quote measure))
(list (quote hs-measure) (hs-to-sx (nth ast 1))))
((= head (quote increment!)) (emit-inc (nth ast 1)))
((= head (quote decrement!)) (emit-dec (nth ast 1)))
((= head (quote increment!))
(emit-inc
(nth ast 1)
(if (> (len ast) 2) (nth ast 2) nil)))
((= head (quote decrement!))
(emit-dec
(nth ast 1)
(if (> (len ast) 2) (nth ast 2) nil)))
((= head (quote on)) (emit-on ast))
((= head (quote init))
(list
@@ -563,6 +571,49 @@
pos
(if target target (quote me)))
render-call)))))
((= head (quote not-in?))
(list
(quote not)
(list
(quote contains?)
(hs-to-sx (nth ast 2))
(hs-to-sx (nth ast 1)))))
((= head (quote in?))
(list
(quote contains?)
(hs-to-sx (nth ast 2))
(hs-to-sx (nth ast 1))))
((= head (quote type-check))
(list
(quote hs-type-check)
(hs-to-sx (nth ast 1))
(nth ast 2)))
((= head (quote type-check!))
(list
(quote hs-type-check!)
(hs-to-sx (nth ast 1))
(nth ast 2)))
((= head (quote strict-eq))
(list
(quote hs-strict-eq)
(hs-to-sx (nth ast 1))
(hs-to-sx (nth ast 2))))
((= head (quote some))
(list
(quote some)
(list
(quote fn)
(list (make-symbol (nth ast 1)))
(hs-to-sx (nth ast 3)))
(hs-to-sx (nth ast 2))))
((= head (quote every))
(list
(quote every?)
(list
(quote fn)
(list (make-symbol (nth ast 1)))
(hs-to-sx (nth ast 3)))
(hs-to-sx (nth ast 2))))
(true ast))))))))
;; ── Convenience: source → SX ─────────────────────────────────