Programming Languages: Interpreters


 ::==  |  |  

 ::==  | ()
 ::== () | ( {,}*)
  ::== 
   ::== 


3 ?
n
+(3, n)
add1(+(3, n))
(add1) (+(3,n))




(define eval-exp
  (lambda (exp)
    (variant-case exp
      [lit (datum) datum]
      [varref (var) (apply-env init-env var)]
      [app (rator rands)
	(let ([proc (eval-exp rator)]
	      [args (eval-rands rands)])
	  (apply-proc proc args))]
      [else (error
	      'Eval-exp
	      "Invalid abstract syntax: ~s"
	      exp)])))


What are

apply-env?
init-env?
eval-rands?
apply-proc?



If exp represents a varref, the value of
exp is the value bound to the variable var.

Q: Where do we get the value from?
A: From the ENVIRONMENT

What is the ENVIRONMENT?
A finite function that takes a symbol and returns
the associated value.

So in this language we consult the environment to
find the value of a bound variable. Currently we 
have only one environment (init-env)

apply-env is simply apply-ff from our ff ADT

(define the-empty-env (create-empty-ff))
(define extend-env extend-ff*)
(define apply-env apply-ff)



eval-rands  maps eval-exp across operands

then we need to apply proc to args

How we apply a proc to args depends on our
representation of procedures.

We abstract away from our representation of
procedures and arguments by using a procedure
apply-proc.

How do we represent procedures?



Initially:
We only have primitive procedures:

 ::== 
 ::==  |  
 ::==  | 
 ::== 

	+, - , * , add1, sub1

we will use records to represent procedures:



(define-record prim-proc (prim-op))

(define apply-proc
  (lambda (proc args)
    (variant-case proc
      [prim-proc (prim-op) (apply-prim-op prim-op args)]
      [else (error
	      'apply-proc
	      "Invalid Procedure")])))

(define apply-prim-op
  (lambda (prim-op args)
    (case prim-op
      [(+) (+ (car args) (cadr args))]
      [(-) (- (car args) (cadr args))]
      [(*) (* (car args) (cadr args))]
      [(add1) (+ (car args) 1)]
      [(sub1) (- (car args) 1)]
      [else (error
	      'apply-prim-op
	      "invalid primitive operator name: ~s"
	      prim-op)])))



(define-record prim-proc (prim-op))
(define the-empty-env (create-empty-env))
(define extend-env extend-ff*)
(define apply-env apply-ff)

According to this representation, we are using the 
same names as scheme to represent our primitive ops.
Therefore:

(define prim-op-names
  '(+ - * add1 sub1))

(define init-env
  (extend-env
    prim-op-names
    (map make-prim-proc prim-op-names)
    the-empty-env))

extends the empty environemnt with
a finite function that associates 

the list of prim-op-names
with
the list prim-proc-records (returned by map)

Note that extend-env is extend-ff*

(extend-ff* '(a b c) '(27 992 78) ff)
adds the associations:
	(a 27)	
	(b 992)
	(c 78)
to ff



We have our first interpreter!
(define eval-exp
  (lambda (exp)
    (variant-case exp
      [lit (datum) datum]
      [varref (var) (apply-env init-env var)]
      [app (rator rands)
	(let ([proc (eval-exp rator)]
	      [args (eval-rands rands)])
	  (apply-proc proc args))]
      [else (error
	      'Eval-exp
	      "Invalid abstract syntax: ~s"
	      exp)])))

(define eval-rands
  (lambda (rands)
    (map eval-exp rands)))



How do we parse expressions for this interpreter?

Well we can write a new parser that handles
add1(+ (3, n))

or we can change our language to be more like
scheme
and use the parse we wrote earlier

(add1 (+ 3 n))

Our interpreter does not change, only the parser
changes! ===> Good design

(define run
  (lambda (exp)
    (eval-exp (parse exp))))

> (run '(+ 5 10))
15
> (run '5)
5
...



(define repl
  (lambda ()
    (display "==> ")
    (write (eval-exp (parse (read))))
    (newline)
    (repl)))

> > (repl)
==> 5
5
==> (+ 1 2)
3
==> (- 3 4)
-1
==> (* (+ 0 9) 7 8)
63
==> 

Please read pages 139 - 146
aka: section 5.1

    

Sushil Louis
Last modified: Fri Nov 5 12:40:25 PST 1999