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

@@ -110,7 +110,14 @@
((= typ "string") (do (adv!) val))
((and (= typ "keyword") (= val "true")) (do (adv!) true))
((and (= typ "keyword") (= val "false")) (do (adv!) false))
((and (= typ "keyword") (= val "null")) (do (adv!) nil))
((and (= typ "keyword") (or (= val "null") (= val "nil")))
(do (adv!) nil))
((and (= typ "keyword") (= val "not"))
(do (adv!) (list (quote not) (parse-expr))))
((and (= typ "keyword") (= val "no"))
(do (adv!) (list (quote no) (parse-expr))))
((and (= typ "keyword") (= val "the"))
(do (adv!) (parse-the-expr)))
((and (= typ "keyword") (= val "me"))
(do (adv!) (list (quote me))))
((and (= typ "keyword") (or (= val "it") (= val "result")))
@@ -118,9 +125,13 @@
((and (= typ "keyword") (= val "event"))
(do (adv!) (list (quote event))))
((and (= typ "keyword") (= val "target"))
(do (adv!) (list (quote .) (list (quote event)) "target")))
(do
(adv!)
(list (make-symbol ".") (list (quote event)) "target")))
((and (= typ "keyword") (= val "detail"))
(do (adv!) (list (quote .) (list (quote event)) "detail")))
(do
(adv!)
(list (make-symbol ".") (list (quote event)) "detail")))
((and (= typ "keyword") (= val "my"))
(do (adv!) (parse-poss-tail (list (quote me)))))
((and (= typ "keyword") (= val "its"))
@@ -152,6 +163,13 @@
((expr (parse-expr)))
(if (= (tp-type) "paren-close") (adv!) nil)
expr)))
((= typ "bracket-open") (do (adv!) (parse-array-lit)))
((and (= typ "op") (= val "-"))
(do
(adv!)
(let
((operand (parse-atom)))
(list (quote -) 0 operand))))
(true nil)))))
(define
parse-poss
@@ -201,6 +219,24 @@
(do (adv!) (list (quote and) left (parse-expr))))
((and (= typ "keyword") (= val "or"))
(do (adv!) (list (quote or) left (parse-expr))))
((and (= typ "keyword") (= val "as"))
(do
(adv!)
(let
((type-name (tp-val)))
(adv!)
(list (quote as) left type-name))))
((and (= typ "keyword") (= val "of"))
(do
(adv!)
(let
((target (parse-expr)))
(if
(and (list? left) (= (first left) (quote ref)))
(list (make-symbol ".") target (nth left 1))
(list (quote of) left target)))))
((and (= typ "keyword") (= val "in"))
(do (adv!) (list (quote in?) left (parse-expr))))
(true left)))))
(define
parse-expr
@@ -211,7 +247,9 @@
(if
(nil? left)
nil
(let ((left2 (parse-poss left))) (parse-cmp left2))))))
(let
((left2 (parse-poss left)))
(let ((left3 (parse-arith left2))) (parse-cmp left3)))))))
(define
parse-tgt-kw
(fn (kw default) (if (match-kw kw) (parse-expr) default)))
@@ -307,12 +345,22 @@
parse-wait-cmd
(fn
()
(if
(= (tp-type) "number")
(let
((tok (adv!)))
(list (quote wait) (parse-dur (get tok "value"))))
(list (quote wait) 0))))
(cond
((match-kw "for")
(let
((event-name (tp-val)))
(adv!)
(let
((source (if (match-kw "from") (parse-expr) nil)))
(if
source
(list (quote wait-for) event-name :from source)
(list (quote wait-for) event-name)))))
((= (tp-type) "number")
(let
((tok (adv!)))
(list (quote wait) (parse-dur (get tok "value")))))
(true (list (quote wait) 0)))))
(define
parse-detail-dict
(fn
@@ -405,10 +453,12 @@
(fn
()
(let
((url (parse-expr)))
((url-atom (parse-atom)))
(let
((fmt (if (match-kw "as") (get (adv!) "value") "json")))
(list (quote fetch) url fmt)))))
((url (if (nil? url-atom) url-atom (parse-arith (parse-poss url-atom)))))
(let
((fmt (if (match-kw "as") (let ((f (tp-val))) (adv!) f) "json")))
(list (quote fetch) url fmt))))))
(define
parse-call-args
(fn
@@ -453,6 +503,206 @@
(define
parse-go-cmd
(fn () (match-kw "to") (list (quote go) (parse-expr))))
(begin
(define
parse-arith
(fn
(left)
(let
((typ (tp-type)) (val (tp-val)))
(if
(and
(= typ "op")
(or
(= val "+")
(= val "-")
(= val "*")
(= val "/")
(= val "%")))
(do
(adv!)
(let
((op (cond ((= val "+") (quote +)) ((= val "-") (quote -)) ((= val "*") (quote *)) ((= val "/") (quote /)) ((= val "%") (make-symbol "%")))))
(let
((right (let ((a (parse-atom))) (if (nil? a) a (parse-poss a)))))
(parse-arith (list op left right)))))
left))))
(define
parse-the-expr
(fn
()
(let
((typ (tp-type)) (val (tp-val)))
(if
(or (= typ "ident") (= typ "keyword"))
(do
(adv!)
(if
(match-kw "of")
(list (make-symbol ".") (parse-expr) val)
(cond
((= val "result") (list (quote it)))
((= val "first") (parse-pos-kw (quote first)))
((= val "last") (parse-pos-kw (quote last)))
((= val "closest") (parse-trav (quote closest)))
((= val "next") (parse-trav (quote next)))
((= val "previous") (parse-trav (quote previous)))
(true (list (quote ref) val)))))
(parse-atom)))))
(define
parse-array-lit
(fn
()
(define
al-collect
(fn
(acc)
(if
(or (= (tp-type) "bracket-close") (at-end?))
(do (if (= (tp-type) "bracket-close") (adv!) nil) acc)
(let
((elem (parse-expr)))
(if (= (tp-type) "comma") (adv!) nil)
(al-collect (append acc (list elem)))))))
(cons (quote array) (al-collect (list)))))
(define
parse-return-cmd
(fn
()
(if
(or
(at-end?)
(and
(= (tp-type) "keyword")
(or
(= (tp-val) "end")
(= (tp-val) "then")
(= (tp-val) "else"))))
(list (quote return) nil)
(list (quote return) (parse-expr)))))
(define parse-throw-cmd (fn () (list (quote throw) (parse-expr))))
(define
parse-append-cmd
(fn
()
(let
((value (parse-expr)))
(expect-kw! "to")
(let
((target (parse-expr)))
(list (quote append!) value target)))))
(define
parse-tell-cmd
(fn
()
(let
((target (parse-expr)))
(let
((body (parse-cmd-list)))
(match-kw "end")
(list (quote tell) target body)))))
(define
parse-for-cmd
(fn
()
(let
((var-name (tp-val)))
(adv!)
(expect-kw! "in")
(let
((collection (parse-expr)))
(let
((idx (if (match-kw "index") (let ((iname (tp-val))) (adv!) iname) nil)))
(let
((body (parse-cmd-list)))
(match-kw "end")
(if
idx
(list (quote for) var-name collection body :index idx)
(list (quote for) var-name collection body))))))))
(define
parse-make-cmd
(fn
()
(if (= (tp-val) "a") (adv!) nil)
(let
((type-name (tp-val)))
(adv!)
(let
((called (if (match-kw "called") (let ((n (tp-val))) (adv!) n) nil)))
(if
called
(list (quote make) type-name called)
(list (quote make) type-name))))))
(define
parse-install-cmd
(fn
()
(let
((name (tp-val)))
(adv!)
(if
(= (tp-type) "paren-open")
(let
((args (parse-call-args)))
(cons (quote install) (cons name args)))
(list (quote install) name)))))
(define
parse-measure-cmd
(fn
()
(let
((tgt (parse-expr)))
(list (quote measure) (if (nil? tgt) (list (quote me)) tgt)))))
(define
parse-param-list
(fn () (if (= (tp-type) "paren-open") (parse-call-args) (list))))
(define
parse-feat-body
(fn
()
(define
fb-collect
(fn
(acc)
(if
(or
(at-end?)
(and (= (tp-type) "keyword") (= (tp-val) "end")))
acc
(let
((feat (parse-feat)))
(if
(nil? feat)
acc
(fb-collect (append acc (list feat))))))))
(fb-collect (list))))
(define
parse-def-feat
(fn
()
(let
((name (tp-val)))
(adv!)
(let
((params (parse-param-list)))
(let
((body (parse-cmd-list)))
(match-kw "end")
(list (quote def) name params body))))))
(define
parse-behavior-feat
(fn
()
(let
((name (tp-val)))
(adv!)
(let
((params (parse-param-list)))
(let
((body (parse-feat-body)))
(match-kw "end")
(list (quote behavior) name params body)))))))
(define
parse-cmd
(fn
@@ -502,6 +752,22 @@
(do (adv!) (list (quote settle))))
((and (= typ "keyword") (= val "go"))
(do (adv!) (parse-go-cmd)))
((and (= typ "keyword") (= val "return"))
(do (adv!) (parse-return-cmd)))
((and (= typ "keyword") (= val "throw"))
(do (adv!) (parse-throw-cmd)))
((and (= typ "keyword") (= val "append"))
(do (adv!) (parse-append-cmd)))
((and (= typ "keyword") (= val "tell"))
(do (adv!) (parse-tell-cmd)))
((and (= typ "keyword") (= val "for"))
(do (adv!) (parse-for-cmd)))
((and (= typ "keyword") (= val "make"))
(do (adv!) (parse-make-cmd)))
((and (= typ "keyword") (= val "install"))
(do (adv!) (parse-install-cmd)))
((and (= typ "keyword") (= val "measure"))
(do (adv!) (parse-measure-cmd)))
(true (parse-expr))))))
(define
parse-cmd-list
@@ -530,21 +796,25 @@
(fn
()
(let
((event-name (get (adv!) "value")))
((every? (match-kw "every")))
(let
((flt (if (= (tp-type) "bracket-open") (do (adv!) (let ((expr (parse-expr))) (if (= (tp-type) "bracket-close") (adv!) nil) expr)) nil)))
((event-name (let ((v (tp-val))) (adv!) v)))
(let
((source (if (match-kw "from") (parse-expr) nil)))
((flt (if (= (tp-type) "bracket-open") (do (adv!) (let ((f (parse-expr))) (if (= (tp-type) "bracket-close") (adv!) nil) f)) nil)))
(let
((body (parse-cmd-list)))
(match-kw "end")
((source (if (match-kw "from") (parse-expr) nil)))
(let
((parts (list (quote on) event-name)))
((body (parse-cmd-list)))
(match-kw "end")
(let
((parts (if source (append parts (list :from source)) parts)))
((parts (list (quote on) event-name)))
(let
((parts (if flt (append parts (list :filter flt)) parts)))
(append parts (list body)))))))))))
((parts (if every? (append parts (list :every true)) parts)))
(let
((parts (if flt (append parts (list :filter flt)) parts)))
(let
((parts (if source (append parts (list :from source)) parts)))
(append parts (list body)))))))))))))
(define
parse-init-feat
(fn
@@ -562,6 +832,8 @@
(cond
((= val "on") (do (adv!) (parse-on-feat)))
((= val "init") (do (adv!) (parse-init-feat)))
((= val "def") (do (adv!) (parse-def-feat)))
((= val "behavior") (do (adv!) (parse-behavior-feat)))
(true (parse-cmd-list))))))
(define
coll-feats