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