§1. Undocumented features of Inform are no longer very secret (here you are reading about them, after all), but they can be hard to find by raking through the source code at random. What we mean by "undocumented features" here are capabilities intentionally included in Inform, but not mentioned in any of the user-facing documentation. That documentation used to be just the two books built in to the apps (Writing with Inform and The Recipe Book), but those are now supplemented by the manuals for the three command-line tools inform7, inbuild and present available only for command-line users.
This page covers a handful of other features still, but which are intended mainly for maintainers of Inform and may change at any time without notice.
§2. In working out what the compiler is doing (or has just done), the debugging log is always the first thing to investigate. This a text file like a running journal of what the compiler is doing; if it halts partway, either with problem messages or an internal error, then the log is still written up to that point, and can be revealing. The log can be viewed in the Inform apps, but usually only if an "advanced" preference setting has been ticked. Once that is done, the log appears as one of the tabs in the Results pane after each compilation (successful or not). A similar preference setting enables the Inform 6 output to be viewed, too, though of course only if the compiler gets that far.
The compiler is more instrumented than a first look at the log might suggest. Most of its "debugging aspects" are switched off by default: if they were all switched on, the log would be simply enormous. Each aspect has a textual name -- for example, "predicate calculus" shows the logical propositions formed and simplified inside the compiler. (On each run, the bottom of the debugging log lists the range of available aspects.) There are four ways to change what aspects are switched on:
-
Add a sentence like "Include predicate calculus in the debugging log." to the source text for the project. For obvious reasons, this can only take effect after sentences begin to be understood by the compiler, but in practice that's early enough to be useful.
-
Add a command-line switch like
-log predicate-calculus(note that any spaces in the name are replaced by hyphens) when runninginform7from the command line.-log listprints a list of the aspects. -
If you're interested in how Inform is reading an assertion sentence (say, "A ball is on the oak table"), try placing the special sentences
*.and*.either side of it. This switches on, and then off again, a convenient mode of logging which shows (i) the primary verb, (ii) the full sentence diagram, (iii) any logical propositions derived from that, and (iv) any inferences drawn as a result. The log output is mostly self-explanatory, but the notationX =14 Y(or some other number) means that the assertion-maker is coupling the tree nodesXandYtogether in Case 14 of the Assertions (in assertions) algorithm. For example, in the log output from the following, you'll see[ball/PROPER_NOUN_NT] =36 [is on/RELATIONSHIP_NT]:
The French Kitchen is a room. An oak table is in the Kitchen. *. A ball is on the oak table. *.
- If you're interested in how Inform is reading a phrase inside a rule or
phrase definition, and particularly in the running of the Dash typechecker, try
placing the special phrases
***;and***;either side of whatever phrase you're interested in. This shows exactly what code path through Dash is being followed, and which kind conformances are being checked. For example:
To consider (N - a number): ***; showme N times N; ***.
- It is also possible to write
*** "predicate calculus";to switch on just a single named debugging aspect, so:
Every turn: *** "predicate calculus"; now the rock is in the bucket; ***.
§3. Inform compiles first to an intermediate representation called Inter, and only then compiles that in turn to its actual output. The Inter produced is then discarded and normally never leaves memory, so it's invisible to the user. When debugging, though, we sometimes want to see what it was.
Two special debugging aspects are provided for this.
-
"Include Inter in the debugging log." will cause Inform (strictly speaking, the debugging log. Be warned that this is an awful lot of output, and will cause a pause of an extra couple of seconds just to write.
-
"Include Inform Inter in the debugging log." will cause only the Inter generated by Inform itself to be logged. Logged earlier in the run, this is a smaller tree not yet containing material from kits such as BasicInformKit or WorldModelKit. (Smaller, yes: small, no.)
-
If you're interested only in the code for a single phrase or rule, place the pseudo-phrase
*** "inter";somewhere (anywhere -- makes no difference) inside its body. Note that this shows the Inter generated by the whole definition, and of course that never happens if problem messages are generated instead. For example: Every turn: *** "inter"; now the rock is in the bucket.
§4. The "Test... with... " feature of Inform is familiar to most users, since it's used by almost all of the examples, usually with source text like:
Test me with "examine box / take box".
Less well-known is that it can also run unit tests built in to the compiler. For example:
Test description (internal) with a number which is even.
The test here is called description. The marker (internal) is what causes
this sentence to be read as an internal test case. Most tests, description
among them, then expect some details to follow the word with. In this case,
it's a description, "a number which is even", and what the test does is to
convert this to a logical proposition and print that proposition out.
What happens is not that inform7 prints the test output to the console, or even to the debugging log. It compiles the test output into the story file it is generating, so that this is printed out when the story file runs. That may sound needlessly indirect, but it's convenient both in the app and also for intest to check. Add the above test sentence to some project, click Go in the app and you see the result.
Test description (internal)converts a description such as "a number which is even" to a proposition, showing the kinds of its variables and whether they are free or bound:
1. a number which is even << number(x) & even(x) >> x (free) - number.
-
Test eps (internal)tests just the EPS-file-maker part of the spatial map index, and therefore isn't very useful. -
Test evaluation (internal)is used for verifying that literal arithmetic quantities written in unusual notations are evaluated correctly. For example,Test evaluation (internal) with 1 sq m divided by 20cm. -
Test headline (internal)is not a test at all! It simply prints out a dividing subheading in the test output -- if a project makes hundreds of internal tests then these subheadings make the output much more legible. For example,Test headline (internal) with Unquantified relative clauses.puts the subheading "Unquantified relative clauses" at this point in the test output. -
Test index (internal)produces just one element of the index for test purposes, as a chunk of HTML. For example,Test index (internal) with Mp.tests theMp(spatial map) element. -
Test kind (internal)is not much use, but can produce four different listings of the kinds known to Inform, according to whether thewith ...text is1,2,3or4. -
Test pattern (internal)is for testing that the rather complex grammar used for "action patterns" in Inform is being parsed correctly. It has two different forms: if thewith ...text begins withlistthen a list of actions is expected, and if not, then the text should describe a single action (though possibly quite vaguely). For example:
Test pattern (internal) with asking Hans to try taking the counter. Test pattern (internal) with list looking or taking inventory in the presence of Hans in the Laboratory.
-
Test refinery (internal)shows how assertion sentences are "refined". For example,Test refinery (internal) with Jane is a woman. -
Test sentence (internal)is very likedescription, but expects a whole sentence, that is, something with no free variables rather than one:
1. everyone likes Peter << ForAll x IN< person(x) IN> : amity(x, 'peter') >> x - object.
§5. inform7 has a test suite of roughly 2000 test cases, some of which are large
and elaborate. Running through this test suite must be done at the command line
(indeed, the tests are not shipped inside the apps, only as part of the inform
Git repository). The intest tool is provided for this, and reading the
I7 suite works are laid out in inform/inform7/Tests/inform7.intest. About 25%
of the tests are the Examples from the manuals; another 25% more or less
systematically poke at language features, and edge cases which proved tricky in
the past; and the remaining 50% test that the compiler produces correct problem
messages.
Tests in the suite can be divided up into the following categories:
-
Those in
inform/inform7/Tests/Test Basicare Basic Inform tests, run through the compiler in-basicmode, meaning that the language features for interactive fiction, the command parser and the world model are all missing. (These tests run relatively quickly as a result.) With no command parser loop, such programs run by printing some output and stopping. -
Those in
inform/inform7/Tests/Test Casesare standard tests of Inform not related to Examples in the documentation. This is a mixed bag which has accumulated over the years. -
Those in
inform/resources/Documentation/Examplesare the examples from the manuals -- this makes up about 25% of the whole suite, and in practice tests interactive-fiction features of Inform pretty systematically, besides ensuring that the examples in the manual do what we claim that they do. Note that the name of an example shown in the manual may not be the same as its Intest case name: thus, "Bruneseau's Journey" has test case nameCandle. But you can use intest's-findfeature if stuck:
$ ../intest/Tangled/intest inform7 -find Bruneseau Test cases matching 'Bruneseau': Candle = Bruneseau's Journey
-
A few test cases are extracted and run from the extension documentation for extensions supplied with Inform, such as "Locksmith".
-
Those in
inform/inform7/Tests/Test Internalsrun internal test cases (see above). -
Those in
inform/inform7/Tests/Test Makesare a small number of demonstration programs which are hybrids running partly in C, partly in Inform. These are built using individual makefiles -- hence the name. The test cases correspond to the example projects in the documentation on this: see -
Those in
inform/inform7/Tests/Test Problemsare Inform source texts expected to cause the compiler to halt with a problem message: they check that it does in fact do so, and that the problem is correctly worded. See below. -
Those in
inform/inform7/Tests/Test Releasesare for testing the release instructions which inform7 generates and supplies to inblorb. (Note that But inblorb has its own test suite, in which of course it is run.)
§6. Each individual test has a name. The following naming conventions apply:
-
No two different cases have the same name.
-
If a test case name ends in
-G, it will be compiled to Glulx and run through thedumb-glulxeinterpreter. -
If it ends
-C, it will be compiled to ANSI C, then run through a C compiler to produce a stand-alone executable, and that is what will then be run. -
If the name has neither of these endings, it will be compiled to the Z-machine and run through the
dumb-frotzinterpreter. -
Names beginning
BIP-are Basic Inform tests which systematically verify that language features work. These tests mostly come in all three flavours, so for exampleBIP-Let,BIP-Let-GandBIP-Let-Ccheck the same functionality when code-generating for the Z-machine, for Glulx and for C. (In a few cases to do with real arithmetic, there's no Z-machine test.) -
Names beginning
PM_are tests of problem messages: for example,PM_ConflictedReturnKindsorPM_PropertiesEquated. Almost every I7 problem message has a code-name like this, and a corresponding test case. Though these code-names are not displayed on the Results page shown in the Inform app when a misbehaving project generates these problems, the code-names of any issued problem messages are listed in the debugging log, which also gives a file and line number reference to the Inform source code showing where the problem was generated.
§7. The complete test suite takes a long time to run -- typically 10 to 11 minutes on the author's computer in 2022, even running 16 simultaneous tests on different cores. It's sometimes more practical to run subsets of the suite. For example, if debugging the part of Inform which generates C code from Inter, there's no point verifying the problem messages or the 900 or so tests which compile to Z or Glulx.
-
The Intest keywords
cases,examples, andproblemscatch just the Test Cases, document examples, and Test Problems cases respectively. -
There are also "test groups" stored in
inform/inform7/Tests/Groups. See the intest documentation for how to set up these groups, though they're pretty self-explanatory.
For example, these three commands:
$ ../intest/Tangled/intest inform7 all $ ../intest/Tangled/intest inform7 problems $ ../intest/Tangled/intest inform7 :calculus
run the full test suite, just the Test Problems cases, and just the :calculus
group respectively. (Note the colon.) Particularly useful groups include :c,
which performs every test involving C output, and :basic, which performs the
whole BIP sub-suite.
§8. Internal errors in inform7 generate the infamous "abject failure" problem message. These are essentially failed assertions in the compiler, but they cause a clean termination of the process with exit code 1, just as any more orthodox problem message would. A Results page in HTML is generated normally. As a result, they're no harder for the GUI apps to deal with than any other sort of problem would be.
Harder to handle is an actual crash of the inform7 executable, in which an abrupt exit occurs where no problem message is generated: say if it dereferences a null pointer, or divides by zero. This of course should never happen, but we want the GUI apps to be able to cope if it does. And in order to test that, we give inform7 the ability to simulate such crashes.
This is done with three words too unspeakable to write, the "secret hieroglyphs of dread power". They're actually not much to look at. The first is:
ni--crash--1 is a room.
This crashes out with exit code 1. The second and third are much the same, but
are written ni--crash--10 and ni--crash--11, giving exit codes 10 and 11.
(On MacOS, at least, these are what result from derefencing a bad pointer or
overflowing the stack through infinite recursion.)
§9. If Inform does crash, or throw an internal error, or even simply produce an
unwanted problem message, but in circumstances which are unclear, then it's
useful to see a stack backtrace at the point where the error came to light.
This is where Intest's -debug feature is useful. For example:
$ ../intest/Tangled/intest inform7 -debug Acidity
This runs the test Acidity, but with the inform7 executable running in
the lldb debugger (on MacOS, anyway), and moreover with it set to deliberately
cause a division by zero after any problem message is generated. This forces
inform7 to exit into the debugger, at which point a stack backtrace can be
seen (in lldb, anyway) by typing bt.
Of course, Inform is unlikely to crash on any of the tests in the regular test
suite: more likely you have some wacky source text causing a crash or unwanted
problem message, but which isn't in the suite. In that case, though, you just
need to create a temporary test case for it -- the author likes to keep this as the
file inform/inform7/Tests/Test Cases/temp.txt, and then:
$ ../intest/Tangled/intest inform7 -debug temp
does the trick. But be sure not to add this temporary file to the repository.