RU beehive logo ITEC dept promo banner
ITEC 380
2012fall
ibarland
tlewis32

homelecturesexamshwsbreeze (snow day)

hw07
Interpreting N
project

The following tests are due Nov.02 (Fri).
The full N1,N2 (racket and Java, plus any add'l tests) due Nov.05 (Mon).

Over the course of several homeworks, we'll implement a “tower” of languages (N0, N1, …, N6) each language incorporating more features than the previous. N0 is provided for you. You'll implement N1 in both Racket and Java; after that you choose which of the two languages you'll use to complete N2, …, N6.

The language N0

  Expr      ::= Number | ParenExpr | BinExpr | Is0Expr
  ParenExpr ::= ( Expr ) 
  BinExpr   ::= ( Expr BinOp Expr )
  Is0Expr   ::= if Expr is0 then Expr else Expr ;
  BinOp     ::= plus | minus | times
where Number is any numeric literal (as written in either Java or Racket, your choice1) and Var is string with no whitespace or punctuation (parentheses, “;”) and is not otherwise a reserved word in N0 (i.e. a terminal in the above grammar -- “if”, “plus”, “is0”, etc.). Whitespace is required between all terminals/non-terminals, with the exception of punctuation.

Here is a Racket interpreter for N0 along with helper functions for a scanner for scheme and an optional test harness.
Here is a java interpreter for N0 (N0-java.jar or browse); it includes unit tests, and helpful Java parsing functions.

  1. (10pts) N1 is just like N0, but with two additional types of expressions:

    Expr ::=  | IsNegExpr
    IsNegExpr ::= if Expr isNeg then Expr else Expr ;
    
    BinOp ::=  | mod
    
    Update parse, toString (a.k.a. expr->string), and eval appropriately, for both the scheme and Java implementations. Be sure to write test cases first.

    The only method which cares what these new expressions mean (their semantics) is eval: if Expr0 isNeg then Expr1 else Expr2; first evaluates just Expr0; if that value is negative, then it evaluates Expr1 and returns its value; otherwise it evaluates Expr2 and returns its value. (Note how you are implementing short-circuit semantics for isNegExpr!)

    (a mod b) should evaluate to2 a mod b, where the result is always between 0 (inclusive) and b (exclusive); in particular, the result is never positive if b<0. Notice that this is slightly different behavior than either Java's built-in % (which behaves differently on negative numbers), and from Racket's built-in modulo (which only accepts integers). In Java, you can use a%b (if b>0 or a%b=0) and a%b+b (otherwise). In scheme or Java, you can use b*(a/b-floor(a/b))

    Note that you are provided sufficient test cases for modExprs, except that you have to translate it proper N0. You must make your own test cases for ifNegExprs; include at least two as-simple-as-possible tests, and two tests with more deeply nested Exprs.

  2. (30pts) N2 adds identifiers to N1:

    Expr ::=  | Id | LetExpr
    
    LetExpr ::= let Id = Expr in Expr;
    
    where Id can be any series of letters and digits which isn't interpretable as a number3. (Assume for now that any nested let expressions use different Ids. We'll handle that possibility in N3, later.)

    Update your three methods parse, toString (a.k.a. expr->string), eval. We now need to define the semantics of let Id be (E0) in (E1):

    Observe that when evaluating a (legal) N2 program, eval will never actually encounter an Id -- that Id will have been substituted out before we ever recur down to it.

    The code to make a substitution in an Expr parse-tree4 is similar to taking an Ancestor-tree, and replacing every blue-eyed Child with a green-eyed one. (If you want to write that AncTree function, it may you understand what's needed for this one. The only difference is that an AncTree had only two cond-branches, while Expr has around seven, though the code for most of those are very similar). For example: let x = 5 in (x plus 3)(5 plus 3)85. Be sure to write test cases for your substitution function before you write its code; include several trivial and easy tests, along with a couple of more complicated nestings and one deeply nested expression.


Note: The following is provided so that you can see where we are headed. It might be slightly revised before being officially issued, though the substance will remain the same.

1 This is so we can just use our language's built-in number-parsing functions, without getting bogged down in tokening input. So scheme implementations will allow exactly those strings recognized by number?, (including +nan.0, -inf.0, and 2+3i). Similarly, if using Java, the semantics of N0's arithmetic will be similar to IEEE floating point arithmetic (rather than perfectly-correct arithmetic).      

2 Because we don't need to check for bad inputs, it's fine to have your interpreter crash if b=0. If you prefer to "control" crash — creating a meaningful error message and calling error or throw yourself — you are also welcome to do that.      

3 Note that our different implementations are now varying by more than just precision of arithmetic: in a Java implementation, NaN is a Num, and in a scheme implementation it's an Id. We won't use any test cases involving such subtle differences. However, note how our choices in designing a new language are being influenced by the language we're trying to easily implement it in! This stems from the fact that a primary design constraint on N1 is that implementing an intepreter for N1 doesn't get bogged down in minutae when using either Java or Racket.      

4 By the way: you will receive no credit for doing substitution on a string -- all our code should work on the parse tree itself. String-substitution (like C pre-processor macros) can't be generalized to handle shadowed variables (scope) for N3, and is in general fraught with error. A local-variable construct which requires globally-unique names isn't very impressive!      

5 The notation “let x = 5 in (x plus 3);5 plus 38” is shorthand for

  eval(parse("let x = 5 in (x plus 3);"))
= eval(parse("(5 plus 3)"))
= eval(parse("8"))
Observe how we definitely don't write “"let x = 5 in (x plus 3)" = "(5 plus 3)" = 8” since the two strings are not .equals(·) to each other, and strings are never ints. More specifically: we distinguish between “” (“code evaluates to”) and “=” (“equals”, just as “=” has meant since kindergarten).      

6 nor any “binding occurrences”: The first x in let x = 5 in (x plus 3); is a binding occurrence, and the second x is a bound occurrence. (We say that “a variable is bound inside the scope of its binding occurrence”.)      

7 A third issue, pointed out in Programming Languages and Interpretation, is that evaluating deeply nested lets is an O(n2) algorithm.      

8 Note that the list/map you recur with has the additional binding, but that recursive call shouldn't add/modify the list/map used at the top level. Since java.util.Map is inherently mutable, you'll want to make a new copy of that map before recurring.      

9You'll have to use the keyword #:mutable when you define-struct that structure, to get the setter.      

10 You can think about which implementation you prefer, when using a language: when seeing the program's internal representation of your code (debugging, when debugging or seeing the compiled      

11 Be default, scheme structs are non-mutable; to make them mutable, you have to provide the keyword #:mutable to define-struct.      

homelecturesexamshwsbreeze (snow day)


©2012, Ian Barland, Radford University
Last modified 2012.Dec.06 (Thu)
Please mail any suggestions
(incl. typos, broken links)
to ibarlandradford.edu
Powered by PLT Scheme