Geometry Language Assignment
Contents
Credit
All credit for this assignment goes to Prof. Grossman and his team at UW.
Set-up
For this assignment, you will complete and extend two implementations of an interpreter for a small “language” for two-dimensional geometry objects. An implementation in SML is mostly completed for you. An implementation in Ruby is mostly not completed. The SML implementation is structured with functions and pattern-matching. The Ruby implemented is structured with subclasses and methods, including some mind-bending double dispatch and other dynamic dispatch to stick with an OOP style even where your instructor thinks the functional style is easier to understand.
Language Semantics
Our “language” has five kinds of values and four other kinds of expressions. The representation of expressions depends on the metalanguage (SML or Ruby), with this same semantics:
- A
NoPoints
represents the empty set of two-dimensional points. - A
Point
represents a two-dimensional point with an x-coordinate and a y-coordinate. Both coordinates are floating-point numbers. - A
Line
is a non-vertical infinite line in the plane, represented by a slope and an intercept (as in y = mx + b where m is the slope and b is the intercept), both floating-point numbers. - A
VerticalLine
is an infinite vertical line in the plane, represented by its x-coordinate. - A
LineSegment
is a (finite) line segment, represented by the x- and y-coordinates of its endpoints (so four total floating-point numbers). - An
Intersect
expression is not a value. It has two subexpressions. The semantics is to evaluate the subexpressions (in the same environment) and then return the value that is the intersection (in the geometric sense) of the two subresults. For example, the intersection of two lines could be one of:NoPoints
, if the lines are parallel- a
Point
, if the lines intersect - a
Line
, if the lines have the same slope and intercept (see the note below about what “the same” means for floating-point numbers)
- A
Let
expression is not a value. It is like let-expressions in other languages we have studied: The first subexpression is evaluated and the result bound to a variable that is added to the environment for evaluating the second subexpression. - A
Var
expression is not a value. It is for using variables in the environment: We look up a string in the environment to get a geometric value. - A
Shift
expression is not a value. It has a deltaX (a floating-point number), a deltaY (a floating-point number), and a subexpression. The semantics is to evaluate the subexpression and then shift the result by deltaX (in the x-direction; positive is “to the right”) and deltaY (in the y-direction; positive is “up”). More specifically, shifting for each form of value is as follows:NoPoints
remainsNoPoints
.- A
Point
representing (x, y) becomes aPoint
representing (x + deltaX , y + deltaY). - A
Line
with slope m and intercept b becomes aLine
with slope m and an intercept of b + deltaY − m · deltaX . - A
VerticalLine
becomes aVerticalLine
shifted by deltaX ; the deltaY is irrelevant. - A
LineSegment
has its endpoints shift by deltaX and deltaY
Note on Floating-Point Numbers
Because arithmetic with floating-point numbers can introduce small rounding errors, it is rarely appropriate to use equality to decide if two floating-point numbers are “the same.” Instead, the provided code uses a helper function/method to decide if two floating-point numbers are “real close” (for our purposes, within .00001) and all your code should follow this approach as needed. For example, two points are the same if their x-coordinates are within .00001 and their y-coordinates are within .00001.
Expression Preprocessing
To simplify the interpreter, we first preprocess expressions. Preprocessing takes an expression and produces a new, equivalent expression with the following invariants:
- No
LineSegment
anywhere in the expression has endpoints that are the same as (i.e., real close to) each other. Such a line-segment should be replaced with the appropriatePoint
. For example in ML syntax,LineSegment(3.2,4.1,3.2,4.1)
should be replaced withPoint(3.2,4.1)
. - Every
LineSegment
has its first endpoint (the first two real values in SML) to the left (lower x-value) of the second endpoint. If the x-coordinates of the two endpoints are the same (real close), then theLineSegment
has its first endpoint below (lower y-value) the second endpoint. For anyLineSegment
not meeting this requirement, replace it with aLineSegment
with the same endpoints reordered.
The SML Code
Most of the SML solution is given to you. All you have to do is add preprocessing (problem 1) and Shift
expressions (problem 2). The sample solution added much less than 50 lines of code. As always, line counts
are just a rough guide.
Notice the SML code is organized around a datatype-definition for expressions, functions for the different
operations, and pattern-matching to identify different cases. The interpreter eval_prog
uses a helper function
intersect
with cases for every combination of geometric value (so with 5 kinds of values there are 25 cases
though some are handled together via pattern-matching). The surprisingly complicated part is the algorithm
for intersecting two line segments.