;: Single-file version of the interpreter. ;; Easier to submit to server, probably harder to use in the development process (load "chez-init.ss") ;-------------------+ ; | ; DATATYPES | ; | ;-------------------+ ; parsed expression (define-datatype expression expression? [var-exp ; variable references (id symbol?)] [lit-exp ; "Normal" data. Did I leave out any types? (datum (lambda (x) (ormap (lambda (pred) (pred x)) (list number? vector? boolean? symbol? string? pair? null?))))] [app-exp ; applications (rator expression?) (rands (list-of expression?))] ) ;; environment type definitions (define scheme-value? (lambda (x) #t)) ; datatype for procedures. At first there is only one ; kind of procedure, but more kinds will be added later. (define-datatype proc-val proc-val? [prim-proc (name symbol?)]) (define-datatype environment environment? (empty-env-record) (extended-env-record (syms (list-of symbol?)) (vals (list-of scheme-value?)) (env environment?))) ;-------------------+ ; | ; PARSER | ; | ;-------------------+ ; This is a parser for simple Scheme expressions, such as those in EOPL, 3.1 thru 3.3. ; You will want to replace this with your parser that includes more expression types, more options for these types, and error-checking. ; Procedures to make the parser a little bit saner. (define 1st car) (define 2nd cadr) (define 3rd caddr) (define parse-exp (lambda (datum) (cond [(symbol? datum) (var-exp datum)] [(number? datum) (lit-exp datum)] [(pair? datum) (cond [else (app-exp (parse-exp (1st datum)) (map parse-exp (cdr datum)))])] [else (eopl:error 'parse-exp "bad expression: ~s" datum)]))) ;-------------------+ ; | ; ENVIRONMENTS | ; | ;-------------------+ ; Environment definitions for CSSE 304 Scheme interpreter. ; Based on EoPL sections 2.2 and 2.3 (define empty-env (lambda () (empty-env-record))) (define extend-env (lambda (syms vals env) (extended-env-record syms vals env))) (define list-find-position (lambda (sym los) (list-index (lambda (xsym) (eqv? sym xsym)) los))) (define list-index (lambda (pred ls) (cond ((null? ls) #f) ((pred (car ls)) 0) (else (let ((list-index-r (list-index pred (cdr ls)))) (if (number? list-index-r) (+ 1 list-index-r) #f)))))) (define apply-env (lambda (env sym succeed fail) ; succeed and fail are "callback procedures, (cases environment env ; succeed is appluied if sym is found, otherwise [empty-env-record () ; fail is applied. (fail)] [extended-env-record (syms vals env) (let ((pos (list-find-position sym syms))) (if (number? pos) (succeed (list-ref vals pos)) (apply-env env sym succeed fail)))]))) ;-----------------------+ ; | ; SYNTAX EXPANSION | ; | ;-----------------------+ ; To be added later ;-------------------+ ; | ; INTERPRETER | ; | ;-------------------+ ; top-level-eval evaluates a form in the global environment (define top-level-eval (lambda (form) ; later we may add things that are not expressions. (eval-exp form))) ; eval-exp is the main component of the interpreter (define eval-exp (lambda (exp) (cases expression exp [lit-exp (datum) datum] [var-exp (id) (apply-env init-env id; look up its value. (lambda (x) x) ; procedure to call if it is in the environment (lambda () (eopl:error 'apply-env ; procedure to call if it is not in env "variable not found in environment: ~s" id)))] [app-exp (rator rands) (let ([proc-value (eval-exp rator)] [args (eval-rands rands)]) (apply-proc proc-value args))] [else (eopl:error 'eval-exp "Bad abstract syntax: ~a" exp)]))) ; evaluate the list of operands, putting results into a list (define eval-rands (lambda (rands) (map eval-exp rands))) ; Apply a procedure to its arguments. ; At this point, we only have primitive procedures. ; User-defined procedures will be added later. (define apply-proc (lambda (proc-value args) (cases proc-val proc-value [prim-proc (op) (apply-prim-proc op args)] ; You will add other cases [else (eopl:error 'apply-proc "Attempt to apply bad procedure: ~s" proc-value)]))) (define *prim-proc-names* '(+ - * add1 sub1 cons =)) (define init-env ; for now, our initial global environment only contains (extend-env ; procedure names. Recall that an environment associates *prim-proc-names* ; a value (not an expression) with an identifier. (map prim-proc *prim-proc-names*) (empty-env))) ; Usually an interpreter must define each ; built-in procedure individually. We are "cheating" a little bit. (define apply-prim-proc (lambda (prim-proc args) (case prim-proc [(+) (+ (1st args) (2nd args))] [(-) (- (1st args) (2nd args))] [(*) (* (1st args) (2nd args))] [(add1) (+ (1st args) 1)] [(sub1) (- (1st args) 1)] [(cons) (cons (1st args) (2nd args))] [(=) (= (1st args) (2nd args))] [else (error 'apply-prim-proc "Bad primitive procedure name: ~s" prim-proc)]))) (define rep ; "read-eval-print" loop. (lambda () (display "--> ") ;; notice that we don't save changes to the environment... (let ([answer (top-level-eval (parse-exp (read)))]) ;; TODO: are there answers that should display differently? (eopl:pretty-print answer) (newline) (rep)))) ; tail-recursive, so stack doesn't grow. (define eval-one-exp (lambda (x) (top-level-eval (parse-exp x))))