Created
December 11, 2012 21:36
-
-
Save mnicky/4262355 to your computer and use it in GitHub Desktop.
simple Scheme interpreter, including macros, by Peter Norvig
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;source: http://norvig.com/paip/interp1.lisp | |
;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- | |
;;; Code from Paradigms of Artificial Intelligence Programming | |
;;; Copyright (c) 1991 Peter Norvig | |
;;; File interp1.lisp: simple Scheme interpreter, including macro.sgi | |
(defun interp (x &optional env) | |
"Interpret (evaluate) the expression x in the environment env." | |
(cond | |
((symbolp x) (get-var x env)) | |
((atom x) x) | |
((case (first x) | |
(QUOTE (second x)) | |
(BEGIN (last1 (mapcar #'(lambda (y) (interp y env)) | |
(rest x)))) | |
(SET! (set-var! (second x) (interp (third x) env) env)) | |
(IF (if (interp (second x) env) | |
(interp (third x) env) | |
(interp (fourth x) env))) | |
(LAMBDA (let ((parms (second x)) | |
(code (maybe-add 'begin (rest2 x)))) | |
#'(lambda (&rest args) | |
(interp code (extend-env parms args env))))) | |
(t ;; a procedure application | |
(apply (interp (first x) env) | |
(mapcar #'(lambda (v) (interp v env)) | |
(rest x)))))))) | |
(defun set-var! (var val env) | |
"Set a variable to a value, in the given or global environment." | |
(if (assoc var env) | |
(setf (second (assoc var env)) val) | |
(set-global-var! var val)) | |
val) | |
(defun get-var (var env) | |
"Get the value of a variable, from the given or global environment." | |
(if (assoc var env) | |
(second (assoc var env)) | |
(get-global-var var))) | |
(defun set-global-var! (var val) | |
(setf (get var 'global-val) val)) | |
(defun get-global-var (var) | |
(let* ((default "unbound") | |
(val (get var 'global-val default))) | |
(if (eq val default) | |
(error "Unbound scheme variable: ~a" var) | |
val))) | |
(defun extend-env (vars vals env) | |
"Add some variables and values to an environment." | |
(nconc (mapcar #'list vars vals) env)) | |
(defparameter *scheme-procs* | |
'(+ - * / = < > <= >= cons car cdr not append list read member | |
(null? null) (eq? eq) (equal? equal) (eqv? eql) | |
(write prin1) (display princ) (newline terpri))) | |
(defun init-scheme-interp () | |
"Initialize the scheme interpreter with some global variables." | |
;; Define Scheme procedures as CL functions: | |
(mapc #'init-scheme-proc *scheme-procs*) | |
;; Define the boolean `constants'. Unfortunately, this won't | |
;; stop someone from saying: (set! t nil) | |
(set-global-var! t t) | |
(set-global-var! nil nil)) | |
(defun init-scheme-proc (f) | |
"Define a Scheme procedure as a corresponding CL function." | |
(if (listp f) | |
(set-global-var! (first f) (symbol-function (second f))) | |
(set-global-var! f (symbol-function f)))) | |
(defun scheme (&optional x) | |
"A Scheme read-eval-print loop (using interp)" | |
;; Modified by norvig Jun 11 96 to handle optional argument | |
;; instead of always going into a loop. | |
(init-scheme-interp) | |
(if x | |
(interp x nil) | |
(loop (format t "~&==> ") | |
(print (interp (read) nil))))) | |
;;;; The following version handles macros: | |
(defun interp (x &optional env) | |
"Interpret (evaluate) the expression x in the environment env. | |
This version handles macros." | |
(cond | |
((symbolp x) (get-var x env)) | |
((atom x) x) | |
((scheme-macro (first x)) ;*** | |
(interp (scheme-macro-expand x) env)) ;*** | |
((case (first x) | |
(QUOTE (second x)) | |
(BEGIN (last1 (mapcar #'(lambda (y) (interp y env)) | |
(rest x)))) | |
(SET! (set-var! (second x) (interp (third x) env) env)) | |
(IF (if (interp (second x) env) | |
(interp (third x) env) | |
(interp (fourth x) env))) | |
(LAMBDA (let ((parms (second x)) | |
(code (maybe-add 'begin (rest2 x)))) | |
#'(lambda (&rest args) | |
(interp code (extend-env parms args env))))) | |
(t ;; a procedure application | |
(apply (interp (first x) env) | |
(mapcar #'(lambda (v) (interp v env)) | |
(rest x)))))))) | |
;;; ============================== | |
(defun scheme-macro (symbol) | |
(and (symbolp symbol) (get symbol 'scheme-macro))) | |
(defmacro def-scheme-macro (name parmlist &body body) | |
"Define a Scheme macro." | |
`(setf (get ',name 'scheme-macro) | |
#'(lambda ,parmlist .,body))) | |
(defun scheme-macro-expand (x) | |
"Macro-expand this Scheme expression." | |
(if (and (listp x) (scheme-macro (first x))) | |
(scheme-macro-expand | |
(apply (scheme-macro (first x)) (rest x))) | |
x)) | |
;;; ============================== | |
(def-scheme-macro let (bindings &rest body) | |
`((lambda ,(mapcar #'first bindings) . ,body) | |
.,(mapcar #'second bindings))) | |
(def-scheme-macro let* (bindings &rest body) | |
(if (null bindings) | |
`(begin .,body) | |
`(let (,(first bindings)) | |
(let* ,(rest bindings) . ,body)))) | |
(def-scheme-macro and (&rest args) | |
(cond ((null args) 'T) | |
((length=1 args) (first args)) | |
(t `(if ,(first args) | |
(and . ,(rest args)))))) | |
(def-scheme-macro or (&rest args) | |
(cond ((null args) 'nil) | |
((length=1 args) (first args)) | |
(t (let ((var (gensym))) | |
`(let ((,var ,(first args))) | |
(if ,var ,var (or . ,(rest args)))))))) | |
(def-scheme-macro cond (&rest clauses) | |
(cond ((null clauses) nil) | |
((length=1 (first clauses)) | |
`(or ,(first clauses) (cond .,(rest clauses)))) | |
((starts-with (first clauses) 'else) | |
`(begin .,(rest (first clauses)))) | |
(t `(if ,(first (first clauses)) | |
(begin .,(rest (first clauses))) | |
(cond .,(rest clauses)))))) | |
(def-scheme-macro case (key &rest clauses) | |
(let ((key-val (gensym "KEY"))) | |
`(let ((,key-val ,key)) | |
(cond ,@(mapcar | |
#'(lambda (clause) | |
(if (starts-with clause 'else) | |
clause | |
`((member ,key-val ',(first clause)) | |
.,(rest clause)))) | |
clauses))))) | |
(def-scheme-macro define (name &rest body) | |
(if (atom name) | |
`(begin (set! ,name . ,body) ',name) | |
`(define ,(first name) | |
(lambda ,(rest name) . ,body)))) | |
(def-scheme-macro delay (computation) | |
`(lambda () ,computation)) | |
(def-scheme-macro letrec (bindings &rest body) | |
`(let ,(mapcar #'(lambda (v) (list (first v) nil)) bindings) | |
,@(mapcar #'(lambda (v) `(set! .,v)) bindings) | |
.,body)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment