#lang racket (require racket/mpair) (provide (all-defined-out)) ;; This is the repl program for section 4.2.2. It is the same as the repl program ;; from section 4.1.4, only updated to use lazy evaluation. In particular, this repl ;; program does not separate syntactic analysis from evaluation, nor does it ;; implement letrec, like the repl program from section 4.1.7 does. Use ;; "4-2-2-repl-test.rkt" to test basic functionality. ;; NOTES: ;; 1. Thunks, like environments, are mutable lists. Other lists are immutable. ;; 2. tagged-list? must be changed to work on both mutable and immutable lists. ;; PROGRAM SECTIONS: ;; 1. my-eval and my-apply ;; 2. eval procedures ;; 3. self-evaluating expressions, variables, and quotations ;; 4. definition and assignment ;; 5. thunks, lambdas, procedures and applications ;; 6. sequences and begin expressions ;; 7. boolean expressions ;; 8. if and cond expressions ;; 9. let, let*, and named-let ;; 10. environment and frames ;; 11. primitive procedures and the global environment ;; 12. repl operations ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 1. MY-EVAL AND MY-APPLY ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (my-eval expr env) (cond [(self-evaluating? expr) expr] [(variable? expr) (lookup-variable-value expr env)] [(quoted? expr) (text-of-quotation expr)] [(definition? expr) (eval-definition expr env)] [(assignment? expr) (eval-assignment expr env)] [(if? expr) (eval-if expr env)] [(cond? expr) (my-eval (cond->if expr) env)] [(begin? expr) (eval-sequence (begin-actions expr) env)] [(and? expr) (eval-and expr env)] [(or? expr) (eval-or expr env)] [(let? expr) (my-eval (let->combination expr) env)] [(let*? expr) (my-eval (let*->nested-lets expr) env)] [(named-let? expr) (my-eval (named-let->sequence expr) env)] [(lambda? expr) (make-procedure (lambda-parameters expr) (lambda-body expr) env)] [(application? expr) (my-apply (actual-value (operator expr) env) (operands expr) env)] [else (error "Unknown expression type -- MY-EVAL" expr)])) (define (my-apply procedure arguments env) (cond [(primitive-procedure? procedure) (apply-primitive-procedure procedure (list-of-arg-values arguments env))] [(compound-procedure? procedure) (eval-sequence (procedure-body procedure) (extend-environment (procedure-parameters procedure) (list-of-delayed-args arguments env) (procedure-environment procedure)))] [else (error "Unknown procedure type -- APPLY" procedure)])) ;;;;;;;;;;;;;;;;;;;;;;;;; ;; 2. EVAL PROCEDURES ;; ;;;;;;;;;;;;;;;;;;;;;;;;; (define (eval-definition expr env) (define-variable! (definition-variable expr) (my-eval (definition-value expr) env) env) 'ok) (define (eval-assignment expr env) (set-variable-value! (assignment-variable expr) (my-eval (assignment-value expr) env) env) 'ok) (define (list-of-values exprs env) (if (no-operands? exprs) empty (cons (my-eval (first-operand exprs) env) (list-of-values (rest-operands exprs) env)))) (define (eval-sequence exprs env) (cond [(last-exp? exprs) (my-eval (first-exp exprs) env)] [else (my-eval (first-exp exprs) env) (eval-sequence (rest-exps exprs) env)])) (define (eval-if expr env) (if (true? (actual-value (if-predicate expr) env)) (my-eval (if-consequent expr) env) (my-eval (if-alternative expr) env))) (define (eval-and expr env) (define ops (and-operands expr)) (cond [(empty? ops) 'true] ; (and) should be true [else (define val (my-eval (first ops) env)) (define rest-ops (rest ops)) (cond [(empty? rest-ops) val] ; (and x) should be x [(false? val) false] [else (eval-and (make-and rest-ops) env)])])) (define (eval-or expr env) (define ops (or-operands expr)) (cond [(empty? ops) 'false] ; (or) should be false [else (define val (my-eval (first ops) env)) (define rest-ops (rest ops)) (cond [(empty? rest-ops) val] ; (or x) should be x [(not (false? val)) val] [else (eval-or (make-or rest-ops) env)])])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 3. SELF-EVALUATING EXPRESSIONS, VARIABLES, AND QUOTATIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (tagged-list? expr tag) (or (and (pair? expr) (eq? (first expr) tag)) (and (mpair? expr) (eq? (mcar expr) tag)))) ;; Only numbers and strings are self-evaluating. (define (self-evaluating? expr) (cond [(number? expr) true] [(string? expr) true] [else false])) (define (variable? expr) (symbol? expr)) ;; Quotations have the form: (quote ) (define (quoted? expr) (tagged-list? expr 'quote)) (define (text-of-quotation expr) (second expr)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 4. 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)))) ;; Assignments have the form: (set! ) (define (assignment? expr) (tagged-list? expr 'set!)) (define (assignment-variable expr) (second expr)) (define (assignment-value expr) (third expr)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 5. THUNKS, LAMBDAS, PROCEDURES AND APPLICATIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (thunk? obj) (tagged-list? obj 'thunk)) (define (thunk-exp my-thunk) (mcar (mcdr my-thunk))) (define (thunk-env my-thunk) (mcar (mcdr (mcdr my-thunk)))) (define (delay-it expr env) (mlist 'thunk expr env)) (define (evaluated-thunk? obj) (tagged-list? obj 'evaluated-thunk)) (define (thunk-value evaluated-thunk) (mcar (mcdr evaluated-thunk))) (define (force-it obj) (cond [(thunk? obj) (define result (actual-value (thunk-exp obj) (thunk-env obj))) (set-mcar! obj 'evaluated-thunk) (set-mcar! (mcdr obj) result) (set-mcdr! (mcdr obj) empty) result] [(evaluated-thunk? obj) (thunk-value obj)] [else obj])) ;; NON-MEMOIZING FORCE-IT: ;; (define (force-it obj) ;; (if (thunk? obj) ;; (actual-value (thunk-exp obj) (thunk-env obj)) ;; obj)) ;; 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))) ;; Procedures: (define (make-procedure parameters body env) (list 'procedure parameters body env)) (define (compound-procedure? p) (tagged-list? p 'procedure)) (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 (actual-value expr env) (force-it (my-eval expr env))) (define (list-of-arg-values exprs env) (if (no-operands? exprs) empty (cons (actual-value (first-operand exprs) env) (list-of-arg-values (rest-operands exprs) env)))) (define (list-of-delayed-args exprs env) (if (no-operands? exprs) empty (cons (delay-it (first-operand exprs) env) (list-of-delayed-args (rest-operands exprs) env)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 6. 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)])) ;; begin constructor used by cond->if (define (make-begin seq) (cons 'begin seq)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 7. 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 (or? expr) (tagged-list? expr 'or)) (define (or-operands expr) (rest expr)) (define (make-or sequence) (cons 'or sequence)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 8. 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)) ;; 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 isn't 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))])])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 9. LET, LET*, AND NAMED-LET ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 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)))])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 10. 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 b (binding-value b) (error "Unbound variable" var))) (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))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 11. 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?) (list 'displayln displayln) (list 'list list) )) (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)) ;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 12. REPL OPERATIONS ;; ;;;;;;;;;;;;;;;;;;;;;;;;;; (define input-prompt ";;; L-Eval input:") (define output-prompt ";;; L-Eval value:") (define (driver-loop) (prompt-for-input input-prompt) (let* ([input (read)] [output (actual-value 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)))