Last week, I learned that David M. Tilbrook has died, which made me sad. I did not know him personally and cannot say much about his life, but I studied his publications and software ideas a lot, and consider them interesting, especially from a historic perspective.
Unfortunately, most of these things are on websites taken down already, so this post will refer to pages on the Internet Archive extensively. [Update 2020-01-16: His son Joe Tilbrook sent me a mail stating that http://qef.com/ is up again.]
I first came across David when I researched the history of the QED editor,
the direct predecessor of the Unix standard text editor ed
.
The history of QED is
well documented by Dennis Ritchie.
However, for a long time the QED source code was hard to find,
and I heard that David still maintained a version.
Around 2016, I swooped a copy of Caltech qed from USENIX tape 80.1 and tried making it work on modern platforms, with moderate success. Thanks to efforts by Arnold Robbins there is now an QED Archive which also features a copy of 1992 QED from Toronto, which contains contributions by Tom Duff, Robert Pike, Hugh Redelmeier and David Tilbrook. If you want to run it yourself, there is a modernized, UTF-8 aware version available now!
I do not know what David exactly contributed to QED, but from his writings its clear he was a heavy user of QED and wrote many scripts in it. Remember that in the early 80’s, awk was quite limited and Perl did not exist, so general-purpose string processing on Unix was difficult. We will have an example of a small QED script at the end of this post.
David’s opus magnum was a suite of tools called QEF, quod erat faciendum. Euclid wrote this at the end of geometric constructions, and in a software sense, we want a build system to produce what was to be made. At its time of creation, David was responsible for maintaining a (for the time) large system, essentially a Unix distribution. Tooling was stuck in the era of 1977’s make(1). For the details and basic ideas, see Tilbrook and Place (1986), “Tools for the Maintenance and Installation of a Large Software Distribution” (Huge thanks to Alan Grosskurth for making a copy available.) My favorite footnote is the one about their Prolog prototype of a build system: “Is the cray free? I need to reinstall /bin/true!”
Back then, Unix software development was plagued with all kinds of incompatibilities and vendor-specific workarounds, and QEF was one of the first tools to provide concepts like out-of-tree builds, automatic dependency detection, and provided portable abstractions for things like creating shared libraries, which required performing occult rituals back then.
The QEF whitepaper from 1996 explains the system at a more developed state.
What is intriguing is that the whole toolkit is created in classic Unix manner from small tools and little languages. I acquired an evaluation copy of QEF in 2015, but sadly it had no copy of QED included. However, I could read the full manpages for many of his tools.
Looking at these tools was instructive; many are obsoleted now because the
features have been added to standard tools, or we can afford using
more general, inefficient tools now. But for example, his go
tool
directly influenced the user interface of my nq
tool,
and reading about rls
, lls
, fexists
, and ftest
inspired my lr
.
His rpl
lives on as my mend
.
There are also additional resources and talks by David worth checking out.
Well, I promised you some QED code earlier, so let’s do fizzbuzz. (I heard this is useful in job interviews, so next time why not ask whether you can use QED for the task!)
It turns out this does not get as obscure as expected, but I’m not claiming what I wrote is idiomatic QED. I hacked it together while studying the tutorial a little bit.
ba
"loop
zc#+1
a\N\N.
za#:\zc%3=0 yf s/$/fizz/
za#:\zc%5=0 yf s/$/buzz/
s/^$/\zc/
zc#=100 yf`loop
,p
Q
We can run this using the convenient -x
flag:
% qed -x fizzbuzz.qed
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
...
So, how does it work? First, we switch to buffer a
of QED’s 56
available buffers (ba
). By default we are in the script buffer named
~
due to the -x
flag, which made me scratch my head in the beginning
because the script always printed its own source at first!
Next, we set up a loop.
QED provides structured programming in the form of a while loop,
but that requires fitting the loop body on the same line.
Instead, we will use jumps.
We start with a label "loop
and increment the c
register using zc#+1
. The #
enables arithmetic mode for registers,
they also can be strings.
Since the register is empty, it will be regarded as zero.
The end of the loop is the
zc#=100 yf`loop
line, which checks whether the c
register equals 100,
and if not (yf
) jumps back to the label loop
.
Curiously, QED has different commands for jumping forwards ('
)
and backwards (`
).
Having explicit directions is, I assume,
an implementation detail of the buffer-based program storage,
but compare with
Unix’s goto
or TECO’s O command,
which both search from the top.
Inside the loop, we first append an empty line (a\N\N.
)
This works like in ed:
a
.
But QED allows using \N
as a newline, so we can save some screen space.
Then, we need to do the fizzbuzz logic. The command
za#:\zc%3=0
will copy the c
register to the a
register (we need
to do this, as all arithmetic operations store their result in a
register), then we take the a
register modulo 3 and check if the
register is then zero. If it isn’t, we jump a line forward (yf
).
If we didn’t jump, we append fizz
to the line, using the s
command
(pretty much like in ed).
We deal with the buzz
case the same way, but modulo 5.
If the line is still empty, we have to say the number, so we
append it using s
again and this time insert the c
register into
the input stream using \zc
. Note that this not interpolated by the
s
command, but by QED itself. You can write \zc
(or \N
)
essentially everywhere and it will work as if you’d have typed it in!
A \
can be quoted as \c
, so we can do this:
za:foo\czb
zb:bar
a
\za
.
p
foobar
Note how the insertion of the a
register resulted in the b
register being inserted as well! Luckily, the people involved with QED
did better when they wrote shells later.
At the end of the loop, we have the fizzbuzz output in the buffer,
so we can just print out the whole thing (,p
)
and quit QED with no questions asked (Q
).
That wasn’t so bad, was it? Just like the tutorial says:
By striking a harmonious balance between Qed and UNIX’s other tools, the intelligent user will find Qed powerful, flexible, easy to master and fun!
NP: Talking Heads—Seen And Not Seen