Skip to content

Commit 27563b3

Browse files
committed
Add testing infrastructure and some rudimentary tests
- Add a compiler mode to dump all registers at the end, rather than loop forever (by providing a custom epilog, borrowed from http://www.fysnet.net/yourhelp.htm). - Extend the assembler to be able to assemble that epilog (some opcodes were missing). - Add a compiler driver that: - compiles and assembles the program to DOS .COM as usual; - copies the binary and the bundled CAPTURE.COM (from http://www.pc-tools.net/dos/dosutils/) to the same directory; - runs them with DOSBox via a generated batch file; - parses the resulting textual screen dump to get the CPU register values. - Add some tests that compile an expression and check AX for the expected result. - Update README.md to cater for licenses of bundled assembly code that is not mine. - Move some code around.
1 parent 11ff46e commit 27563b3

File tree

7 files changed

+173
-37
lines changed

7 files changed

+173
-37
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ In the REPL:
2222

2323
# License
2424

25+
Unless otherwise noted, code in this repository is copyright by
26+
Daniel Janus and released under the MIT license:
27+
28+
```
2529
Copyright 2012–2021 Daniel Janus
2630
2731
Permission is hereby granted, free of charge, to any person obtaining
@@ -42,6 +46,26 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
4246
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
4347
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
4448
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
49+
```
50+
51+
The file `resources/capture.com` comes from the DOS Utilities Collection as obtained
52+
from http://www.pc-tools.net/dos/dosutils/, is copyright by Jem Berkes, and carries the
53+
following copyright notice:
54+
55+
```
56+
You may use and share the programs in this freeware package. However,
57+
remember that YOU MAY NOT MODIFY THESE PROGRAMS IN ANY WAY. They are
58+
NOT PUBLIC DOMAIN, but rather COPYRIGHTED FREEWARE.
59+
60+
I, the author, take NO RESPONSIBILITY for any damage that may result
61+
from using any of these programs.
62+
63+
Jem Berkes <jberkes@pc-tools.net>
64+
```
65+
66+
The assembly code in `register-dump` is a s-expression version of a snippet
67+
that comes from http://www.fysnet.net/yourhelp.htm and is copyright by
68+
Forever Young Software.
4569

4670
[1]: https://blog.danieljanus.pl/2012/05/14/lithium/
4771
[2]: http://scheme2006.cs.uchicago.edu/11-ghuloum.pdf

resources/capture.com

1.59 KB
Binary file not shown.

src/lithium/assembler.clj

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,27 @@
7070
[:xor rm16 reg16] [0x31 :r]
7171
[:push reg16] [[:r+ 0x50]]
7272
[:pop reg16] [[:r+ 0x58]]
73+
[:push :cs] [0x0e]
74+
[:pushf] [0x9c]
75+
[:pusha] [0x60]
7376
[:stosb] [0xaa]
77+
[:stosw] [0xab]
78+
[:movsw] [0xa5]
79+
[:rol rm16 imm8] [0xc1 :0 :ib]
80+
[:daa] [0x27]
7481
[:ret] [0xc3]
7582
[:cli] [0xfa]
7683
[:inc reg16] [[:r+ 0x40]]
7784
[:inc rm8] [0xfe :0]
85+
[:dec reg16] [[:r+ 0x48]]
86+
[:dec rm8] [0xfe :1]
7887
[:cmp :al imm8] [0x3c :ib]
7988
[:cmp :ax imm16] [0x3d :iw]
8089
[:cmp rm8 imm8] [0x80 :7 :ib]
8190
[:cmp rm16 imm16] [0x81 :7 :iw]
8291
[:cmp reg8 rm8] [0x3a :r]
8392
[:cmp reg16 rm16] [0x3b :r]
93+
[:adc :al imm8] [0x14 :ib]
8494
[:add rm8 reg8] [0x00 :r]
8595
[:add rm16 reg16] [0x01 :r]
8696
[:add reg8 rm8] [0x02 :r]
@@ -197,10 +207,14 @@
197207
[(+ (second byte-desc) (-> instr (extract-cc instr-template) +condition-codes+))])))
198208

199209
(defn assemble-instruction [instr]
200-
(let [[template parts] (find-template instr)]
201-
(when-not template (throw (Exception. (str "Could not assemble instruction: " (pr-str instr)))))
202-
(let [assembled-parts (map (partial parse-byte instr template) parts)]
203-
(apply concat assembled-parts))))
210+
(cond
211+
(= (first instr) 'string) (map int (second instr))
212+
(= (first instr) 'bytes) (second instr)
213+
:otherwise
214+
(let [[template parts] (find-template instr)]
215+
(when-not template (throw (Exception. (str "Could not assemble instruction: " (pr-str instr)))))
216+
(let [assembled-parts (map (partial parse-byte instr template) parts)]
217+
(apply concat assembled-parts)))))
204218

205219
;; This is the value added to absolute addresses of labels, telling
206220
;; the assembler where the code starts from. Defaults to 0x100 for
@@ -233,18 +247,7 @@
233247
cnt (count assembled)]
234248
(recur (next prog) (into code assembled) (+ pc cnt) labels)))))))
235249

236-
(defn hexdump [prog]
237-
(string/join " " (map #(format "%02x" %) (asm prog))))
238-
239-
(defn assemble-file [prog out]
240-
(let [assembled (asm (if (string? prog) (read-string (str "[" (slurp prog) "]")) prog))
241-
byte-arr (into-array Byte/TYPE (map #(byte (if (>= % 128) (- % 256) %)) assembled))]
242-
(with-open [f (java.io.FileOutputStream. out)]
243-
(.write f (into-array Byte/TYPE (map #(byte (if (>= % 128) (- % 256) %)) assembled)))
244-
nil)))
245-
246-
(defn run-program! [prog]
247-
(let [filename "/tmp/a.com"]
248-
(assemble-file prog filename)
249-
(sh "dosbox" filename)
250-
nil))
250+
(defn assemble [prog]
251+
(asm (if (string? prog)
252+
(read-string (str "[" (slurp prog) "]"))
253+
prog)))

src/lithium/compiler.clj

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,59 @@
44
[lithium.compiler.code :refer [codeseq compile-expr genkey]]
55
[lithium.compiler.primitives :as primitives]
66
[lithium.compiler.repr :as repr]
7+
[lithium.driver :as driver]
78
[lithium.utils :refer [read-all]]))
89

9-
(def prolog [['cli]
10-
['mov :bp :sp]
11-
['mov :si :heap-start]
12-
['add :si 7]
13-
['and :si 0xfff8]])
14-
(def epilog [:forever ['jmp :forever] :heap-start])
10+
(def prolog
11+
[['cli]
12+
['mov :bp :sp]
13+
['mov :si :heap-start]
14+
['add :si 7]
15+
['and :si 0xfff8]])
16+
17+
(def endless-loop-epilog
18+
[:forever
19+
['jmp :forever]])
20+
21+
(def register-dump
22+
;; taken from http://www.fysnet.net/yourhelp.htm
23+
[['pushf]
24+
['pusha]
25+
['push :cs]
26+
['mov :di :buff]
27+
['mov :si :msg1]
28+
['mov :cx 10]
29+
:loop1
30+
['movsw]
31+
['mov :al (int \=)]
32+
['stosb]
33+
['pop :ax]
34+
['mov :bx 4]
35+
:ploop
36+
['rol :ax 4]
37+
['push :ax]
38+
['and :al 0x0f]
39+
['daa]
40+
['add :al 0xf0]
41+
['adc :al 0x40]
42+
['stosb]
43+
['pop :ax]
44+
['dec :bx]
45+
['jnz :ploop]
46+
['mov :ax 0x0d0a]
47+
['stosw]
48+
['loop :loop1]
49+
['mov :al 0x24]
50+
['stosb]
51+
['mov :dx :buff]
52+
['mov :ah 9]
53+
['int 0x21]
54+
['mov :ah 0x4c]
55+
['int 0x21]
56+
:msg1
57+
['string "CSDISIBPSPBXDXCXAXFL"]
58+
:buff
59+
['bytes (repeat 100 0)]])
1560

1661
(defn primcall? [x]
1762
(or (list? x) (seq? x)))
@@ -224,15 +269,17 @@
224269
sexps))
225270

226271
(defn compile-program
227-
[sexps]
272+
[prog & [{:keys [epilog]
273+
:or {epilog endless-loop-epilog}}]]
228274
(concat prolog
229-
(:code (compile-program* sexps))
230-
epilog))
231-
232-
(defn compile-file
233-
[f]
234-
(compile-program (read-all f)))
275+
(:code (compile-program* (read-all prog)))
276+
epilog
277+
[:heap-start]))
235278

236279
(defn compile-and-run!
237-
[f]
238-
(assembler/run-program! (compile-file f)))
280+
([f] (compile-and-run! f true))
281+
([f wait?]
282+
(-> f
283+
(compile-program {:epilog (if wait? endless-loop-epilog register-dump)})
284+
assembler/assemble
285+
(driver/run-program! wait?))))

src/lithium/driver.clj

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
(ns lithium.driver
2+
(:require [clojure.java.io :as io]
3+
[clojure.java.shell :refer [sh]]
4+
[clojure.string :as string]))
5+
6+
(defn run-and-wait! [prog]
7+
(let [binary "/tmp/runwait.com"]
8+
(with-open [f (java.io.FileOutputStream. binary)]
9+
(.write f (byte-array prog))
10+
(sh "dosbox" binary)
11+
nil)))
12+
13+
(defn parse-registers [str]
14+
(->> (string/split str #"\r\n")
15+
(map string/trim)
16+
(filter seq)
17+
(map (fn [s]
18+
(let [[reg val] (string/split s #"=")]
19+
[(keyword (string/lower-case reg))
20+
(Long/parseLong val 16)])))
21+
(into {})))
22+
23+
(defn run-and-capture-registers! [prog]
24+
(let [tmpdir "/tmp"
25+
binary (str tmpdir "/runcapt.com")
26+
batch (str tmpdir "/run.bat")
27+
screen-grab (str tmpdir "/TEXT.VID")]
28+
(when (.exists (io/file screen-grab))
29+
(io/delete-file screen-grab))
30+
(with-open [f (java.io.FileOutputStream. binary)]
31+
(.write f (byte-array prog)))
32+
(with-open [in (io/input-stream (io/resource "capture.com"))]
33+
(io/copy in (io/file (str tmpdir "/capture.com"))))
34+
(spit batch "@cls\n@runcapt.com\n@capture.com t\n@exit\n")
35+
(sh "dosbox" batch :env {"SDL_VIDEODRIVER" "dummy"})
36+
(parse-registers (slurp screen-grab))))
37+
38+
(defn run-program! [prog wait?]
39+
((if wait? run-and-wait! run-and-capture-registers!) prog))
40+
41+
(defn hexdump [bytes]
42+
(string/join " " (map #(format "%02x" %) bytes)))

src/lithium/utils.clj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
(defn read-all
66
[input]
7-
(with-open [f (java.io.PushbackReader. (io/reader input))]
8-
(let [eof (Object.)]
9-
(doall (take-while #(not= % eof) (repeatedly #(read f false eof)))))))
7+
(if (or (string? input) (instance? java.io.File input))
8+
(with-open [f (java.io.PushbackReader. (io/reader input))]
9+
(let [eof (Object.)]
10+
(doall (take-while #(not= % eof) (repeatedly #(read f false eof))))))
11+
input))

test/lithium/compiler_test.clj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
(ns lithium.compiler-test
2+
(:require [lithium.compiler :as compiler]
3+
[lithium.compiler.repr :as repr]
4+
[clojure.test :refer [deftest are]]))
5+
6+
(deftest test-compiler
7+
(are [expr expected-result] (let [{:keys [ax]} (compiler/compile-and-run! [expr] false)]
8+
(= ax (repr/immediate expected-result)))
9+
'(+ 3 4) 7
10+
'(< 10 15) true
11+
'(if true 3 4) 3
12+
'(if (< 15 10) 3 4) 4
13+
'(let [x 2 y (+ x 3)]
14+
(+ x y)) 7
15+
'(let [x 2
16+
f (fn [y]
17+
(+ x y))]
18+
(f 3)) 5))

0 commit comments

Comments
 (0)