Com S 342 meeting -*- Outline -*- * A Simple Interpreter (3.1) ** overview ------------------------------------------ WHAT AN INTERPRETER DOES (3.1) concrete syntax | v abstract --------> Expressed syntax Value ------------------------------------------ show the defining language (Scheme) define the *defined* language concrete run syntax --------\ / or parse | \ interpreter - read-eval-print v eval-program v abstract --------> Expressed- syntax Value | ^ defined| | A | | defined v eval | Scheme ----------> datum | ^ | | A v machine | Scheme Machine ---------> bytes code ** language design ------------------------------------------ DESIGN OF THE DEFINED LANGUAGE 1. What values? Expressed Value = Number Denoted Value = Number 2. What grammar? (concrete syntax) ::= "a-program (exp)" ::= "lit-exp (datum)" | "var-exp (id) | "primapp-exp (prim ( {}*(,) ) rands)" ::= + | - | * | add1 | sub1 ------------------------------------------ Q: What's the difference between the defined and defining languages? defining language is Scheme Q: What's the difference between an expressed and denoted value? e.g., in Scheme lots of kinds of expressed values, numbers, booleas, etc, but only one kind of denoted value: locations ------------------------------------------ FOR YOU TO DO (IN PAIRS) 1. Give 4 examples of in the defined language's grammar. 2. Give 2 examples of things that are expressions in Scheme, but not in the defined language's grammar ------------------------------------------ Q: Why is the grammar for operands like that? Could it be simplified? because it uses commas, no... ** implementation Q: What cases are there in the syntax of ? Of ? ------------------------------------------ ABSTRACT SYNTAX (define-datatype program program? (a-program (exp expression?))) (define-datatype expression expression? (lit-exp (datum number?)) (var-exp (id symbol?)) (primapp-exp (prim primitive?) (rands (list-of expression?)))) (define-datatype primitive primitive? (add-prim) (subtract-prim) (mult-prim) (incr-prim) (decr-prim)) ------------------------------------------ Q: Does this look familiar? Q: How can that be when the concrete syntax is Algol-like? The parser is generated automatically be SLLGEN (see appendix A) The whole program for this section is gotten by (require (lib "ch3-1.scm" "lib342")) See also $PUB/lib342/README.txt for details. *** domains ------------------------------------------ DOMAINS def: the domain *Expressed-Value* is def: the domain *Denoted-Value* is To start, the defined languge will have: Expressed-Value = Number Denoted-Value = Expressed-Value ------------------------------------------ ... the set of values that can be the results of expressions ... the set of values that can be bound in an environment we've mentioned these before ------------------------------------------ EXPRESSED-VALUE OPERATIONS ;; upcasts (deftype number->expressed (-> (number) Expressed-Value)) ;; downcasts (deftype expressed->number (-> (Expressed-Value) number)) ;; debugging (deftype expressed->printable (-> (Expressed-Value) datum)) ;; tests (deftype number->expressed? (-> (Expressed-Value) boolean)) (define-datatype Expressed-Value expval? (number->expressed (num number?))) ------------------------------------------ When there is more than one kind of expressed value, this datatype will be more useful Q: How would you program expressed->number? number->expressed? ? (define expressed->number (lambda (ev) (cases Expressed-Value ev (number->expressed (num) num) (else (error "expressed->number passed non-number argument: " ev))))) (define number->expressed? (lambda (ev) (cases Expressed-Value ev (number->expressed (num) #t) (else #f)))) *** interpreter itself ------------------------------------------ SIMPLE INTERPRETER ------------------------------------------ ...;; figure 3.2, page 74, $PUB/lib342/ch3-1.scm (deftype eval-program (-> (program) datum)) (define eval-program (lambda (pgm) (cases program pgm (a-program (body) (expressed->printable (eval-expression body (init-env))))))) (deftype eval-expression (-> (expression environment) Expressed-Value)) (define eval-expression (lambda (exp env) (cases expression exp (lit-exp (datum) (number->expressed datum)) (var-exp (id) (apply-env env id)) (primapp-exp (prim rands) (let ((args (eval-rands rands env))) (apply-primitive prim args))) ))) (deftype eval-rands (-> ((list-of expression) environment) (list-of Expressed-Value))) (define eval-rands (lambda (rands env) (map (lambda (x) (eval-rand x env)) rands))) (deftype eval-rand (-> (expression environment) Expressed-Value)) (define eval-rand (lambda (rand env) (eval-expression rand env))) Q: What order of evaluation does this specify? applicative, because that what the defining language does. Q: Is the ordering between arguments specified? no, because map in Scheme doesn't specify that. Q: Will this run as is? What's left to program? (Any other free variables?) make a list on the board, check them off: - environments: apply-env, init-env - primitives: apply-primitive *** environments **** definition and implementation ------------------------------------------ ENVIRONMENTS Def: an *environment* is a mapping from variable names to denoted values: Expressed-Value = Number Denoted-Value = Expressed-Value Environment = -> Denoted-Value ------------------------------------------ Q: Can you ever have an infinite domain in this mapping? Q: How shall we implement them? Note: when drawing pictures of environments, make the drawing grow down the page. In this section, use the representation (the ribs, as in Figure 2.4) ... ;;; from $PUB/lib342/environment-as-ribcage.scm (module environment-as-ribcage (lib "typedscm.ss" "lib342") (provide environment? empty-env extend-env apply-env defined-in-env?) ;; type predicate (deftype environment? (type-predicate-for environment)) ;; constructors (deftype empty-env (-> () environment)) (deftype extend-env (-> ((list-of symbol) (list-of Expressed-Value) environment) environment)) ;; observers (deftype apply-env (-> (environment symbol) Expressed-Value)) (deftype defined-in-env? ; added (-> (environment symbol) boolean)) ;;;;;;;;; skip the details below ;;;;;;;;;;;;;;; (require (lib "list-index.scm" "lib342")) (define-datatype environment environment? (empty-env) (extended-env-record (syms (list-of symbol?)) (vec vector?) ; can use this for anything. (env environment?)) ) (define extend-env (lambda (syms vals env) ;; ENSURES: result is an extended-env-record that corresponds ;; to this constructor call, with a vector containing ;; the values instead of the list passed in. (extended-env-record syms (list->vector vals) env))) (define apply-env (lambda (env sym) (cases environment env (empty-env () (eopl:error 'apply-env "No binding for ~s" sym)) (extended-env-record (syms vals env) (let ((position (list-index sym syms))) (if (<= 0 position) (vector-ref vals position) (apply-env env sym))))))) (define defined-in-env? (lambda (env sym) (cases environment env (empty-env () #f) (extended-env-record (syms vals env) (let ((position (list-index sym syms))) (or (<= 0 position) (defined-in-env? env sym))))))) ) ;; end module **** initial environment Q: What goes in the initial environment? usually, built-in procedures postpone this until later... ------------------------------------------ INITIAL ENVIRONMENT (deftype init-env (-> () environment)) (define init-env ------------------------------------------ ... ;;; from $PUB/lib342/ch3-1.scm (define init-env (lambda () (extend-env '(i v x) (map number->expressed '(1 5 10)) (empty-env)))) NOTE: leave room for adding more variables on the slide Q: What would be needed to add zero as the name of a constant? just bind the name to 0 in the initial env, be sure to use lists! (define init-env (lambda () (extend-env '(zero) (list (number->expressed 0)) ...))) *** primitives for now, we'll only have primitive procedures ------------------------------------------ APPLYING PRIMITIVES (deftype apply-primitive (-> (primitive (list-of Expressed-Value)) Expressed-Value)) (define apply-primitive (lambda (prim args) (cases primitive prim ------------------------------------------ ... (add-prim () (number->expressed (+ (expressed->number (car args)) (expressed->number (cadr args))))) (subtract-prim () (number->expressed (- (expressed->number (car args)) (expressed->number (cadr args))))) (mult-prim () (number->expressed (* (expressed->number (car args)) (expressed->number (cadr args))))) (incr-prim () (number->expressed (+ (expressed->number (car args)) 1))) (decr-prim () (number->expressed (- (expressed->number (car args)) 1))) ))) Q: What would be needed to add division as a primitive? IMPORTANT, be sure to do this!! Copy ch3-1.scm to ch3-1-modified-in-class.scm Change the module name... ;; ask them how to do division ... (div-prim () (number->expressed (quotient (expressed->number (car args)) (expressed->number (cadr args))))) Q: What else could this check? length of args Q: Anything else that has to change? the define-datatype for the primitives You can get it all by typing: (require (lib "ch3-1.scm" "lib342")) This includes the... *** front-end (3.2) ------------------------------------------ THE FRONT END at run-time off-line program string | lexical grammar v v !---------! !-----------! ! scanner ! <====== ! SLLGEN ! !---------! !-----------! | | token stream grammar v v !---------! !-----------! ! parser ! <====== ! SLLGEN ! !---------! !-----------! | | abstract syntax | tree for program v !---------------! ! eval-program ! !---------------! | v Expressed-Value ------------------------------------------ **** non-interactive ------------------------------------------ ;; file ch3-1.scm (define the-lexical-spec '((whitespace (whitespace) skip) (comment ("%" (arbno (not #\newline))) skip) (identifier (letter (arbno (or letter digit "_" "-" "?"))) symbol) (number (digit (arbno digit)) number))) (define the-grammar '((program (expression) a-program) (expression (number) lit-exp) (expression (identifier) var-exp) (expression (primitive "(" (separated-list expression ",") ")") primapp-exp) (primitive ("+") add-prim) (primitive ("-") subtract-prim) (primitive ("*") mult-prim) (primitive ("add1") incr-prim) (primitive ("sub1") decr-prim) )) (deftype scan&parse (-> (string) program)) (define scan&parse (sllgen:make-string-parser the-lexical-spec the-grammar)) (deftype run (-> (string) Expressed-Value)) (define run (lambda (string) (eval-program (scan&parse string)))) ------------------------------------------ ------------------------------------------ USING IT > (require (lib "ch3-1.scm" "lib342")) > (run "5") > ------------------------------------------ ... 5 ... (run "-(4,1)") 3 etc. This is handy for regression testing. For playing with it, you want the read-eval-print loop... **** read-eval-print loop ------------------------------------------ A READ-EVAL-PRINT LOOP ;; file ch3-1.scm (deftype read-eval-print (-> () poof)) (define read-eval-print (lambda () ((sllgen:make-rep-loop "--> " (lambda (pgm) (eval-program pgm)) (sllgen:make-stream-parser the-lexical-spec the-grammar))))) USING IT > (require (lib "ch3-1.scm" "lib342")) > (read-eval-print) --> --> ------------------------------------------ ... 5 5 --> add1(2) 3 --> -(4,3) 1 --> *(add1(i), -(6,4)) 4 --> can get out by causing an error or interrupting... That's what poof means: it doesn't return to you. **** tracing it (if have time, do on computer) Can't use trace directly in DrScheme, but works in Chez Scheme Have to provide/export the procedures to be traced. ------------------------------------------ TRACING THE INTERPRETER (IN CHEZ SCHEME) > (require (lib "ch3-1-modified-in-class.scm" "lib342")) > (trace eval-expression eval-rands) > (trace apply-primitive apply-env) > (read-eval-print) --> 3 |(eval-expression (lit-exp 3) (extended-env-record (i v x) #((number->expressed 1) (number->expressed 5) (number->expressed 10)) (empty-env-record))) |3 3 --> i |(eval-expression (var-exp i) (extended-env-record (i v x) #((number->expressed 1) (number->expressed 5) (number->expressed 10)) (empty-env-record))) | (apply-env (extended-env-record (i v x) #((number->expressed 1) (number->expressed 5) (number->expressed 10)) (empty-env-record)) i) | 1 |1 1 ------------------------------------------ ------------------------------------------ MORE TRACES --> +(3,i) |(eval-expression (primapp-exp (add-prim) ((lit-exp 3) (var-exp i))) (extended-env-record (i v x) #((number->expressed 1) (number->expressed 5) (number->expressed 10)) (empty-env-record))) | (eval-rands ((lit-exp 3) (var-exp i)) (extended-env-record (i v x) #((number->expressed 1) (number->expressed 5) (number->expressed 10)) (empty-env-record))) | |(eval-expression (var-exp i) (extended-env-record (i v x) #((number->expressed 1) (number->expressed 5) (number->expressed 10)) (empty-env-record))) | | (apply-env (extended-env-record (i v x) #((number->expressed 1) (number->expressed 5) (number->expressed 10)) (empty-env-record)) i) | | 1 | |1 | |(eval-expression (lit-exp 3) (extended-env-record (i v x) #((number->expressed 1) (number->expressed 5) (number->expressed 10)) (empty-env-record))) | |3 | (3 1) |(apply-primitive (add-prim) (3 1)) |4 4 --> ------------------------------------------