core
(ns assembler.core
(:require [clojure.string :as str]
[assembler.symbol-table :as sym]
[assembler.parser :as psr]
[assembler.machine-codes :as mc]))
(defn load-source
[filename]
(str/split-lines (slurp filename)))
(defn load-asm
"loads the .asm file returning a list of lines"
[filename]
(-> filename
load-source
psr/sanitise-source))
(defn address-binary
[line st]
(mc/int-to-binary (Integer/parseInt (psr/address-value line st)) 16))
(defn comp-binary
[line st]
(str "111"
(mc/comp-code (psr/get-comp-string line))
(mc/dest-code (psr/get-dest-string line))
(mc/jump-code (psr/get-jump-string line))))
(defn into-binary
[src st]
(reduce (fn [res line]
(let [c (psr/command-type line)]
(cond (= c :a-command) (conj res (address-binary line st))
(= c :c-command) (conj res (comp-binary line st))
(= c :l-command) res)))
[] src))
(defn hack-filename
[filename]
(str (first (str/split filename #"\.asm")) "-isg.hack"))
(defn compile-to-hack
[src]
(into-binary src
(sym/create-symbol-table src)))
(defn save-hack
[hackcode filename]
(spit (hack-filename filename)
(str (str/join "\n"
hackcode)
"\n")))
(defn main
[filename]
(-> filename
load-asm
compile-to-hack
(save-hack filename)))
symbol-table
(ns assembler.symbol-table
(:require [assembler.parser :as psr]))
(def *predefined-symbols*
{"SP" 0 "LCL" 1 "ARG" 2 "THIS" 3 "THAT" 4
"R0" 0 "R1" 1 "R2" 2 "R3" 3 "R4" 4
"R5" 5 "R6" 6 "R7" 7 "R8" 8 "R9" 9
"R10" 10 "R11" 11 "R12" 12 "R13" 13 "R14" 14
"R15" 15 "SCREEN" 16384 "KBD" 24576})
(defn predefined-symbols
[]
(reduce (fn [r [k v]] (conj r [k (str v)]))
{}
*predefined-symbols*))
(defn parse-labels
"returns the labels along with their ROM addresses"
[lines]
(loop [res {} count 0 src lines]
(if (empty? src)
res
(if-let [label (psr/label-matcher (first src))]
(recur (conj res {label (str count)}) count (rest src))
(recur res (inc count) (rest src))))))
(defn get-new-variable
""
[line st]
(if-let [match (psr/address-variable-matcher line)]
(when (not (st match))
match)))
(defn parse-variables
"finish creating the symbol table by adding the valuess of any variables"
[lines st]
(loop [res st count 16 src lines]
(if (empty? src)
res
(if-let [variable (get-new-variable (first src) res)]
(recur (conj res {variable (str count)}) (inc count) (rest src))
(recur res count (rest src))))))
(defn create-symbol-table
[lines]
(parse-variables lines
(conj (predefined-symbols)
(parse-labels lines))))
parser
(ns assembler.parser
(:require [clojure.string :as str]))
(defn remove-comments
[lines]
(map #(str/replace % #"//.*$" "") lines))
(defn trim-lines
[lines]
(map str/trim lines))
(defn blank?
[line]
(re-seq #"^$" line))
(defn remove-blank-lines
[lines]
(filter (complement blank?)
lines))
(defn sanitise-source
[lines]
(-> lines
remove-comments
trim-lines
remove-blank-lines))
(defn matches
[re line]
(second (first (re-seq re line))))
(defn address-variable-matcher
[line]
(matches #"@([A-Za-z].*)" line))
(defn address-matcher
[line]
(matches #"^@(.*)" line))
(defn comp-matcher
[line]
(matches #".*[=;].*" line))
(defn label-matcher
[line]
(matches #"\((.*)\)" line))
(defn command-type
"what sort of command is on the line?"
[line]
(cond
(address-matcher line) :a-command
(comp-matcher line) :c-command
(label-matcher line) :l-command
:else :unknown-command))
(defn address-value
[line st]
(if-let [v (address-variable-matcher line)]
(st v)
(address-matcher line)))
(defn get-dest-string
[cline]
(let [p (str/split cline #"\=")]
(if (> (count p) 1)
(first p)
"")))
(defn get-comp-string
[cline]
(let [after-eq (second (str/split cline #"\="))
comp-plus-jump (if after-eq after-eq cline) ]
(when-let [comp-part (first (str/split comp-plus-jump #";"))]
comp-part)))
(defn get-comp-string-old
[cline]
(let [after-eq (second (str/split cline #"\="))
comp-plus-jump (if after-eq after-eq cline) ]
(when-let [comp-part (first (str/split comp-plus-jump #";"))]
comp-part)))
(defn get-jump-string
[cline]
(let [p (str/split cline #";")]
(if (= (count p) 2)
(second p)
"")))
machine-codes
(ns assembler.machine-codes
(:require [clojure.string :as str])
(:require [assembler.parser :as psr]))
(def *dest-code-table* {"M" "001"
"D" "010"
"MD" "011"
"A" "100"
"AM" "101"
"AD" "110"
"MAD" "111"})
(def *jump-code-table* {"JGT" "001"
"JEQ" "010"
"JGE" "011"
"JLT" "100"
"JNE" "101"
"JLE" "110"
"JMP" "111"})
(def *comp-code-table* {"0" "0101010"
"1" "0111111"
"-1" "0111010"
"D" "0001100"
"A" "0110000"
"!D" "0001101"
"!A" "0110001"
"-D" "0001111"
"-A" "0110011"
"D+1" "0011111"
"A+1" "0110111"
"D-1" "0001110"
"A-1" "0110010"
"D+A" "0000010"
"D-A" "0010011"
"A-D" "0000111"
"D&A" "0000000"
"D|A" "0010101"
"M" "1110000"
"!M" "1110001"
"-M" "1110011"
"M+1" "1110111"
"M-1" "1110010"
"D+M" "1000010"
"D-M" "1010011"
"M-D" "1000111"
"D&M" "1000000"
"D|M" "1010101"})
(defn- int-to-binary-
[int]
(loop [res "" val int]
(if (= 0 val)
res
(recur (str (bit-and 1 val) res)
(bit-shift-right val 1)))))
(defn int-to-binary
[int bits-required]
(let [bin (int-to-binary- int)]
(str (str/join "" (repeat (- bits-required (count bin)) "0"))
bin)))
(defn code-lookup
[code table default]
(if-let [res (table code)]
res
default))
(defn dest-code
[dest-string]
(code-lookup dest-string *dest-code-table* "000"))
(defn jump-code
[jump-string]
(code-lookup jump-string *jump-code-table* "000"))
(defn comp-code
[code-string]
(code-lookup code-string *comp-code-table* "0000000"))