Name: WRITE YOUR NAME HERE
// TEST HELPER
def passed(points: Int) {
require(points >=0)
if (points == 1) print(s"
*** Tests Passed (1 point) ***
")
else print(s"
*** Tests Passed ($points points) ***
")
}
Problem 1 (30 Points): Mutual Recursion in Lettuce
In class, we have explored recursive functions in lettuce using the let rec syntax. In this problem, we will explore, mutually recursive function, specifically two mutually recursive functions.
Consider:
let rec
pos = function (x)
if (x >= 0)
then x
else neg(x)
neg = function (y)
if (y <= 0)
then pos (1 + y * y)
else pos(1 - y)
in
neg(10.0)
The two functions are mutually recursive since pos
calls neg
and vice-versa. Convince yourself that thhe program must return 82
.
1A (5 points): Extending the Abstract Syntax
Consider the grammar specification we have seen thus far.
We wish to add a new rule for two mutually recursive functions
Such that a mutual call such as
let rec
f1 = function (x1) e1
f2 = function (x2) e2
in
e3
is represented in the AST as
LetRec2(f1, x1, e1, f2, x2, e2, e3)
Extend the existing AST specification to add support for LetRec2
.
sealed trait Program
sealed trait Expr
case class Const(f: Double) extends Expr
case class Ident(s: String) extends Expr
case class Minus(e1: Expr, e2: Expr) extends Expr
case class Plus(e1: Expr, e2: Expr) extends Expr
case class Mult(e1: Expr, e2: Expr) extends Expr
case class Eq(e1: Expr, e2: Expr) extends Expr
case class Geq(e1: Expr, e2: Expr) extends Expr
case class IfThenElse(e1: Expr, e2: Expr, e3: Expr) extends Expr
case class Let(x: String, e1: Expr, e2: Expr) extends Expr
case class FunDef(id: String, e: Expr) extends Expr
case class FunCall(calledFun: Expr, argExpr: Expr) extends Expr
case class LetRec(funName: String, param: String, funExpr: Expr, bodyExpr: Expr) extends Expr
??? // YOUR CODE HERE
case class TopLevel(e: Expr) extends Program
//BEGIN TEST
val x = Ident("x")
val y = Ident("y")
val foo = Ident("foo")
val bar = Ident("bar")
val e1 = IfThenElse( Geq(x, Const(0.0)), x, FunCall(bar, Plus(x, Const(1.0)))) // if x >= 0 then x else bar(1 + x)
val e2 = IfThenElse( Geq(Const(1.0), x), Plus(Const(2.0), x), FunCall(foo, Minus(x, Const(2.0)))) // if 1 >= x then 2 + x else foo(x-2)
val e3 = FunCall(bar, Const(10))
val lr2 = LetRec2("foo", "x", e1, "bar", "x", e2, e3)
val p1 = TopLevel(lr2)
passed(3)
//END TEST
//BEGIN TEST
val x = Ident("x")
val y = Ident("y")
val foo = Ident("foo")
val bar = Ident("bar")
val e11 = IfThenElse(Geq(x, Const(0.0)), FunCall(bar, Minus(Const(1.0), x)),Mult(x, FunCall(foo, Minus(x, Const(1.0))) ))
val e1 = IfThenElse(Eq(x, Const(0.0)), Const(1.0), e11)
val e2 = IfThenElse(Geq(Const(0.0), y), Mult(Const(-0.5), y) , FunCall(foo, y))
val e3 = FunCall(foo, Const(10.5))
val lr3 = LetRec2("foo", "x", e1, "bar", "y", e2, e3)
passed(2)
//END TEST
1B (10 points): Build an Environment to Handle Mutual Recursion
In class we saw how to build an environment that handles recursive calls using the ExtendRec
construct.
Now we propose an ExtendMutualRec2
construct such that
Complete the mathematical specification of ExtendMutualRec2ExtendMutualRec2. Let π=ExtendMutualRec2(σ,f1, x1, e1, f2, x2, e2))π=ExtendMutualRec2(σ,f1, x1, e1, f2, x2, e2)).
Fill in the appropriate values for 1,2,31,2,3.
Write your answer in the cell bellow. You can make a numbered list in markdown to represent your answers as follows:
- First
- Second
- And so on…
YOUR ANSWER HERE
1C (9 points): Code up the Environment Spec
Implement the environment for ExtendMutualRec
.
sealed trait Environment
sealed trait Value
case object EmptyEnv extends Environment
case class Extend(x: String, v: Value, sigma: Environment) extends Environment
case class ExtendRec(f: String, x: String, e: Expr, sigma: Environment ) extends Environment
case class ExtendMutualRec2(
??? // YOUR CODE HERE
) extends Environment
/* -- We need to redefine values to accomodate the new representation of environments --*/
case class NumValue(d: Double) extends Value
case class BoolValue(b: Boolean) extends Value
case class Closure(x: String, e: Expr, pi: Environment) extends Value
case object ErrorValue extends Value
/*2. Operators on values */
def valueToNumber(v: Value): Double = v match {
case NumValue(d) => d
case _ => throw new IllegalArgumentException(s"Error: Asking me to convert Value: $v to a number")
}
def valueToBoolean(v: Value): Boolean = v match {
case BoolValue(b) => b
case _ => throw new IllegalArgumentException(s"Error: Asking me to convert Value: $v to a boolean")
}
def valueToClosure(v: Value): Closure = v match {
case Closure(x, e, pi) => Closure(x, e, pi)
case _ => throw new IllegalArgumentException(s"Error: Asking me to convert Value: $v to a closure")
}
/*-- Operations on environments --*/
def lookupEnv(pi: Environment, x: String): Value = pi match {
case EmptyEnv => throw new IllegalArgumentException(s"Error could not find string $x in environment")
case Extend(y, v, _) if y == x => v
case Extend(_, _, sigma) => lookupEnv(sigma, x)
case ExtendRec(f, y, e, sigma) => if (x == f)
Closure(y, e, pi)
else
lookupEnv(sigma, x)
case ExtendMutualRec2(f1, x1, e1, f2, x2, e2, sigma ) =>
{
??? // YOUR CODE HERE
}
}
// BEGIN TEST
val env: Environment = ExtendMutualRec2("f1", "x1", Const(0.0), "f2", "x2", Const(0.0), EmptyEnv)
passed(4)
// END TEST
// BEGIN TEST
val env: Environment = ExtendMutualRec2("f1", "x1", Const(0.0), "f2", "x2", Const(0.0), EmptyEnv)
// Ensure looking up either function gets us the right value, no matter how many times we recurse.
val f1 @ Closure(_, _, pi1) = lookupEnv(env, "f1")
val f2 @ Closure(_, _, pi2) = lookupEnv(env, "f2")
lookupEnv(pi1, "f1") == f1
lookupEnv(pi1, "f2") == f2
lookupEnv(pi2, "f2") == f1
lookupEnv(pi2, "f2") == f2
passed(5)
// END TEST
1D (6 points): Interpreter
Complete the interpreter for this new node.
def evalExpr(e: Expr, env: Environment): Value = {
/* Method to deal with binary arithmetic operations */
def applyArith2 (e1: Expr, e2: Expr) (fun: (Double , Double) => Double) = {
val v1 = valueToNumber(evalExpr(e1, env))
val v2 = valueToNumber(evalExpr(e2, env))
val v3 = fun(v1, v2)
NumValue(v3)
} /* -- We have deliberately curried the method --*/
/* Helper method to deal with unary arithmetic */
def applyArith1(e: Expr) (fun: Double => Double) = {
val v = valueToNumber(evalExpr(e, env))
val v1 = fun(v)
NumValue(v1)
}
/* Helper method to deal with comparison operators */
def applyComp(e1: Expr, e2: Expr) (fun: (Double, Double) => Boolean) = {
val v1 = valueToNumber(evalExpr(e1, env))
val v2 = valueToNumber(evalExpr(e2, env))
val v3 = fun(v1, v2)
BoolValue(v3)
}
e match {
case Const(f) => NumValue(f) // Same as before
case Ident(x) => lookupEnv(env, x) // Changed to accomodate the new environment definitions.
/* Ditto as before */
case Plus(e1, e2) => applyArith2 (e1, e2) ( _ + _ )
/* Ditto as before */
case Minus(e1, e2) => applyArith2(e1, e2) ( _ - _ )
/* Ditto as before */
case Mult(e1, e2) => applyArith2(e1, e2) (_ * _)
/* Ditto as before */
case Geq(e1, e2) => applyComp(e1, e2)(_ >= _)
/* Ditto as before */
case Eq(e1, e2) => applyComp(e1, e2)(_ == _)
/* Ditto as before */
case IfThenElse(e1, e2, e3) => {
val v = evalExpr(e1, env)
v match {
case BoolValue(true) => evalExpr(e2, env)
case BoolValue(false) => evalExpr(e3, env)
case _ => throw new IllegalArgumentException(s"If-then-else condition expr: ${e1} is non-boolean -- evaluates to ${v}")
}
}
/* Ditto as before */
case Let(x, e1, e2) => {
val v1 = evalExpr(e1, env) // eval e1
val env2 = Extend(x, v1, env) // create a new extended env
evalExpr(e2, env2) // eval e2 under that.
}
/* Ditto as before */
case FunDef(x, e) => {
Closure(x, e, env) // Return a closure with the current enviroment.
}
/* Ditto as before */
case FunCall(e1, e2) => {
val v1 = evalExpr(e1, env)
val v2 = evalExpr(e2, env)
v1 match {
case Closure(x, closure_ex, closed_env) => {
// First extend closed_env by binding x to v2
val new_env = Extend(x, v2, closed_env)
// Evaluate the body of the closure under the extended environment.
evalExpr(closure_ex, new_env)
}
case _ => throw new IllegalArgumentException(s"Function call error: expression $e1 does not evaluate to a closure")
}
}
case LetRec(rfun, x, fExpr, bExpr) => {
val env2 = ExtendRec(rfun, x, fExpr, env)
evalExpr(bExpr, env2)
}
case LetRec2(f1, x1, e1, f2, x2, e2, e) => {
??? // YOUR CODE HERE
}
}
}
def evalProgram(p: Program) = {
p match {
case TopLevel(e) => evalExpr(e, EmptyEnv)
}
}
//BEGIN TEST
val x = Ident("x")
val y = Ident("y")
val foo = Ident("foo")
val bar = Ident("bar")
val e1 = IfThenElse( Geq(x, Const(0.0)), x, FunCall(bar, Plus(x, Const(1.0)))) // if x >= 0 then x else bar(1 + x)
val e2 = IfThenElse( Geq(Const(1.0), x), Plus(Const(2.0), x), FunCall(foo, Minus(x, Const(2.0)))) // if 1 >= x then 2 + x else foo(x-2)
val e3 = FunCall(bar, Const(10))
val lr2 = LetRec2("foo", "x", e1, "bar", "x", e2, e3)
val p1 = TopLevel(lr2)
assert(evalProgram(p1) == NumValue(8.0), "Test 1 of Set 1 failed")
val e4 = FunCall(foo, Const(12.0))
val p2 = TopLevel(LetRec2("foo", "x", e1, "bar", "x", e2, e4))
assert(evalProgram(p2) == NumValue(12.0), "Test 2 of Set 1 failed")
val e5 = FunCall(foo, Const(-12.0))
val p3 = TopLevel(LetRec2("foo", "x", e1, "bar", "x", e2, e5))
assert(evalProgram(p3) == NumValue(-9.0), "Test 3 of Set 1 failed")
val e6 = FunCall(bar, Const(-12.0))
val p4 = TopLevel(LetRec2("foo", "x", e1, "bar", "x", e2, e6))
assert(evalProgram(p4) == NumValue(-10.0), "Test 4 of Set 1 failed")
val e7 = FunCall(bar, Const(1.9))
val p5 = TopLevel(LetRec2("foo", "x", e1, "bar", "x", e2, e7))
assert(evalProgram(p5) == NumValue(2.9), "Test 5 of Set 1 failed")
passed(3)
//END TEST
//BEGIN TEST
/*
let rec
pos = function (x)
if (x >= 0)
then
if (x <= 0.5)
then x
else 0.5 + pos(x-1)
else neg(x)
neg = function (y)
if (y <= 0)
then pos (1 + y * y)
else pos(1 - y)
in
...
*/
val x = Ident("x")
val y = Ident("y")
val pos = Ident("pos")
val neg = Ident("neg")
val e11 = IfThenElse(Geq(Const(0.5), x), x, Plus(Const(0.5), FunCall(pos, Minus(x, Const(1.0)))))
val e1 = IfThenElse(Geq(x, Const(0.0)), e11, FunCall(neg, x))
val e2 = IfThenElse(Geq(Const(0.0), y), FunCall(pos, Plus(Const(1.0), Mult(y,y))), FunCall(pos, Minus(Const(1.0), y)))
val t1 = FunCall(neg, Const(10.0))
val lr1 = LetRec2("pos", "x", e1, "neg", "y", e2, t1)
val p1 = TopLevel(lr1)
assert(evalProgram(p1) == NumValue(41.0), "Test 1 of set 2 failed")
val t2 = FunCall(pos, Const(-8.0))
val lr2 = LetRec2("pos", "x", e1, "neg", "y", e2, t2)
val p2 = TopLevel(lr2)
assert(evalProgram(p2) == NumValue(32.5), "Test 2 of set 2 failed")
val t3 = FunCall(pos, Const(-0.5))
val lr3 = LetRec2("pos", "x", e1, "neg", "y", e2, t3)
val p3 = TopLevel(lr3)
assert(evalProgram(p3) == NumValue(0.75), "Test 3 of set 2 failed")
passed(3)
//END TEST
Problem 2 (15 points): Block of Statements in Lettuce with References.
You have been using a sequence of statements in almost all languages that you have learned but for Lettuce. If we write a sequence of expressions (statements) as follows
<expression 1> ;
<expression 2> ;
<expression 3> ;
<expression 4> ;
...
<expression n>
Each of these expressions is interpreted in sequence starting from 1 to n. The return value of the entire block of expressions (statements) is simply the value returned by the very last statement.
We would like to add this to Lettuce in the form of a new production.
The expression Seq(e1, e2)Seq(e1, e2) is a sequence e1
followed by e2
. The overall value of a Seq
is that returned by e2
. However, since we have Lettuce with references, it is possible that e1
may have side effects that are visible in e2
.
Longer compositions can be obtained by nesting Seq.
Seq( e1, Seq(e2, Seq(e3, Seq(e4, e5))))
2A (5 points): Extend the abstract syntax with Seq
sealed trait Program
sealed trait Expr
case class TopLevel(e: Expr) extends Program
case class Const(v: Double) extends Expr // Expr -> Const(v)
case class Ident(s: String) extends Expr // Expr -> Ident(s)
// Arithmetic Expressions
case class Plus(e1: Expr, e2: Expr) extends Expr // Expr -> Plus(Expr, Expr)
case class Minus(e1: Expr, e2: Expr) extends Expr // Expr -> Minus(Expr, Expr)
case class Mult(e1: Expr, e2: Expr) extends Expr // Expr -> Mult (Expr, Expr)
// Boolean Expressions
case class Geq(e1: Expr, e2:Expr) extends Expr
case class Eq(e1: Expr, e2: Expr) extends Expr
//If then else
case class IfThenElse(e: Expr, eIf: Expr, eElse: Expr) extends Expr
//Let bindings
case class Let(s: String, defExpr: Expr, bodyExpr: Expr) extends Expr
//Function definition
case class FunDef(param: String, bodyExpr: Expr) extends Expr
// Function call
case class FunCall(funCalled: Expr, argExpr: Expr) extends Expr
// New Ref
case class NewRef(e: Expr) extends Expr
//DeRef
case class DeRef(lval: Expr) extends Expr
//AssignRef
case class AssignRef(lval: Expr, rval: Expr) extends Expr
// Seq
??? // YOUR CODE HERE
//BEGIN TEST
val s1 = Seq(Const(10.0), NewRef(Const(15.0)))
val s2 = Seq(DeRef(NewRef(Const(20.0))), Let("x", DeRef(NewRef(Const(25.0))), Ident("x")))
val s3 = Seq(Const(10.0), Seq(Const(25.0), Const(35.0)))
val s4 = Seq(Seq(Const(5.0), Const(10.0)), Seq(Const(25.0), Const(35.0)) )
passed(5)
//END TEST
2B (5 Points): Write Semantics
We would like to write a semantic rule for evaluating Seq(e1, e2)Seq(e1, e2). Complete the rules for the OK and error cases by filling in the ??? marks below.
Fill in the appropriate values for 1,2,31,2,3.
Write your answer in the cell bellow. You can make a numbered list in markdown to represent your answers as follows:
- First
- Second
- And so on…
YOUR ANSWER HERE
2C (5 points): Implement the code
Extend the implementation of the interpreter from the notebook on references to add support for Seq.
sealed trait Value
/*-- Now we can finish the rest --*/
case class NumValue(f: Double) extends Value
case class BoolValue(b: Boolean) extends Value
/*-- Note: to get recursion working, we will need to make environments different --*/
case class Closure(x: String, e: Expr, pi: Map[String, Value]) extends Value
/* -- references are here -- */
case class Reference(j: Int) extends Value
case object ErrorValue extends Value
/*2. Operators on values */
def valueToNumber(v: Value): Double = v match {
case NumValue(d) => d
case _ => throw new IllegalArgumentException(s"Error: Asking me to convert Value: $v to a number")
}
def valueToBoolean(v: Value): Boolean = v match {
case BoolValue(b) => b
case _ => throw new IllegalArgumentException(s"Error: Asking me to convert Value: $v to a boolean")
}
def valueToClosure(v: Value): Closure = v match {
case Closure(x, e, pi) => Closure(x, e, pi)
case _ => throw new IllegalArgumentException(s"Error: Asking me to convert Value: $v to a closure")
}
/*3. Immutable Store */
case class ImmutableStore(val nCells: Int, val storeMap: Map[Int, Value])
def createNewCell(s: ImmutableStore, v: Value): (ImmutableStore, Int) = {
/*- make a new cell -*/
val j = s.nCells
val nMap = s.storeMap + (j -> v)
val nStore = ImmutableStore(s.nCells + 1, nMap) // Make a new store with one more cell
(nStore, j)
}
def lookupCellValue(s: ImmutableStore, j: Int): Value = {
if (s.storeMap.contains(j)){
s.storeMap(j)
} else {
throw new IllegalArgumentException(s"Illegal lookup of nonexistant location $j")
}
}
def assignToCell(s: ImmutableStore, j: Int, v: Value): ImmutableStore = {
if (s.storeMap.contains(j)){
val nMap = s.storeMap + (j -> v) // Update the store map.
ImmutableStore(s.nCells, nMap)
} else {
throw new IllegalArgumentException(s"Illegal assignment to nonexistent location $j")
}
}
def evalExpr(e: Expr, env: Map[String, Value], store: ImmutableStore): (Value, ImmutableStore) = {
/* Method to deal with binary arithmetic operations */
def applyArith2 (e1: Expr, e2: Expr) (fun: (Double , Double) => Double) = {
val (v1, store1) = evalExpr(e1, env, store)
val (v2, store2) = evalExpr(e2, env, store1)
val v3 = fun(valueToNumber(v1), valueToNumber(v2))
(NumValue(v3), store2)
} /* -- We have deliberately curried the method --*/
/* Helper method to deal with unary arithmetic */
def applyArith1(e: Expr) (fun: Double => Double) = {
val (v,store1) = evalExpr(e, env, store)
val v1 = fun(valueToNumber(v))
(NumValue(v1), store1)
}
/* Helper method to deal with comparison operators */
def applyComp(e1: Expr, e2: Expr) (fun: (Double, Double) => Boolean) = {
val (v1, store1) = evalExpr(e1, env, store)
val (v2, store2) = evalExpr(e2, env, store1)
val v3 = fun(valueToNumber(v1), valueToNumber(v2))
(BoolValue(v3), store2)
}
e match {
case Const(f) => (NumValue(f), store)
case Ident(x) => {
if (env contains x)
(env(x), store)
else
throw new IllegalArgumentException(s"Undefined identifier $x")
}
case Plus(e1, e2) => applyArith2 (e1, e2) ( _ + _ )
case Minus(e1, e2) => applyArith2(e1, e2) ( _ - _ )
case Mult(e1, e2) => applyArith2(e1, e2) (_ * _)
case Geq(e1, e2) => applyComp(e1, e2)(_ >= _)
case Eq(e1, e2) => applyComp(e1, e2)(_ == _)
case IfThenElse(e1, e2, e3) => {
val (v, store1) = evalExpr(e1, env, store)
v match {
case BoolValue(true) => evalExpr(e2, env, store1)
case BoolValue(false) => evalExpr(e3, env, store1)
case _ => throw new IllegalArgumentException(s"If-then-else condition expr: ${e1} is non-boolean -- evaluates to ${v}")
}
}
case Let(x, e1, e2) => {
val (v1, store1) = evalExpr(e1, env, store) // eval e1
val env2 = env + (x -> v1) // create a new extended env
evalExpr(e2, env2, store1) // eval e2 under that.
}
case FunDef(x, e) => {
(Closure(x, e, env), store) // Return a closure with the current enviroment.
}
case FunCall(e1, e2) => {
val (v1, store1) = evalExpr(e1, env, store)
val (v2, store2) = evalExpr(e2, env, store1)
v1 match {
case Closure(x, closure_ex, closed_env) => {
// First extend closed_env by binding x to v2
val new_env = closed_env + ( x -> v2)
// Evaluate the body of the closure under the extended environment.
evalExpr(closure_ex, new_env, store2)
}
case _ => throw new IllegalArgumentException(s"Function call error: expression $e1 does not evaluate to a closure")
}
}
case NewRef(e) => {
val (v, store1) = evalExpr(e, env, store)
val (store2, j) = createNewCell(store1, v)
(Reference(j), store2)
}
case DeRef(e) => {
val (v, store1) = evalExpr(e, env, store)
v match {
case Reference(j) => {
val v = lookupCellValue(store1, j)
(v, store1)
}
case _ => throw new IllegalArgumentException(s"Deref applied to an expression that does not evaluate to a reference")
}
}
case AssignRef(e1, e2) => {
val (v1, store1) = evalExpr(e1, env, store)
v1 match {
case Reference(j) => {
val (v2, store2) = evalExpr(e2, env, store1)
val store3 = assignToCell(store2, j, v2)
(v2, store3)
}
case _ => throw new IllegalArgumentException(s"AssignRef applied to argument that is not a reference")
}
}
case Seq(e1, e2) => {
??? // YOUR CODE HERE
}
}
}
def evalProgram(p: Program) = p match {
case TopLevel(e) => {
// Start with empty environment and empty store
val (v1, s1) = evalExpr(e, Map(), new ImmutableStore(0, Map()))
v1
}
}
//BEGIN TEST
val incr = Ident("incr")
val decr = Ident("decr")
val zero = Ident("zero")
val r = Ident("r")
val x = Ident("x")
val e4 = Seq(Seq(Seq(FunCall(zero, r), FunCall(incr, r)), Seq(FunCall(incr, r), FunCall(incr, r))), Seq(FunCall(decr, r), FunCall(decr,r)))
val rr = Let("r", NewRef(Const(15.0)), e4)
val z = Let("zero", FunDef("x", AssignRef(x, Const(0.0))), rr)
val d = Let("decr", FunDef("x", AssignRef(x, Minus(DeRef(x), Const(1.0)))), z)
val i = Let("incr",FunDef("x", AssignRef(x, Plus(DeRef(x), Const(1.0)))), d )
val prog = TopLevel(i)
assert(evalProgram(prog) == NumValue(1.0), "Test 1 Set 3 Failed")
passed(5)
//END TEST
Reviews
There are no reviews yet.