This series of exercises introduces you to the
An example project is provided without a makefile. It is up to you to
The example program is an interpreter for a very simple
lambda-calculus-like programming language, written in C. It reads
in lambda-terms from standard input such as:
(\x . \y . x) ((\x . x) 1000) 2000
denotes lambda-abstraction) and reduces the
term to normal form using a lazy rewriting strategy, e.g. yielding:
The actual purpose of the program is not very important, though. More
important are the following facts about the implementation:
- The program consists of several C source files and header files such as
- The parser is implemented using the Bison parser generator which translates a Bison parser specificition ---
parser.y --- into a C-language parser corresponding to the specified grammar.
- Likewise, the lexical analyser (lexer) is implemented using the Flex lexer generator. The lexer is specified in
A shell script
is provided that builds the program.
- You can get the code for these exercises at http://www.cs.uu.nl/~eelco/interpreter-0.1.tgz. You can unpack this file as follows:
gunzip < interpreter-0.1.tgz | tar xvf -
Build the program by running
buildme.sh. Try if the
resulting program works, e.g.:
$ ./interpreter < test-03.lam
This should print out
The problem with
buildme.sh is that it is inefficient: it
recompiles everything. This is not a problem for such a small
program, but for real-world software this approach becomes
intractable. In addition, the order of statements in
buildme.sh is important; for example,
bison must be run
main.c is compiled (why is this?).
Write a simple makefile.
Note: in makefiles, commands have to be prefixed with a tab character
(not 8 spaces!).
What about dependencies? What files depend on what other files?
Use pattern rules to make the makefile shorter.
clean target to delete derived files.
install target to copy
interpreter to some
other location, e.g.
Rather than writing header file dependencies by hand, they
should be generated automatically. You can use
to let the C compiler generate a list of dependencies for a C file.
Unit testing: add a
check target that runs
interpreter on every input file (
*.lam) and checks
that the output equals the corresponding output file
*.out). Pattern rules are useful here. You can use the
diff Unix commands to check whether files are