The basic loop construct is rather easy to write and looks like this. I used the var A as the counter.
(defn looping "Loops over a varname with command list. Takes care of decrementing the counter." [state varname command-list] (let [all-commands (conj command-list {:decr varname})] (loop [new-state state] (if (zero? (new-state varname)) new-state (recur (execute new-state all-commands)))))) (deftest test-looping (is (= (looping {"A" 3} "A" []) {"A" 0})) (is (= (looping {"A" 3 "B" 2} "B" [{:incr "A"}]) {"A" 5 "B" 0})))
The problem is that doesn’t have a body. All it does it increment its counter until it is zero. So let’s write a function which takes a vector of parsed commands and returns the final state.
(defn execute-command "Executes a parsed command" [state parsed-command] (let [[command varname] (first parsed-command)] (cond (= command :incr) (do-var state varname command) (= command :decr) (do-var state varname command) :else nil))) (deftest test-execute (is (= (execute-command {} {:incr "A"}) {"A" 1})) (is (= (execute-command {"A" 3} {:decr "A"}) {"A" 2})) (is (= (execute-command {"A" 3} {:decr "B"}) {"A" 3 "B" 0}))) (defn execute "Executes a list of parsed commands given a state" [state parsed-source] (loop [rest-source parsed-source new-state state] (if (seq rest-source) (if (contains? (first rest-source) :while) (let [inner-loop (take-while #(not (contains? % :end)) (rest rest-source))] (recur (rest (drop-while #(not (contains? % :end)) rest-source)) (looping new-state ((first rest-source) :while) inner-loop))) (recur (rest rest-source) (execute-command new-state (first rest-source)))) new-state))) (with-test (def source "incr A; incr B; decr A; incr B;") (def parsed-source (map parse-statement (get-statements source))) (is (= (execute {} parsed-source) {"A" 0 "B" 2})) (is (= (execute {"A" 2 "B" 5} parsed-source) {"A" 2 "B" 7})))
Okay, that works. The idea is again pretty basic. You can see that I use a looping
function. It does what the name says it does. It loops over a var name. Interesting enough it calls the execute
function:
(defn looping "Loops over a varname with command list. Takes care of decrementing the counter." [state varname command-list] (let [all-commands (conj command-list {:decr varname})] (loop [new-state state] (if (zero? (new-state varname)) new-state (recur (execute new-state all-commands))))))
So they basically bootstrap each other. If you followed me alone you noticed that execute
is also the interpreter. We write some last tests and we’re done:
(deftest test-interpreter (def source "incr i; incr i; while i /= 0 do; incr j; end; incr j;") (def parsed-source (map parse-statement (get-statements source))) (is (= (execute {} parsed-source) {"j" 3 "i" 0})) (is (= (execute {"i" 4 "k" 6} parsed-source) {"i" 0 "j" 7 "k" 6})))
It works. This took quite some time, probably the longest so far. But I’m quite proud that I wrote my first interpreter :D