In this part of the project you will implement function calls that use either pass by value or pass by reference.
This last part of the project is a bit more complex, since now executing a function may change its caller's environment. To handle this we will change the environment type from a single dictionary to a list of dictionaries which represent the stack of activation records. We will also add an additional case to our data type Value to represent a reference variable. The reference variable will be represented by a pair (String,Int) where the string is the variable name and the integer value is the depth in the stack where the reference is found. For example,
func
f(x:ref)
func g(y:ref)
y := y + 1;
return y;
endfunc
x := g(x) + 1;
return x;
endfunc
a := 5;
write f(a);
write a;
At the point when we are executing the function g, the stack would look like:
At the bottom of the stack is the dictionary for the main program with a containing the value of 5. (The function definitions have been omitted in this diagram). The function f is evaluated adding a new dictionary with the reference variable x with value (a,1). This means that x refers to the variable a in the previous dictionary. Within the body of f, the function g is evaluated. The top of the stack contains the new dictionary for g containing the reference variable y with value (a,2). Since y references x, the value of y actually references the variable a two records down.
So we will now define
data Value = Val Integer |
Fun Function | Ref (String, Int)
type Environment = [Dictionary.T
String Value]
When creating a new environment we need to set up the links correctly for the reference parameters. The following function takes the formal argument, the actual argument (which needs to be a Var) and the environment stack, and returns the modified environment stack with the top dictionary modified with the reference value.
passByRef :: String ->
Expr ->
Environment->
Environment
passByRef f (Var a) (d1:d2:dicts) =
case
Dictionary.lookup
a d2 of
Just (Val n)
->
(Dictionary.insert (f,Ref (a,1)) d1) : d2 :
dicts
Just (Ref (x,n)) ->
(Dictionary.insert
(f, Ref (x,n+1)) d1) : d2 : dicts
If the value of the actual is a Val then the reference
will be one dictionary down. If the value is a Ref then we increment the
reference link.
With the addition of reference variables,
the evaluation of an expression can now possibly change an environment.
We therefore need to change value to return both the integer value of the
expression and the new environment:
value :: Expr
->
Environment ->
(Integer,Environment)
The case for evaluating a variable will now be:
value (Var v) (dict:dicts)=
case Dictionary.lookup v dict ofNotice that the environment is now a list. We lookup the variable in the top dictionary. If it is a Val then we return that value and the unchanged environment. If it is Ref, then we evaluate that variable in the proper dictionary by removing the top n dictionaries from the list.
Modify the rest of value so that it handles pass by reference and returns the modified environment.
We now need to modify exec to handle the new type returned by evaluating expressions. For example,
exec (Assignment var expr : stmts) env input =
exec stmts env2 input
where
(v,env1) =
(value expr env)
env2 = changeVar var v env1
The function changeVar will need to modified so the if var is a reference variable you modify the value in the proper location in the stack.
Modify the rest of exec appropriately.
The following main program includes creating the initial environment.
-- The main program executes the interpreter and runs the program
run :: Program
-> InputList -> [Integer]Send me your modified zipped project in the dropbox in BlackBoard.