This project extends the Lettuce language with arithmetic, let bindings, function calls and recursive functions with an extra value type: figures. We will augment the language with operations that can allow us to
- Draw figures made up of rectangles, lines, triangles and circles.
- Place these figures besides each other
- Rotate these figures
Your assigment for this project will include:
- Complete the interpreter for this language based on the semantics that are provided.
- Write some programs to generate some of the example figures shown.
As previously, you are given some code that includes code we have written for Lettuce with arithmetic and Booleans from the notebooks. Your job is to write the missing code. Please read this documentation properly.
Beta Notice: As always, we make up things as we go along. Please feel free to point out ambiguities/ask for clarifications on piazza.
Running the code and submitting
Environment setup is the same as for project 1.
Submission instructions are same as for project 1. Run the command checkAndZipSubmission
to create a submission.zip file.
The Language Extension
We will extend Lettuce to allow us to write programs like thus
let a = rectangle(1) in
let b = circle(0.5) in
a/b
a
is assigned to a rectangle of side length 1
(it will be a square) and b
to a circle of radius 0.5
. Thus a,b
are of type Figures
We will define the division of two figures as placing the figure a
on top of figure b
.
The result should place a rectangle on top of a circle like thus.
We are using the /
operator to operate both on numbers or figures. This is an example of operator overloading.
Likewise we will overload other operators as follows:
Operator | Argument 1 Type | Argument 2 Type | Meaning |
---|---|---|---|
+ | Number | Number | Add two numbers |
+ | Figure(f1) | Figure(f2) | Make a composite figure by overlapping the two figures f1, f2 |
* | Number | Number | Multiply two numbers |
* | Figure(f1) | Figure(f2) | Make a composite figure by placing figure f2 to the right of figure f1 |
/ | Number | Number | Division |
/ | Figure(f1) | Figure(f2) | Place figure f1 on top of figure f2 |
/ | Figure(f1) | Number (d) | Rotate figure f1 around (0,0) by angle d in radians |
– | Number | Number | Subtract |
Note that all other operations are meaningless. For instance, trying to subtract one figure from other leads to error. Likewise, adding a number to a figure leads to an error.
The table above is going to be very important. Please refer back to it when you interpret the programs below.
What does the program below do?
let f1 = triangle(1) in
(f1 * f1)/f1
The star operator places two triangles one besides other and the / operator places a new triangle on top.
The language allows functions as well:
let f = function (x)
rectangle(x) * ((triangle(x)/triangle(x)) * circle(x))
in
f(1) + f(2) / f(3)
Can you try interpreting what figure will be generated by the program above?
We will also include recursion and now interesting things happen.
letrec serp = function (x)
if (x <= 1)
then (triangle(x)*triangle(x))/triangle(x)
else (
let f = serp(x-1) in
(f * f) / f
) in
serp(5)
Or try this one
letrec mandala = function (x)
let ang = 3.1415/10 in
if (x <= 1)
then rectangle(2)+triangle(2)
else (rectangle(2)+triangle(2))+(mandala(x-1)/ang)
in
mandala(20)
Intrigued? Interested in bringing this language to life? March on forward.
Abstract Syntax of LettuceWithFig
We will have the following abstract syntax borrowed from the language with recursive calls. Note that the key extensions are shown in red.
A program is simply a expression wrapped around the TopLevel constructor.
Expressions are given as follows:
Canvas: A value type for representing figures.
We are first going to define a value type called Canvas. A canvas is going to collect a bunch of shapes with coordinates. We will define some basic operations on canvases:
- creating a canvas with a single shape.
- bounding box for a canvas.
- overlap of two canvases.
- placing one canvas on top of another to yield a new canvas.
- placing one canvas to the left of another to yield a new canvas.
- rotation of a canvas.
A canvas is of the form Canvas({o1,…,on})Canvas({o1,…,on}), wherein o1,…,ono1,…,on are the objects in the canvas. Each object oioi is itself one of the following:
- Polygon({(x0,y0),…,(xk−1,yk−1)})Polygon({(x0,y0),…,(xk−1,yk−1)}): A polygon whose vertex coordinates are specified.
- Circle((xc,yc),r)Circle((xc,yc),r): A circle with the specified center (xc,yc)(xc,yc) and radius.
Example of Canvas
Consider a canvas
Canvas ( {
Polygon( { (0,0), (1,0), (0.5,0.5) } ) ,
Circle ( (1,1), 1) ,
Polygon( { (1,1), (2,1), (2,2), (1,2) } )
} )
Canvases are going to be a value type for our interpreter along with numbers, booleans, closures and (nominally) error.
Operations on canvas.
Bounding Box of a canvas.
A bounding box for a canvas cc is given by boundingBox(c)boundingBox(c) which is a tuple of 44 numbers of the form (xmin,xmax,ymin,ymax)(xmin,xmax,ymin,ymax) where
- xminxmin is the smallest xx coordinate for any object in the canvas.
- xmaxxmax is the largest xx coordinate for any object in the canvas.
- yminymin is the smallest yy coordinate for any object in the canvas.
- ymaxymax is the largest yy corrdinate for any object in the canvas.
Once again consider the example canvas from before:
Canvas ( {
Polygon( { (0,0), (1,0), (0.5,0.5) } ) ,
Circle ( (1,1), 1) ,
Polygon( { (1,1), (2,1), (2,2), (1,2) } )
} )
Its bounding box is given by (0,2,0,2)(0,2,0,2) with xmin=ymin=0xmin=ymin=0 and xmax=ymax=2xmax=ymax=2.
Translate operation on a canvas
Given a Canvas object cc , its translation by (xShift,yShift)(xShift,yShift) is denoted translate(c,xShift,yShift)translate(c,xShift,yShift) and yields a new canvas.
The translate operation adds the value xShift
to all x coordinates of the object in the canvas and yShift
to all y coordinates of the object in the canvas.
Once again consider the example canvas from before:
Canvas ( {
Polygon( { (0,0), (1,0), (0.5,0.5) } ) ,
Circle ( (1,1), 1) ,
Polygon( { (1,1), (2,1), (2,2), (1,2) } )
} )
We wish to perform a translation by (2,3)(2,3) where xShift = 2 and yShift = 3. The result will be the canvas.
Canvas ( {
Polygon( { (2,3), (3,3), (2.5,3.5) } ) ,
Circle ( (3,4), 1) ,
Polygon( { (3,4), (4,4), (4,5), (3,5) } )
} )
Rotation Operation on a canvas.
Given a Canvas object cc , its rotation by θθ in radians is denoted rotate(c,θ)rotate(c,θ) and yields a new canvas.
Recall from your calculus class that rotating a point (x,y)(x,y) by angle θθ in radians yields
x′=xcos(θ)−ysin(θ), y′=xsin(θ)+ycos(θ)x′=xcos(θ)−ysin(θ), y′=xsin(θ)+ycos(θ)
As an example, let us rotate the canvas from before:
Canvas ( {
Polygon( { (0,0), (1,0), (0.5,0.5) } ) ,
Circle ( (1,1), 1) ,
Polygon( { (1,1), (2,1), (2,2), (1,2) } )
} )
by 30∘30∘ or π6π6 radians.
Rotating a canvas is given by rotating each object in the canvas.
- Rotating a polygon is given by applying rotation by θθ to each vertex
- Rotating a circle is given by applying rotation by θθ to the center of the circle. Radius is unchanged.
Overlapping one canvas with other.
Let c1:Canvas(O1)c1:Canvas(O1) with objects in set O1O1 and likewise c2:Canvas(O2)c2:Canvas(O2). We define the canvas
overlap(c1,c2)=Canvas(O1∪O2)overlap(c1,c2)=Canvas(O1∪O2)
The new canvas has objects which are simply the union of objects from canvases c1 and c2.
Placing one canvas on top of another
The placeTop
operator will place one canvas on top of another. Let us consider the operation of placing canvas c2
on top of canvas c1
. To achieve this, we are going to first
- translate the objects in canvas
c2
by ashiftX
andshiftY
that we will compute. - next we will make a new canvas that combines the objects in both.
Let c1:Canvas(O1)c1:Canvas(O1) and c2:Canvas(O2)c2:Canvas(O2) be the two canvases. The operation placeTop(c1,c2)placeTop(c1,c2) is defined as follows:
- Let (xmin,1,xmax,1,ymin,1,ymax,1)(xmin,1,xmax,1,ymin,1,ymax,1) be the result of boundingBox(c1)boundingBox(c1).
- Let (xmin,2,xmax,2,ymin,2,ymax,2)(xmin,2,xmax,2,ymin,2,ymax,2) be the result of boundingBox(c2)boundingBox(c2).
- Define xShift=(xmax,1−xmin,1)/2−(xmax,2−xmin,2)/2xShift=(xmax,1−xmin,1)/2−(xmax,2−xmin,2)/2.
- Define yShift=(ymax,1−ymin,1)yShift=(ymax,1−ymin,1).
- Let c^2c^2 be the canvas obtained by transate(c2,xShift,yShift)transate(c2,xShift,yShift)
- Define the result of placeTop(c1,c2)=overlap(c1,c^2)placeTop(c1,c2)=overlap(c1,c^2).
The calculations above sound somewhat arbitrary but can be easily justified if you look at the figure above.
Placing one canvas to the right of another
The placeRight
operator will place one canvas on top of another. Let us consider the operation of placing canvas c2
to the right of canvas c1
. To achieve this, we are going to first
- translate the objects in canvas
c2
by ashiftX
andshiftY
that we will compute. - next we will make a new canvas that combines the objects in both.
Let c1:Canvas(O1)c1:Canvas(O1) and c2:Canvas(O2)c2:Canvas(O2) be the two canvases. The operation placeRight(c1,c2)placeRight(c1,c2) is defined as follows:
- Let (xmin,1,xmax,1,ymin,1,ymax,1)(xmin,1,xmax,1,ymin,1,ymax,1) be the result of boundingBox(c1)boundingBox(c1).
- Let (xmin,2,xmax,2,ymin,2,ymax,2)(xmin,2,xmax,2,ymin,2,ymax,2) be the result of boundingBox(c2)boundingBox(c2).
- Define xShift=(xmax,1−xmin,1)xShift=(xmax,1−xmin,1).
- Define yShift=(ymax,1−ymin,1)/2−(ymax,2−ymin,2)/2yShift=(ymax,1−ymin,1)/2−(ymax,2−ymin,2)/2.
- Let c^2c^2 be the canvas obtained by transate(c2,xShift,yShift)transate(c2,xShift,yShift)
- Define the result of placeRight(c1,c2)=overlap(c1,c^2)placeRight(c1,c2)=overlap(c1,c^2).
The calculations above sound somewhat arbitrary but can be easily justified if you look at the figure above.
Task 1
Implement the canvas operations using the data structures defined in the file MyCanvas.scala
. Please follow the given scheme and do not modify existing code.
Your results must pass the unit tests provided under CanvasTests
Interpreting Lettuce With Figs.
We will interpret our language now. We will have the following value types:
- NumValue(Double)
- BoolValue(Boolean)
- Closure(String, Expression, Environment)
- FigValue(Canvas)
- Error
Note that we will not really consider error and just throw an exception whenever we encounter error.
For your convenience the operations over values are defined in the file Value.scala
. Notice some refactoring is happening to help out with implementing the operator.
- You may want to modify the functions
- ValueOps.plus, ValueOps.mult, ValueOps.divide, ValueOps.minus and so on.
The semantics of Lettuce with Figs are going to inherit all the existing semantic rules used for handling
- arithmetic
- booleans
- comparisons
- function calls
- let bindings
- recursion
The additional rules need to deal with operations over figures. We provide these rules below.
Semantics of Figs.
The first few rules specify the creation of basic figures.
The rule for a rectangle is as follows:
evalExpr(e,σ)=v, v∈RevalExpr(Rectangle(e),σ)=Canvas({Polygon({(0,0),(0,v),(v,v),(v,0)})}) (rectangle-ok)evalExpr(e,σ)=v, v∈RevalExpr(Rectangle(e),σ)=Canvas({Polygon({(0,0),(0,v),(v,v),(v,0)})}) (rectangle-ok)
The rule tells us how to create a rectangle whose side length is given by the expression e
. The error rules for this is simple and left to the reader.
The rule for a triangle is as follows:
evalExpr(e,σ)=v, v∈RevalExpr(Triangle(e),σ)=Canvas({Polygon({(0,0),(v,0),(v2,3√v2)})}) (triangle-ok)evalExpr(e,σ)=v, v∈RevalExpr(Triangle(e),σ)=Canvas({Polygon({(0,0),(v,0),(v2,3v2)})}) (triangle-ok)
Note that the vertex (v2,3√v2)(v2,3v2) ensures that the triangle is equilateral.
The rules for a circle and line are as follows:
evalExpr(e,σ)=v, v∈RevalExpr(Circle(e),σ)=Canvas({Circle((v,v),v)}) (circle-ok)evalExpr(e,σ)=v, v∈RevalExpr(Circle(e),σ)=Canvas({Circle((v,v),v)}) (circle-ok)
evalExpr(e,σ)=v, v∈RevalExpr(Line(e),σ)=Canvas({Polygon({(0,0),(v,0)})}) (line-ok)evalExpr(e,σ)=v, v∈RevalExpr(Line(e),σ)=Canvas({Polygon({(0,0),(v,0)})}) (line-ok)
The rule for error is simple:
evalExpr(e,σ)=v, v∉R, T∈{Line,Circle,Rectangle,Triangle}evalExpr(T(e),σ)=error (basic-shape-error)evalExpr(e,σ)=v, v∉R, T∈{Line,Circle,Rectangle,Triangle}evalExpr(T(e),σ)=error (basic-shape-error)
Overloading Arithmetic Operators on Figures
We will talk about the semantics of arithmetic operators on figures. Here is the helpful table from before.
Operator | Argument 1 Type | Argument 2 Type | Meaning |
---|---|---|---|
+ | Number | Number | Add two numbers |
+ | Figure(f1) | Figure(f2) | Make a composite figure by overlapping the two figures f1, f2 |
* | Number | Number | Multiply two numbers |
* | Figure(f1) | Figure(f2) | Make a composite figure by placing figure f2 to the right of figure f1 |
/ | Number | Number | Division |
/ | Figure(f1) | Figure(f2) | Place figure f1 on top of figure f2 |
/ | Figure(f1) | Number (d) | Rotate figure f1 around (0,0) by angle d in radians |
– | Number | Number | Subtract |
Rules for arithmetic opertations:
Rule for Plus
evalExpr(e1,σ)=v1, v1∈R, evalExpr(e1,σ)=v2, v2∈R, evalExpr(Plus(e1, e2),σ)=v1+v2 (plus-numbers-ok)evalExpr(e1,σ)=v1, v1∈R, evalExpr(e1,σ)=v2, v2∈R, evalExpr(Plus(e1, e2),σ)=v1+v2 (plus-numbers-ok)
The rule above is the usual rule for plus. We also have a rule for plus involving figures.
evalExpr(e1,σ)=v1, v1=Canvas(O1) evalExpr(e1,σ)=v2, v2=Canvas(O2)evalExpr(Plus(e1, e2),σ)=overlap(v1,v2) (plus-figures-ok)evalExpr(e1,σ)=v1, v1=Canvas(O1) evalExpr(e1,σ)=v2, v2=Canvas(O2)evalExpr(Plus(e1, e2),σ)=overlap(v1,v2) (plus-figures-ok)
We are not writing the error rules. But note that adding any other combination of values other than a number plus number or figure plus figure results in an error.
Rule For Multiplication
evalExpr(e1,σ)=v1, v1∈R, evalExpr(e1,σ)=v2, v2∈R, evalExpr(Mult(e1, e2),σ)=v1×v2 (mult-numbers-ok)evalExpr(e1,σ)=v1, v1∈R, evalExpr(e1,σ)=v2, v2∈R, evalExpr(Mult(e1, e2),σ)=v1×v2 (mult-numbers-ok)
The rule above is the usual rule for multiplication of numbers. We also have a rule involving figures.
evalExpr(e1,σ)=v1, v1=Canvas(O1) evalExpr(e1,σ)=v2, v2=Canvas(O2)evalExpr(Plus(e1, e2),σ)=placeRight(v1,v2) (mult-figures-ok)evalExpr(e1,σ)=v1, v1=Canvas(O1) evalExpr(e1,σ)=v2, v2=Canvas(O2)evalExpr(Plus(e1, e2),σ)=placeRight(v1,v2) (mult-figures-ok)
Note Place the figure generated by e2
to the right of that generated by e1
.
Similarly we are skipping error rules that cover all other combinations (other than multiplying a number by a number or multiplying a figure by a figure)
Rule for Division
Division of two numbers (usual rule):
evalExpr(e1,σ)=v1, v1∈R, evalExpr(e1,σ)=v2, v2∈R, v2≠0evalExpr(Div(e1, e2),σ)=v1/v2 (div-numbers-ok)evalExpr(e1,σ)=v1, v1∈R, evalExpr(e1,σ)=v2, v2∈R, v2≠0evalExpr(Div(e1, e2),σ)=v1/v2 (div-numbers-ok)
Division of one figure by another:
evalExpr(e1,σ)=v1, v1=Canvas(O1) evalExpr(e1,σ)=v2, v2=Canvas(O2)evalExpr(Div(e1, e2),σ)=placeTop(v2,v1) (div-figures-ok)evalExpr(e1,σ)=v1, v1=Canvas(O1) evalExpr(e1,σ)=v2, v2=Canvas(O2)evalExpr(Div(e1, e2),σ)=placeTop(v2,v1) (div-figures-ok)
Note Place the figure generated by e1
on top of that generated by e2
.
Division of a figure by a number:
evalExpr(e1,σ)=v1, v1=Canvas(O1) evalExpr(e1,σ)=v2, v2∈RevalExpr(Div(e1, e2),σ)=rotate(v1,v2) (div-figure-number)evalExpr(e1,σ)=v1, v1=Canvas(O1) evalExpr(e1,σ)=v2, v2∈RevalExpr(Div(e1, e2),σ)=rotate(v1,v2) (div-figure-number)
Note Rotate the figure generated by e1
using the angle in radians that expression e2
evaluates to.
Rule for Other Operations
The rules for the other operations, let bindings, function definitions, function calls and recursion remain unchanged.
Task 2: Write the Interpreter
Your job is to fill in the missing code for the interpreter.
The interpreter is defined in the file Interpreter.scala
.
The value types are in Value.scala
The environment (for handling recursion as well) is defined in Environment.scala
.
You can throw an IllegalArgumentException
whenever you encounter an error. A string message should be added to the exception to indicate why you fail.
Reviews
There are no reviews yet.