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>