Assignment #13: SlugOut: Wednesday, April 7th, Due: Tuesday, April 13th, 9:00pm |
Same pairs.
The language for this homework is:
#lang pl 13 |
In this homework you are given an extended version of the cached SLOTH evaluator, which you will further improve on. To keep things in the world of slow animals, the new language is called SLUG. Begin your work with the SLUG source. The extensions to SLOTH that makes it SLUG are described below, make sure you understand the code before you modify it.
Don’t panic! This homework requires understanding of a few lectures, and reading through some code. Make sure you understand the material, and go through this work according to the specified steps.
SLUG is extended in several ways to support I/O. Make sure you understand these changes before you begin your work.
(run "{list 1 'foo'}") |
The “I/O Execution” section is incomplete. The three execute-?
functions are not really doing anything and you need to implement their
functionality. Some tests are included at the bottom of the file, which
should help you make sure that your implementation works. Note that these
tests use a new form of the test form — using a given string as
simulated input with input:, and testing its simulated output with
=output>. For reading, you should use the read-line function.
Note also that execute-receiver is a generic helper for invoking a
receiver function, it will be useful in other cases below. It expects a
SLOTH receiver value (a FunV), and a producer thunk to get a value to
send to the receiver. The reason the latter is a thunk is that it might
include a side-effect, and we shouldn’t perform such a side-effect if the
receiver is bad. Make sure that it uses wrap-in-val to wrap the value,
because the value might itself be a VAL. (This will be relevant below.)
For example, to prompt and read a name, and then print it, you build a read-description that contains a function and does the printout:
(run-io
"{begin2 {print 'What is your name?'}
{read {fun {name}
{begin2 {print 'Your name is '''}
{begin2 {print name}
{print '''\n'}}}}}}") |
Another extension to SLUG is that it has a generic macro preprocessor. An additional input syntax — with-stx — specifies macro transformers in a way that is similar to Scheme’s syntax-rules. The syntax of with-stx is:
{ with-stx {<id> { <id> ... } { <pattern> <pattern> } ...}
<SLUG> } |
Macro expansion is implemented during parse, which now carries around an environment-like argument holding bindings for syntax transformers. Whenever parse encounters a with-stx input, it builds a transformer function and adds it to the recursive call. All forms are first checked — if their first expression is a name that is a registered macro name, then the transformer is applied and parsing continues with the result. (Note that these macros are simple sexpr-based transformations, they’re not hygienic.)
For example, there is a test at the bottom of the file that implements a let macro that is equivalent to bind, except that it is translated to function calls, and then it implements a let* form as shown in class.
As seen above, you can now do I/O in your lazy language, but the syntax for doing this is very inconvenient. Your job is to write a do macro that will make things easier to write. For example, the new syntax will allow running this:
(run-io
"{with-stx {do ???}
{do {print 'What is your name?\n'}
{name <- {read}}
{print 'What is your email?\n'}
{email <- {read}}
{print 'Your address is '''}
{print name}
{print ' <'}
{print email}
{print '>''\n'}}}") |
For this question, all you need to do is to fill in the macro body which is marked by ??? in the above code. To make things easier, here is a bit more from this macro — you have three cases to complete:
(run-io
"{with-stx {do {<-}
{{do {id <- {read}} next more ...}
???}
{{do {print str} next more ...}
???}
{{do expr}
???}}
{do ...
... same as above ...
...}}") |
The same approach can be used to deal with mutation: we can add boxes to the language, and handle them through operation descriptions in the same way that we handled I/O — in fact, we can extend IO with new descriptions. Do this in steps:
;; I/O descriptions
(define-type IO
[Print (string VAL)]
[ReadLine (receiver VAL)]
[Begin2 (1st VAL) (2nd VAL)]
;; mutation
[NewBox (init VAL) (receiver VAL)]
[UnBox (boxed VAL) (receiver VAL)]
[SetBox (boxed VAL) (newval VAL)]) |
(run-io
"{bind {{incbox {fun {b}
{unbox b
{fun {curval}
{set-box! b {+ 1 curval}}}}}}}
{newbox 0
{fun {i}
{begin2
{incbox i}
{begin2
{print 'i now holds: '}
{unbox i
{fun {v}
{begin2 {print {number->string v}}
{print '\n'}}}}}}}}}") |
Finally, use the same approach as above — extend the previous macro so the new constructs can also be used in a do context. You should be able to get to the following code:
(run-io
"{with-stx {do {<-}
???}
{bind {{incbox {fun {b}
{do {curval <- {unbox b}}
{set-box! b {+ 1 curval}}}}}}
{do {i <- {newbox 0}}
{incbox i}
{print 'i now holds: '}
{v <- {unbox i}}
{print {number->string v}}
{print '\n'}}}}") |