You will need to brush up on the ENVIRONMENT MODEL to understand this
lecture. 

----------------------------------------------------------------------

Metacircular Evaluator
  - Writing an interpreter for Dylan in Dylan
  - Disadvantages: not of much value in practice, because
      you can't run it unless you already have Dylan
  - Advantages: 
    * Better understanding of how the language works 
    * See that languages are not magic, just another program!
    * You can change the language (that the interpreter evaluates) -- PS6
  - Environment Model in action
  - Similar to the expression evaluator that we did in PS3, but more complex
     expressions (most of Dylan)

This is an INTERPRETER
  - You give it a program, and it "walks over" the program and does
    stuff, running pieces of it.
It is not a COMPILER
  - You give it a program, and it translates it into another program in
    the machine's native language (machine code).

Interpreters are generally:
  + Easier to build, understand, and get right
  - *MUCH* slower

In PS6, we will build an interpreter based on what is covered in the next few
lectures and recitations.

----------------------------------------------------------------------

We've been looking at techniques for implementing computational
processes as ABSTRACTIONS:

   2 ---> [square] ---> 4


We're going to look at a rather unusual kind of box today, a UNIVERSAL
MACHINE -- an evaluator for Dylan, called Brenda.

  * It can do *anything* that any other machine can.

You give it a DESCRIPTION of any computational process, and it
acts like that process:

       +----------------------------------------------------+
       |                                                    |
 2 --->| (method ((x <number>)) (* x x)) ---> [EVALUATOR]   | ---> 4
       |                                                    |
       +----------------------------------------------------+

Note that the input to the evaluator is a LIST (a DESCRIPTION of
a function to compute) not a Dylan procedure!

Given a description of squaring -- Or anything else! -- act like that.


  * This is a pretty strange kind of box:
       It reconfigures itself so it can act like any other box.

  * The processor one inside a computer is one -- programmed in the
       native machine code for the processor.

Dylan-expr ---> [Metacircular Evaluator] ---> values

It's called a Metacircular Evaluator because:
  * They like fancy words, 
  * It's a Dylan interpreter written in Dylan.

That means there are TWO SEPARATE DYLAN LANGUAGES here!
  * Dylan, the implementation language
  * metacircular Dylan, the one we're writing (a subset)
    We call this one "Brenda" (for all you 90210 fans)

WARNING:
  * They look exactly the same 
    - until we start tinkering with Brenda at least
  * But don't get them confused!

Brenda won't handle all Dylan expressions, some things are too involved.

NOTE, there are *many* layers of evaluation/compilation here:

The Brenda evaluator is written in Dylan, the Dylan evaluator is
written in Java, there is a Java compiler which translates Java code
to JVM code, and another compiler (``just in time'') which runs under
IE or Netscape and translates JVM code to the machine code for your
processor.

----------------------------------------------------------------------

The top level of a Dylan interpreter is a read-eval-print loop which does four
things

  1. Prints the prompt
  2. READS an expression
  3. EVALUATES the expression
  4. PRINTS the result.

(define read-eval-print-loop (method ()
  (bind ((raw (read))
         (result (brenda-eval raw *brenda-global-environment*)))
      (print "Brenda? " raw)
      (print "Brenda==> " result)
      (read-eval-print-loop))))

The reader, READ, builds list structures out of characters. You used
READ in PS3.
  If you type an input
  * it is initially just a sequence of characters, not symbols, lists, etc.
  * The reader turns these characters into list structure.
  * There's a whole big technology of readers we don't have time to
    discuss. 
    - Uses finite state machines (which we did in PS4)

Recall from the environment model that top level expressions are
evaluated wrt the global environment which here is called
*brenda-global-environment*

----------------------------------------------------------------------

What does brenda-eval do?

Remember the environment model:
  * To evaluate a compound expression other than a special form,
    evaluate the subexpressions and apply the value of the first to the rest. 
  * To evaluate a primitive expression, either look up the variable value
    or if it is self evaluating return its value.

It's a dispatch on the type of expression, implemented using generic functions.

Because arguments are always evaluated for regular functions and not
evaluated for special forms, we let brenda-apply handle the evaluation
of arguments, depending on the kind of application (regular function
or special form).  But the first position of the combination must be
evaluated so brenda-apply will know what it is (regular function,
special form, etc).

(define-generic-function brenda-eval ((obj <object>) (env <brenda-frame>)))

Most kinds of objects evaluate to themselves.

(add-method brenda-eval (method ((obj <object>) (env <brenda-frame>)) obj))

Evaluating symbols causes their associated value to be looked up.

(add-method brenda-eval (method ((obj <symbol>) (env <brenda-frame>))
  (lookup obj env)))

Evaluating a compound expression (a list structure) calls brenda-apply

(add-method brenda-eval (method ((obj <pair>) (env <brenda-frame>))
  (brenda-apply (brenda-eval (head obj) env) (tail obj) env)))


It is also often necessary to evaluate a sequence of expressions and
then return the value of the last expression, like in the body of a
method or a bind, so this is handy.

(define eval-sequence 
  (method ((seq <list>) (env <brenda-frame>))
    (cond ((null? seq) #f)
          ((null? (tail seq)) (brenda-eval (head seq) env))
          (else: (brenda-eval (head seq) env)
                 (eval-sequence (tail seq) env)))))

-------------------------------------------------------------------

What does brenda-apply do?

Remember the environment model:
  * To apply a procedure, evaluate the body of the procedure in a new
      environment that is built by extending the procedure's environment
  * To apply a special form, use a particular rule for that special form
  * To apply a primitive, do the computation for that primitive

(define-generic-function brenda-apply ((obj <object>)
                                       (args <list>)
                                       (env <brenda-frame>)))

(add-method brenda-apply (method ((obj  <object>)
                                  (args <list>)
                                  (env  <brenda-frame>))
  (brenda-error "Apply : First element in combination isn't a function."
		obj)))

For a primitive
----------------

Evaluate the arguments in the environment in which the primitive is
being called.  Apply the Dylan function that performs the work of the
primitive only after verifying the arguments satisfy the type
constraints of the parameters.

(add-method brenda-apply (method ((obj  <brenda-primitive>)
                                  (args <list>)
                                  (env  <brenda-frame>))
  (bind (((evaluated-args <list>) (map (method (x) (brenda-eval x env)) args)))
     (args-params-match? (params obj) evaluated-args)
     (apply (calls obj) evaluated-args)))
     
CALLS is an accessor of <brenda-primitive> that looks up what Dylan
form to apply.

For a special form
-------------------

Just apply the Dylan function that does the work of the special form
without evaluating the arguments.  Assume the special form will
perform it own type checking.

(add-method brenda-apply (method ((obj  <brenda-special-form>)
                                  (args <list>)
                                  (env  <brenda-frame>))
  ((calls obj) args env)))



For regular functions (user-defined methods), 
---------------------------------------------- 

we evaluate the arguments and then evaluate the body in the extended
environment

(add-method brenda-apply
 (method ((obj  <brenda-user-method>)
          (args <list>)
          (e    <brenda-frame>))
   (bind (((evaluated-args <list>)
           (map (method (x) (brenda-eval x e))
                args)))
      (args-params-match? (params obj) evaluated-args)
      (bind ((new-env (expand-environment (params obj)
					  evaluated-args
					  (env obj))))
         (eval-sequence (body obj) new-env)))))
     
args-params-match? is a useful function that checks that each evaluated
argument is of a matching type to the corresponding parameter's type.

----------------------------------------------------------------------

That's all very nice, but how do we actually represent something like
an environment or a procedure object??

  * Internal representations in the interpreter!
    - (Dylan uses is own such internal structures, which are Java data
       structures; interested folks can check out the source for Dylan)


ENVIRONMENT: sequence of frames, each frame is a set of bindings

Binding: variable name and value  

(define-class <brenda-frame> (<object>)
  (bindings <list>))

(define-class <brenda-local-frame> (<brenda-frame>)
  (previous  <brenda-frame>))

(define-class <brenda-global-frame> (<brenda-frame>))

(define-class <brenda-binding> (<object>)
  (variable  <symbol>)
  (value     <object>))

(define make-brenda-local-frame 
  (method ((prev <brenda-frame>))
    (make <brenda-local-frame> bindings: '() previous: prev)))

(define make-brenda-global-frame
 (method ()  
    (make <brenda-global-frame> bindings: '())))

(define make-brenda-binding (method ((var <symbol>) (val <object>))
  (make <brenda-binding> variable: var value: val)))

----------------------------------------------------------------


The procedure expand-environment, which is used to create a new frame
and link it in, is a good illustration of how the environment data
structures work:

Create a new frame, link it into the current frame, an puts bindings
for each variable into that frame

p = params
a = arguments
e = environment

(define expand-environment
  (method ((p <list>) (a <list>) (e <brenda-frame>))
    (bind (((e1 <brenda-frame>) (make-brenda-local-frame e)))
      (set! (bindings e1)
            (map make-brenda-binding (map variable p)
		                     a))
        e1)))

[Each elt in p has two parts, VARIABLE and TYPE. The accessor VARIABLE
extracts out the variable part. Note that p is a list of
<brenda-parameter>s so VARIABLE is an accessor of this type. It is
defined below.]

--------------------------------------------------------------------------


PROCEDURES:
  Recall that they have three parts:
  >>> Draw a double-circle procedure object <<<
    1. Parameter list
    2. Body
    3. Environment where created
  So, do the obvious thing:

The class <function> is for all ``runnables'' including special forms

(define-class <brenda-function> (<object>))  

The class <brenda-method> is for both user-defined and primitive methods, it
provides argument list.

(define-class <brenda-method> (<brenda-function>)
  (params  <list>))

(define-class <brenda-user-method> (<brenda-method>)
  (body  <list>)
  (env   <brenda-frame>))

This actually creates an internal representation of a procedure
object, given an internal representation of the parameter list, the
body text and the internal representation of the procedure's
environment (it gets called when a method or special form is
evaluated).

(define make-brenda-user-method (method ((p <list>)
                                         (body <list>)
                                         (e <brenda-frame>))
  (make <brenda-user-method> params: p body: body e: env)))


It is used like this:
    (make-brenda-user-method (convert-params (head args) env)
                             (tail args)
                             env))))

   [To see that this makes sense, consider

   (method ((x <number>) (* x x)))

    we strip off the METHOD to obtain

    args =  (((x <number>) (* x x)))

    so (head args) is ((x <number>)) and (tail args) is ((* x x)) 
     as desired.]



Where convert-params makes internal parameter representations using the
following structure:

(define-class <brenda-parameter> (<object>)
  (variable  <symbol>)
  (type      <brenda-class>))

(define make-brenda-parameter (method ((var <symbol>) (t <brenda-class>))
  (make <brenda-parameter> variable: var type: t)))


----------------------------------------------------------------------

Let's try it out.

Brenda? ((method ((x <number>)) (+ x 3)) 2)

1. The top level REPL calls brenda-eval with this expression and the
   global environment.

2. In brenda-eval, this is a combination (i.e., a list structure)
    so the first argument gets evaluated and then brenda-apply gets
    called

     brenda-eval gets called with the first elt of the combination,
          which is (method ((x <number>)) (+ x 3))
        This is a list structure so the first argument gets evaluated
          and then brenda-apply gets called

           brenda-eval gets called with the first elt of the comb,
                which is method
              This is a symbol which gets looked up (it value
               is defined in the global envt as a special form)
           brenda-apply gets called with the internal rep of
               the method special form as its first arg, and with
               the unevaluated arglist (((x <number>)) (+ x 3))
             this call returns an internal representation of a
               procedure object with a single parameter x and body
               (+ x 3), by calling make-brenda-user-method

3.  brenda-apply gets called with its first argument being
     the procedure object created above, and its second argument
     being the list of unevaluated arguments (2)  
    This calls brenda-eval on the body of the procedure, with
     the environment extended by evaluated argument list

4. Brenda-apply extends the current environment by binding the
   parameters of the function to the given arguments
     in this case one parameter (x <number>) and one parameter 2
   And evaluates the body in the new envt

5. brenda-eval gets called with (+ x 3).
     This is a list structure so the first argument gets evaluated
       and then brenda-apply gets called with the resulting
       value which is the addition primitive.

6. Brenda-apply applies the addition primitive to the values
    of the argumetn list (x 3), so brenda-eval gets mapped down this
    list
          brenda-eval x -- look up in this extended envt = 2
          brenda-eval 3 -- 3
      
Value of applying the primitive is 5.