HS parser: fix number+comparison keyword collision, eval-hs uses hs-compile

Parser: skip unit suffix when next ident is a comparison keyword
(starts, ends, contains, matches, is, does, in, precedes, follows).
Fixes "123 starts with '12'" returning "123starts" instead of true.

eval-hs: use hs-compile directly instead of hs-to-sx-from-source with
"return " prefix, which was causing the parser to consume the comparison
as a string suffix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 11:29:01 +00:00
parent a93e5924df
commit 4f02f82f4e
377 changed files with 9517 additions and 8694 deletions

View File

@@ -0,0 +1,33 @@
;; ---------------------------------------------------------------------------
;; L2: State Machines
;; ---------------------------------------------------------------------------
(defcomp ()
(~docs/page :title "Layer 2: State Machines"
(p (~tw :tokens "text-stone-500 text-sm italic mb-8")
"Modal state management where machine state IS a signal. "
"Composes naturally with computed and effects.")
(~docs/section :title "Live Demo" :id "demo"
(p "A traffic light built from a signal and a transitions dict. Auto-advance uses an effect with " (code "set-timeout") " — when state changes, the effect re-runs and schedules the next transition.")
(~reactive-runtime/demo-machine)
(~docs/code :src (highlight "(defisland ~demo-machine ()\n (let ((state (signal \"red\"))\n (transitions (dict \"red\" \"green\" \"green\" \"yellow\" \"yellow\" \"red\"))\n (auto (signal false)))\n ;; Auto-advance: effect depends on both state and auto\n (effect (fn ()\n (when (deref auto)\n (let ((delay (if (= (deref state) \"yellow\") 1000 2500)))\n (let ((id (set-timeout\n (fn () (reset! state (get transitions (deref state))))\n delay)))\n (fn () (clear-timeout id)))))))\n ;; Display: reactive class on each light\n (div :class (str \"...\" (if (= (deref state) \"red\")\n \"bg-red-500\" \"bg-red-900/30\")) ...)))" "lisp"))
(p "The machine state is a signal — " (code "(deref state)") " in the effect body subscribes, so when the timeout fires and resets state, the effect re-runs with the new delay. The " (code "make-machine") " function wraps this pattern with transition tables, guards, and enter/exit hooks."))
(~docs/section :title "API" :id "machine-api"
(~docs/code :src (highlight
"(define drawing-tool (make-machine\n {:initial :idle\n :states {:idle {:on {:pointer-down (fn (ev)\n {:state :drawing\n :actions (list (fn () (start-shape! ev)))})}}\n :drawing {:on {:pointer-move (fn (ev)\n {:state :drawing\n :actions (list (fn () (update-shape! ev)))})\n :pointer-up (fn (ev)\n {:state :idle\n :actions (list (fn () (finish-shape! ev)))})}}}}))\n\n;; Send events\n(machine-send! drawing-tool :pointer-down event)\n\n;; Read state (reactive — triggers re-render)\n(deref (machine-state drawing-tool)) ;; => :drawing\n(machine-matches? drawing-tool :idle) ;; => false"
"lisp"))
(p (code "make-machine") " returns a dict with a state signal, transitions map, and send function. "
"Handlers return " (code "{:state :new-state :actions [fn ...]}") " — "
"actions are called after the state transition."))
(~docs/section :title "Implementation Sketch" :id "machine-impl"
(~docs/code :src (highlight
"(define make-machine (fn (config)\n (let ((current (signal (get config :initial)))\n (states (get config :states)))\n (dict\n \"__machine\" true\n \"state\" current\n \"states\" states))))\n\n(define machine-state (fn (m) (get m \"state\")))\n\n(define machine-matches? (fn (m s)\n (= (deref (get m \"state\")) s)))\n\n(define machine-send! (fn (m event &rest data)\n (let ((current-state (deref (get m \"state\")))\n (state-config (get (get m \"states\") current-state))\n (handlers (get state-config :on))\n (handler (get handlers event)))\n (when handler\n (let ((result (apply handler data)))\n (when (get result :state)\n (reset! (get m \"state\") (get result :state)))\n (when (get result :actions)\n (for-each (fn (action) (action))\n (get result :actions))))))))"
"lisp"))
(p "~200 lines with guard conditions, context data, and enter/exit hooks."))))