Other Configuration.
Setting the Grammar__Version constant is a rather creaky old way to tell the I6 compiler to use more detailed grammar tables: GV1 has not in fact been used since about 1994.
The Inform Control Language is a mini-language for controlling the I6 compiler, able to set command-line switches, memory settings and so on. I6 ordinarily discards lines beginning with exclamation marks as comments, but at the very top of the file, lines beginning !% are read as ICL commands: as soon as any line (including a blank line) doesn't have this signature, I6 exits ICL mode. So, basically, we'd better do this here:
Setting the Grammar__Version constant is a rather creaky old way to tell the I6 compiler to use more detailed grammar tables: GV1 has not in fact been used since about 1994.
Both of the compiler and template layer, and of the story file to be produced.
The "library" identifying texts are now a little anachronistic, since the template layer is not strictly speaking an I6 library in the same way as the original: but it is surely the spiritual successor to 6/11N, so we may as well mark it 6/12N.
Use options are translated into I6 constant declarations, and NI puts them here:
These are not the only global variables defined in the template layer: those needed locally only by single sections (and not used in definitions of phrases in the Standard Rules, or referred to by NI directly) are defined within those sections – they can be regarded as unimportant implementation details, subject to change at whim. The variables here, on the other hand, are more important to understand.
(1) The first three variables to be defined are special in that they are significant to very early-style Z-machine interpreters, where they are used to produce the status line display (hence sline1 and sline2). The first variable must always equal a valid object number, which is why we – pretty weirdly – set it equal to the placeholder object InformLibrary, which takes no part in play, and is not a valid I7 object. This is not typesafe in I7 terms, but that doesn't matter because initialisation will correct it to a typesafe value before any I7 source text can execute. (sline1 and sline2 are entirely unused on when we target Glulx.) Once these variables are defined, the sequence of definition of the rest is not significant.
(2) The say__* are used for the finite state machine used in printing text, which keeps track of automatic paragraph breaking and the like. For details see the Printing.i6t section.
(3) standard_interpreter is used only for the Z-machine VM, and is always 0 for Glulx. For Z, a non-zero value here is the version number of the Z-Machine Standards Document which the interpreter claims to support, in the form (upper byte).(lower). undo_flag, similarly, behaves slightly differently on the two platforms according to whether they support multiple consecutive UNDOs. UNDO basically works by taking memory snapshots of the whole VM ("saving UNDO") to revert to at a later point ("performing UNDO"), so it is expensive on memory, and traditional VMs can only store a single memory snapshot – making two UNDOs in a row, going back two steps, impossible. Given this, undo_flag has three possible states: 0 means UNDO is not available at all, 1 means it is not available now because there is no further saved state to go back to from here, and 2 means it is available.
(4) deadflag in normally 0, or false, meaning play continues; 1 means the game ended in death; 2 for ended in victory; higher numbers represent exotic endings. (As from May 2010, the use of 2 for victory is deprecated, and a separate flag, story_complete, records whether the story is "complete" in the sense that we don't expect the player to replay.) deadflag switching state normally triggers an end to rulebook processing, so is the single most important global variable to the running of a story file.
(5) At present, time_rate is not made use of in I7: if positive, it is the number of minutes which pass each turn; if negative, the number of turns which pass each minute. This is quite a neat way to approximate a wide range of time steps with an integer such that fractions are exact and we can approximate any duration to a fair accuracy (the worst case being 3/4 minute, where we have to choose between 1 minute or 1/2 minute).
(6) Note that notify_mode is irrelevant if the use option "Use no scoring" is in force: it isn't looked at, and can't be changed, and shouldn't have an effect anyway since score will never be altered.
(7) player is a variable, not a constant, since the focus of play can change. SACK_OBJECT is likewise an unexpected variable: in the I6 library, there could only be one player's holdall, a single rucksack-like possession which had to be the value of the constant SACK_OBJECT. Here we define SACK_OBJECT as a global variable instead, the value of which is the player's holdall currently in use. visibility_ceiling is the highest object in the tree visible from the player's point of view: usually the room, but sometimes nothing (in darkness), and sometimes a closed non-transparent container.
(8) See OrderOfPlay.i6t for the meaning of action variables.
(9) This is a slate of global variables used by the parser to give some context to the general parsing routines (GPRs) which it calls; in the I6 design, any object can provide its own GPR, in the form of a parse_name property. GPRs are in effect parser plug-ins, and I7 makes extensive use of them.
(10) Similarly, variables for the parser to give context to another sort of plug-in routine: a scope filter. I7 uses these too.
(11) The move_* variables are specific to the ##Going action.
(12) These variables hold current settings for listing objects and, more elaborately, performing room descriptions.
(13) The current colour scheme is stored in variables in order that it can be saved in the save game state, and changed correctly on an UNDO: if it were a transient state inside the VM interpreter's screen model, then a RESTORE or UNDO will upset what the original author may have intended the appearance of text in particular scenes to be. (Cf. Adam Cadre's I6 patch L61007.)
(14) These pixel dimensions are used both for the Glulx and v6 Z-machines, but not for the more commonly used versions 5 or 8, whose screen model is based on character cells.
(15) For debugging. debug_flag is traditionally called so, but is actually now a bitmap of flags for tracing actions, calls to object routines, and so on.
These sections of code contain different definitions of the same routines, and in some cases the same arrays, to handle low-level functions in the virtual machine – saving the game, performing UNDO, parsing typed text into dictionary word addresses and so on.
I6 identified compass directions as being children of the pseudo-object Compass, so we define it. (Note that Compass is not a valid I7 object, and is used for no other purpose.) Because of the traditional structure of language definitions, this needs to come first.
The equivalent of I6's language definition file, though here the idea is that a translation should have an inclusion to replace the Language.i6t segment, which contains the English definition.
The I6 library consisted essentially of the parser, the verb routines, and a pile of utilities and world-modelling code, of which the biggest single component was the list-writer. The parser lives on below; the verb routines are gone, with the equivalent functionality having moved upstairs into I7 source text in the Standard Rules; and the rest of the library largely lives here:
The largest single block of code in the traditional I6 library part of the template layer is the parser.
The two pseudo-objects InformParser and InformLibrary are relics of the object-oriented approach in I6, and are used only very slightly in the template layer; they are not used at all in I7, and are not valid for the "object" kind of value.
The parser includes arrays for typed text and some parsing information derived from it, and if these should overrun it would cause enigmatic bugs, as the next arrays in memory would be corrupted: as a tripwire, the Protect_I7_Arrays array consists of two magic values in sequence. If it is ever discovered to contain the wrong data, the alarm sounds.
The Main routine, where execution begins, and the primitive rules in the principal rulebooks.
Some either/or properties are compiled to I6 attributes, which must be predeclared, so we do that first. (All other properties can simply be used without declaration.)
What then follows is a table of property metadata: in particular, specifying which properties can be used with which I6 classes or objects. Policing this at run-time costs a little speed, but traps many errors of programming, and keeps everything typesafe. It is the price we pay for the relatively lenient compile-time checking of I7's "object" kind of value. To make it as efficient as possible, we calculate offsets into the metadata: this has to be done (once) at run-time, with the routine compiled.
These are numbered upwards from 0 in order of creation. The following arrays taken together provide, for each activity number: (i) the rulebook numbers for the before, for, and after stages of the activity, and (ii) a flag indicating whether the activity is "future action"-capable, that is, is a parsing activity allowed to make use of the action which conjecturally might result from the current grammar line being parsed. (This is called the "action to be", hence "atb".)
The I6 object tree contains Class definitions as well as objects, but we precede both with a pseudo-object called property_numberspace_forcer. It does nothing except to ensure that properties are declared in I6 in the same sequence as I7 (which need not otherwise happen); it plays no part in play, and is not a valid I7 "object" value.
The initial state of the I6 arrays corresponding to each I7 table: see Tables.i6t for details.
Routines to evaluate from equations.
The following innocent-looking commands tell NI to compile I6 definitions for all of the rules which are not I6-written primitives, and also for adjective definitions, so it results in a fairly enormous cataract of code.
Some of the phrases are simply called in the course of other phrases, but some are rules in rulebooks or in the table of timed events, so those come next:
The literally hundreds of rulebooks are set up here. (In the end a rulebook is only a (word) array of rule addresses, terminated with a NULL.)
The gleaming, aluminium and glass extension to the library: almost all of it material new in I7 usage.
GPRs, scope and noun filters to be used in grammar lines, but no actual grammar lines as yet.
This paragraph contains no code, by default: it's a hook on which to hang verbatim I6 material.
Well: most of them, anyway. In particular, all of those which are lists of texts with substitution will be swept up, which is important for timing reasons. A second round later on will catch any later ones.
We now compile all of the remaining code in the source text: the "To..." phrases and all of their attendant text routines, loop-over-scope routines and so on.
We now have to be quite careful about the sequence of events. Compiling the text routines is an irrevocable step, after which we must not compile any new text with substitutions. On the other hand we mustn't leave it any later, because a text substitution might contain references to the past, or involve propositions which must be deferred into routines.
Similarly, this is where we wrap up all references to past tenses: after this point, we cannot safely compile any I7 condition in the past tense.
This is the trickiest matter of timing. We had to leave the grammar lines until now because the past-tense code above might have needed to investigate whether the player's command matched a given pattern at some time in the past (a case which arose naturally in one of the example games, so which should not be dismissed as an aberration). This is therefore the earliest point at which we can know for sure that no further grammar lines are needed.
Most conditions, such as "the score is 10", and descriptions, such as "open doors which are in lighted rooms", are translated by NI into propositions in a form of predicate calculus. Sometimes these can be compiled immediately to I6 code, but other times they involve complicated searches and have to be "deferred" into special routines which will perform them. This is where we compile those routines.
And we still aren't done, because we still have:
(1) Routines which switch between possible interpretations of phrases by performing run-time type checking. (Note that these cannot involve grammar, or the past tenses, or text substitutions, or deferred propositions.)
(2) Arrays holding constant lists, such as {2, 3, 4}, if any.
(3) The string constants, named in the pattern SC_*, in alphabetical order. (This ensures that their packed addresses will have unsigned comparison ordering equivalent to alphabetical order.)
(4) "Stub" I6 constants for property names where properties aren't used, to prevent them causing errors if they are referred to in code but not actually present in any object (as can easily happen with extensions presenting optional features which the user chooses not to employ). cap_short_name is similarly stubbed: this doesn't correspond to any I7 property, but is used by NI to record capitalised forms of the printed name (which in turn goes into short_name).
(5) Counters are used to allocate cells of storage to inline phrases which need a permanent state associated with them: see the Standard Rules. Since all I7 source text has been compiled by now, we know the final values of the counters, and therefore the amount of storage we need to allocate.
(6) Similarly, each "quotation" box needs its own cell of memory.
These are values which are pointers to more elaborate data on the memory heap, rather than values in themselves: they point to "blocks". A section of code handles the heap, and there is then one further section to support each of the kinds of value in question.
And that's all, folks.