Skip to content

Instantly share code, notes, and snippets.

@LuckyKoala
Last active November 2, 2018 12:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LuckyKoala/922f30326f9e9d37aa03c298860154de to your computer and use it in GitHub Desktop.
Save LuckyKoala/922f30326f9e9d37aa03c298860154de to your computer and use it in GitHub Desktop.
SICP 4.1,Lisp解释器实现(包含习题解答)
(define (eval exp env) ((analyze exp) env))
(define (analyze exp)
(cond ((self-evaluating? exp)
(analyze-self-evaluating exp))
((quoted? exp)
(analyze-quoted exp))
((variable? exp)
(analyze-variable exp))
((assignment? exp)
(analyze-assignment exp))
((definition? exp)
(analyze-definition exp))
((if? exp)
(analyze-if exp))
((lambda? exp)
(analyze-lambda exp))
((begin? exp)
(analyze-sequence
(begin-actions exp)))
((cond? exp)
(analyze (cond->if exp)))
((and? exp)
(analyze-and
(and-clauses exp)))
((or? exp)
(analyze-or
(or-clauses exp)))
((let? exp)
(analyze (let->combination exp)))
((let*? exp)
(analyze (let*->nested-lets exp)))
((letrec? exp)
(analyze (letrec->let exp)))
((do? exp)
(analyze (do->combination exp) env))
((application? exp)
(analyze-application exp))
(else
(error "Unknown expression
type: ANALYZE" exp))))
;; ================
;; Analyze part
;; ================
(define (analyze-self-evaluating exp)
(lambda (env) exp))
(define (analyze-quoted exp)
(let ((qval (text-of-quotation exp)))
(lambda (env) qval)))
(define (analyze-variable exp)
(lambda (env) (lookup-variable-value exp env)))
;; Assignments and definitions
(define (analyze-assignment exp)
(let ((avar (assignment-variable exp))
(aproc (analyze (assignment-value exp))))
(lambda (env)
(set-variable-value! avar (aproc env) env)
'assignment-ok)))
(define (analyze-definition exp)
(let ((dvar (definition-variable exp))
(dproc (analyze (definition-value exp))))
(lambda (env)
(define-variable! dvar (dproc env) env)
'definition-ok)))
;; Conditionals
(define (analyze-if exp)
(let ((pproc (analyze (if-predicate exp)))
(cproc (analyze (if-consequent exp)))
(aproc (analyze (if-alternative exp))))
(lambda (env)
(if (true? (pproc env))
(cproc env)
(aproc env)))))
(define (analyze-lambda exp)
(let ((params (lambda-parameters exp))
(body (lambda-body exp)))
(lambda (env) (make-procedure params body env))))
;; Sequences
(define (analyze-sequence exps)
(define (sequentially proc1 proc2)
(lambda (env) (proc1 env) (proc2 env)))
(define (loop first-proc rest-procs)
(if (null? rest-procs)
first-proc
(loop (sequentially first-proc
(car rest-procs))
(cdr rest-procs))))
(let ((procs (map analyze exps)))
(if (null? procs)
(error "Empty sequence: ANALYZE"))
(loop (car procs) (cdr procs))))
;; Application
(define (analyze-application exp)
(let ((fproc (analyze (operator exp)))
(aprocs (map analyze (operands exp))))
(lambda (env)
(execute-application
(fproc env)
(map (lambda (aproc) (aproc env))
aprocs)))))
(define (execute-application proc args)
(cond ((primitive-procedure? proc)
(apply-primitive-procedure proc args))
((compound-procedure? proc)
(eval (procedure-body proc)
(extend-environment
(procedure-parameters proc)
args
(procedure-environment proc))))
(else (error "Unknown procedure type:
EXECUTE-APPLICATION"
proc))))
;; And and Or
(define (analyze-and exps)
(define (iter proc1 proc2)
(lambda (env)
(cond ((not (true? (proc1 env))) 'false)
((not (true? (proc2 env))) 'false)
(else 'true))))
(define (loop first-proc rest-procs)
(if (null? rest-procs)
first-proc
(loop (iter first-proc
(car rest-procs))
(cdr rest-procs))))
(let ((procs (map analyze exps)))
(if (null? procs)
(error "Empty and: ANALYZE"))
(loop (car procs) (cdr procs))))
(define (analyze-or exps)
(define (iter proc1 proc2)
(lambda (env)
(cond ((true? (proc1 env)) 'true)
((true? (proc2 env)) 'true)
(else 'false))))
(define (loop first-proc rest-procs)
(if (null? rest-procs)
first-proc
(loop (iter first-proc
(car rest-procs))
(cdr rest-procs))))
(let ((procs (map analyze exps)))
(if (null? procs)
(error "Empty or: ANALYZE"))
(loop (car procs) (cdr procs))))
;; -----------------------------------------------
;; the specification of the syntax of our language
;; -----------------------------------------------
(define (self-evaluating? exp)
(cond ((number? exp) true)
((string? exp) true)
(else false)))
(define (variable? exp) (symbol? exp))
(define (quoted? exp)
(tagged-list? exp 'quote))
(define (text-of-quotation exp)
(cadr exp))
(define (tagged-list? exp tag)
(if (pair? exp)
(eq? (car exp) tag)
false))
(define (assignment? exp)
(tagged-list? exp 'set!))
(define (assignment-variable exp)
(cadr exp))
(define (assignment-value exp) (caddr exp))
(define (definition? exp)
(tagged-list? exp 'define))
(define (definition-variable exp)
(if (symbol? (cadr exp))
(cadr exp)
(caadr exp)))
(define (definition-value exp)
(if (symbol? (cadr exp))
(caddr exp)
(make-lambda
(cdadr exp) ; formal parameters
(cddr exp)))) ; body
(define (lambda? exp)
(tagged-list? exp 'lambda))
(define (lambda-parameters exp) (cadr exp))
(define (lambda-body exp) (cddr exp))
(define (make-lambda parameters body)
(cons 'lambda (cons parameters body)))
;;(list 'lambda parameters (sequence->exp body)))
(define (if? exp) (tagged-list? exp 'if))
(define (if-predicate exp) (cadr exp))
(define (if-consequent exp) (caddr exp))
(define (if-alternative exp)
(if (not (null? (cdddr exp)))
(cadddr exp)
'false))
(define (make-if predicate
consequent
alternative)
(list 'if
predicate
consequent
alternative))
(define (begin? exp)
(tagged-list? exp 'begin))
(define (begin-actions exp) (cdr exp))
(define (sequence->exp seq)
(cond ((null? seq) seq)
((null? (cdr seq)) (car seq))
(else (make-begin seq))))
(define (make-begin seq) (cons 'begin seq))
(define (application? exp) (pair? exp))
(define (operator exp) (car exp))
(define (operands exp) (cdr exp))
(define (no-operands? ops) (null? ops))
(define (first-operand ops) (car ops))
(define (rest-operands ops) (cdr ops))
(define (cond? exp)
(tagged-list? exp 'cond))
(define (cond-clauses exp) (cdr exp))
(define (cond-else-clause? clause)
(eq? (cond-predicate clause) 'else))
(define (cond-predicate clause)
(car clause))
(define (cond-actions clause)
(cdr clause))
(define (cond->if exp)
(expand-clauses (cond-clauses exp)))
;;ex4.4
(define (and? exp)
(tagged-list? exp 'and))
(define (and-clauses exp) (cdr exp))
(define (or? exp)
(tagged-list? exp 'or))
(define (or-clauses exp) (cdr exp))
;;Add an additional syntax for cond clauses (⟨test⟩ => ⟨recipient⟩)
;;ex4.5
(define (=>? exp)
(eq? (cadr exp) '=>))
(define (cond-recipient exp)
(caddr exp))
(define (expand-clauses clauses)
(if (null? clauses)
'false ; no else clause
(let ((first (car clauses))
(rest (cdr clauses)))
(if (cond-else-clause? first)
(if (null? rest)
(sequence->exp
(cond-actions first))
(error "ELSE clause isn't
last: COND->IF"
clauses))
(let ((predicate (cond-predicate first)))
(make-if predicate
(if (=>? first)
(cons (cond-recipient first) predicate)
(sequence->exp (cond-actions first)))
(expand-clauses rest)))))))
;;ex 4.6
(define (let? exp)
(tagged-list? exp 'let))
(define (let-vars exp)
(map car (cadr exp)))
(define (let-exps exp)
(map cadr (cadr exp)))
(define (let-body exp)
(cddr exp))
(define (let->combination exp)
(if (name-let? exp)
(cons (make-lambda '()
(list (make-define (name-let-name exp)
(make-lambda (let-vars (name-let-remain exp))
(let-body (name-let-remain exp))))
(cons (name-let-name exp) (let-exps (name-let-remain exp)))))
'())
(cons (make-lambda (let-vars exp)
(let-body exp))
(let-exps exp))))
;;ex4.7
(define (let*? exp)
(tagged-list? exp 'let*))
(define (let*-bindings exp)
(cadr exp))
(define (let*-body exp)
(cddr exp))
(define (make-let bindings body)
(list 'let bindings body))
(define (let*->nested-lets exp)
(expand-lets (let*-bindings exp) (let*-body exp)))
(define (expand-lets bindings body)
(if (null? bindings)
(sequence->exp body)
(make-let (list (car bindings))
(expand-lets (cdr bindings)
body))))
;;ex4.8
(define (name-let? exp)
(symbol? (cadr exp)))
(define (name-let-name exp)
(cadr exp))
(define (name-let-remain exp)
(cdr exp))
(define (make-define var exp)
(list 'define var exp))
;;ex4.9
;; Syntax:
;; (do ((variable init step) ...) (test expr ...) commands)
;; Example:
;; (let ((x '(1 3 5 7 9)))
;; (do ((x x (cdr x))
;; (sum 0 (+ sum (car x))))
;; ((null? x) sum)))
;; Output:
;; 25
(define (do? exp)
(tagged-list? exp 'do))
(define (do-vars exp)
(map car (cadr exp)))
(define (do-inits exp)
(map cadr (cadr exp)))
(define (do-steps exp)
(map caddr (cadr exp)))
(define (do-test exp)
(caaddr exp))
(define (do-exprs exp)
(cdaddr exp))
(define (do-commands exp)
(cdddr exp))
(define (do-body exp)
(list (make-if (do-test exp)
(sequence->exp (do-exprs exp))
(sequence->exp (list (do-commands exp)
(cons 'do-iter (do-steps exp)))))))
(define (do->combination exp)
(cons (make-lambda '()
(list (make-define 'do-iter
(make-lambda (do-vars exp)
(do-body exp)))
(cons 'do-iter (do-inits exp))))
'()))
;; 4.1.3 Evaluator Data Structures
;; Testing of predicates
(define (true? x)
(not (eq? x false)))
(define (false? x)
(eq? x false))
;; Representing procedures
(define (make-procedure parameters body env)
(list 'procedure parameters (sequence->exp (scan-out-defines body)) env))
(define (compound-procedure? p)
(tagged-list? p 'procedure))
(define (procedure-parameters p) (cadr p))
(define (procedure-body p) (caddr p))
(define (procedure-environment p) (cadddr p))
;; ex4.16
(define (let-unassigned-bindings bindings)
(map (lambda (binding)
(list (car binding) '(quote *unassigned)))
bindings))
(define (let-assignment-sequence bindings)
(map (lambda (binding)
(list 'set! (car binding) (cadr binding)))
bindings))
(define (scan-out-defines body)
(define (let-definition-sequence body)
(filter definition? body))
(define (let-rest-sequence body)
(filter (lambda (exp)
(not (definition? exp)))
body))
(let ((bindings (map (lambda (exp) (list (definition-variable exp) (definition-value exp)))
(let-definition-sequence body))))
(if (= (length bindings) 0)
body
(list (make-let (let-unassigned-bindings bindings)
(sequence->exp (list (sequence->exp (let-assignment-sequence bindings))
(sequence->exp (let-rest-sequence body)))))))))
;; ex4.20
(define (letrec? exp)
(tagged-list? exp 'letrec))
(define (letrec-bindings exp)
(cadr exp))
(define (letrec-body exp)
(cddr exp))
(define (letrec->let exp)
(let ((bindings (letrec-bindings exp)))
(make-let (let-unassigned-bindings bindings)
(sequence->exp (list (sequence->exp (let-assignment-sequence bindings))
(sequence->exp (letrec-body exp)))))))
;; ex4.21
;; It is indeed possible to specify recursive procedures without using letrec (or even define)
#|
(define (f x)
((lambda (even? odd?)
(even? even? odd? x))
(lambda (ev? od? n)
(if (= n 0)
true
(od? ev? od? (- n 1))))
(lambda (ev? od? n)
(if (= n 0)
false
(ev? ev? od? (- n 1))))))
|#
;; Operations on Environments(a pair of lists)
(define (enclosing-environment env) (cdr env))
(define (first-frame env) (car env))
(define the-empty-environment '())
(define (make-frame variables values)
(cons variables values))
(define (frame-variables frame) (car frame))
(define (frame-values frame) (cdr frame))
(define (add-binding-to-frame! var val frame)
(set-car! frame (cons var (car frame)))
(set-cdr! frame (cons val (cdr frame))))
(define (extend-environment vars vals base-env)
(if (= (length vars) (length vals))
(cons (make-frame vars vals) base-env)
(if (< (length vars) (length vals))
(error "Too many arguments supplied"
vars
vals)
(error "Too few arguments supplied"
vars
vals))))
(define (lookup-variable-value var env)
(define (env-loop env)
(define (scan vars vals)
(cond ((null? vars)
(env-loop
(enclosing-environment env)))
((eq? var (car vars))
(let ((val (car vals)))
(if (eq? val '*unassigned*)
(error "Attempt is made to use the value of the not-yet-assigned variable")
val)))
(else (scan (cdr vars)
(cdr vals)))))
(if (eq? env the-empty-environment)
(error "Unbound variable" var)
(let ((frame (first-frame env)))
(scan (frame-variables frame)
(frame-values frame)))))
(env-loop env))
(define (set-variable-value! var val env)
(define (env-loop env)
(define (scan vars vals)
(cond ((null? vars)
(env-loop
(enclosing-environment env)))
((eq? var (car vars))
(set-car! vals val))
(else (scan (cdr vars)
(cdr vals)))))
(if (eq? env the-empty-environment)
(error "Unbound variable: SET!" var)
(let ((frame (first-frame env)))
(scan (frame-variables frame)
(frame-values frame)))))
(env-loop env))
(define (define-variable! var val env)
(let ((frame (first-frame env)))
(define (scan vars vals)
(cond ((null? vars)
(add-binding-to-frame!
var val frame))
((eq? var (car vars))
(set-car! vals val))
(else (scan (cdr vars)
(cdr vals)))))
(scan (frame-variables frame)
(frame-values frame))))
;; 4.1.4 Running the Evaluator as a Program
(define (setup-environment)
(let ((initial-env
(extend-environment
(primitive-procedure-names)
(primitive-procedure-objects)
the-empty-environment)))
(define-variable! 'true true initial-env)
(define-variable! 'false false initial-env)
initial-env))
(define (primitive-procedure? proc)
(tagged-list? proc 'primitive))
(define (primitive-implementation proc)
(cadr proc))
(define primitive-procedures
(let ((f-cons
(list
(list 'car car)
(list 'cdr cdr)
(list 'cons cons)
(list 'null? null?)
(list 'list list)
(list 'append append)
(list 'cadr cadr)
(list 'caddr caddr)))
(f-oper
(list
(list '= =)
(list '+ +)
(list '- -)
(list '* *)
(list '/ /)))
(f-pred
(list
(list 'eq? eq?)
(list 'pair? pair?)
(list 'null? null?)
(list 'number? number?)
(list 'symbol? symbol?)
(list 'string? string?)))
(f-print
(list
(list 'display display)
(list 'error error)
(list 'newline newline)))
(f-stream
(list
(list 'map map))))
(append f-cons f-oper f-pred f-print f-stream)))
(define (primitive-procedure-names)
(map car primitive-procedures))
(define (primitive-procedure-objects)
(map (lambda (proc)
(list 'primitive (cadr proc)))
primitive-procedures))
(define (apply-primitive-procedure proc args)
(apply
(primitive-implementation proc) args))
(define the-global-environment
(setup-environment))
;; REPL
(define input-prompt ";;; M-Eval input:")
(define output-prompt ";;; M-Eval value:")
(define (driver-loop)
(prompt-for-input input-prompt)
(let ((input (read)))
(if (eq? input 'exit)
'exit
(let ((output
(eval input
the-global-environment)))
(announce-output output-prompt)
(user-print output)
(driver-loop)))))
(define (prompt-for-input string)
(newline) (newline)
(display string) (newline))
(define (announce-output string)
(newline) (display string) (newline))
(define (user-print object)
(if (compound-procedure? object)
(display
(list 'compound-procedure
(procedure-parameters object)
(procedure-body object)
'<procedure-env>))
(display object)))
;; Input (driver-loop) to start driver loop
;; Using functions from underlying Scheme implementation:
#|
list, cons, car, cdr (include cadr caddr etc.)
function, let -> enviroment, lambda and function application
cond -> if
number?, string?, null?, symbol?, eq?, pair?
display, error, newline
quote (include "'" shorthand for quote)
map
apply
|#
;; TODO
;; Reduce usage of functions import from underlying scheme
;; Add test case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment