Interpreters

So whats an interpreter?

A program that accepts statements, executes them immediately and then outputs the result.

At it's core these are the only 3 things an interpreter does

input 8 into python interpreter

  1. Accepts input
  2. Executes statement inputted
  3. Outputs result

An interpreter needs to loop through these three steps over and over to allow for more and more input (the Python interpreter doesn't stop after typing one thing). We call this the Read-Eval-Print-Loop (REPL)

Note: an interpreter is likely to be implemented in a different language than which it interprets (example: scheme interpreter written in python)

REPL

Each part of this loop can be further divided up:

Pictorally:

repl diagram

Read

This is an example of the lexer/parser from the scheme project (pulled from lec 21):

lexer/parser

Eval/Apply

Follow the same steps as usual for call expression!

  1. Evaluate operator
  2. Evaluate operand(s)
  3. Apply operator to operand(s)

Apply

When we apply a function we follow the process we followed before when constructing environment diagrams:

  1. Create a new frame
  2. Assign parameters to values
  3. Evaluate the body of the function
EvalApply
Base Cases:Primitive valuesBuilt-in primitive procedures
Symbols
Recursive Calls:eval(sub_expression) for special formseval(body) of user-defined procedure
eval(operator, operands) for call expressions
apply(procedure, arguments)

Counting Calls

This is the process of determining the number of calls to eval and apply are needed to fully evaluate a statement.

You'll want to use the process for evaluating call expressions to determine the number of eval/apply calls.

For example:

 
xxxxxxxxxx
(+ 1 2)

To determine the number of calls let's walk through the statement element by element:

  1. (+ 1 2) this is what we were inputted and we have to call eval on it to do anything with it (+1 eval)

  2. now we follow the 3 previously mentioned steps

    1. + here we evaluate the operator (+1 eval)
    2. 1 we evaluate an operand (+1 eval)
    3. 2 we evaluate an operand (+1 eval)
    4. apply + to 1 and 2, this is the final step for call expressions (+1 apply)

In this case we had: 4 evals and 1 apply

Problems

What does lexer turn these into?

  1.  
    (1 2 4)
  2.  
    (define (f x) 5)

What does the parser turn these into?

  1.  
    xxxxxxxxxx
    ['(', '+', 1, 2, ')']
  2.  
    xxxxxxxxxx
    ['(', 1, '(', 2, ')', 3, ')']

How many calls to scheme_eval/scheme_apply?

  1.  
    xxxxxxxxxx
    (define (f x) (* ((lambda () x)) 2))
  2.  
    xxxxxxxxxx
    ((lambda (x y) (+ (y 2) x)) 3 f)
  3.  
    xxxxxxxxxx
    (let ((f 4)
          (g f))
         (g (+ f (eval (define h ((lambda (a b) (* a b)) f 5)))))
    )
  4.  
    xxxxxxxxxx
    (+ (* (if (and #t) 1 2) 4) (begin (+ 1 2) (+ 3 4)))

Generalized counting Eval/Apply

Given these general forms, write the number of Eval/Apply calls in terms of the expressions (for these problems assume these can be anything so we'll generalize by using and ):

Example:

 
xxxxxxxxxx
(/ a b)

Solution:

  1.  
    xxxxxxxxxx
    (define a (+ b c))
  2.  
    xxxxxxxxxx
    (if a b c)
  3.  
    xxxxxxxxxx
    (define (fn a b) c)
  4.  
    xxxxxxxxxx
    (let ((a b) (c d)) e)
  5.  
    xxxxxxxxxx
    ((lambda (a) b) c)
  6.  
    xxxxxxxxxx
    (and a b c)
  7.  
    AخA
    (begin a b c)