Salvaged from worktree-agent-* branches killed during sx-tree MCP outage: - lua: tokenizer + parser + phase-2 transpile (~157 tests) - prolog: tokenizer + parser + unification (72 tests, plan update lost to WIP) - forth: phase-1 reader/interpreter + phase-2 colon/VARIABLE (134 tests) - erlang: tokenizer + parser (114 tests) - haskell: tokenizer + parse tests (43 tests) Cherry-picked file contents only, not branch history, to avoid pulling in unrelated ocaml-vm merge commits that were in those branches' bases.
275 lines
7.4 KiB
Plaintext
275 lines
7.4 KiB
Plaintext
;; Phase 2 — colon definitions, compile mode, VARIABLE/CONSTANT/VALUE/TO, @/!/+!.
|
|
;;
|
|
;; Compile-mode representation:
|
|
;; A colon-definition body is a list of "ops", each an SX lambda (fn (s) ...).
|
|
;; : FOO 1 2 + ; -> body = (push-1 push-2 call-plus)
|
|
;; References to other words are compiled as late-binding thunks so that
|
|
;; self-reference works and redefinitions take effect for future runs.
|
|
;;
|
|
;; State additions used in Phase 2:
|
|
;; "compiling" : bool — are we inside :..; ?
|
|
;; "current-def" : dict {:name "..." :body (list)} during compile
|
|
;; "vars" : dict {"addr-name" -> cell-value} for VARIABLE storage
|
|
|
|
(define
|
|
forth-compile-token
|
|
(fn
|
|
(state tok)
|
|
(let
|
|
((w (forth-lookup state tok)))
|
|
(if
|
|
(not (nil? w))
|
|
(if
|
|
(get w "immediate?")
|
|
(forth-execute-word state w)
|
|
(forth-compile-call state tok))
|
|
(let
|
|
((n (forth-parse-number tok (get state "base"))))
|
|
(if
|
|
(not (nil? n))
|
|
(forth-compile-lit state n)
|
|
(forth-error state (str tok " ?"))))))))
|
|
|
|
(define
|
|
forth-compile-call
|
|
(fn
|
|
(state name)
|
|
(let
|
|
((op (fn (s) (let ((w (forth-lookup s name))) (if (nil? w) (forth-error s (str name " ? (compiled)")) (forth-execute-word s w))))))
|
|
(forth-def-append! state op))))
|
|
|
|
(define
|
|
forth-compile-lit
|
|
(fn
|
|
(state n)
|
|
(let ((op (fn (s) (forth-push s n)))) (forth-def-append! state op))))
|
|
|
|
(define
|
|
forth-def-append!
|
|
(fn
|
|
(state op)
|
|
(let
|
|
((def (get state "current-def")))
|
|
(dict-set! def "body" (concat (get def "body") (list op))))))
|
|
|
|
(define
|
|
forth-make-colon-body
|
|
(fn (ops) (fn (s) (for-each (fn (op) (op s)) ops))))
|
|
|
|
;; Override forth-interpret-token to branch on compile mode.
|
|
(define
|
|
forth-interpret-token
|
|
(fn
|
|
(state tok)
|
|
(if
|
|
(get state "compiling")
|
|
(forth-compile-token state tok)
|
|
(let
|
|
((w (forth-lookup state tok)))
|
|
(if
|
|
(not (nil? w))
|
|
(forth-execute-word state w)
|
|
(let
|
|
((n (forth-parse-number tok (get state "base"))))
|
|
(if
|
|
(not (nil? n))
|
|
(forth-push state n)
|
|
(forth-error state (str tok " ?")))))))))
|
|
|
|
;; Install `:` and `;` plus VARIABLE, CONSTANT, VALUE, TO, @, !, +!, RECURSE.
|
|
(define
|
|
forth-install-compiler!
|
|
(fn
|
|
(state)
|
|
(forth-def-prim!
|
|
state
|
|
":"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((name (forth-next-token! s)))
|
|
(when (nil? name) (forth-error s ": expects name"))
|
|
(let
|
|
((def (dict)))
|
|
(dict-set! def "name" name)
|
|
(dict-set! def "body" (list))
|
|
(dict-set! s "current-def" def)
|
|
(dict-set! s "compiling" true)))))
|
|
(forth-def-prim-imm!
|
|
state
|
|
";"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((def (get s "current-def")))
|
|
(when (nil? def) (forth-error s "; outside definition"))
|
|
(let
|
|
((ops (get def "body")))
|
|
(let
|
|
((body-fn (forth-make-colon-body ops)))
|
|
(dict-set!
|
|
(get s "dict")
|
|
(downcase (get def "name"))
|
|
(forth-make-word "colon-def" body-fn false))
|
|
(dict-set! s "current-def" nil)
|
|
(dict-set! s "compiling" false))))))
|
|
(forth-def-prim-imm!
|
|
state
|
|
"IMMEDIATE"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((def-name (get (get s "current-def") "name"))
|
|
(target
|
|
(if
|
|
(nil? (get s "current-def"))
|
|
(forth-last-defined s)
|
|
(get (get s "current-def") "name"))))
|
|
(let
|
|
((w (forth-lookup s target)))
|
|
(when (not (nil? w)) (dict-set! w "immediate?" true))))))
|
|
(forth-def-prim-imm!
|
|
state
|
|
"RECURSE"
|
|
(fn
|
|
(s)
|
|
(when
|
|
(not (get s "compiling"))
|
|
(forth-error s "RECURSE only in definition"))
|
|
(let
|
|
((name (get (get s "current-def") "name")))
|
|
(forth-compile-call s name))))
|
|
(forth-def-prim!
|
|
state
|
|
"VARIABLE"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((name (forth-next-token! s)))
|
|
(when (nil? name) (forth-error s "VARIABLE expects name"))
|
|
(dict-set! (get s "vars") (downcase name) 0)
|
|
(forth-def-prim!
|
|
s
|
|
name
|
|
(fn (ss) (forth-push ss (downcase name)))))))
|
|
(forth-def-prim!
|
|
state
|
|
"CONSTANT"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((name (forth-next-token! s)) (v (forth-pop s)))
|
|
(when (nil? name) (forth-error s "CONSTANT expects name"))
|
|
(forth-def-prim! s name (fn (ss) (forth-push ss v))))))
|
|
(forth-def-prim!
|
|
state
|
|
"VALUE"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((name (forth-next-token! s)) (v (forth-pop s)))
|
|
(when (nil? name) (forth-error s "VALUE expects name"))
|
|
(dict-set! (get s "vars") (downcase name) v)
|
|
(forth-def-prim!
|
|
s
|
|
name
|
|
(fn
|
|
(ss)
|
|
(forth-push ss (get (get ss "vars") (downcase name))))))))
|
|
(forth-def-prim!
|
|
state
|
|
"TO"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((name (forth-next-token! s)) (v (forth-pop s)))
|
|
(when (nil? name) (forth-error s "TO expects name"))
|
|
(dict-set! (get s "vars") (downcase name) v))))
|
|
(forth-def-prim!
|
|
state
|
|
"@"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((addr (forth-pop s)))
|
|
(forth-push s (or (get (get s "vars") addr) 0)))))
|
|
(forth-def-prim!
|
|
state
|
|
"!"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((addr (forth-pop s)) (v (forth-pop s)))
|
|
(dict-set! (get s "vars") addr v))))
|
|
(forth-def-prim!
|
|
state
|
|
"+!"
|
|
(fn
|
|
(s)
|
|
(let
|
|
((addr (forth-pop s)) (v (forth-pop s)))
|
|
(let
|
|
((cur (or (get (get s "vars") addr) 0)))
|
|
(dict-set! (get s "vars") addr (+ cur v))))))
|
|
state))
|
|
|
|
;; Track the most recently defined word name for IMMEDIATE.
|
|
(define forth-last-defined (fn (state) (get state "last-defined")))
|
|
|
|
;; forth-next-token!: during `:`, VARIABLE, CONSTANT, etc. we need to pull
|
|
;; the next token from the *input stream* (not the dict/stack). Phase-1
|
|
;; interpreter fed tokens one at a time via for-each, so a parsing word
|
|
;; can't reach ahead. We rework `forth-interpret` to keep the remaining
|
|
;; token list on the state so parsing words can consume from it.
|
|
|
|
(define
|
|
forth-next-token!
|
|
(fn
|
|
(state)
|
|
(let
|
|
((rest (get state "input")))
|
|
(if
|
|
(or (nil? rest) (= (len rest) 0))
|
|
nil
|
|
(let
|
|
((tok (first rest)))
|
|
(dict-set! state "input" (rest-of rest))
|
|
tok)))))
|
|
|
|
(define rest-of (fn (l) (rest l)))
|
|
|
|
;; Rewritten forth-interpret: drives a token list stored in state so that
|
|
;; parsing words like `:`, `VARIABLE`, `CONSTANT`, `TO` can consume the
|
|
;; following token.
|
|
(define
|
|
forth-interpret
|
|
(fn
|
|
(state src)
|
|
(dict-set! state "input" (forth-tokens src))
|
|
(forth-interpret-loop state)
|
|
state))
|
|
|
|
(define
|
|
forth-interpret-loop
|
|
(fn
|
|
(state)
|
|
(let
|
|
((tok (forth-next-token! state)))
|
|
(if
|
|
(nil? tok)
|
|
state
|
|
(begin
|
|
(forth-interpret-token state tok)
|
|
(forth-interpret-loop state))))))
|
|
|
|
;; Re-export forth-boot to include the compiler primitives too.
|
|
(define
|
|
forth-boot
|
|
(fn
|
|
()
|
|
(let
|
|
((s (forth-make-state)))
|
|
(forth-install-primitives! s)
|
|
(forth-install-compiler! s)
|
|
s)))
|