ZIL Language Guide
==================

Jesse McGrew, 2009-10

Introduction
============

This document is a description of ZIL, the language Infocom used
to write their interactive fiction titles. ZIL stands for Zork
Implementation Language, as it was developed to fit Zork –
originally written for a DEC mainframe – into the tiny home
computers of the early 1980s. The compiled form of a ZIL
program is a platform-independent executable for a virtual
machine (the Z-machine), which is emulated by a
platform-specific program which Infocom called a ZIP (Z-machine
Interpreter Program) and the modern community simply calls an
interpreter.

A full discussion of the influences and design constraints that
went into the development of ZIL is beyond the scope of this
document. However, to understand ZIL, one should know the
following:

Zork was originally written in a LISP-like general purpose
language called MDL.

MDL can be used “conversationally”, i.e. in an interactive mode
where the user enters an MDL expression, MDL evaluates it and
immediately prints a response. A program written in MDL has
access to the same features that MDL itself uses to parse and
evaluate these expressions.

Infocom's ZIL compiler, called ZILCH, was written in MDL.
Although the home systems on which the Zork series ran didn't
have enough power or memory to implement many features of MDL,
the mainframes on which ZILCH ran did, and thus ZIL code can
take advantage of those features.

In fact, there is significant overlap between MDL and ZIL, with
no clear boundary separating the two languages. One could view
ZILCH as a set of extensions to the MDL interpreter which
allowed it to produce code for a ZIP, and thus view the ZIL
language as an extension of MDL.

This document describes ZIL from two perspectives:

The “Shiny Surface”, comprising the parts that translate
directly into structures used by ZIP – global variables,
tables, objects, vocabulary and grammar, and routines – as well
as high-level directives that control how the game is compiled.
Game designers who could base their new works on existing
games, without needing to make any low-level changes to the
“substrate” (parser and world model; roughly “library” in
modern terms), would only need to know about the Shiny Surface.

The “Dark Underbelly”, comprising the entire subset of MDL that
a programmer would need to know in order to modify a game's
substrate, or that a compiler would need to implement in order
to successfully compile a game. Luckily for programmers and
compilers, this does not include many of the more esoteric
features of MDL (such as memory management, coroutines and
continuations, network and file I/O) which will not be
discussed here.

To be clear, the descriptions above refer to the presumed
practices of the Infocom era. Knowledge of the Dark Underbelly
is not required for writing a new game from scratch; although
the Dark Underbelly is convenient for making macros that allow
for higher-level code, one can get by with only the Shiny
Surface.

In this document, the term “ZIL” refers to the complete
language, both Shiny Surface and Dark Underbelly. The term “ZIP
instructions” will also be used to refer to the subset of the
(Shiny Surface) language used inside routine definitions, which
translates almost directly into Z-machine opcodes.

The Shiny Surface
=================

This chapter describes ZIL from the perspective of a game
author: either one who is basing a new work on an existing one,
and thus has a “substrate” to work from that needs no
modification, or one who is writing a small work from scratch
and is content without conveniences like macros that would be
demanded by larger works or series.

This is, therefore, not a full description of ZIL. The reader
may notice odd coincidences or omissions hinting at the
existence of something deeper: that is exactly the case. For a
more complete explanation, read the following chapter about the
Dark Underbelly.

General Syntax
--------------

A ZIL program consists of a series of expressions, some of which
may recursively contain other expressions. Some types of
expressions include:

Type

Examples

Description

FIX

123

*3777*

#2 11001001

An integer. (May be given in decimal, octal, or binary as seen
here.)

STRING

"hello"

A piece of text.

LIST

(1 2 3)

((1 2) (3 4))

A series of expressions.

FORM

<+ 1 2 3>

<+ <- 2 1> <+ 2 1>>

An operation that produces a value or causes something to
happen. The first expression inside the FORM (“+” here)
indicates which operation; the rest are parameters given to it.

ATOM

FOO

FREQUENT-WORDS?

A word that identifies an operation, variable, object, verb,
etc. (Often contains question marks or dashes; may contain some
other punctuation as well.)

Comment

;"this part is tricky"

Ignored by the compiler. Note that the thing after the semicolon
is a complete expression (a string here), so this can be used to
comment out an entire LIST or FORM.

An Example Program
------------------

This is a simple but complete ZIL program that can be compiled
into a working “game”:

"Hello World sample"

<ROUTINE GO ()
<PRINTI "Hello, world!">
<CRLF>>

Note the first line: a string that isn't inside any other
expression can be used as a comment.

This program consists of a single routine, named “GO”. Every
program must have a “GO” routine in order to be compiled to
Z-code, since it is the first routine executed by the
interpreter. Inside that routine are two statements, one that
prints a message and another that prints a carriage return.
Note the balanced angle brackets: the second bracket after
“CRLF” ends the FORM that started before “ROUTINE”.

When this program is compiled to Z-code and run in an
interpreter, it will print the message “Hello, world!” followed
by a line break, and then quit.

Directives
----------

These control the compilation process and should be placed at
top level (i.e. not inside a routine or any other expression).

<VERSION code>

Sets the version of the Z-machine that will be targeted.
Acceptable values for code are the atoms “ZIP” (version 3),
“EZIP” (version 4), “XZIP” (version 5), and “YZIP” (version
6).Jesse McGrew2009-07-07T23:51:22.56

ZILF also accepts the numbers 3 through 8.

The choice of Z-machine version affects the ZIP instructions
that will be allowed in routines, the maximum size of the game
(number of objects, amount of code and text, etc.), and the
availability of features like undo, custom status lines,
graphics, and sound. For more information, see the Z-Machine
Standard.

<CONSTANT RELEASEIDJesse McGrew2009-07-07T23:51:00.57

ZILF also accepts “ZORKID”.

number>

Sets the game's release number.

<INSERT-FILE "filename">Jesse McGrew2009-07-07T23:51:44.57

ZILF ignores any arguments after the first.

Includes another file. If the file's extension is omitted,
“.ZIL” will be assumed.

This is useful for splitting a game up into multiple files:
typically there will be one “main” file that includes other
files containing the parser, verb definitions, objects
(possibly multiple files for various regions of the game), and
so on.

<FREQUENT-WORDS?>Jesse McGrew2009-07-07T23:50:05.57

Not yet implemented.

Indicates that the compiler and assembler should generate or use
a list of “frequent words” – also known in the Z-Machine
Standard as “abbreviations” – in order to save space.

<FUNNY-GLOBALS?>Jesse McGrew2009-07-07T23:50:10.57

Not yet implemented.

Indicates that the compiler should simulate the ability to have
more than 240 global variables.

<ZIP-OPTIONS option option...>Jesse McGrew2009-07-07T23:50:15.58

Not yet implemented.

Indicates that the game wants to use one or more optional
Z-machine features. The options can be UNDO, COLOR, MOUSE,
and/or DISPLAY. This will cause the corresponding bits to be
set in the game header.

<ORDER-OBJECTS? ordering>

Changes the order in which object numbers are assigned. The
ordering can be ROOMS-FIRST, ROOMS-LAST, or DEFINED (which
arranges objects in source code order). If this directive is
not used, the ordering of objects is undefined.

ROOMS-FIRST allows room numbers to be stored in byte tables.
ROOMS-LAST allows non-room object numbers to be stored in byte
tables.

Global Variables

<GLOBAL name initial-value>

Defines a global variable. The variable can then be referred to
inside a routine as ,name (the name prefixed with a comma), or
changed with <SETG name new-value>.

<CONSTANT name value>

Defines a global constant, which can be referred to in the same
way as a global variable, but cannot be changed. This can be
used to give a name to frequently used tables or strings
without allocating a global variable.

Tables
------

Tables are arrays or buffers that can be located in either
static memory (ROM) or dynamic memory (RAM).

<ITABLE [length-type] count [(flags...)] [default...]>

Defines a table of count elements filled with default values:
either zeros or, if the default list is specified, the
specified list of values repeated until the table is full.

The optional length-type may be the atoms “NONE”, “BYTE”, or
“WORD”. “BYTE” and “WORD” change the type of the table and
also turn on the length marker, the same as giving them as
flags together with “LENGTH”. (For an explanation of flags, see
below.)

<[P][L]TABLE [(flags...)] values...>

Defines a table containing the specified values.

If this command is invoked as PLTABLE, the “PURE” and “LENGTH”
flags are implied; as PTABLE, “PURE” is implied; as LTABLE,
“LENGTH” is implied; and as TABLE, none are implied. In all
cases, additional flags may be given. (For an explanation of
flags, see below.)

Note: all of the table generating commands produce a table
address, which must be assigned to a constant, variable, or
property to be used within the game. For example:

<CONSTANT MYTABLE <ITABLE BYTE 50>>

Types of Tables
---------------

These flags control the format of the table:

“WORD” causes the elements to be 2-byte words. This is the
default.

“BYTE” causes the elements to be single bytes.

“LEXV” causes the elements to be 4-byte records. If default
values are given to ITABLE with this flag, they will be split
into groups of three: the first compiled as a word, the next
two compiled as bytes. The table is also prefixed with a byte
indicating the number of records, followed by a zero byte.

“STRING” causes the elements to be single bytes and also
changes the initializer format. This flag may not be used with
ITABLE. When this flag is given, any values given as strings
will be compiled as a series of individual ASCII characters,
rather than as string addresses.

Table Options
-------------

These flags alter the table without changing its basic format:

“LENGTH” causes a length marker to be written at the beginning
of the table, indicating the number of elements that follow. The
length marker is a byte if “BYTE” or “STRING” are also given;
otherwise the length marker is a word. This flag is ignored if
“LEXV” is given.

“PURE” causes the table to be compiled into static memory
(ROM).Jesse McGrew2009-07-08T00:47:13.61

ZILF also accepts the flag “KERNEL” but ignores it.

Objects
-------

Objects are structures usually used to represent physical
objects in the simulated world of an IF game. There are a fixed
number of objects laid out at compile time.

Each object has a printable name, a set of boolean flags, a set
of properties, and a position within the object tree.

Object Structures
-----------------

Objects are defined with either OBJECT or ROOM. Here is a simple
object definition:

<OBJECT CLOAK
(DESC "cloak")
(SYNONYM CLOAK)
(IN PLAYER)
(FLAGS TAKEBIT WEARBIT WORNBIT)
(ACTION CLOAK-R)>

CLOAK is the symbolic name used to refer to the object in source
code.

DESC defines the printable name (short description) of the
object, which must be given as a string.

SYNONYM defines nouns that refer to the object (in this case,
only the word CLOAK). Similarly, ADJECTIVE may be used to
define adjectives that refer to the object. Both of these cause
a vocabulary word to be compiled.

IN defines the object's location, which must be given as the
name of a parent object (in this case, PLAYER). LOC can be used
instead of IN.

FLAGS defines the object's initial set of flags, given as a
series of atoms.

ACTION is a user-defined property, which in this case is being
used to store the name of a routine to handle the object's
action responses. Any atom other than DESC, SYNONYM, ADJECTIVE,
IN, LOC, and FLAGS may be used as a property name.

Directions

Directions are properties that are linked with vocabulary words
and use a special definition syntax to define the links between
rooms. Directions must be defined at top level before any
objects are defined:

<DIRECTIONS NORTH SOUTH EAST WEST IN OUT>

Notice that IN may be reused as a direction name.

Each direction name will be compiled as a vocabulary word, and a
direction property may be defined on an object using the words
SORRY, TO, or PER:

(NORTH SORRY "A swiftly moving river blocks your path.")Jesse
McGrew2009-07-10T01:27:13.61

The SORRY token is optional in ZILF.

Defines a non-exit. Movement in this direction is not allowed,
and the player will see a message instead.

(SOUTH TO BAR)

Defines an unconditional exit. Movement in this direction leads
to a specific room.

(EAST TO OUTER-SPACE IF POD-BAY-DOOR IS OPEN)

Defines a door exit dependent on a particular object. Movement
in this direction leads to a specific room, but only if the
state of the object allows it; otherwise a failure message will
be shown. Optionally, a custom failure message may be given at
the end: ELSE "I can't let you do that, Dave."

(WEST TO VAULT IF COMBINATION-ENTERED)

Defines a conditional exit dependent on a global variable.
Movement in this direction leads to a specific room, but only
if the named global variable has a nonzero value. Optionally, a
custom failure message may be given at the end: ELSE "The handle
won't budge."

(IN PER TELEPORTER-FCN)

Defines a conditional exit controlled by a routine. Movement in
this direction causes the named routine to be called; it must
either return a room, or return zero and print an appropriate
failure message.

Property Defaults
-----------------

Although an object's property values can change at runtime, the
set of properties defined by any particular object cannot.
Writing to a property that an object does not possess is
illegal. Reading from such a property returns a default value.

Usually, the default value of a property is zero. The default
value may be customized, though:

<PROPDEF WEIGHT 10>