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