;; -*- islisp -*- ;;; BASIC interpreter ;; TODO: rewrite some cond/cases as defmethods. Look for calls to "instancep". ;;; Abstract syntax ;; If these were only C enums, without any payload, I'd just use symbols and (error) in the t case. ;; But classes seem better for the associated data, in discriminated unions. (defclass () () (:abstractp t)) (defclass () ((int :accessor int))) (defclass () ((var :accessor var))) (defclass () ((str :accessor str))) (defclass () ((op :accessor op) (expr :accessor expr))) (defclass () ((exp1 :accessor exp1) (op :accessor op) (exp2 :accessor exp2))) (defclass () () (:abstractp t)) (defclass () ((rem :initarg r :accessor rem))) (defclass () ((goto :initarg g :accessor goto))) (defclass () ((expr :accessor expr))) (defclass () ((var :initarg v :accessor var))) (defclass () ((expr :initarg e :accessor expr) (num :initarg n :accessor num))) (defclass () ((var :initarg v :accessor var) (expr :initarg e :accessor expr))) (defclass () ((num :accessor num) (cmd :accessor cmd))) (defclass () () (:abstractp t)) (defclass () ((line :accessor line))) (defclass () ()) (defclass () ()) (defclass () ()) (defun priority-uop (unr-op) (case unr-op ((not) 1) ((uminus) 7))) (defun priority-binop (bin-op) (cond ((member bin-op '(mult div)) 6) ((member bin-op '(plus minus)) 5) ((eql bin-op 'mod) 4) ((member bin-op '(equal less lesseq great greateq diff)) 3) ((member bin-op '(and or)) 2))) ;;; Program pretty printing (defun pp-binop (bin-op) (case bin-op ((plus) "+") ((mult) "*") ((mod) "%") ((minus) "-") ((div) "/") ((equal) " = ") ((less) " < ") ((lesseq) " <= ") ((great) " > ") ((greateq) " >= ") ((diff) " <> ") ((and) " & ") ((or) " | "))) (defun pp-unrop (unr-op) (case unr-op ((uminus) "-") ((not) "!"))) (defun parenthesis (x) (string-append "(" x ")")) (defgeneric ppl (pr expr)) (defmethod ppl (pr (expr )) (convert (int expr) )) (defmethod ppl (pr (expr )) (var expr)) (defmethod ppl (pr (expr )) (string-append "\"" (str expr) "\"")) (defmethod ppl (pr (expr )) (let* ((op (op expr)) (res-op (pp-unrop op)) (pr2 (priority-uop op)) (res-e (ppl pr2 (expr expr)))) (if (= pr 0) (string-append res-op res-e) (parenthesis (string-append res-op res-e))))) (defmethod ppl (pr (expr )) (let* ((op (op expr)) (pr2 (priority-binop op)) (res (string-append (ppl pr2 (expr1 expr)) (pp-binop op) (ppr pr2 (expr2 expr))))) (if (>= pr2 pr) res (parenthesis res)))) (defgeneric ppr (pr expr)) (defmethod ppr (pr (expr )) (let* ((op (op expr)) (pr2 (priority-binop op)) (res (string-append (ppl pr2 (expr1 expr)) (pp-binop op) (ppr pr2 (expr2 expr))))) (if (> pr2 pr) res (parenthesis res)))) (defmethod ppr (pr (expr )) (ppl pr expr)) (defmethod ppr (pr (expr )) (ppl pr expr)) (defmethod ppr (pr (expr )) (ppl pr expr)) (defmethod ppr (pr (expr )) (ppl pr expr)) (defun pp-expression (expr) (ppl 0 expr)) (defgeneric pp-command (cmd)) (defmethod pp-command ((cmd )) (string-append "REM " (str cmd))) (defmethod pp-command ((cmd )) (string-append "GOTO " (convert (num cmd) ))) (defmethod pp-command ((cmd )) (string-append "PRINT " (pp-expression (expr cmd)))) (defmethod pp-command ((cmd )) (string-append "INPUT " (var cmd))) (defmethod pp-command ((cmd )) (string-append "IF " (pp-expression (expr cmd)) " THEN " (convert (num cmd) ))) (defmethod pp-command ((cmd )) (string-append "LET " (var cmd) " = " (pp-expression (expr cmd)))) (defun pp-line (l) (string-append (convert (num l) ) " " (pp-command (cmd l)))) ;;; Lexing (defclass () () (:abstractp t)) (defclass () ((int :initarg i :reader int))) (defclass () ((ident :initarg i :reader ident))) (defclass () ((lsymbol :initarg s :reader lsymbol))) (defclass () ((lstring :initarg s :reader lstring))) (defclass () ()) (defclass () ((string :initarg s :accessor string) (current :initform 0 :accessor current) (size :accessor size))) (defmethod initialize-object :after ((self ) initargs) (setf (size self) (length (string self)))) (defgeneric forward (cl &rest args)) (defmethod forward ((cl ) &rest args) (let ((incr (if (null args) 1 (car args)))) (setf (current cl) (+ (current cl) incr)))) (defgeneric extract (pred cl)) (defmethod extract (pred (cl )) (flet ((ext (n) (if (and (< n (size cl)) (apply #'pred (elt st n))) (ext (+ n 1)) n))) (let* ((st (string cl)) (pos (current cl)) (res (ext pos))) (setf (current cl) res) (subseq (string cl) pos (- res pos))))) ;; Some functions from C's ctype.h: (defun isdigit (c) (and (char>= x #\0) (char<= x #\9))) (defun isalpha (c) (or (and (char>= c #\a) (char<= c #\z)) (and (char>= c #\A) (char<= c #\Z)))) (defun isalnum (c) (or (isalpha c) (isdigit c))) (defgeneric extract-int (cl)) (defmethod extract-int ((cl )) (convert (extract #'isdigit cl) )) (defgeneric extract-ident (cl)) (defmethod extract-ident ((cl )) (flet ((is-alpha-num (x) (or (isalnum x) (char= x #\_)))) (extract #'is-alpha-num))) (defgeneric lexer (cl)) (defmethod lexer ((cl )) (flet ((lexer-char (c) (cond ((or (char= c #\space) (char= c #\tab)) (forward cl) (lexer cl)) ((isalpha c) (create (class ) 'i (extract-ident cl))) ((isdigit c) (create (class ) 'i (extract-int cl))) ((char= c #\") (forward cl) (let ((res (create (class ) 's (extract (lambda (c) (char/= c #\")) cl)))) (forward cl) res)) ((member c '(#\+ #\- #\* #\/ #\% #\& #\| #\! #\= #\( #\))) (forward cl) (create (class ) 's (create-string 1 c))) ((or (char= c #\<) (char= c #\>)) (forward cl) (if (>= (current cl) (size cl)) (create (class ) 's (create-string 1 c)) (let ((cs (elt (string cl) (current cl)))) (cond ((and (char= c #\<) (char= cs #\=)) (forward cl) (create (class ) 's "<=")) ((and (char= c #\>) (char= cs #\=)) (forward cl) (create (class ) 's ">=")) ((and (char= c #\<) (char= cs #\>)) (forward cl) (create (class ) 's "<>")) (t (create (class ) 's (create-string 1 c))))))) (t (error "Lexer error"))))) (if (>= (current cl) (size cl)) (create (class )) (lexer-char (elt (string cl) (current cl)))))) ;;; Parsing (defclass () () (:abstractp t)) (defclass () ((expr :accessor expr))) (defclass () ((bin-op :initarg o :accessor bin-op))) (defclass () ((unr-op :initarg o :accessor unr-op))) (defclass () ()) (defun unr-symb (s) (case-using #'string= s (("!") 'not) (("-") 'uminus) (t (error "Parse error")))) (defun bin-symb (s) (case-using #'string= s (("+") 'plus) (("-") 'minus) (("*") 'mult) (("/") 'div) (("%") 'mod) (("=") 'equal) (("<") 'less) (("<=") 'lesseq) ((">") 'great) ((">=") 'greateq) (("<>") 'diff) (("&") 'and) (("|") 'or) (t nil))) (defun tsymb (s) (let ((maybe-bin (bin-symb s))) (if (null maybe-bin) (create (class ) 'o (unr-symb s)) (create (class ) 'o maybe-bin)))) (defun reduce (pr) ) (defun stack-or-reduce (lex stack) ) (defun reduce-all (st) (cond ((null st) (error "Parse error")) ((and (= (length st) 1) (instancep (car st) (class ))) (expr (car st))) (t (reduce-all (reduce 0 st))))) (defun parse-exp (stop cl) (let ((p 0)) (flet ((parse-one (stack) (setq p (current cl)) (let ((l (lexer cl))) (if (not (stop l)) (parse-one (stack-or-reduce l stack)) (progn (setf (current cl) p) (reduce-all stack)))))) (parse-one '())))) (defun parse-cmd (cl) (let ((tok (lexer cl))) (if (instancep tok (class )) (case-using #'string= (ident tok) (("REM") (create (class ) 'r (extract (lambda (x) t) cl))) (("GOTO") (create (class ) 'g (let ((tok (lexer cl))) (if (instancep tok (class )) (int tok) (error "Parse error"))))) (("INPUT") (create (class ) 'v (let ((tok (lexer cl))) (if (instancep tok (class )) (ident tok) (error "Parse error"))))) (("PRINT") (create (class ) 'e (parse-exp (lambda (x) (instancep x (class ))) cl))) (("LET") (let ((l2 (lexer cl)) (l3 (lexer cl))) (if (and (instancep l2 (class )) (instancep l3 (class )) (string= (lsymbol l3) "=")) (create (class ) 'v (ident l2) 'e (parse-exp (lambda (x) (instancep x )) cl)) (error "Parse error")))) (("IF") (let ((test (parse-exp (lambda (x) (and (instancep x ) (string= (ident x) "THEN"))) cl))) (progn (lexer cl) (let ((tok (lexer cl))) (if (instancep tok (class )) (create (class ) 'e test 'n (int tok)) (error "Parse error")))))) (t (error "Parse error"))) (error "Parse error")))) (defun parse (str) (let* ((cl (init-lex str)) (tok (lexer cl))) (cond ((instancep tok (class )) (create (class ) 'n n 'c (parse-cmd cl))) ((instancep tok (class )) (case-using #'string= (ident tok) (("LIST") (create (class ))) (("RUN") (create (class ))) (("END") (create (class ))) (t (error "Parse error")))) (t (error "Parse error"))))) ;;; Evaluation (defclass () () (:abstractp t)) (defclass () ((int :accessor int))) (defclass () ((str :accessor str))) (defclass () ((bool :accessor bool))) (defclass () ((env :accessor env))) (defclass () ((line :reader line) (xprog :reader xprog) (xenv :reader xenv))) (defun runerr (n) (throw 'run-error n)) ;;;; Assembly (defun lookup-index (tprog num-line) (block result-lookup-index (for ((i 0 (+ i 1))) ((>= i (length tprog))) (let ((num-i (num (elt tprog i)))) (if (= num-i num-line) (return-from result-lookup-index i) (if (> num-i num-line) (return-from result-lookup-index -1))))) -1)) (defun assemble (prog) (let ((tprog (apply #'vector prog))) (for ((i 0 (+ i 1))) ((>= i (length tprog))) ()))) ;;;; Expression evaluation (defgeneric eval-exp (n envt expr)) (defmethod eval-exp (n envt (expr )) (create (class ) 'i (int expr))) (defmethod eval-exp (n envt (expr )) (case (op expr) ((uminus) (let ((result (eval-exp (exp expr)))) (if (instancep result (class )) (progn (setf (exp result) (- (exp result))) result) (runerr n)))) ((not) (let ((result (eval-exp (exp expr)))) (if (instancep result (class )) (progn (setf (exp result) (not (exp result))) result) (runerr n)))))) ;;;; Command evaluation ;;;; Program evaluation (defun run (state) ) ;;; Finishing touches ;; Not sure yet if it's a good idea or not, ;; but I'm trying to keep the number of top-level functions the same as in OCaml. (defun one-command (st) (format (standard-output) "> ") (with-handler #'error-handler (let ((l parse (read-line))) (case (car l) ((line) (insert (cadr c))) ((p-end) (throw 'end nil)))))) ; throw and conditions are orthogonal (defclass () ((prog :accessor prog) (env :accessor env))) (defmethod initialize-object :after ((self ) initargs) (setf (prog self) nil) (setf (env self) nil)) (defun main () (catch 'end (lambda () (format (standard-output) "BASIC version 0.1~%~%") (for ((st (create (class )))) (()) (format (standard-output) "> ") (catch 'error (one-command st))))) (format (standard-output) "See you later...~%"))