Created
December 3, 2016 06:56
-
-
Save armstnp/2ca668d72117c5f8fe5ca58ea750acba to your computer and use it in GitHub Desktop.
A micro interpreter presuming error-free input, based on a community challenge from codingame.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns Solution | |
(:require [clojure.string :as str]) | |
(:gen-class)) | |
(defn parse-int | |
"Parses an integer from the beginning of the given string | |
(in lieu of Java's Integer class)" | |
[s] | |
(with-in-str s (read))) | |
(defn environment | |
"Creates a fresh environment with the given initial register states" | |
[a-val b-val c-val d-val] | |
{:a a-val | |
:b b-val | |
:c c-val | |
:d d-val | |
:instruction 0}) | |
(def str->constant parse-int) | |
(def str->register keyword) | |
(defn gen-fetch-constant | |
"Returns a function that provides the constant value c given an environment." | |
[c] | |
(fn [env] (str->constant c))) | |
(defn gen-fetch-register | |
"Returns a function that provides the value of register r given an environment." | |
[r] | |
(fn [env] (get env (str->register r)))) | |
(defn register? | |
"Returns whether the given symbol is a valid register symbol." | |
[sym] | |
(re-matches #"[abcd]" sym)) | |
(defn gen-fetch | |
"Returns a function that provides the value of symbol sym given an environment, | |
whether the symbol is a constant value or the name of a register." | |
[sym] | |
(if (register? sym) | |
(gen-fetch-register sym) | |
(gen-fetch-constant sym))) | |
(defn gen-put | |
"Returns an environment transformer that accepts a value, and places it in the | |
register with the given symbolic name." | |
[sym] | |
(let [register (str->register sym)] | |
(fn [env val] (assoc env register val)))) | |
(defn inc-instruction | |
"Returns an environment with the instruction index incremented." | |
[env] | |
(assoc env :instruction (inc (get env :instruction)))) | |
(def set-instruction (gen-put "instruction")) | |
(defn gen-mov | |
"Returns an environment transformer with the value of src placed | |
in the register with the symbolic name dest. Increments the | |
environment's instruction index." | |
[dest src] | |
(let [fetch-src (gen-fetch src) | |
put-dest (gen-put dest)] | |
(fn [env] | |
(-> env | |
(put-dest (fetch-src env)) | |
inc-instruction)))) | |
(defn gen-binary | |
"Returns an environment transformer with the value of the given | |
operator op evaluated against the values of src-a and src-b | |
placed in the register with the symbolic name dest. Increments | |
the environment's instruction index." | |
[dest src-a src-b op] | |
(let [fetch-src-a (gen-fetch src-a) | |
fetch-src-b (gen-fetch src-b) | |
put-dest (gen-put dest)] | |
(fn [env] | |
(-> env | |
(put-dest (op (fetch-src-a env) | |
(fetch-src-b env))) | |
inc-instruction)))) | |
(defn gen-add | |
"Returns an environment transformer with the sum of the | |
values of src-a and src-b placed in the register with the | |
symbolic name dest. Increments the environment's instruction | |
index." | |
[dest src-a src-b] | |
(gen-binary dest src-a src-b +)) | |
(defn gen-sub | |
"Returns an environment transformer with the difference of the | |
values of src-a and src-b placed in the register with the | |
symbolic name dest. Increments the environment's instruction | |
index." | |
[dest src-a src-b] | |
(gen-binary dest src-a src-b -)) | |
(defn gen-jne | |
"Returns an environment transformer with the instruction index | |
set to inst if the values of src and compare are not equal, or | |
simply incremented if they are equal." | |
[inst src compare] | |
(let [instruction (str->constant inst) | |
fetch-src (gen-fetch-register src) | |
fetch-compare (gen-fetch compare)] | |
(fn [env] (if (not= (fetch-src env) (fetch-compare env)) | |
(set-instruction env instruction) | |
(inc-instruction env))))) | |
(def instruction->gen | |
{"MOV" gen-mov | |
"ADD" gen-add | |
"SUB" gen-sub | |
"JNE" gen-jne}) | |
(defn gen-instruction | |
"Takes an instruction as a sequence of strings and returns an | |
environment transformer function." | |
[[inst & args]] | |
(apply (instruction->gen inst) args)) | |
(defn complete? | |
"Returns whether the given environment has been marked as | |
complete, i.e. can no longer run instructions." | |
[env] | |
(= (:instruction env) :complete)) | |
(defn gen-instruction-list | |
"Returns a function that applies the current instruction | |
for the given environment and returns the resulting environment. | |
If the environment is complete, the returned function will simply | |
hand back the passed environment." | |
[instructions] | |
(let [total-inst (count instructions)] | |
(fn [env] | |
(let [current-inst (:instruction env)] | |
(if (and (not (complete? env)) | |
(< current-inst total-inst)) | |
((nth instructions current-inst) env) | |
(assoc env :instruction :complete)))))) | |
(defn str->instruction-list | |
"Given a sequence of instruction strings, returns an | |
environment transformer that applies the current environment | |
instruction and returns the updated environment." | |
[lines] | |
(->> lines | |
(map #(re-seq #"\S+" %)) | |
(map gen-instruction) | |
gen-instruction-list)) | |
(defn run-instructions | |
"Runs the instructions on the initial environment repeatedly until | |
the environment is complete, then returns the resulting environment." | |
[instructions init-env] | |
(first (filter complete? (iterate instructions init-env)))) | |
(defn read-n-lines | |
"Reads a sequence of n lines from the current input stream." | |
[n] | |
(loop [n n | |
lines []] | |
(if (zero? n) | |
lines | |
(recur (dec n) (conj lines (read-line)))))) | |
(defn -main [& args] | |
(let [init-a (read) | |
init-b (read) | |
init-c (read) | |
init-d (read) | |
n (read) | |
_ (read-line) | |
raw-instructions (read-n-lines n) | |
instructions (str->instruction-list raw-instructions) | |
start-env (environment init-a init-b init-c init-d) | |
result-env (run-instructions instructions start-env) | |
{:keys [a b c d]} result-env] | |
(println (str/join " " [a b c d])))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment