Inform 7 for Programmers/Part 5

From IFWiki

Testing Commands

We have one testing command in the language itself:

Test me with " look / test foobar / wave ".
Test foobar with " look / take me / jump ".

This will execute the slash-separated list of runtime commands when the TEST runtime command is issued. "Me" is, by convention, the usual name for a script, and extension examples are encourage to name their overarching test script "me".

We have several run-time testing commands at our disposal:

  • SHOWME an object : will list the current values of all the properties of that object, including any relations it's involved in
  • RULES : will list the name of a rule before it executes
  • RULES ALL : will list the name of a rule before it considers execution
  • RULES OFF : turns off the mode
  • ACTIONS / ACTIONS OFF : will list when actions begin and end
  • RELATIONS : lists the relations
  • SCENES / SCENES OFF : lists the scenes currently happening, and will update us when one begins or end
  • TREE : shows all instantiations, indented according to the containment relation
  • TEST a test-script : runs the list of commands
  • PURLOIN an object : immediately places the object in the player's inventory, no questions asked
  • ABSTRACT object TO object: similar to purloin, but gives the first object to the second object, which is likely to be a container or some such
  • GONEAR an object : moves the player to the room which has the object
  • RANDOM: random number generator now predictable
  • SHOWVERB: shows I6-level information about a verb: synonyms, arguments, whether the arguments are reversed for a particular line
  • SCOPE: show all objects currently in scope
  • SCOPE object: show the scope from the particular object
  • TRACE a number: shows very low-level parsing information. The number ranges from 1 (the default) to 6 (the most detailed)

The IDE has the Skein -- which is a tree of commands of every playthrough we've done in the IDE -- and the Transcript -- which is a detail view of one path in the Skein; it has the output text from those commands. Transcript also has the Bless button, so automated re-testing of changes is possible.

Indistinguishable Memory

In spite of Inform's wish to remain statically-allocated, it has several aggregates that just won't allow themselves to remain so. Lists, Indexed Text, and Stored Actions all conspire to work against it. Inform tries very hard to uphold its slogan of "one pointer, one block", For example, it will tend to make deep copies of anything that is passed to a function. However, the underlying virtual machine does allow dynamic allocation, and extensions have been written to expose this.

Inform does have a special syntax for creating up to 100 indistinguishable instantiations of things:

A coin is a kind of thing.
55 coins are in the couch. 3 coins are carried by the player. There are 7 coins. [the last definition creates objects without putting them anywhere]

There's many things to know about this. One, it only works on kinds (classes), not pre-existing instantiations, so creating a subclass for the purpose is usually needed. Two, the subclass must be defined before the multi-instantiation; else we'll have a single object called "55 coins". Three, unlike an array of objects, neither the programmer nor the player can specify any particular instance, such as "the thirty-second coin". Four, the programmer can't specify a quantity of them, as in "move 3 coins to the recliner" or "try taking three coins" because Inform will say "which 3 coins?". (As a general rule, Inform does not allow the programmer to specify several things for an action.) Five, the player can "TAKE THREE COINS" if he spells-out the number, and the action's Understand line applies to [things], as the Taking action is already declared to be.

Six, the programmer can specify one particular coin from the bunch based on its properties and situation. This is where the Description type is essential. Since we're subclassing anyway, we can give coins additional properties, such as "tarnished", or a named value like "metal", and then use the word "random" to pick one while observing those constraints:

try taking a random tarnished coin which is carried by the player;

However, this can't type-check that such a coin exists. And finally, the property adjectives can be expressed to the player's parser to give the player a similar ability to distinguish between indistinguishable objects:

Understand the tarnished property as describing a coin. [tarnished by itself won't grab a coin]
Understand the metal property as referring to a coin. [but copper will]

Though we wouldn't normally expect the player's parser to be more expressive than the programmer's, in the case of taking three coins, the runtime always has the option of interactively asking for details ("Which did you mean?") while the programmer's does not.

Grammar Gotchas

  • The articles "the", "an", and "a" are almost always optional. We can think of them as a peculiar kind of whitespace. But there are a few exceptions where articles are either significant or required. First, "the" (not "a" or "an") is required when defining a rule's name:
    [..] (this is the my favorite rule): [...]
    Second, "a" (not "the" or "an") is required in front of the word "random":
    let N be a random number from 1 to 5;
    Third, "an" (not "the" or "a") is required in front of the word "actor" when in the subject of a rule header. Though the others will compile, they will compile as reading the current value of the variable "actor" and comparing it to itself, rather than skipping the condition altogether as it should. This almost works, except when the player is performing an action, the variable "actor" is set to nothing (NULL). This also means that Descriptions in the subject won't match the player.
    Report an actor jumping: [...]. [OK; catches everyone]
    Report actor jumping: [...]. [won't catch the player]
    Report a laconic person examining: [...]. [won't catch the player]
    Finally, articles in an action's or activity's name are captured, so will remain required. This is because actions and activities are intended to begin with a verb or participle, such as "printing" or "asking". So as a general rule, do not begin an action's or activity's name with an article, nor name them after nouns or noun phrases:
    The speech engine is an activity. [ "the" appears, so will always be required ]
    Before speech engine: [error: unknown rulebook, action, or activity]
    Before the speech engine: [correct]
    Speech engine is an activity. [ this definition allows both of the above rules ]
    The last is a common error with activities because their invocation begs for a "the":
    carry out the speaking engine activity;
  • Object names can unfortunately occlude one another. If we have an object "car", and then an object "car key", it frequently happens that the car cannot be referred to. Inform supplies objects with a property, "privately-named", which will not automatically export an object's source code name to the player's parser; an explicit Understand As line will be required.
  • The words "rules" and "rulebook" are exactly synonymous in source code. Though programmers know that a list of items isn't the same as the items themselves -- an empty list is still a space-using construct, even when no items exist to use space -- non-programmers would call it a distinction without a difference. ("A grocery list without items isn't a grocery list; it's blank paper.") So "rules" is synonymous with "rulebook" in source code.
  • Relatedly, because rules compose rulebooks, and rulebooks compose actions, the terms are sometimes used seemingly interchangably. Know that this is an allowance to the non-programmer, who is interested in "the reason the action failed", even though "the rule that caused the action's rulebook to fail" is more correct in most instances, and "the non-anonymous rule that most recently returned failure" is completely correct. Likewise for "if the rule succeeded" and its many cousins; it may be worth noting that the middleman here, "rulebook", is almost never explicitly mentioned where either the base-level "rule" or the top-level "action" could instead be used. Hence, "stop the action" means "rule fails", and "continue the action" means "make no decision". The sole exception, "the outcome of the rulebook" (synonymous with "the result of the rule") makes sense since the outcome/result of a protagonist action would be a permanent change in worldstate.
  • Finally, know that a rule variable can hold either a rule or a rulebook, but a rulebook variable can only hold a rulebook -- not a rule.
    My favorite rule is a rule that varies.
    My favorite book is a rulebook that varies.
    change my favorite rule to the can't walk through closed doors rule; [OK]
    change my favorite rule to the every turn rulebook; [OK]
    change my favorite book to the every turn rulebook; [OK]
    change my favorite book to the can't walk through closed doors rule; [type mismatch error!]
    This may be counter-intuitive to those of us who love our polymorphism, which states the more general type can accept itself as well as its subclasses. One way of viewing this is that polymorphism works on the is-a relation, while rulebooks & rules associate through the has-a relation. Alternately, anything that can be done to a item can be done to a collection by doing it to every item in that collection, but doing something specific to a collection -- like ignoring its third part -- makes no sense when applied directly to a single item.
  • And a final note about actual whitespace. First, even if we don't use the Python style of code block demarcation, tabs are required between table columns. (One tab will absorb all other whitespace touching it, however, so we can manually format tables to look well.) Secondly, a blank line is significant: it will end an imperative flow (assuming we didn't end it properly with a period), and one is required both before & after a table or a section heading.
  • Very occasionally, the compiler will have difficulty parsing an invocation that has a NPC as a subject. In these cases, add the word "trying" before the action: "Bob trying examining the player". It frequently happens in Tables with a Stored Action column.
  • A verb ending in -ed may be acting as an adjective. Observe the given example in chapter 9.13 of the manual, which gives this example for the simple past tense.
    if the lantern was switched on, change the lantern to switched off;
    if the lantern was switched off, change the lantern to switched on;
    This example can be misleading due to the identical forms of passive voice and participial adjectives. In other words, we can parse that as either "if (the lantern) (was switched) (on)" or "if (the lantern) (was) (switched on)". After all, there is both a property called "switched on / switched off" as well as two actions that set the relation either way, "switching on" and "switching off", with the past participles "switched on" and "switched off". So are we referring to the properties or to the actions up there? The answer is: the properties. Verbs of change -- Actions -- are never in any form of past tense. They are either present continuous tense "[we are] switching" or present-perfect tense "we have switched", never in simple past tense 'we switched' or in past-perfect tense 'we had switched'. Furthermore, actions require "we have" when using its tensed variation. Long story short: a word ending in -ed does not guarantee past tense. It could be the present-perfect.
  • There is a difference between the following valid lines of Inform:
    if the lantern was switched on,
    if we have switched on the lantern,
    Categorizing tense properly is essential, because the used tense determines the implementation and limitations. The first line is in simple past tense, which holds the previous turn's gamestate. This means that past tenses change every turn. The second line is in present-perfect tense, which holds a truth about the present: once we have done an action to a thing, it always, for the rest of the game, remains true that we have done it. Even if it was later undone, the fact that we once did it remains true. This means the two lines act quite differently, the first being an echo of the previous turn's "if the lantern is (switched on)", and the latter asking if the switching on action had ever been used on the lantern.

Paradigm Leaning

Given its hybrid nature, we must watch ourselves when we code algorithms. Sometimes Inform's declarative features are more elegant than doing the same work imperatively, but other times, its domain-specific nature frequently prevents higher-order programming algorithms. Here are a few examples that, like GOTO, probably signal a "wrong way" to do something.

Rules aren't Functions

This is the foobar rule:

The named but non-headered rule implies we are thinking too procedurally. Whatever invokes that rule is itself invoked at a particular time under particular circumstances, so those circumstances should be put in the header. Even if the rule is invoked from multiple places, at least one place can use the header. A possible exception would be:

The foobar rule is listed instead of the X rule in the Y rulebook. which case the foobar rule will steal the replaced rule's header.

Callbacks in the Rulebook Paradigm

The callback rules are a rulebook. [usually empty]
To foobar:
ponder foo;
consider the callback rulebook;
mull over bar.

This implies we're halfway between the procedural and rule-based paradigms. The fix here is usually to put the two pieces of imperative code into rules of their own, and the whole To phrase becomes a rulebook itself.

The foobar rules are a rulebook.
Foobar rule (this is the pre-callback work rule): ponder foo.
Foobar rule (this is the post-callback work rule): mull over bar.

Now client code may insert code in a variety of places, and even replace the imperative sections as well, if desired. (We should, of course, use descriptive names for the rules to encourage this.)

Declarative Lists... Sometimes

Building a list is one of the more onerous things to do imperatively: declare an empty list, loop through some items evaluating and possibly adding some of them to the list, pass the list to where it's needed (always elsewhere, because if it were right here we'd simply loop & use immediately), use the list via another loop, and remember to deallocate the list at the right time & place to avoid memory leaks.

If we're building a list of objects in Inform, the proper way is to use a collection of adjectives on the loop that would use the list:

repeat with associate running through every chatty not grumpy spiffy person in the location who is not the player begin;
say "Hi [associate].";
end repeat;

Or, at the very least (and probably using a global variable):

let L be a list of every chatty not grumpy spiffy person in the location who is not the player;
change my friends list to the list of every chatty not grumpy spiffy person in the location who is not the player;

To Hack... or not... to Hack?

Inform has several ways that deeper changes can be effected into the language. The first is the various activities, as previously seen. Most activities are a single function dressed up with hooks and such so code can easily be inserted to preempt the default behavior. Herein are more drastic examples.

Procedural Rules

The procedural rulebook -- a meta-rulebook -- is second. It can shuffle around named rules at runtime.

A procedural rule when the player is a ghost: ignore the can't walk through closed doors rule.

This meta-rule will "nuke from orbit" the particular Check Going rule that simulates a door's ability to stop movement. The "when" clause on a procedural rule is important. If there were no clause, the check rule would always be ignored, so we are spending runtime cycles to do what could be done at compile time:

The can't walk through closed doors rule is not listed in any rulebook.

When we look at the scaffolding of Inform 7 -- the action-processing rulebook, the specific action-processing rulebook, the visibility rulebook, the accessibility rulebook, the player's action awareness rulebook, and the turn sequence rulebook -- we discover that Procedural rules and the ability to list, de-list, swap and substitute rules allows us a great deal of flexibility in changing nearly anything.

The compile-time meta-rules, and their run-time equivalents, are:

The <rule> is not listed in any rulebook.
[...]; ignore <rule>;
[...]; reinstate <rule>; [undoes Ignore]
The <rule> is listed instead of the <second rule> in the <rulebook>;
[...]; substitute <rule> for <second rule> ;
[...]; restore the original <rule>;
The <rule> is listed before/after the <second rule> in the <rulebook>;
[...]; move <rule> to before/after <second rule> ;
[no compile-time equivalent]
[...]; reject the result of <rule>; [some rulebooks, like Instead, have a default outcome]
[...]; accept the result of <rule>; [undoes Reject]

The run-time meta-rules can be used by a normal rule to nuke its peer within the same rulebook (assuming he's run before his peer), but this is a non-standard usage, and may change in later builds.

Section Replacement

Third is replacing named sections of preexisting extensions.

Chapter 2a (for use with Locksmith by Emily Short)
Chapter 2b (for use without Locksmith by Emily Short)
Section 6 - Hacked locking (in place of Section 1 - Regular locking in Locksmith by Emily Short)

Though we'll have to be careful of causing compiler errors and such, a good extension writer will break up the extension into names sections in order to facilitate replacability. Also, all the most basic information on the class hierarchy, the built-in actions, variables, etc., of Inform 7 is found in an automatically included extension, the Standard Rules. In there, we find definitions like:

Section SR1/3 - Things
A thing can be privately-named or publically-named. A thing is usually publically-named.
A thing can be plural-named or singular-named. A thing is usually singular-named.
A thing can be proper-named or improper-named. A thing is usually improper-named.
A thing can be described or undescribed. A thing is usually described.
A thing has a text called an indefinite article.
A thing has a text called a description.
A thing has a text called an initial appearance.
A thing has a text called printed name.
A thing has a text called a printed plural name.

That's only a small excerpt from the definition of the Thing class, but you get the idea. The standard rules can be changed just like any other extension:

Section 8 - My thing class (in place of Section SR1/3 - Things in the Standard Rules by Graham Nelson)

Avoiding compiler errors by changing so much of the most basic assumptions of the code library is difficult, but the option is open to us.

Inform 6 Interface

Fourth is interfacing with the underlying language, Inform 6. How to program in I6 is beyond the scope of this document, but here's a taste.

To really clear the/-- screen: (- VM_ClearScreen(0); statuswin_cursize = 0; -)
To decide what number is the currently-chosen table row: (- ct_1 -).
To (w - value) is now (p - property): (- Write{p}{-delete},{w}); -).
To decide what number is the first misunderstood parser word: (- (wn - 1) -)
To say pattern: (- PrintPatternCommand(); -)
Include (-
[ PrintPatternCommand m pcount pcount2;
if (etype == UPTO_PE) {
for (m=0 : m<32 : m++) pattern-->m = pattern2-->m;
pcount = pcount2; PrintCommand(0);

Template Modifications

Fifth and finally, is using a feature like section-replacement, but on the Inform 6 template files. Every Inform 7 installation comes with many files with an I6T extension. After Inform 7 compiles to Inform 6, all of the I7-specific features must be implemented in I6. These I6T files hold that implementation.

Include (- ... -) before "Relations.i6t".
Include (- ... -) instead of "Relations.i6t".
Include (- ... -) after "Symmetric One To One Relations" in "Relations.i6t".

This is only most of Inform 7. It is a large language with many nooks and crannies to explore. Finding them is frequently a game in itself. We hope you find it enjoyable.

Inform 7 for Programmers by Ron Newcomb
Contents - Part 1 - Part 2 - Part 3 - Part 4 - Part 4b - Part 5