The language for this homework is:
#lang pl 14 |
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 Racket’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.
Note: there is an important technical problem with boxes: we will want to use Racket boxes inside RktV wrappers, and detect when a wrapped value is a box using box?, but the box? predicate is not useful in that Typed Racket will not allow you to do any box-related operation on something that passed a box? test. The reason for this is the lack of subtype relationship between all Boxof values — even if you know that something is a box using box?, you don’t know what type it is supposed to have. To avoid this, we use a new kind of mutable type: a ref. This type has the same interface as boxes (and it’s actually implemented using a box), except that it always holds an Any value. You don’t need to implement it — the implementation is provided as part of the given SLUG source. Inside the SLUG language we could have used ‘box’ as the user provided functionality, but we’ll use ‘ref’ there too, to reduce confusion.
Implement mutation in steps:
;; I/O descriptions (define-type IO [Print VAL] ; String [ReadLine VAL] ; receiver: String -> IO [Begin2 VAL VAL] ; IO IO ;; mutation [NewRef VAL VAL] ; init, receiver for new ref [UnRef VAL VAL] ; ref, receiver for its value [SetRef VAL VAL]) ; ref, new value |
(run-io
"{bind {{incref {fun {b}
{unref b
{fun {curval}
{set-ref! b {+ 1 curval}}}}}}}
{newref 0
{fun {i}
{begin2
{incref i}
{begin2
{print 'i now holds: '}
{unref 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 {{incref {fun {b}
{do {curval <- {unref b}}
{set-ref! b {+ 1 curval}}}}}}
{do {i <- {newref 0}}
{incref i}
{print 'i now holds: '}
{v <- {unref i}}
{print {number->string v}}
{print '\n'}}}}") |