Assembler, Part 3
April 23, 2014
The first phase of the assembler does nearly what we want. The only problem is that it discards empty lines or lines that consist of only a comment; of course, we need those lines for our listing. So we make two passes: the first pass is the same as the first pass of the assembler, and the second pass reads the assembly program a second time, creating output as it goes:
(define (listing file-name)
(let* ((lines (asm1 file-name))
(mem (asm2 lines))
(widths (widths lines)))
(define (wid n)
(string-append "%-" (number->string (vector-ref widths n)) "s "))
(with-input-from-file file-name
(lambda ()
(let loop ((k 0) (line (read-line)) (lines lines))
(when (not (eof-object? line))
(if (or (string=? line "")
(char=? (string-ref line 0) #\#))
(begin (printf "%s\n" line) (loop k (read-line) lines))
(begin (printf "%03d: " k)
(printf "%05d " (vector-ref mem k))
(printf (wid 0) (vector-ref (car lines) 1))
(printf (wid 1) (vector-ref (car lines) 2))
(printf (wid 2) (vector-ref (car lines) 3))
(printf (wid 3) (vector-ref (car lines) 4))
(printf "\n")
(loop (+ k 1) (read-line) (cdr lines))))))))))
We use an auxiliary function to compute the column widths:
(define (widths lines)
(apply vector
(map (lambda (xs) (apply max xs))
(apply map list
(map (lambda (line)
(map string-length (cdr (vector->list line))))
lines)))))
To create the sample output on the first page of the exercise, say (listing "program.asm").
We used the printf function from a recent exercise. You can run the program at http://programmingpraxis.codepad.org/Ep7u8l1h.
(defn listing [C code] (let [mem (:M (load-asm C code))] (println (second (reduce (fn [[counter text] e] (let [pref (if (whitespace? e) "" (format "%03d: %s\t" counter (aget (:M C) counter))) new-counter (if (whitespace? e) counter (inc counter))] [new-counter (str text "\n" pref e)])) [0 ""] (clojure.string/split code #"\n"))))))