;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- ;;; Code from Paradigms of Artificial Intelligence Programming ;;; Copyright (c) 1991 Peter Norvig ;;;; File compile2.lisp: Scheme compiler with tail recursion ;;;; and some optimizations and primitive instructions. (requires "interp1") ; Uses the Scheme macro facility (defun comp (x env val? more?) "Compile the expression x into a list of instructions" (cond ((member x '(t nil)) (comp-const x val? more?)) ((symbolp x) (comp-var x env val? more?)) ((atom x) (comp-const x val? more?)) ((scheme-macro (first x)) (comp (scheme-macro-expand x) env val? more?)) ((case (first x) (QUOTE (arg-count x 1) (comp-const (second x) val? more?)) (BEGIN (comp-begin (rest x) env val? more?)) (SET! (arg-count x 2) (assert (symbolp (second x)) (x) "Only symbols can be set!, not ~a in ~a" (second x) x) (seq (comp (third x) env t t) (gen-set (second x) env) (if (not val?) (gen 'POP)) (unless more? (gen 'RETURN)))) (IF (arg-count x 2 3) (comp-if (second x) (third x) (fourth x) env val? more?)) (LAMBDA (when val? (let ((f (comp-lambda (second x) (rest2 x) env))) (seq (gen 'FN f) (unless more? (gen 'RETURN)))))) (t (comp-funcall (first x) (rest x) env val? more?)))))) ;;; ============================== (defun arg-count (form min &optional (max min)) "Report an error if form has wrong number of args." (let ((n-args (length (rest form)))) (assert (<= min n-args max) (form) "Wrong number of arguments for ~a in ~a: ~d supplied, ~d~@[ to ~d~] expected" (first form) form n-args min (if (/= min max) max)))) ;;; ============================== (defun comp-begin (exps env val? more?) "Compile a sequence of expressions, returning the last one as the value." (cond ((null exps) (comp-const nil val? more?)) ((length=1 exps) (comp (first exps) env val? more?)) (t (seq (comp (first exps) env nil t) (comp-begin (rest exps) env val? more?))))) (defun comp-list (exps env) "Compile a list, leaving them all on the stack." (if (null exps) nil (seq (comp (first exps) env t t) (comp-list (rest exps) env)))) ;;; ============================== (defun comp-const (x val? more?) "Compile a constant expression." (if val? (seq (if (member x '(t nil -1 0 1 2)) (gen x) (gen 'CONST x)) (unless more? (gen 'RETURN))))) (defun comp-var (x env val? more?) "Compile a variable reference." (if val? (seq (gen-var x env) (unless more? (gen 'RETURN))))) ;;; ============================== (defun comp-if (pred then else env val? more?) "Compile a conditional (IF) expression." (cond ((null pred) ; (if nil x y) ==> y (comp else env val? more?)) ((constantp pred) ; (if t x y) ==> x (comp then env val? more?)) ((and (listp pred) ; (if (not p) x y) ==> (if p y x) (length=1 (rest pred)) (primitive-p (first pred) env 1) (eq (prim-opcode (primitive-p (first pred) env 1)) 'not)) (comp-if (second pred) else then env val? more?)) (t (let ((pcode (comp pred env t t)) (tcode (comp then env val? more?)) (ecode (comp else env val? more?))) (cond ((equal tcode ecode) ; (if p x x) ==> (begin p x) (seq (comp pred env nil t) ecode)) ((null tcode) ; (if p nil y) ==> p (TJUMP L2) y L2: (let ((L2 (gen-label))) (seq pcode (gen 'TJUMP L2) ecode (list L2) (unless more? (gen 'RETURN))))) ((null ecode) ; (if p x) ==> p (FJUMP L1) x L1: (let ((L1 (gen-label))) (seq pcode (gen 'FJUMP L1) tcode (list L1) (unless more? (gen 'RETURN))))) (t ; (if p x y) ==> p (FJUMP L1) x L1: y ; or p (FJUMP L1) x (JUMP L2) L1: y L2: (let ((L1 (gen-label)) (L2 (if more? (gen-label)))) (seq pcode (gen 'FJUMP L1) tcode (if more? (gen 'JUMP L2)) (list L1) ecode (if more? (list L2)))))))))) ;;; ============================== (defun comp-funcall (f args env val? more?) "Compile an application of a function to arguments." (let ((prim (primitive-p f env (length args)))) (cond (prim ; function compilable to a primitive instruction (if (and (not val?) (not (prim-side-effects prim))) ;; Side-effect free primitive when value unused (comp-begin args env nil more?) ;; Primitive with value or call needed (seq (comp-list args env) (gen (prim-opcode prim)) (unless val? (gen 'POP)) (unless more? (gen 'RETURN))))) ((and (starts-with f 'lambda) (null (second f))) ;; ((lambda () body)) => (begin body) (assert (null args) () "Too many arguments supplied") (comp-begin (rest2 f) env val? more?)) (more? ; Need to save the continuation point (let ((k (gen-label 'k))) (seq (gen 'SAVE k) (comp-list args env) (comp f env t t) (gen 'CALLJ (length args)) (list k) (if (not val?) (gen 'POP))))) (t ; function call as rename plus goto (seq (comp-list args env) (comp f env t t) (gen 'CALLJ (length args))))))) ;;; ============================== (defstruct (prim (:type list)) symbol n-args opcode always side-effects) ;;; Note change from book: some of the following primitive fns have had ;;; trailing NIL fields made explicit, because some Lisp's will give ;;; an error (rather than NIL), when asked to find the prim-side-effects ;;; of a three-element list. (defparameter *primitive-fns* '((+ 2 + true nil) (- 2 - true nil) (* 2 * true nil) (/ 2 / true nil) (< 2 < nil nil) (> 2 > nil nil) (<= 2 <= nil nil) (>= 2 >= nil nil) (/= 2 /= nil nil) (= 2 = nil nil) (eq? 2 eq nil nil) (equal? 2 equal nil nil) (eqv? 2 eql nil nil) (not 1 not nil nil) (null? 1 not nil nil) (cons 2 cons true nil) (car 1 car nil nil) (cdr 1 cdr nil nil) (cadr 1 cadr nil nil) (list 1 list1 true nil) (list 2 list2 true nil) (list 3 list3 true nil) (read 0 read nil t) (write 1 write nil t) (display 1 display nil t) (newline 0 newline nil t) (compiler 1 compiler t nil) (name! 2 name! true t) (random 1 random true nil))) (defun primitive-p (f env n-args) "F is a primitive if it is in the table, and is not shadowed by something in the environment, and has the right number of args." (and (not (in-env-p f env)) (find f *primitive-fns* :test #'(lambda (f prim) (and (eq f (prim-symbol prim)) (= n-args (prim-n-args prim))))))) (defun list1 (x) (list x)) (defun list2 (x y) (list x y)) (defun list3 (x y z) (list x y z)) (defun display (x) (princ x)) (defun newline () (terpri)) ;;; ============================== (defun gen-set (var env) "Generate an instruction to set a variable to top-of-stack." (let ((p (in-env-p var env))) (if p (gen 'LSET (first p) (second p) ";" var) (if (assoc var *primitive-fns*) (error "Can't alter the constant ~a" var) (gen 'GSET var))))) ;;; ============================== (defun init-scheme-comp () "Initialize the primitive functions." (dolist (prim *primitive-fns*) (setf (get (prim-symbol prim) 'global-val) (new-fn :env nil :name (prim-symbol prim) :code (seq (gen 'PRIM (prim-symbol prim)) (gen 'RETURN)))))) ;;; ============================== (defun comp-lambda (args body env) "Compile a lambda form into a closure with compiled code." (new-fn :env env :args args :code (seq (gen-args args 0) (comp-begin body (cons (make-true-list args) env) t nil)))) (defun gen-args (args n-so-far) "Generate an instruction to load the arguments." (cond ((null args) (gen 'ARGS n-so-far)) ((symbolp args) (gen 'ARGS. n-so-far)) ((and (consp args) (symbolp (first args))) (gen-args (rest args) (+ n-so-far 1))) (t (error "Illegal argument list")))) (defun make-true-list (dotted-list) "Convert a possibly dotted list into a true, non-dotted list." (cond ((null dotted-list) nil) ((atom dotted-list) (list dotted-list)) (t (cons (first dotted-list) (make-true-list (rest dotted-list)))))) (defun new-fn (&key code env name args) "Build a new function." (assemble (make-fn :env env :name name :args args :code (optimize code)))) ;;; ============================== (defun optimize (code) code) (defun assemble (fn) fn)