Lab 2 - Compiler Infrastructure and Testing
This lab aims at preparing you for Homework 2. Specifically, this lab will cover:
- The
asm
library and directives - The compiler runtime
- The differential tester, writing new (good) examples
- Running the interpreter and the compiler manually
Prepare Stencil
-
Accept the assignment on Github Classroom here.
-
In shell, run
dune build
. This repo is identical to the one for HW2, except this README file.
Directory Layout
- ./examples: tests for compiler and interperter. You will add test cases here every time you implement a new language feature.
- ./lib: the interesting parts of interpreters and compilers. Typically, you will modify both ./lib/interp.ml and ./lib/compile.ml in each assignment. Sometimes (e.g. in HW2) you also need to modify ./lib/runtime/runtime.c.
- ./asm: a library for producing assembly programs. Don't change anything there!
- ./s_exp: a library for working with S-expressions. Don't change anything there!
- ./bin: the "main functions" of the compiler and the interpreter. Don't change anything there!
- ./test: a library for testing your interpreter and compiler. Don't change anything there!
Working with Assembly
It is easy to forget the definition of directive
, operand
, and register
. Don't worry, there are three convenient ways to remind yourself their definitions.
(Assuming that you are editing ./lib/compile.ml)
- Place your cursor over an
directive
(e.g.Add
), right click to see the context menu, and clickGo To Definition
- Alternatively, click
Add
while pressingCtrl
- Place your cursor over
directive
(e.g. thedirective
inlet rec compile_expr : s_exp -> directive list = function
) and wait a few milliseconds. - Open ./asm/directive.ml and search for
type directive
Task: From lib/compile.ml/
, try all these four approaches of finding the definition of directive
.
Writing tests
When you want to test if the result of a program (e.g. (add1 42)
) is as expected (e.g. 42
), you should
- write the program in a
examples/*.lisp
file (e.g.examples/test42.lisp
) - write the expected output in a
examples/*.out
file (e.g.examples/test42.out
)
Some programs are expected to result in an error instead of a value. For example, (char->num 42)
should result in an error because 42
is not a char
. To test this behavior, you should use an *.err
file instead of *.out
. The content of *.err
files are ignored but you are free to put descriptions of why an error should occur there. (You are not require to make use of *.err
files in HW2, as we will not test your implementations against "bad" programs.)
When you want to test only if the interpreter and the compiler agree on the result of a program (e.g. (char->num #\newline)
), you may leave out the *.out
file. (For HW2, the ASCII encoding is required, so the result must be 10
. However, there is nothing wrong with using other encodings as long as (num->char (char->num c)) = c
holds for all c
and all implementations agree on the encoding)
Task: Choose 2 unary operator and 1 constants (i.e. num, bool, and char), and write a test for each of them (3 tests in total).
Working with the Compiler Runtime
Updating the compiler typically involves modifying ./lib/compile.ml. If you introduce a new kind of data (e.g. HW2, where char
is introduced), you will also need to modify ./lib/runtime/runtime.c because print_value
needs to know how to print the new data.
Note: You might want to write some tests before changing interpreters and compilers.
Task: Change the compiler such that both booleans and numbers use 1 bit for tagging. Boolean are tagged with 0
and numbers are tagged with 1
.
Task: Change the interpreter and the compiler such that the boolean true is printed as #t
and the boolean false is printed as #f
Task: Implement bool?
Running the Interpreter and the Compiler manually
Assume there is a program.lisp
file containts the following code
(add1 41)
To interprete this program,
dune exec -- bin/interp.exe program.lisp
To compile this program, (assuming that there is no file or directory named out
in the current directory)
dune exec -- bin/compile.exe program.lisp ./out/ ./out/program.lisp.exe
There will be three files in ./out/
program.lisp.exe
: the executableprogram.lisp.s
: assembly ofprogram.lisp
. You may want to read this file when debugging.
Task: Interprete the 3 testing programs that you just wrote.
Task: Compile and run the 3 testing programs that you just wrote.