Skip to content

Instantly share code, notes, and snippets.

@mahaidong
Forked from mnicky/interp1.lisp
Created October 31, 2019 11:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mahaidong/8db08423ed67f76e9de419bf84a173a5 to your computer and use it in GitHub Desktop.
Save mahaidong/8db08423ed67f76e9de419bf84a173a5 to your computer and use it in GitHub Desktop.
simple Scheme interpreter, including macros, by Peter Norvig
;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