Recently Written · git

forthlisp-z80

Z80 target for forthlisp: Z80 Forth kernel built by an SBCL assembler, z80pack cpmsim

git clone https://github.com/equwal/forthlisp-z80

Log | Files | Refs


host.lisp (7311 bytes)

1 ;;; host.lisp -- serial driver for the Z80 Forth running in cpmsim (console on TCP).
2 ;;; Run: PORT=n sbcl --script host.lisp COMMAND
3 ;;;   load         send lisp.fs to the Forth, enter the Lisp, send prelude.scm
4 ;;;   test-forth   kernel word tests over serial (fresh kernel expected)
5 ;;;   test [FILE]  load, then run the R7RS subset (default ../r7rs-tests.scm), print pass counts
6 ;;;   eval EXPR    load, then print the value of each EXPR
7 (require :sb-bsd-sockets)
8 (defpackage :z80host (:use :cl))
9 (in-package :z80host)
10 
11 (defvar *dir* (directory-namestring *load-truename*))
12 (defvar *io*)
13 (defvar *log* nil "Echo the conversation to stdout.")
14 
15 (defun connect ()
16   (let ((port (parse-integer (or (sb-ext:posix-getenv "PORT") "4455"))))
17     (loop repeat 100
18           do (handler-case
19                  (let ((s (make-instance 'sb-bsd-sockets:inet-socket :type :stream :protocol :tcp)))
20                    (sb-bsd-sockets:socket-connect s #(127 0 0 1) port)
21                    (setf *io* (sb-bsd-sockets:socket-make-stream s :input t :output t :element-type 'character
22                                                                    :external-format :latin-1 :buffering :full))
23                    (return-from connect))
24                (error () (sleep 0.1))))
25     (error "no emulator on port ~d" port)))
26 
27 (defun read-until (done-p)
28   "Read characters until (DONE-P text) is true; return the text."
29   (let ((buf (make-array 0 :element-type 'character :adjustable t :fill-pointer 0)))
30     (loop for c = (read-char *io*)
31           do (vector-push-extend c buf) (when *log* (write-char c) (finish-output))
32           until (funcall done-p buf))
33     (coerce buf 'simple-string)))
34 
35 (defun ends-with (s suffix)
36   (let ((n (length s)) (m (length suffix)))
37     (and (>= n m) (string= s suffix :start1 (- n m)))))
38 
39 (defun send (line)
40   (write-string line *io*) (write-char #\Return *io*) (finish-output *io*))
41 
42 (defun strip-echo (text)
43   "Drop the echoed input line: everything up to the first CR LF."
44   (let ((p (search (coerce '(#\Return #\Newline) 'string) text)))
45     (if p (subseq text (+ p 2)) text)))
46 
47 (defun forth (line)
48   "Send one Forth line; return its output and whether it ended in ok."
49   (send line)
50   (let* ((out (read-until (lambda (b) (and (ends-with b (coerce '(#\Return #\Newline) 'string))
51                                           (let ((l (string-right-trim '(#\Return #\Newline) b)))
52                                             (or (ends-with l " ok") (ends-with l " ?") (search " error " l)))))))
53          (body (string-right-trim '(#\Return #\Newline) out)))
54     (values (string-trim " " (subseq body (min (length line) (length body))
55                                      (if (ends-with body " ok") (- (length body) 3) (length body))))
56             (ends-with body " ok"))))
57 
58 (defparameter +prompt+ (coerce '(#\Return #\Newline #\> #\Space) 'string))
59 
60 (defun lisp (line)
61   "Send one Lisp line; return what it printed."
62   (send line)
63   (let ((out (read-until (lambda (b) (ends-with b +prompt+)))))
64     (strip-echo (subseq out 0 (- (length out) (length +prompt+))))))
65 
66 (defun file-lines (name)
67   (with-open-file (s (merge-pathnames name *dir*))
68     (loop for l = (read-line s nil) while l collect l)))
69 
70 (defun load-target ()
71   (read-until (lambda (b) (ends-with b (format nil "Forth~c~c" #\Return #\Newline))))
72   (dolist (l (file-lines "lisp.fs"))
73     (unless (or (string= (string-trim " " l) "") (eql 0 (search "\\ " l)))
74       (multiple-value-bind (out ok) (forth l)
75         (unless ok (error "lisp.fs: ~a~%  -> ~a" l out)))))
76   (send "lisp")
77   (read-until (lambda (b) (ends-with b +prompt+)))
78   (dolist (l (file-lines "prelude.scm"))
79     (unless (or (string= (string-trim " " l) "") (char= (char l 0) #\;))
80       (let ((out (lisp l)))
81         (when (search "error:" out) (error "prelude.scm: ~a~%  -> ~a" l out))))))
82 
83 ;;; ---- tests
84 
85 (defparameter *forth-tests*
86   '(("1 2 + ." "3") ("7 2 - ." "5") ("-3 4 * ." "-12") ("100 0 7 um/mod . ." "14 2")
87     ("65535 2 um* . ." "1 -2") ("5 negate ." "-5") ("6 2/ . -6 2/ ." "3 -3")
88     ("1 2 swap . ." "1 2") ("1 2 over . . ." "1 2 1") ("1 2 3 rot . . ." "1 3 2") ("1 2 nip ." "2")
89     ("3 dup * ." "9") ("1 2 drop ." "1") ("1 2 2dup . . . ." "2 1 2 1")
90     ("1 2 = . 2 2 = ." "0 -1") ("1 2 < . 2 1 < . -1 1 < ." "-1 0 -1") ("-1 1 u< ." "0") ("0 0= . 5 0= ." "-1 0")
91     ("-5 0< . 5 0< ." "-1 0") ("12 10 and . 12 10 or . 12 10 xor ." "8 14 6") ("0 invert ." "-1")
92     ("variable v 42 v ! v @ ." "42") ("5 v +! v @ ." "47") ("65 v c! v c@ ." "65")
93     ("7 constant seven seven ." "7") (": sq dup * ; 12 sq ." "144")
94     (": f if 1 else 2 then ; 0 f . 5 f ." "2 1")
95     (": cnt 0 begin dup 5 < while 1+ repeat ; cnt ." "5")
96     (": cd 3 begin dup . 1- dup 0= until drop ; cd" "3 2 1")
97     (": fact dup 1 > if dup 1- recurse * then ; 7 fact ." "5040")
98     ("1 2 3 depth . drop drop drop" "3") ("1 >r r@ r> . ." "1 1")
99     (": hi .\" hello\" ; hi" "hello") ("char A ." "65") (": c [char] B ; c ." "66")
100     ("3 ' sq execute ." "9") ("1 2 3 2 pick . drop drop drop" "1")
101     (": t 7 throw ; ' t catch ." "7") ("1 2 ' + catch . ." "0 3")
102     ("5 0 1 0 d+ . ." "0 6") ("s\" abc\" drop s\" abc\" drop 3 s= ." "-1")
103     ("here 10 allot here swap - ." "10") ("frobnicate" "frobnicate ?")
104     ("( a comment ) 4 ." "4") ("4 . \\ the rest is ignored" "4")))
105 
106 (defun test-forth ()
107   (read-until (lambda (b) (ends-with b (format nil "Forth~c~c" #\Return #\Newline))))
108   (let ((pass 0) (fail 0))
109     (loop for (in want) in *forth-tests*
110           for got = (forth in)
111           do (if (string= got want) (incf pass)
112                  (progn (incf fail) (format t "FAIL ~a~%  want ~s~%  got  ~s~%" in want got))))
113     (format t "forth kernel: ~d passed, ~d failed, ~d total~%" pass fail (+ pass fail))
114     fail))
115 
116 (defun conformance (file)
117   (let ((pass 0) (fail 0))
118     (dolist (l (with-open-file (s file) (loop for l = (read-line s nil) while l collect l)))
119       (unless (or (string= (string-trim " " l) "") (char= (char l 0) #\;))
120         (let* ((p (search "==>" l))
121                (expr (string-trim " " (if p (subseq l 0 p) l)))
122                (got (string-trim " " (lisp expr))))
123           (cond ((search "error:" got) (incf fail) (format t "FAIL ~a~%  got ~a~%" expr got))
124                 ((null p) (incf pass))
125                 ((string= got (string-trim " " (subseq l (+ p 3))))  (incf pass))
126                 (t (incf fail) (format t "FAIL ~a~%  want ~a~%  got  ~a~%" expr (string-trim " " (subseq l (+ p 3))) got))))))
127     (format t "conformance: ~d passed, ~d failed, ~d total~%" pass fail (+ pass fail))
128     fail))
129 
130 (defun main (args)
131   (let ((cmd (first args)))
132     (connect)
133     (cond ((equal cmd "load") (load-target))
134           ((equal cmd "test-forth") (sb-ext:exit :code (if (zerop (test-forth)) 0 1)))
135           ((equal cmd "test")
136            (load-target)
137            (sb-ext:exit :code (if (zerop (conformance (or (second args) (merge-pathnames "../r7rs-tests.scm" *dir*)))) 0 1)))
138           ((equal cmd "eval") (load-target) (dolist (e (rest args)) (format t "~a~%" (lisp e))))
139           (t (format t "usage: host.lisp load | test-forth | test [FILE] | eval EXPR...~%")))))
140 
141 (handler-case (main (cdr sb-ext:*posix-argv*))
142   (sb-sys:interactive-interrupt () (sb-ext:exit :code 130))
143   (error (e) (format t "~&FAILED: ~a~%" e) (sb-ext:exit :code 1)))