; 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)))))))