Step 18 (part 3): Expand parser — expressions, commands, features

Tokenizer:
  * and % now emit as operators (were silently swallowed)
  Added keywords: install, measure, behavior, called
  5 new arithmetic operator tests

Parser — expression layer:
  Arithmetic (+, -, *, /, %) via parse-arith
  Unary not, no, unary minus
  the X of Y possessive (parse-the-expr)
  as Type conversion, X in Y membership, array literals [...]
  fetch URL parsing fixed — no longer consumes "as" meant for fetch

Parser — 8 new commands:
  return, throw, append...to, tell...end, for...in...end,
  make a Type, install Behavior, measure

Parser — 2 new features:
  def name(params)...end, behavior Name(params)...end

Parser — enhanced:
  wait for event [from target], on every event modifier

33 new parser tests (16 suites), 5 tokenizer tests.
3043/3043 full build, zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 08:21:02 +00:00
parent 4cd0e77331
commit f1ba7177e7
4 changed files with 625 additions and 23 deletions

View File

@@ -375,6 +375,291 @@
(assert= "hello" (nth ast 2)))))
;; ── Full expressions (matching tokenizer conformance) ─────────────
(defsuite
"hs-parse-arithmetic"
(deftest
"addition"
(let
((ast (hs-compile "set x to 1 + 2")))
(let
((val (nth ast 2)))
(assert= (quote +) (first val))
(assert= 1 (nth val 1))
(assert= 2 (nth val 2)))))
(deftest
"subtraction"
(let
((ast (hs-compile "set x to 10 - 3")))
(let
((val (nth ast 2)))
(assert= (quote -) (first val))
(assert= 10 (nth val 1))
(assert= 3 (nth val 2)))))
(deftest
"multiplication"
(let
((ast (hs-compile "set x to 4 * 5")))
(let
((val (nth ast 2)))
(assert= (quote *) (first val))
(assert= 4 (nth val 1))
(assert= 5 (nth val 2)))))
(deftest
"division"
(let
((ast (hs-compile "set x to 10 / 2")))
(let
((val (nth ast 2)))
(assert= (quote /) (first val))
(assert= 10 (nth val 1))
(assert= 2 (nth val 2)))))
(deftest
"chained arithmetic"
(let
((ast (hs-compile "set x to 1 + 2 + 3")))
(let
((val (nth ast 2)))
(assert= (quote +) (first val))
(assert= 3 (nth val 2))))))
(defsuite
"hs-parse-unary"
(deftest
"not expr"
(let
((ast (hs-compile "if not x end")))
(let ((cnd (nth ast 1))) (assert= (quote not) (first cnd)))))
(deftest
"no expr"
(let
((ast (hs-compile "if no x end")))
(let ((cnd (nth ast 1))) (assert= (quote no) (first cnd)))))
(deftest
"unary minus"
(let
((ast (hs-compile "set x to -5")))
(let
((val (nth ast 2)))
(assert= (quote -) (first val))
(assert= 0 (nth val 1))
(assert= 5 (nth val 2))))))
(defsuite
"hs-parse-the-of"
(deftest
"the innerHTML of me"
(let
((ast (hs-compile "set the innerHTML of me to 'hi'")))
(let
((tgt (nth ast 1)))
(assert= (make-symbol ".") (first tgt))
(assert= "innerHTML" (nth tgt 2)))))
(deftest
"the as article skip"
(let
((ast (hs-compile "set the result to 5")))
(let ((tgt (nth ast 1))) (assert= (quote it) (first tgt))))))
(defsuite
"hs-parse-as-conversion"
(deftest
"expr as Int"
(let
((ast (hs-compile "set x to y as Int")))
(let
((val (nth ast 2)))
(assert= (quote as) (first val))
(assert= "Int" (nth val 2)))))
(deftest
"expr as String"
(let
((ast (hs-compile "set x to count as String")))
(let
((val (nth ast 2)))
(assert= (quote as) (first val))
(assert= "String" (nth val 2))))))
(defsuite
"hs-parse-in-operator"
(deftest
"x in collection"
(let
((ast (hs-compile "if x in items end")))
(let ((cnd (nth ast 1))) (assert= (quote in?) (first cnd))))))
(defsuite
"hs-parse-array-literals"
(deftest
"empty array"
(let
((ast (hs-compile "set x to []")))
(let
((val (nth ast 2)))
(assert= (quote array) (first val))
(assert= 1 (len val)))))
(deftest
"array with elements"
(let
((ast (hs-compile "set x to [1, 2, 3]")))
(let
((val (nth ast 2)))
(assert= (quote array) (first val))
(assert= 4 (len val))
(assert= 1 (nth val 1))
(assert= 2 (nth val 2))
(assert= 3 (nth val 3))))))
(defsuite
"hs-parse-return-throw"
(deftest
"return expr"
(let
((ast (hs-compile "return 42")))
(assert= (quote return) (first ast))
(assert= 42 (nth ast 1))))
(deftest
"return bare"
(let
((ast (hs-compile "return")))
(assert= (quote return) (first ast))
(assert= nil (nth ast 1))))
(deftest
"throw expr"
(let
((ast (hs-compile "throw 'error'")))
(assert= (quote throw) (first ast))
(assert= "error" (nth ast 1)))))
(defsuite
"hs-parse-append"
(deftest
"append to target"
(let
((ast (hs-compile "append 'hello' to me")))
(assert= (quote append!) (first ast))
(assert= "hello" (nth ast 1))
(assert= (quote me) (first (nth ast 2))))))
(defsuite
"hs-parse-tell"
(deftest
"tell target commands end"
(let
((ast (hs-compile "tell <div/> add .active end")))
(assert= (quote tell) (first ast))
(assert= (quote query) (first (nth ast 1)))
(assert= (quote add-class) (first (nth ast 2))))))
(defsuite
"hs-parse-for"
(deftest
"for x in items end"
(let
((ast (hs-compile "for item in items log item end")))
(assert= (quote for) (first ast))
(assert= "item" (nth ast 1))
(assert= (quote ref) (first (nth ast 2)))
(assert= (quote log) (first (nth ast 3)))))
(deftest
"for with index"
(let
((ast (hs-compile "for item in items index i log item end")))
(assert= (quote for) (first ast))
(assert= "item" (nth ast 1)))))
(defsuite
"hs-parse-make"
(deftest
"make a Object"
(let
((ast (hs-compile "make a Object")))
(assert= (quote make) (first ast))
(assert= "Object" (nth ast 1))))
(deftest
"make a Set called s"
(let
((ast (hs-compile "make a Set called s")))
(assert= (quote make) (first ast))
(assert= "Set" (nth ast 1))
(assert= "s" (nth ast 2)))))
(defsuite
"hs-parse-install"
(deftest
"install behavior"
(let
((ast (hs-compile "install Draggable")))
(assert= (quote install) (first ast))
(assert= "Draggable" (nth ast 1))))
(deftest
"install with args"
(let
((ast (hs-compile "install Sortable(true)")))
(assert= (quote install) (first ast))
(assert= "Sortable" (nth ast 1))
(assert= true (nth ast 2)))))
(defsuite
"hs-parse-measure"
(deftest
"measure target"
(let
((ast (hs-compile "measure me")))
(assert= (quote measure) (first ast))
(assert= (quote me) (first (nth ast 1))))))
(defsuite
"hs-parse-wait-for"
(deftest
"wait for transitionend"
(let
((ast (hs-compile "wait for transitionend")))
(assert= (quote wait-for) (first ast))
(assert= "transitionend" (nth ast 1))))
(deftest
"wait for click from target"
(let
((ast (hs-compile "wait for click from #btn")))
(assert= (quote wait-for) (first ast))
(assert= "click" (nth ast 1))
(assert= :from (nth ast 2)))))
(defsuite
"hs-parse-def-behavior"
(deftest
"def function"
(let
((ast (hs-compile "def greet(name) log name end")))
(assert= (quote def) (first ast))
(assert= "greet" (nth ast 1))
(assert= 1 (len (nth ast 2)))
(assert= (quote log) (first (nth ast 3)))))
(deftest
"behavior with on handler"
(let
((ast (hs-compile "behavior Clickable on click add .clicked end end")))
(assert= (quote behavior) (first ast))
(assert= "Clickable" (nth ast 1))
(assert= 1 (len (nth ast 3)))))
(deftest
"def no params"
(let
((ast (hs-compile "def reset() set x to 0 end")))
(assert= (quote def) (first ast))
(assert= "reset" (nth ast 1))
(assert= 0 (len (nth ast 2))))))
(defsuite
"hs-parse-every-modifier"
(deftest
"on every click"
(let
((ast (hs-compile "on every click add .pulse end")))
(assert= (quote on) (first ast))
(assert= "click" (nth ast 1))
(assert= :every (nth ast 2))
(assert= true (nth ast 3)))))
(defsuite
"hs-parse-conformance"
(deftest