Project 1: Arith compiler

We won’t spend much time in this course talking about compilers. But for this first project you will explore a very simple compiler for the Arith language.

Remember that you must complete this project independently. The project is due Tuesday, October 2, at 9:45am.

First, some extensions and imports we will need for the parser. Don’t forget to download Parsing.hs and put it in the same directory as ArithCompiler.lhs.

Here are the data types we used to represent Arith abstract syntax in class, along with a simple interpreter.

The abstract stack machine

Instead of compiling Arith programs to machine code, you will compile them to an abstract machine. An abstract machine is just like a real machine except for the fact that it is imaginary.

Our imaginary machine is quite simple. It keeps track of a list of instructions to execute, and a stack of integers (recall that Haskell lists can also be used as stacks). There are four instructions it knows how to execute:

  1. Make a data type called Instruction to represent the four stack machine instructions described above.

Our machine can also be in one of three states. Each state may additionally store some information.

  1. Make a data type called MachineState to represent the possible states of the machine, as described above. Each different state should contain whatever information the machine needs to remember in that state.

  2. Write a function step :: MachineState -> MachineState which executes a single step of the machine. For example, in the WORKING state it should try executing the next instruction and return an appropriate next state for the machine.

  3. Write execute :: [Instruction] -> MachineState, which takes a program and runs the machine (starting with an empty stack) until the machine won’t run anymore (that is, it has reached a DONE or ERROR state).

  4. Finally, write run :: [Instruction] -> Maybe Integer, which executes the program and then returns Nothing if the machine halted with an ERROR or an empty stack, or Just the top integer on the stack if the machine successfully finished and left at least one integer on the stack.

The compiler

Now that you have a working abstract machine, you can compile Arith expressions into equivalent programs that run on the abstract machine.

  1. Write a function compile which takes an Arith and yields a list of Instructions.

Of course, your compiler should output not just any list of instructions! It should output a program which, when run on the abstract machine, successfully produces the same integer result as the Arith interpreter would. That is, for any a :: Arith,

run (compile a) == Just (interp a)

To test the above it will be convenient to write a function which finally puts some of these things together:

  1. Write a function exec :: String -> Maybe Integer which takes a String, parses it, compiles the resulting Arith, and then runs the generated abstract machine instructions. It should return Nothing if the given String does not parse.

You should now be able to test that if s is any String, then eval s == exec s.

Parser

This parser is provided for your convenience, to help you test your functions. Use the readArith function to parse concrete Arith syntax into an AST.

Pay no attention to the man behind the curtain