# Assignment 7: Imperative Interpreter This is a **pair or solo Gradescope assignment**—you will submit 2 OCaml files (`a7.ml` and `a7test.ml`) and a README on Gradescope. :::info Recall that assignments, if completed as a pair, must be completed **together** rather than dividing work. See the syllabus for more. ::: In this assignment, you will define a single OCaml function, `interp_stmt`, that is an **interpreter** for _statements_ in a new language called `imp` (for imperative). The pieces of the interpreter required for _expressions_ are already provided for you in the starter code: your task is just to extend the interpreter to handle statements and control flow. :::warning For this assignment, you may reference any OCaml language documentation, the [free online OCaml textbook](https://cs3110.github.io/textbook/cover.html), and the class notes. You may find it helpful to look up additional sources on continuations; you may do so (1) if you do not directly copy code, and (2) if you cite them in your README. For example: - [Textbook chapter on continuation passing style](https://cs.brown.edu/courses/cs173/2004/Textbook/18-10-04.pdf) - [Blog post on continuations](https://matt.might.net/articles/by-example-continuation-passing-style/). - [Wikipedia](https://en.wikipedia.org/wiki/Continuation). You should not need to use any mutation within your implementation. ::: ### Change log I will avoid making changes to the assignment after it is posted, but any necessary changes will be added here: # Setup :::success As with Assignment 5, the [stencil code](https://drive.google.com/file/d/1q6AZxtBPyyJTU-fCiKwEVchXQMVZs3-V/view?usp=sharing) is set up as a `dune` OCaml project. Run `dune test` to compile your code in `a7.ml` and run the inline tests in `a7tests.ml`. ::: ### Attribution This assignment is adapted from an assignment from [CS4110](https://www.cs.cornell.edu/courses/cs4110/2024sp/) at Cornell University, by Adrian Sampson and others. # Introduction `imp` is a small imperative language, with a similar AST to what we designed in class to model Python (though we avoid Python's whitespace syntax, which is difficult to parse/test). `imp` has a more restricted expression format than `MiniLang`: it makes a distinction between arithmetic expressions on integers, and boolean expressions. :::info The `imp` parser handles arithmetic and boolean expressions separately. This means that unlike Assignment 3, you **do not need to insert dynamic type checks**. ::: We represent the AST with the following OCaml variant type: ```ocaml (* Arithmetic operators *) type aop = | PlusOp | MinusOp | TimesOp (* Arithmetic expressions *) type aexp = | Int of int | Var of string | ABinOp of aop * aexp * aexp (* Comparison operators *) type cmp = | Equals | NotEquals | Less | LessEq | Greater | GreaterEq (* Arithmetic operators *) type bop = | AndOp | OrOp (* Boolean expressions *) type bexp = | True | False | Cmp of cmp * aexp * aexp | Not of bexp | BBinOp of bop * bexp * bexp (* Statements *) type stmt = | Assign of string * aexp | If of bexp * stmt * stmt | While of bexp * stmt | Test of info * bexp | Break | Continue | Block of stmt list | Pass | Print of aexp ``` The stencil code provides all of the needed functionality *except* a complete `interp_stmt` function. :::success Your job is to complete the definition of the following function: ```ocaml let rec interp_stmt (e : env) (curr : stmt) (next : stmt) (conts: (stmt * stmt) list) ``` ::: Where `e` is the current dynamic environment, `curr` is the current statement (about to be interpreted), `next` is the continuation (the next statement to be run) under normal control flow, and `conts` is a stack of continuations for `break` and `continue`, respectively. The stencil code also provides the function to parse and then call your interpreter on an input: ```ocaml let parse_and_interp (input : string) : string = let c = parse input in get_output c ``` :::success You should write a series of tests using `parse_and_interp`. 10 examples are provided for you in `a7tests.ml`. ::: # Grammar `imp` has the following concrete grammar. :::warning Note that many places can only use arithmetic expressions (integers): for example, assignments can only be done using integers. Thus, our dynamic environment only needs to track integer values. ::: ``` <aexpr> ::= <num> | <var> | <aexpr> + <aexpr> | <aexpr> - <aexpr> | <aexpr> * <aexpr> <bexpr> ::= true | false | <aexpr> == <aexpr> | <aexpr> != <aexpr> | <aexpr> < <aexpr> | <aexpr> <= <aexpr> | <aexpr> > <aexpr> | <aexpr> >= <aexpr> | <bexpr> or <bexpr> | <bexpr> and <bexpr> <stmts> ::= <stmt> ";" <stmts> | <stmt> ";" <stmt> ::= pass | print <aexpr> | test <bexpr> | break | continue | { <stmts> } | if <bexp> then <stmt> else <stmt> | while <bexp> do <stmt> | <var> = <aexpr> ``` The `a7tests.ml` file has many examples of `imp` programs. Here is one of those examples: ``` x = 0; while x < 4 do { print x; x = x + 1; }; ``` Note that `;` is required even after braces for a block. # `imp` dynamic evaluation rules ## Arithmetic expressions (provided) - The `imp` dynamic evaluation rules for arithmetic (`+`, `-`, `*`) are the same as `MiniLang'`s (or OCaml's, without the static type checking). ## Boolean expressions (provided) - The `imp` dynamic evaluation rules for boolean comparisons are the same as `MiniLang'`s (or OCaml's, without the static type checking). - `and` and `or` do **not** short circuit. For example, in the expression `b1 or b2`, both `b1` and `b2` are evaluated even if `b2` interprets to `true`. ## Statement evaluation rules - The statement `pass` does nothing (just like in Python). - The statement `print a` interprets `a` and calls the helper function `output_int` on the result :::warning To facilitate testing, call `output_int` rather than e.g. `print_endline` directly. ::: - The statement `test b` is like an assert. If `b` interprets to `true`, it behaves like `pass`. Otherwise, it raises exception `TestFailed`. - The statements `break` and `continue` modify the flow of control in `while` loops. - `continue`: skips the remainder of the current loop iteration and proceeds with the next iteration. - `break`: terminates the loop and proceeds with the next statement after the loop. (These are the same semantics as in real imperative languages, such as Python.) :::warning A `break` or `continue` outside of any `while` loop should be treated as an error. In your interpreter, such configurations should raise `IllegalBreak` and `IllegalContinue`. ::: - `if` should work how you expect: if the first argument `b` interprets to `true`, interpret the first branch, otherwise interpret the second branch. - `while` should work how you expect: repeat the body as long as the condition interprets to `true` (0 or more times). - Blocks of statements are combined with `;` and should be interpreted one after the other. - Variable assignment is more simple in `imp` than let bindings in `MiniLang`: `imp` has no functions, and essentially has a global scope (once a variable has been assigned to in the dynamic execution, it can be used at any future point). For example, the following program should not raise an error, even though `x` is only assigned to inside the `while` braces (but referenced after). ``` i = 0; while i != 1 do { x = 42; i = 1; }; test i == 1; test x == 42; ``` ## Unbound variables Unbound variables should raise the `UndefVar` exception. For example: ``` print x; ``` Should raise `UndefVar` at the use of `x`. ## Error Exceptions The stencil code provides the following exceptions: ```ocaml (* You should use in a7.ml *) exception IllegalBreak exception IllegalContinue (* Used by the stencil code*) exception TestFailure of string exception UndefVar of string ``` # Implementation summary Your `interp_stmt` function should call the existing helper functions for interpreting expressions and handle control flow via the continuation stack. Note that you can call `interp_stmt` recursively on _different_ statements than those passed in: see the `Test` match case for an example of this. ## Using continuations To implement `break` and `continue`, you'll use a continuation stack to keep track of the statement that should be executed after one of these is encountered. #### Example Program ```= x = 1; y = 1; while (true) do { x = x + 1; if (4 < x) then { break; } else { pass; }; continue; y = y + 1; }; x = x * 2; ``` After the `break` is executed, execution should proceed with line 9: ```imp x = x * 2; ``` This concept is called a **continuation**, because it describes how execution should proceed after control flow changes. We extend our understanding of what the interpreter acts on to include **continuations** during evaluation. Because we have both `break` and `continue`, and since loops can be nested, we keep track of a **list of continuation pairs**. Each recursive call to `interp_stmt` should include: ``` <env, curr, next, conts> ``` Where: - `env` is the dynamic environment - `curr` is the current statement - `next` is a continuation representing the next statement - `conts` is a list of continuation pairs `(bstmt, cstmt)` - `bstmt`: what to do after `break` - `cstmt`: what to do after `continue` Initially, `next` is `Pass` and `conts` is `[]`. Executing `break` or `continue` should discard `next` and proceed with the appropriate continuation from `conts`. Executing a `while` statement should **add** a pair of statements to `conts`. To model the stack of nested statements, you should add and remove from the head of `conts`. #### Example Case One case of `interp_stmt`, for `test <b>`, has been provided in the stencil code. Note how when the test succeeds, the interpreter uses both the `next` statement and `Pass` in the recursive call. :::info Think carefully about how to use similar logic (e.g., using `Pass` or other AST constructs in recursive calls) for other cases. ::: :::warning This is an assignment where you'll want to make sure you have a plan *before* you start coding! ::: ## Debugging and pretty-printing The stencil code includes a pretty-printed in `pprint.ml`; `a7.ml` has commented-out examples of using it. You can also find commented-out examples of printing from `a7tests.ml`. In addition, if you are working on a Unix-based system that has `make` installed, you can run your `imp` interpreter from the command line. Run `make`, then you can run, for example `./imp test-basic.imp` and see the output to your terminal. # Submission Submit the following **3 files** on **Gradescope**: :::success - `a7.ml` - `a7tests.ml` - `README` as either a `txt`, `md`, or `pdf` file, with: - Roughly how long did this assignment take you (in hours)? - Which was the most challenging part of this assignment? - Which resources did you find to be the most useful? :::