I attempted a problem I threw out as a suggested code-golf for 1p5.
In c, lex and yacc I needed more than 9600 characters ungolfed (fully commented, errors handled, some debugging code left in place, but some efficiency sacrificed in the name of shorter code), which seems pretty long, but c is about the most pessimal language you could choose for this problem except fortran 77 or something from the Turing Tarpit. The reference implementation can run its own build, which has some of that bootstrapping voodoo.
None-the-less, this is a relatively big project, and I don't want to post it unless people feel it is both well specified and interesting.
As yet there is no validation script, and I am not sure how one could be written as the acceptable output order could be post-order depth first or post-order breadth first and there is a left-first vs. right-first ambiguity on both. What a bother.
Aside: I'm quite proud of the lex and yacc part of my code, as I consider it spiffy.
A minimal implementation of the make (1) utility.
By minimal I mean,
- No built in rules, and no pattern or suffix rules.
- No variables and therefore no variable assignment or
manipulations; also no variable expansion which includes no
expansion of environment variables.
- No automatic variables like $< and $@.
This only leaves constructs (called rules) of the form
<target> ":" <prerequisite>* "\n" ["\t" <action> "\n"]*
Where each <target> and <prerequisite> is a whitespace delimited
string which may (or may not) represent a filename. Empty lines
have no effect and "#" marks the beginning of a end of
line comment (the sequence "#[^\n]*\n" should be treated as "\n"
so it does not interfere with rules; this has the side effect of
making "#" illegal in targets, prerequisites and actions). Colons
are prohibited in identifiers.
The program should take its input from the standard input or by
reading a file called "makefile" - implementer's choice. The program then attempts to
"build" every target named on the command line. Any targets
specified on the command line which do not appear in the makefile
and do not represent an existing file should generate an error and cause the program to exit
before execution of any rules. In the event that no target is
named on the command line, default to building the first target
in the input.
Duplicate targets may (not must!) be treated as an error.
A target is deemed already built if
- It names an existing file and
- All its prerequisites are fulfilled
Otherwise it is built by
- Building all unfulfilled prerequisites then
- Running each
<action> sequentially in the order they appear in the input,
and if the action returns an exceptional exit state, stopping the program.
A prerequisite is deemed fulfilled if
- The prerequisite represents an existing file and
- The prerequisite is built and
- The target is "newer" than the (fully built) prerequisite
A target is deemed "older" (i.e. not "newer") than its prerequisite if one of
- Both represent files and the prerequisite has been
modified more recently than the target.
- The target does not represent an existing file, and the
prerequisite does.
apply.
Authors on systems which do not support fork/exec semantics may
write a batch file or script which is invoked as the program
terminates, but that script must stop on the first
unsuccessful action.
Sample Input
# Babymake compatible makefile for babymake
all:babymake
babymake : lex.yy.o y.tab.o babymake.o
cc -o babymake lex.yy.o y.tab.o babymake.o
babymake.o : babymake.c babymake.h
cc -c babymake.c
lex.yy.o: lex.yy.c y.tab.h
cc -c lex.yy.c
lex.yy.c : babymake.l
lex babymake.l
y.tab.o: y.tab.c babymake.h
cc -c y.tab.c
y.tab.c : babymake.y
yacc -d babymake.y
clean:
rm -f babymake.o lex.yy.o y.tab.o
cleaner: clean # just testing end of line comments
rm -f y.tab.c y.tab.h
rm -f lex.yy.c
bogus: boguser
echo "building bogus" # test in another context
Sample output
$ ./babymake < babymake.example cleaner
rm -f babymake.o lex.yy.o y.tab.o
rm -f y.tab.c y.tab.h
rm -f lex.yy.c
$ ./babymake < babymake.example all
cc -c babymake.c
yacc -d babymake.y
cc -c y.tab.c
lex babymake.l
cc -c lex.yy.c
cc -o babymake lex.yy.o y.tab.o babymake.o
$ ./babymake < babymake.example
$ ./babymake < babymake.example bogus
ERRNO: 2: No such file or directory No rule to make target 'boguser'.