#lang racket (require racket/mpair) (provide (all-defined-out)) ;; This is the repl program through the end of section 4.1.7. Syntactic analysis has ;; been separated from evaluation, and letrec has been added. Use ;; "4-1-7-repl-test.rkt" to test basic functionality. ;; NOTES: ;; 1. Environments are mutable lists. All other lists are regular immutable lists. ;; 2. We use the implementation for frames from exercises 4.11 and 4.12. ;; 3. Book terms that shadow Racket built-ins are prefixed with my-. ;; 4. The alternate cond test, when true, will be inefficiently evaluated twice. ;; 5. The body of a lambda or let statement can be a sequence. ;; 6. '*unassigned* is a self-evaluating reserved symbol, used to implement letrec. ;; 7. scan-out-defines has been moved from procedure-body, which is now a procedure ;; and not an expression, to analyze-lambda. ;; PROGRAM SECTIONS: ;; 1. my-eval and analyze ;; 2. self-evaluating expressions, variables, and quotations ;; 3. definition and assignment ;; 4. lambdas, procedures and applications ;; 5. sequences and begin expressions ;; 6. boolean expressions ;; 7. if and cond expressions ;; 8. let, let*, named-let, and letrec ;; 9. environment and frames ;; 10. primitive procedures and the global environment ;; 11. repl operations ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 1. MY-EVAL AND ANALYZE ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (my-eval expr env) ((analyze expr) env)) (define (analyze expr) (cond [(self-evaluating? expr) (analyze-self-evaluating expr)] [(quoted? expr) (analyze-quoted expr)] [(variable? expr) (analyze-variable expr)] [(assignment? expr) (analyze-assignment expr)] [(definition? expr) (analyze-definition expr)] [(if? expr) (analyze-if expr)] [(lambda? expr) (analyze-lambda expr)] [(begin? expr) (analyze-sequence (begin-actions expr))] [(cond? expr) (analyze (cond->if expr))] [(and? expr) (analyze-and expr)] [(or? expr) (analyze-or expr)] [(let? expr) (analyze (let->combination expr))] [(let*? expr) (analyze (let*->nested-lets expr))] [(named-let? expr) (analyze (named-let->sequence expr))] [(letrec? expr) (analyze (letrec->simultaneous-lets expr))] [(application? expr) (analyze-application expr)] [else (error "Unknown expression type -- ANALYZE" expr)])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 2. SELF-EVALUATING EXPRESSIONS, VARIABLES, AND QUOTATIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (tagged-list? expr tag) (if (pair? expr) (eq? (first expr) tag) false)) (define *reserved-symbols* (list '*unassigned* )) ;; Only numbers, strings, and reserved symbols are self-evaluating. (define (self-evaluating? expr) (or (number? expr) (string? expr) (member expr *reserved-symbols*))) (define (analyze-self-evaluating expr) (lambda (env) expr)) ;; variables (define (variable? expr) (and (symbol? expr) (not (member expr *reserved-symbols*)))) (define (analyze-variable expr) (lambda (env) (lookup-variable-value expr env))) ;; Quotations have the form: (quote ) (define (quoted? expr) (tagged-list? expr 'quote)) (define (text-of-quotation expr) (second expr)) (define (analyze-quoted expr) (define qval (text-of-quotation expr)) (lambda (env) qval)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 3. DEFINITION AND ASSIGNMENT ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Variable definitions have the form: (define ) ;; Procedure definitions have the form: ;; (define ( ... ) ) ;; which is equivalent to: ;; (define (lambda ( ... ) )) (define (definition? expr) (tagged-list? expr 'define)) (define (definition-variable expr) (if (symbol? (second expr)) (second expr) (first (second expr)))) (define (definition-value expr) (if (symbol? (second expr)) (third expr) (make-lambda (rest (second expr)) ; formal parameters (drop expr 2)))) (define (analyze-definition expr) (define var (definition-variable expr)) (define vproc (analyze (definition-value expr))) (lambda (env) (define-variable! var (vproc env) env) 'ok)) ;; Assignments have the form: (set! ) (define (assignment? expr) (tagged-list? expr 'set!)) (define (assignment-variable expr) (second expr)) (define (assignment-value expr) (third expr)) (define (analyze-assignment expr) (define var (assignment-variable expr)) (define vproc (analyze (assignment-value expr))) (lambda (env) (set-variable-value! var (vproc env) env) 'ok)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 4. LAMBDAS, PROCEDURES AND APPLICATIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Lambda expressions have the form: ;; (lambda () ) (define (lambda? expr) (tagged-list? expr 'lambda)) (define (lambda-parameters expr) (second expr)) (define (lambda-body expr) (drop expr 2)) (define (make-lambda parameters body) (cons 'lambda (cons parameters body))) (define (scan-out-defines body) (define defines (filter definition? body)) (cond [(empty? defines) body] [else (define rest-of-body (filter-not definition? body)) (define vars (map definition-variable defines)) (define vals (map definition-value defines)) (define bindings (for/list ([v vars]) (list v '*unassigned*))) (define assigns (for/list ([v vars] [e vals]) (list 'set! v e))) ; a body is a list of expressions (list (make-let bindings (append assigns rest-of-body)))])) (define (analyze-lambda expr) (define vars (lambda-parameters expr)) (define bproc (analyze-sequence (scan-out-defines (lambda-body expr)))) (lambda (env) (make-procedure vars bproc env))) ;; Procedures: (define (compound-procedure? p) (tagged-list? p 'procedure)) (define (make-procedure parameters body env) (list 'procedure parameters body env)) (define (procedure-parameters p) (second p)) (define (procedure-body p) (third p)) (define (procedure-environment p) (fourth p)) ;; Procedure applications have the from: ;; ( ...) (define (application? expr) (pair? expr)) (define (operator expr) (first expr)) (define (operands expr) (rest expr)) (define (no-operands? ops) (empty? ops)) (define (first-operand ops) (first ops)) (define (rest-operands ops) (rest ops)) (define (analyze-application expr) (define fproc (analyze (operator expr))) (define aprocs (map analyze (operands expr))) (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) ((procedure-body proc) (extend-environment (procedure-parameters proc) args (procedure-environment proc)))] [else (error "Unknown procedure type -- EXECUTE-APPLICATION" proc)])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 5. SEQUENCES AND BEGIN EXPRESSIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Begin has the form: (begin ) (define (begin? expr) (tagged-list? expr 'begin)) (define (begin-actions expr) (rest expr)) (define (last-exp? seq) (empty? (rest seq))) (define (first-exp seq) (first seq)) (define (rest-exps seq) (rest seq)) (define (sequence->exp seq) (cond [(empty? seq) seq] [(last-exp? seq) (first-exp seq)] [else (make-begin seq)])) (define (make-begin seq) (cons 'begin seq)) (define (analyze-sequence exprs) (define (sequentially proc1 proc2) (lambda (env) (proc1 env) (proc2 env))) (define (loop first-proc rest-procs) (if (empty? rest-procs) first-proc (loop (sequentially first-proc (first rest-procs)) (rest rest-procs)))) (define procs (map analyze exprs)) (if (null? procs) (error "Empty sequence -- ANALYZE") (loop (first procs) (rest procs)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 6. BOOLEAN EXPRESSIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (true? x) (not (false? x))) (define (and? expr) (tagged-list? expr 'and)) (define (and-operands expr) (rest expr)) (define (make-and sequence) (cons 'and sequence)) (define (analyze-and expr) (define (and-sequentially proc1 proc2) (lambda (env) (and (proc1 env) (proc2 env)))) (define (loop first-proc rest-procs) (if (empty? rest-procs) first-proc (loop (and-sequentially first-proc (first rest-procs)) (rest rest-procs)))) (define procs (map analyze (and-operands expr))) (if (empty? procs) true (loop (first procs) (rest procs)))) (define (or? expr) (tagged-list? expr 'or)) (define (or-operands expr) (rest expr)) (define (make-or sequence) (cons 'or sequence)) (define (analyze-or expr) (define (or-sequentially proc1 proc2) (lambda (env) (or (proc1 env) (proc2 env)))) (define (loop first-proc rest-procs) (if (empty? rest-procs) first-proc (loop (or-sequentially first-proc (first rest-procs)) (rest rest-procs)))) (define procs (map analyze (or-operands expr))) (if (empty? procs) false (loop (first procs) (rest procs)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 7. IF AND COND EXPRESSIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Conditionals have the form: (if ) ;; If no alternative, use false. (define (if? expr) (tagged-list? expr 'if)) (define (if-predicate expr) (second expr)) (define (if-consequent expr) (third expr)) (define (if-alternative expr) (if (not (empty? (drop expr 3))) (fourth expr) 'false)) (define (make-if predicate consequent alternative) (list 'if predicate consequent alternative)) (define (analyze-if expr) (define pproc (analyze (if-predicate expr))) (define cproc (analyze (if-consequent expr))) (define aproc (analyze (if-alternative expr))) (lambda (env) (if (true? (pproc env)) (cproc env) (aproc env)))) ;; Cond has the form: ;; (cond (( ) ;; (else ))) ; if no else, assume (else false) clause (define (cond? expr) (tagged-list? expr 'cond)) (define (cond-clauses expr) (rest expr)) ;; regular cond clause (define (cond-predicate clause) (first clause)) (define (cond-actions clause) (rest clause)) ;; alternate test clause (define (cond-alternate-clause? clause) (eq? (second clause) '=>)) (define (cond-test clause) (first clause)) (define (cond-recipient clause) (third clause)) ;; else clause (define (cond-else-clause? clause) (eq? (cond-predicate clause) 'else)) ;; derive cond from if (define (cond->if expr) (expand-clauses (cond-clauses expr))) (define (expand-clauses clauses) (cond [(empty? clauses) 'false] ; no else clause [else (define first-clause (first clauses)) (define rest-clauses (rest clauses)) (cond [(cond-else-clause? first-clause) (if (empty? rest-clauses) (sequence->exp (cond-actions first-clause)) (error "ELSE clause is not last -- COND->IF" clauses))] [(cond-alternate-clause? first-clause) (define test (cond-test first-clause)) ; gets evaluated twice (make-if test (list (cond-recipient first-clause) test) (expand-clauses rest-clauses))] [else (make-if (cond-predicate first-clause) (sequence->exp (cond-actions first-clause)) (expand-clauses rest-clauses))])])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 8. LET, LET*, NAMED-LET, AND LETREC ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Let has the form: (let () ) (define (let? expr) (and (tagged-list? expr 'let) (list? (second expr)))) (define (let-bindings expr) (second expr)) (define (let-parameters expr) (map first (let-bindings expr))) (define (let-expressions expr) (map second (let-bindings expr))) (define (let-body expr) (drop expr 2)) (define (let->combination expr) (cond [(empty? (let-bindings expr)) ; do not unnecessarily lambda wrap (sequence->exp (let-body expr))] [else (cons (make-lambda (let-parameters expr) (let-body expr)) (let-expressions expr))])) (define (make-let bindings body) (cons 'let (cons bindings body))) ;; Let* has the same form as let, but bindings are sequential. (define (let*? expr) (tagged-list? expr 'let*)) (define (let*-bindings expr) (second expr)) (define (let*-body expr) (drop expr 2)) (define (make-let* bindings body) (cons 'let* (cons bindings body))) (define (let*->nested-lets expr) (define bindings (let*-bindings expr)) (cond [(empty? bindings) (sequence->exp (let-body expr))] [else (list 'let (list (first bindings)) (let*->nested-lets (make-let* (rest bindings) (let-body expr))))])) ;; Named-let has the form: (let () ) (define (named-let? expr) (and (tagged-list? expr 'let) (not (list? (second expr))))) (define (named-let-name expr) (second expr)) (define (named-let-bindings expr) (third expr)) (define (named-let-parameters expr) (map first (named-let-bindings expr))) (define (named-let-expressions expr) (map second (named-let-bindings expr))) (define (named-let-body expr) (drop expr 3)) (define (named-let->sequence expr) (define bindings (named-let-bindings expr)) (cond [(empty? bindings) (sequence->exp (named-let-body expr))] [else (list 'begin (cons 'define ; first define the named function (cons (cons (named-let-name expr) (named-let-parameters expr)) (named-let-body expr))) (cons (named-let-name expr) ; then apply it to the expressions (named-let-expressions expr)))])) ;; Letrec has the same form as let, but letrec parameters are first bound to ;; '*unassigned* and then set! to their values to mimic simultaneous definitions. (define (letrec? expr) (tagged-list? expr 'letrec)) (define (letrec-bindings expr) (second expr)) (define (letrec-body expr) (drop expr 2)) (define (letrec->simultaneous-lets expr) (define bindings (letrec-bindings expr)) (cond [(empty? bindings) (letrec-body expr)] [else (define vars (map first bindings)) (define vals (map second bindings)) (define new-bindings (for/list ([v vars]) (list v '*unassigned*))) (define assigns (for/list ([v vars] [e vals]) (list 'set! v e))) (make-let new-bindings (append assigns (letrec-body expr)))])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 9. ENVIRONMENT AND FRAMES ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; An environment is a mutable list of frames. The enclosing environment is the mcdr ;; of the list. A frame is a mutable list of bindings with a 'frame header. A ;; binding is a var-val pair ie (mcons var val). ;; Environments support four procedures: ;; lookup-variable-value ;; extend-environment ;; define-variable ;; set-variable-value ;; Two helper functions support the environment procedures: ;; find-binding-in-frame ;; find-binding-in-environment (define (enclosing-environment env) (mcdr env)) (define (first-frame env) (mcar env)) (define the-empty-environment empty) (define the-empty-frame (mlist 'frame)) (define (empty-frame? frame) (empty? (frame-bindings frame))) (define (make-frame vars vals) (mcons 'frame (mmap mcons (list->mlist vars) (list->mlist vals)))) (define (frame-bindings frame) (mcdr frame)) (define (frame-variables frame) (mmap mcar (frame-bindings frame))) (define (frame-values frame) (mmap mcdr (frame-bindings frame))) (define (binding-variable binding) (mcar binding)) (define (binding-value binding) (mcdr binding)) (define (set-value! binding val) (set-mcdr! binding val)) (define (add-binding-to-frame! var val frame) (mappend! frame (mlist (mcons var val)))) (define (find-binding-in-frame var frame) ; Return the var-val pair if present else false. (define (loop bindings) (cond [(empty? bindings) false] [else (define b (mcar bindings)) (if (eq? var (binding-variable b)) b (loop (mcdr bindings)))])) (loop (frame-bindings frame))) (define (find-binding-in-env var env) ; Return the closest binding for var if present else false. (cond [(eq? env the-empty-environment) false] [else (define b (find-binding-in-frame var (first-frame env))) (or b (find-binding-in-env var (enclosing-environment env)))])) (define (lookup-variable-value var env) (define b (find-binding-in-env var env)) (if (false? b) (error "Unbound variable" var) (binding-value b))) (define (extend-environment vars vals base-env) (cond [(= (length vars) (length vals)) (mcons (make-frame vars vals) base-env)] [else (if (< (length vars) (length vals)) (error "Too many arguments supplied" vars vals) (error "Too few arguments supplied" vars vals))])) (define (define-variable! var val env) (define frame (first-frame env)) (define b (find-binding-in-frame var frame)) (if b (set-value! b val) (add-binding-to-frame! var val frame))) (define (set-variable-value! var val env) (define b (find-binding-in-env var env)) (if b (set-value! b val) (error "Unbound variable -- SET!" var))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 10. PRIMITIVE PROCEDURES AND THE GLOBAL ENVIRONMENT ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define primitive-procedures (list (list 'cons cons) (list 'car car) (list 'cdr cdr) (list '+ +) (list '* *) (list '- -) (list '< <) (list '> >) (list '<= <=) (list '>= >=) (list '= =) (list 'not not) (list 'false? false?) (list 'true? (lambda (x) (not (false? x)))) (list 'empty? empty?) )) (define (primitive-procedure? proc) (tagged-list? proc 'primitive)) (define (primitive-implementation proc) (second proc)) (define (primitive-procedure-names) (map first primitive-procedures)) (define (primitive-procedure-objects) (map (lambda (proc) (list 'primitive (second proc))) primitive-procedures)) (define apply-in-underlying-scheme apply) ;; The metacircular evaluator's apply is my-apply. (define (apply-primitive-procedure proc args) (apply-in-underlying-scheme (primitive-implementation proc) args)) (define (setup-environment) (define 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 the-global-environment (setup-environment)) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 11. REPL OPERATIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;; (define input-prompt ";;; M-Eval input:") (define output-prompt ";;; M-Eval value:") (define (driver-loop) (prompt-for-input input-prompt) (let* ([input (read)] [output (my-eval input the-global-environment)]) (announce-output output-prompt) (user-print output)) (driver-loop)) (define prompt-for-input displayln) (define announce-output displayln) (define (user-print object) (if (compound-procedure? object) (displayln (list 'compound-procedure (procedure-parameters object) (procedure-body object) ')) (displayln object)))