Advanced Functional Programming Problem 2

John Hughes, Magnus Carlsson & Lennart Augustsson

The Problem

Your new problem is to implement a debugger for a simple functional language. After loading a program, your debugger should enable the user to

This last point may seem surprising, but it is actually very useful to be able to run as far as an error, and then step backwards to see how it occurred. Reverse execution requires storing earlier execution states, so they can be re-instated. If every previous state is stored, the debugger can support arbitrary backwards single-stepping, which is nice but costly. Perhaps a good compromise is to keep a stack of states on entry to each currently executing function: the user can then `pop the stack' and go back to the beginning of the caller, and single step forwards from there.

Your debugger will need to print arbitrary values, and perhaps parts of programs at break-points. All such output should be pretty-printed.

Method

You should write an interpreter for the simple language, using a monad to keep track of the information that must be collected, and to support suspending the computation at a break-point. Your interpreter will probably contain a function such as

eval :: Program -> Env -> Expr -> M Value
where
type Env = [(Ident, Value)]
So an expression is evaluated in an environment in which names are bound to values, and the result is a computation. Other than using return and >>= (or do), you should aim to make minimal changes to a `vanilla' interpreter to turn it into your debugging one. In other words, start doing a monadic interpreter for the language, and then turn it into a debugger.

You will need to use continuations in your monad, to capture the interpreter state when computation is suspended.

The Simple Language

I suggest you write your debugger for a first-order, untyped, call-by-value language.

A suggested syntax is
program ::= defn*
defn ::= lhs = expr ;
lhs ::= ident ident+
expr ::= if expr then expr else expr
      |  expr op expr
      |  ident expr+
      |  list
      |  (expr)
      |  number
      |  string
      |  ident
list ::= [] 
      |  [expr {, expr}*]
op ::= + | - | * | / | = | <= | :
with primitive functions including hd, tl, null, isNum, isBool, isString, isList. This grammar is ambiguous, of course, ambiguity and the conflicts have been resolved in the usual way. If you do not want to write your own parser, there is one, and a lexer, that you can use together with the abstract syntax for the language.

The parser uses a parsing library.

The Interface

At a minimum, your interface should enable the user to load a program and interactively carry out the operations described above. Of course, it would be nice to provide a graphical interface, with toggles to turn tracing and break-pointing on and off, and the ability to edit the program during debugging (check out editorF if you want to do this). But a graphical interface is not required.

Information Sources

Learning Goals

The purpose of this problem is to:

Deadline

This project is intended to take around three weeks. Solutions should be ready by Monday 29 November.

Collaboration

You may either produce individual solutions, or collaborate in a group. Collaboration is recommended, because this is quite a large problem.