indy.io

Assembler

TECS

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