Add handling of lists to SL1.scala
. Support lists of the form x :: y :: z :: Nil
and list functions head
, tail
, and isEmpty
. We will call them as functions, not methods. For example, evaluating head(1 :: Nil)
yields 1
.
Define a case class
case class Cons(a: Expr, b: Expr) extends Expr
Parsing ::
isn't hard—add a right-associative operator that binds more strongly than *
or /
and yields a Cons
.
Evaluating Cons
is a little icky. We want eval(Cons(a, b), symbols)
to be the Scala list eval(a, symbols) :: eval(b, symbols)
. But eval
has return type Any
. Apply a cast like this:
eval(a, symbols) :: eval(b, symbols).asInstanceOf[List[Any]]
Add four entries to the initial symbol table. Bind "Nil"
to an empty Scala list and head
, tail
, isEmpty
to instances of
case class ListOp(f: List[Any] => Any)
For example, isEmpty
should be bound to ListOp(l => if (l.isEmpty) 1 else 0)
Then define what it means to evaluate a ListOp
. First figure out where it is evaluated. (Hint: head(x)
looks like a function call fac(n)
, except when looking up head
in the symbol table, you get a ListOp
, whereas a function like fac
is a Closure
.) Once you have found the right spot, you just need to add a case ListOp(f) => ...
, evaluating the function on the first argument. You will again need a cast to List[Any]
.
Do not worry about handling incorrect programs. If someone calls head(x, y)
or head(1)
or head()
, let it crash. This won't be tested.
To show that your list operations work, provide a file revlist.sl1
in which you define a function reverse
that reverses a list, like in lecture 3, followed by a call reverse(1 :: 2 :: 3 :: 4 :: 5 :: Nil)
. (It doesn't have to be tail recursive, and you don't have to use a Y combinator. But you can. If you do, remember to curry append
or reverseHelper
—the Y combinator produces functions with one argument.)