Assignment #4: Algae, part 2

Out: Sunday, October 19th   Due: Friday, October 24th, 10:00pm



Administrative

This assignment is only required for Master students. Undergrads can do it too, and you will be graded as usual — but you will not be able to withdraw a graded homework grade, so submit only if you think that your work is good enough to improve your grade. (Note: the homework grades are all weighted to compute your overall grade, so adding another grade means that you get smaller portions for more grades, which can server as protection against local disasters.)
Note that while undergrads are not required to submit a solution, it will be a very good idea to read the homework and go through the solution when it is later posted.

The assignment is for pair submissions, which is going to be the default until the end of the semester (unless explicitly said otherwise). By default, you should work with the same pairs — email me if you want to change.

The language for this homework is:

#lang CSU660 04



Warning

The language that you will be implementing in this homework is very different in nature than the FLANG language that we are discussing in class.

Specifically, both the syntax and the semantics are different. Do not try to ‘borrow’ class code!



Introduction

In this homework, you will finish the Algae implementation. You will add function definitions, which will make Algae a more usable language. Begin your work with the solution for the previous homework, do not use your own solution file (if you really want to, please ask).



Part 3b: Further Extensions

Your boss has officially declared binary-only and and or forms as ‘un-fun’. Bring happiness back to the office by extending them to handle any number of sub-expressions. The semantics should still be short circuiting, and they should behave like they do in Scheme.

(The new translators are easy — you will need to have two special cases for no expressions and for a single expression, and the general case will use them recursively.)



Part 4: Moving to Programs

New Syntax

We will begin by modifying the syntax of the language. It's easiest to go through an example first — here is a complete valid Algae program (look for ‘Collatz’ if you want to know about this program):

{program {fun even? {n} {if {= 0 n} True {call odd? {- n 1}}}} {fun odd? {n} {if {= 0 n} False {call even? {- n 1}}}} {fun main {n} {if {= n 1} 1 {+ 1 {call main {if {call even? n} {/ n 2} {+ 1 {* n 3}}}}}}}}
So the syntax of programs is now:
<PROGRAM> ::= { program <FUN> ... } <FUN> ::= { fun <id> { <id> } <ALGAE> } <ALGAE> ::= Same as before

To reflect this new syntax, you will first modify the type definitions accordingly. We now have three non-terminals, so we define three types, one for each:

(define-type PROGRAM [Funs (funs (Listof FUN))]) (define-type FUN [Fun (name Symbol) (arg Symbol) (body ALGAE)]) (define-type ALGAE Same as before)
And the same holds for the parser: instead of a single parse-sexpr function, you should now have the following definitions:
(: parse-program : Sexpr -> PROGRAM) ;; parses a whole program s-expression into a PROGRAM (define (parse-program sexpr) ...) (: parse-fun : Sexpr -> FUN) ;; parses a function s-expression syntax to an instance of FUN (define (parse-fun sexpr) ...) (: parse-expr : Sexpr -> ALGAE) ;; parses an s-expression into an ALGAE abstract syntax tree (define (parse-expr sexpr) ...)
Filling in the code is simple: parse-program should use parse-fun (hint: use map). parse-fun checks for a proper fun syntax to construct a FUN instance using Fun. parse-expr is just like the previous parse-sexpr (make sure you modify the name in the recursive calls).

Finally, change parse to parse a whole program using parse-program. Make sure you declare the correct type for it.

Note: the formal specs for subst and eval will get more complicated if they will reflect the new evaluator, so you can get rid of them at this point. Unless, of course, you do want to update them (subst is easy to specify, but eval will be harder.).

Syntax for calling functions

We're not done with syntax additions: the function call syntax is still missing. A function call is specified as {call id expr} — add this new derivation to the ALGAE BNF, type definition (use Call) and to parse-expr. You will also need to add a case to the definition of subst that deals with function call expressions. (Note that subst is still dealing with ALGAE syntax only.)

Note that the function to call is just an identifier — not an arbitrary expression. This means that parsing a call form is not like parsing other operators. (We will have another construct to do that below.)

Function lookup

Clearly, your code will need to lookup function values (Fun instances) from a PROGRAM. Fill in the following code for this:

(: lookup-fun : Symbol PROGRAM -> FUN) ;; looks up a FUN instance in a PROGRAM given its name (define (lookup-fun name prog) ...)
You will need either a helper function that will search the function list, or you can use a lambda with the built-in ormap function. If the needed function name is not found, your code should raise an error. (For simplicity, you can assume that there are no duplicate function names.)

Evaluating programs

You now need to handle the meaning of programs — first, it needs to be specified. The meaning of a program is now not a numeric (or boolean) value, but rather a function. Actually, the meaning of a program can be taken as a collection of functions, and ‘running’ the program with an argument invokes the function called main as an entry point.

So first of all, to make things easy, we will change run to deal with running a program, and later deal with eval. run needs to take an extra argument (a number or a boolean) which is used with the program's main function definition. Change it so that it looks like the following, taking an extra argument:

(: run : ...some type...) ;; evaluate an ALGAE complete program contained in a string ;; using a given value for the `main' function (define (run str arg) (let ([prog (parse str)]) (eval ...build expression... prog)))
Another change is that it now uses eval with an extra argument: the whole program. This is needed because when eval will make a function call, it will need the whole program to search the defined functions. Besides adding the prog argument, we don't change eval: it still operates on a single ALGAE expression — so how do we start evaluating the body of main with the given argument? Simple: we construct a Call expression that would have been the result of parsing the {call main arg} expression. This is the part that you need to fill in.

Now, as we said, eval needs to take an extra argument that is the whole program. This affects not only the way that run uses eval, but also the many places in which it calls itself (or the eval-number and eval-boolean functions) recursively, as well as adding the argument to the eval-? functions. First of all, this is how the changed eval should look like:

(: eval : ALGAE PROGRAM -> ...) ;; evaluates ALGAE expressions by reducing them to numbers ;; `prog' is provided for function lookup (define (eval expr prog) ...)
Now, all recursive calls to eval and eval-? need to carry over the program argument. When you do that, you will notice that some of these calls are performed indirectly via map. This cannot be easily changed, since map will call its argument with a single element from the list. There are several ways to fix this. An obvious solution that we used in the past is to write a helper function like eval-list. But now that we have lambda we can solve things in a much better way: change eval by wrapping its whole body with a let that rebinds eval and eval-? to single-argument functions:
(: eval : ALGAE PROGRAM -> ...) ;; evaluates ALGAE expressions by reducing them to numbers ;; `prog' is provided for function lookup (define (eval expr prog) (let ([eval ...] [eval-number ...] [eval-boolean ...]) ...))
With this, fixing the code will be extremely easy.

Once you're done with all of this, you will be ready to add a case for dealing with function calls. This is the core part of this extension, but it is not too difficult: a function call is quite similar to what you do when you evaluate a with expression, except that the body and the binding name are part of the function definition which you can get by using lookup-fun. Note that lookup-fun should already take care of signaling an error if the function is not found.

Testing

Now that all the pieces are in place, you can go ahead and add sufficient tests. The problem is that your old tests are all broken now, because run is used differently now. An easy way to avoid rewriting all of your existing tests is to write the following definition:

;; run* : String -> (U Number Boolean) ;; a version for testing simple ALGAE expressions without function calls (define (run* str) (eval (parse-expr (string->sexpr str)) (Funs null)))
and then change your existing tests to use run*. You still need to add run tests, of course. (The program at the beginning of part 3 is a good test.)

Now would be another good time to submit a checkpoint version of your code.



Part 5: Making the Language Higher Order

Note that the ALGAE language, as currently defined, is a first-class language: you cannot pass around function values around, you cannot get a function value as an argument, and you cannot create new functions at run-time. As an indication of this, the following function definition:

{fun main {foo} {call foo foo}}
will always call the function called foo — the foo argument to main is a binding instance that binds the last foo occurrence, but it does not bind the first expression in the call form.

In this part, we will enhance ALGAE one last time — we will turn it into a higher-order language. We will not, however, make it have first class functions. The resulting ALGAE language will be quite similar to C in the way functions are treated. This change will be easier than the previous one.

We will need to add two features to the language: first, a way to specify function names — this will be a new form and a new type that for this form to evaluate to (symbols will be convenient for this too). Note that we need a new form for this — because otherwise, occurrences of identifiers in ALGAE programs have the semantics of referencing some bound name. We also need a way to use these values for calling functions: vcall a form that is similar to call, but it has a function sub-expression rather than a function identifier, and the sub-expression is expected to evaluate to a function name (a symbol).

Here is an example of using this new feature for the same ‘Collatz’ program above:

{program {fun even? {n} {if {= 0 n} True {call odd? {- n 1}}}} {fun odd? {n} {if {= 0 n} False {call even? {- n 1}}}} {fun do_even {n} {/ n 2}} {fun do_odd {n} {+ 1 {* n 3}}} {fun main {n} {if {= n 1} 1 {+ 1 {call main {vcall {if {call even? n} {quote do_even} {quote do_odd}} n}}}}}}

Again, work in small steps:

And this is everything you need for this extension, and this homework.