This is the archived website of SI 413 from the Fall 2012 semester. Feel free to browse around; you may also find more recent offerings at my teaching page.
This lab is due at 0900 next Tuesday, October 30.
It should contain all the files in the starter code listed below,
as well as a subfolder called tests
with your tests.
See the submit page for more info.
The starter code for this week's lab is... the solution to last week's lab. You can stick with your own solution or use mine (or some combination thereof). But in either case you should download the updated Makefile, as well as builtin.hpp to keep things running smoothly.
We saw in class last week that our SPL interpreter is most definitely not type-safe. This means we can do all kinds of nasty and meaningless things and the compiler won't even notice:
spl>new f := lambda n { write 5; }; # Look ma, no return value!
spl>write f@12 * 17;
5 0 spl>write f - 20;
164221140 spl>write f and 16;
false spl>new x := true;
spl>write x < false;
true spl>write (x < false) + 20;
21 spl>write x@5;
Segmentation fault
In today's lab we will implement type safety in our SPL interpreter. This will explicitly dis-allow all the terrible things in the example above, with nice informative error messages for the poor SPL programmer.
Actually, most of the machinery for the dynamic type-checking that we're going to do is already in the interpreter. Do you know where?
Then we'll look at the concept of built-in functions and make a few useful ones.
Recall that dynamic type checking requires that type information is stored alongside values in the running program. Every time we execute a statement or evaluate an expression, the types of all values are checked for compatibility, depending on what the statement is.
If you look at the Value
class, you will see that we have
been storing this information all along! Each Value object has a field
type
of type VType
, which is also defined in
the value.hpp
file.
If you want to do error checking in a consistent way with the rest of
SPL, you will have to add the following declarations after the other
#include
's near the top of the value.hpp
file:
#include "colorout.hpp" extern bool error; extern colorout errout;
num()
, tf()
, and func()
in the Value
class. So for instance, before returning
the integer value, num()
should confirm that the type of
the object is actually NUM_T
. If not, it should display
a nice error message, like
Type mismatch: expected NUM, got UNSET
A built-in function in a programming language is one that looks like a regular function, but is not written in the language itself but rather hard-coded into the compiler or interpreter.
So how will this work? How can we trigger the execution of arbitrary C++
code within an SPL program? Let's look at an example step by step. We'll write
a built-in function sqr
to compute the square of a number.
Of course this is a really stupid example because we could just
write the function in SPL like this:
new sqr := lambda n { ret := n * n; };
Hopefully the stupidity of this example will make the concepts more clear
for when you have to do more interesting functions.
Now the first thing we will
need is a new kind of Stmt
node whose exec
method
will do whatever it is our built-in function is supposed to do. This new node
type will correspond to the abstract class Builtin
.
I've actually
written this for you already, in the builtin.hpp
file in
today's starter code. Take a look at it.
You will see the
Builtin
abstract class, as well as the Sqr
subclass containing the functionality for squaring numbers.
Important to notice is the getParam()
function in
the Builtin
class. This gets the name of the argument to this
built-in function. This is set to "x" by Sqr's constructor.
The actual code for
squaring is in the exec
method in Sqr
, of course.
Now how do we get this into our interpreter so we can call sqr
from a SPL program and have it execute this node? The first thing to do is
to make a Lambda
whose body contains this new kind of statement.
Specifically, we will make a little AST that looks like this:
The odd thing is that, rather than the scanner and parser generating this
AST, we will create it manually, perhaps in the main
function
in spl.ypp
. The following lines will create the little AST
above:
Id* param = new Id("x"); Sqr* sqr = new Sqr; Block* sqbody = new Block(sqr); Lambda* sqlam = new Lambda(param, sqbody);
So now we have a Lambda
node for our built-in function.
All that remains is to give our baby a name and put it into the global
Frame so it can be accessed anywhere. The following code will do that:
Closure sqc; sqc.func = sqlam; sqc.env = NULL; global.bind("sqr", sqc); // Note: global is the name of the global frame.
main
so that the "sqr" built-in function works in your SPL interpreter just like
any other function. Important: you will need to
do a #include "builtin.hpp"
in your spl.ypp
file.
pause
that takes an integer argument and makes the
execution pause for that number of seconds. So for example, doing
pause@3;Should just make the interpreter "hang" for 3 seconds and then display the prompt again.
sleep
command
from the unistd.h
header file; see
this page out of the GNU libc manual
for the full details.