Files
rose-ash/lib/tcl/runtime.sx
giles 82da16e4bb
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 17s
tcl: Phase 2 eval engine — tcl-eval-script + set/puts/incr/append (+20 tests, 87 total)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 14:02:52 +00:00

210 lines
5.8 KiB
Plaintext

; Tcl-on-SX runtime evaluator
; State: {:frame frame :commands cmd-table :result last-result :output accumulated-output}
(define make-frame (fn (level parent) {:level level :locals {} :parent parent}))
(define
frame-lookup
(fn
(frame name)
(if
(nil? frame)
nil
(let
((val (get (get frame :locals) name)))
(if (nil? val) (frame-lookup (get frame :parent) name) val)))))
(define
frame-set-top
(fn
(frame name val)
(assoc frame :locals (assoc (get frame :locals) name val))))
(define make-tcl-interp (fn () {:result "" :output "" :frame (make-frame 0 nil) :commands {}}))
(define
tcl-register
(fn
(interp name f)
(assoc interp :commands (assoc (get interp :commands) name f))))
(define
tcl-var-get
(fn
(interp name)
(let
((val (frame-lookup (get interp :frame) name)))
(if
(nil? val)
(error (str "can't read \"" name "\": no such variable"))
val))))
(define
tcl-var-set
(fn
(interp name val)
(assoc interp :frame (frame-set-top (get interp :frame) name val))))
(define
tcl-eval-parts
(fn
(parts interp)
(reduce
(fn
(acc part)
(let
((type (get part :type)) (cur-interp (get acc :interp)))
(cond
((equal? type "text") {:values (append (get acc :values) (list (get part :value))) :interp cur-interp})
((equal? type "var") {:values (append (get acc :values) (list (tcl-var-get cur-interp (get part :name)))) :interp cur-interp})
((equal? type "var-arr")
(let
((key-acc (tcl-eval-parts (get part :key) cur-interp)))
(let
((key (join "" (get key-acc :values)))
(next-interp (get key-acc :interp)))
{:values (append (get acc :values) (list (tcl-var-get next-interp (str (get part :name) "(" key ")")))) :interp next-interp})))
((equal? type "cmd")
(let
((new-interp (tcl-eval-string cur-interp (get part :src))))
{:values (append (get acc :values) (list (get new-interp :result))) :interp new-interp}))
(else (error (str "tcl: unknown part type: " type))))))
{:values (quote ()) :interp interp}
parts)))
(define
tcl-eval-word
(fn
(word interp)
(let
((type (get word :type)))
(cond
((equal? type "braced") {:interp interp :value (get word :value)})
((equal? type "compound")
(let
((result (tcl-eval-parts (get word :parts) interp)))
{:interp (get result :interp) :value (join "" (get result :values))}))
((equal? type "expand") (tcl-eval-word (get word :word) interp))
(else (error (str "tcl: unknown word type: " type)))))))
(define
tcl-list-split
(fn (s) (filter (fn (x) (not (equal? x ""))) (split (str s) " "))))
(define
tcl-eval-words
(fn
(words interp)
(reduce
(fn
(acc w)
(let
((cur-interp (get acc :interp)))
(if
(equal? (get w :type) "expand")
(let
((wr (tcl-eval-word (get w :word) cur-interp)))
{:values (append (get acc :values) (tcl-list-split (get wr :value))) :interp (get wr :interp)})
(let ((wr (tcl-eval-word w cur-interp))) {:values (append (get acc :values) (list (get wr :value))) :interp (get wr :interp)}))))
{:values (quote ()) :interp interp}
words)))
(define
tcl-eval-cmd
(fn
(interp cmd)
(let
((wr (tcl-eval-words (get cmd :words) interp)))
(let
((words (get wr :values)) (cur-interp (get wr :interp)))
(if
(= 0 (len words))
cur-interp
(let
((cmd-name (first words)) (cmd-args (rest words)))
(let
((cmd-fn (get (get cur-interp :commands) cmd-name)))
(if
(nil? cmd-fn)
(error (str "unknown command: \"" cmd-name "\""))
(cmd-fn cur-interp cmd-args)))))))))
(define
tcl-eval-script
(fn
(interp cmds)
(if
(= 0 (len cmds))
interp
(tcl-eval-script (tcl-eval-cmd interp (first cmds)) (rest cmds)))))
(define
tcl-eval-string
(fn (interp src) (tcl-eval-script interp (tcl-parse src))))
(define
tcl-cmd-set
(fn
(interp args)
(if
(= (len args) 1)
(assoc interp :result (tcl-var-get interp (first args)))
(let
((val (nth args 1)))
(assoc (tcl-var-set interp (first args) val) :result val)))))
(define
tcl-cmd-puts
(fn
(interp args)
(let
((text (last args))
(no-nl
(and
(> (len args) 1)
(equal? (first args) "-nonewline"))))
(let
((line (if no-nl text (str text "\n"))))
(assoc interp :output (str (get interp :output) line))))))
(define
tcl-cmd-incr
(fn
(interp args)
(let
((name (first args))
(delta
(if
(> (len args) 1)
(parse-int (nth args 1))
1)))
(let
((new-val (str (+ (parse-int (tcl-var-get interp name)) delta))))
(assoc (tcl-var-set interp name new-val) :result new-val)))))
(define
tcl-cmd-append
(fn
(interp args)
(let
((name (first args)) (suffix (join "" (rest args))))
(let
((cur (let ((v (frame-lookup (get interp :frame) name))) (if (nil? v) "" v))))
(let
((new-val (str cur suffix)))
(assoc (tcl-var-set interp name new-val) :result new-val))))))
(define
make-default-tcl-interp
(fn
()
(let
((i (make-tcl-interp)))
(let
((i (tcl-register i "set" tcl-cmd-set)))
(let
((i (tcl-register i "puts" tcl-cmd-puts)))
(let
((i (tcl-register i "incr" tcl-cmd-incr)))
(tcl-register i "append" tcl-cmd-append)))))))