ZIL: The origin
The very first version of Zork was written in the late 1970s, at the MIT Laboratory for Computer Science, in a sophisticated (for the time) language called MDL. It ran on a hefty (for the time) computer, the DECsystem-10, which was far more powerful than the “microcomputers” found in homes in the 1980s. That made it difficult to turn into a product they could sell.
In order to turn Zork into something that people could play at home on, say, a TRS-80 or Apple II, Infocom developed two key technologies: a small imaginary computer that could be implemented on home computers using an interpreter, and a language that could be compiled to run on that imaginary computer. They called the imaginary computer the Z-machine, and the language the Zork Implementation Language (ZIL).[1]
Infocom’s compiler (ZILCH) is mostly[2] lost to time, so working with ZIL today typically means using ZILF, a reconstructed set of tools that’s just compatible enough to compile all of Infocom’s games. Because of the tangled relationship between MDL, ZILCH, and ZILF–and the fact that ZILCH’s capabilities evolved over the years–it’s hard to nail down an exact specification of the language, so when I say “ZIL”, I mean “whatever ZILF will accept”.
The basics
Because Infocom was porting a game written in MDL, their new language bore a striking resemblance to MDL. Let’s look at some code from Zork 1 now:
<OBJECT SANDWICH-BAG
(IN KITCHEN-TABLE)
(SYNONYM BAG SACK)
(ADJECTIVE BROWN ELONGATED SMELLY)
(DESC "brown sack")
(FLAGS TAKEBIT CONTBIT BURNBIT)
(FDESC
"On the table is an elongated brown sack, smelling of hot peppers.")
(CAPACITY 9)
(SIZE 9)
(ACTION SANDWICH-BAG-FCN)>
<ROUTINE SANDWICH-BAG-FCN ()
<COND (<AND <VERB? SMELL> <IN? ,LUNCH ,PRSO>>
<TELL "It smells of hot peppers." CR>)>>
The first thing you might notice is that it’s CONSTANTLY YELLING. ZIL is written in all caps!
You might also notice the unusual punctuation. The names of things frequently contain symbols like hyphens, question marks, and slashes. Commas, periods, apostrophes, and exclamation points are used as prefixes. And there are brackets everywhere: parentheses, angle brackets, and occasionally square brackets.
You might even notice that everything is written in prefix notation. We don’t write 1 + 2 * 3; we write <+ 1 <* 2 3>>.The condition that you’d write in Inform 6 as action == ##Smell && lunch in noun, or in Inform 7 as smelling something that contains the lunch, translates into ZIL as <AND <VERB? SMELL> <IN? ,LUNCH ,PRSO>> (PRSO being a variable name that’s short for “parser object”).
What you might not notice from looking at the code is that ZIL uses unfamiliar terms to describe some familiar things. Those variable names aren’t called “identifiers”, they’re “atoms”. Those numbers might be “integers”, but they’re also “fixes”. Objects don’t have “attributes”, they have “flags”. Words don’t go in the “dictionary”, they go in the “vocabulary table”. Performing an action doesn’t even make you the “actor”, it makes you the “winner”. (Very optimistic!)
Inside a routine, one of the most common things to see is COND. It’s the equivalent of an if or switch statement: each parenthesized clause contains a condition followed by what to do if that condition is true. (Side note: “true” in ZIL is usually written as T, and “false” is usually written as <>.)
Inside an object definition, one of the most important bits is the list of FLAGS, which generally control what the player is allowed to do with it: in this case, they can pick it up (TAKEBIT), put things in it (CONTBIT, short for “container”), or burn it. Another important one is ACTION, which gives the name of a routine that handles the object’s unique behaviors: in this case, smelling the sandwich bag when it contains the lunch produces a special message instead of the default.
The strengths
ZIL was influential enough that a lot of what made it special at the time it was created has since become “the way things are done”, thanks largely to Inform having been designed around the Z-machine.
Today, the biggest thing that sets ZIL apart is its macro system. Although, to be fair, it isn’t really “ZIL’s macro system”… it’s literally just MDL, because the ZIL compiler is also a MDL interpreter. That means a ZIL program has access to the full horror power of MDL[3] while it’s being compiled, and one of the things you can do with MDL is write macros to make the language feel more “high level” than it is. For example, Adventure defines this macro to simplify random chances:
<DEFMAC PROB ('N)
<FORM L=? <FORM RANDOM 100> .N>>
Now if there’s something that needs to happen 25% of the time, we can write <PROB 25> and the macro will translate it to <L=? <RANDOM 100> 25>. Those angle-bracket groupings that the whole program is made of are called forms, and this macro uses FORM to construct some new ones based on the argument passed to PROB. (This idea of representing code as data structures that can be manipulated by other code will be familiar to anyone who’s used Lisp[4]; it’s also part of the Lisp-like MDL, and thus part of ZIL.)
That’s a simple example, of course, but the general idea is that you can adapt the language to fit the needs of the project and your own convenience, so that you can think more in terms of your game world rather than the nitty-gritty implementation details. Adventure defines dozens of nearly-identical rooms for its maze, using macros to boil it down to the map connections and avoid rewriting the identical descriptions and property values each time:[5]
<MAZE-ROOM ALIKE-MAZE-3
(EAST 2) (DOWN DEAD-END-3) (SOUTH 6) (NORTH DEAD-END-8)>
<MAZE-ROOM ALIKE-MAZE-4
(WEST 1) (NORTH 2) (EAST DEAD-END-1) (SOUTH DEAD-END-2) (UP 14) (DOWN 14)>
<DEAD-END-ROOM DEAD-END-1 WEST ALIKE-MAZE-4>
<DEAD-END-ROOM DEAD-END-2 EAST ALIKE-MAZE-4>
Bureaucracy uses macros to load text from a file and encrypt it for a puzzle.[6]
Libraries and extensions especially benefit: ZILF’s standard library defines macros for customizing the status line[7], scoring achievements[8], and replacing library messages[9], wrapping these technically complex systems in a friendly package.
The challenges
Unlike the rule-based systems we’ve seen in previous episodes, ZIL takes a strict procedural approach to action handling: each object has a single action routine (or, rarely, two) attached, and each verb has a single handler routine (or, rarely, two) that handles its default behavior.
That means you can’t just write code and let the system figure out when to run it, or even explicitly tell the system when to run it, like you could by ordering rules. The action routines belonging to the verb, objects, location, player, etc., are called in a set order for every action, and you have to code around that order. So the choice of which object should handle a particular action often comes down to the order the system enforces, rather than what makes sense.
ZIL also provides very little by way of data structures: at compile time, macros can use the full set of types provided by MDL, but at runtime, there are Z-machine objects and tables and that’s it. If you want a piece of text you can modify, you’re better off writing code to print it on the fly when it’s needed; if you want a list that can grow and shrink, you’ll have to implement it yourself on top of tables.
And finally, keeping track of all those brackets can be maddening! It’s common to see piles of closing brackets like >>)>)>>>> at the end of a block of code, and most editors aren’t set up to highlight matching angle brackets, so you may find yourself coding with one finger on the screen to make sure they stay balanced.
For more detailed information about ZIL, see the links on the ZILF documentation page.
Creative Computing Magazine (July 1980) Volume 06 Number 07 : Free Download, Borrow, and Streaming : Internet Archive ↩︎
GitHub - ZoBoRf/ZILCH-How-to: How to bring ZILCH back to life again. · GitHub ↩︎
zilf/sample/advent/advent.zil at branch/default · taradinoc/zilf · GitHub ↩︎
https://github.com/historicalsource/bureaucracy/blob/master/mumble.zil ↩︎
zilf/zillib/status.zil at branch/default · taradinoc/zilf · GitHub ↩︎
zilf/zillib/scoring.zil at branch/default · taradinoc/zilf · GitHub ↩︎
https://github.com/taradinoc/zilf/blob/branch/default/zillib/libmsg-defaults.zil ↩︎