Exercise 1: Prototypes
For this exercise, you will manually draw out the metatable and prototype links that are used in Lua. This is the same approach that your PA3 interpreters will take to prototypes. If you aren’t familiar with prototypes, please read the two sections below on Lua prototypes:
Classes http://www.lua.org/pil/16.1.html Inheritance http://www.lua.org/pil/16.2.html
Part a. What will the below code print to the console?
Water = {}
function Water:new (o) o = o or {}
setmetatable(o, self) self.__index = self return o
end
function Water:use(i)
self.amount = self.amount - i -- *** diagram self ***
end
SanPellegrinoWater = Water:new({})
spw = SanPellegrinoWater:new({amount = 100}) spw:use(10)
print(spw.amount)
Soda = {}
function Soda:new (o) o = o or {}
setmetatable(o, {__index = self}) return o
end
function Soda:use(i)
self.amount = self.amount - i -- *** diagram self ***
end
SanPellegrinoLimonata = Soda:new({})
spl = SanPellegrinoLimonata:new({amount = 100}) spl:use(10)
print(spl.amount)
Part b. Please diagram the self object, including prototype links and tables associated with the object, at each of the “*** diagram ***” points. Examples of what we expect can be found in the section notes.
https://sites.google.com/a/bodik.org/cs164/lectures/section3objectsandinheritance
Part c. What differences do you observe between the prototype links (both metatable and __index) of SanPellegrinoWater and SanPellegrinoLimonata? Does this affect the inheritance hierarchy in any noticeable way (assume that any programs using these objects do not access the metatable or __index links)? Would another level of inheritance be affected by these two different implementations? Why did the designers of Lua introduce metatables and __index links?
Part d. Assume pallets of San Pellegrino water come in sizes of 10000 bottles. What additional code would be needed if we wanted to give the amount field of SanPellogrinoWater the default value of 10000. Please provide a code snippet and the line of code before the insertion point (from the code in part a), such that the code below prints 10000.
spw = SanPellegrinoWater:new()
print(spw.amount) -- should print out 10000
Part e. Prototype based inheritance is different from class based inheritance, which is used in languages like Java or Python. Name one advantage of each type of inheritance and provide a small example illustrating the benefit.
Exercise 2: Attribute Grammars
For this exercise, you will be using attribute grammars to layout a simple graphic. Computing layout means that we will compute sizes and positions of visual elements, e.g., figures. (Attribute grammars will be covered in the lecture on Thursday, Sep 24.)
The graphic is composed of three types of nodes: HBox, VBox and Image. HBox and VBox elements stack their children horizontally and vertically, respectively. The user provides a tree composed of these nodes, which is then used to render the graphics. Here is an example of such a tree.
VBox HBox
Image {width:10, height:10, src: evian.jpg} Image {width:20, height:20, src: perrier.jpg} VBox
Image {width:10, height:10, src: fiji.jpg} Image {width:10, height:10, src: smart.jpg} HBox
Image {width:20, height:20, src: vittel.jpg} Image {width:20, height:20, src: farrarelle.jpg}
Next, given the attribute grammar, your code will need to walk over the given tree, possibly multiple times, to compute the width and height of each node along with the absolute x and y coordinates. We now discuss how each type of node should be constructed.
Node = new Object({ x: 0, y: 0,
width: 0, height: 0 })
Node is the base prototype for all elements in the layout. It contains the attributes required to layout the tree.
Box = new Node({ children: [...] })
Box is the base prototype for the other boxes and adds the children attribute to Node’s other attributes.
HBox = new Box() An HBox inherits all of Box’s attributes. An HBox lays its children out horizontally (side by side) starting from the left. The width of an HBox is equal to the sum of the widths of its children. The height of a HBox is equal to the height of its tallest child.
VBox = new Box() A VBox inherits all of Box’s attributes. An VBox lays its children out vertically (top to bottom) starting from the top. The height of an VBox is equal to the sum of the heights of its children. The width of a VBox is equal to the width of its widest child.
Image = new Node() An Image is always a leaf node. The width
and height of an image are provided. For this exercise, we do not need to deal with the src attribute.
Part a. For each kind of node, write the attributegrammar assignments that will compute the values of attributes. The assignments may use child attributes to compute parent attributes or parent attributes to compute child attributes (you decided which are appropriate). These assignments will dictate the order in which attributes need to be computed in Part b. You can use any standard mathematical syntax to express these assignments.
We provide a part of the solution, for x and width of HBox nodes:
HBox.children[i].x = HBox.x + sum(HBox.children[0 to i 1].map(lambda(c) {c.width})) HBox.width = sum(HBox.children.map(lambda(c) {c.width}))
Give your answers for the remaining attributes of HBox, for all attributes of VBox nodes, and for x and y attributes of Image nodes.
Part b. Now we will indicate in what order these assignments need to be computed to evaluate all attributes of the tree. Some of these attributes are given (specifically, the width and height of Image nodes); this is where the computation will need to start. These computations are done by traversing the tree. During each tree pass, a subset of the attributes will be computed using the attributes given or attributes already computed.
How many passes are needed to compute the absolute position (x and y) and dimensions (width and height) for each node?
Part c. Write out the layout passes which compute the absolute position and coordinates. Indicate whether each pass is top down (transfers information from parent to child) or bottom up (transfers information from children to parents).
Example pass using BoomBox object:
Pass # Direction (top down or bottom up)
1 topdown BoomBox.speaker[i].volume = BoomBox.volume
… there can be multiple attributes computed in one pass
Exercise 3: Static Types
Assume we restrict the cs164 language to exclude tables. That is, the language has int and string values, and singleparameter functions. Now we want to add to the language static typing checks. Specifically, we will introduce the following static types:
T ::= int | string | lambda(T) : T
Note, null is a value that can be of any type.
Part a. This is a dynamically typed cs164 program (i.e., it has no static type annotations). The program contains two potential errors. (i) Indicate which expressions contain the errors. (ii) Indicate what errors are these.
function main(x) { def y = 1
if (x < 10) { x(1)
} else { y = null }
y * 4 }
Part b. Add static type annotations to this program such that at least one of the errors from part a is caught by the typechecker. Fill the types in the blanks.
function main(x: ______): ______ { def y: ______ = 1
if (x < 10) { x(1)
} else { y = null }
y * 4 }
Part c. One reason for type systems is to catch program errors before execution. Which errors from part a can the type system prevent?
in cs164 interpreter but you cannot give it static types that the type checker approves. Restrict yourself to the tablefree subset of cs164 from this problem.
function main(x) {
}
Part e. (hard) This question asks why statically typed languages like Java do not attempt to detect division by zero at compile time. Consider this strategy for catching this error at compile time:
Include a type nonZeroInt whose variables would always store nonzero values. Division by variables other than these would be considered to be a compile time error. An example program:
n: nonZeroInt = 2 x: int = 4
n/x // rejected x/n // accepted
Why does this strategy fail on realistic programs? What are the types of various arithmetic expressions, such as x+n.
Part f. In Java, arrays are covariant: If B <: A ("B subtype of A"), then B[] <: A[].
This means, the following passes static type checking because String <: Object:
String[] a = new String[1]; Object[] b = a;
If we don't perform any dynamic checks, this code could lead to a silent error, i.e., the program will break type safety and lead to an undetected error that may corrupt the program state. Finish the following snippet so that such a silent error occurs.
String[] a = new String[1]; Object[] b = a;
b[0] = new Integer(0); // your code here