mirror of
https://github.com/ganelson/inform.git
synced 2024-07-18 15:04:25 +03:00
335 lines
17 KiB
Plaintext
335 lines
17 KiB
Plaintext
Title: Secret features of Inform
|
|
Author: Graham Nelson
|
|
|
|
@ 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
|
|
//inter//, which cover many features invisible from within the app and at
|
|
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.
|
|
|
|
@ 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 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:
|
|
|
|
(1) 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.
|
|
|
|
(2) Add a command-line switch like |-log predicate-calculus| (note that any
|
|
spaces in the name are replaced by hyphens) when running |inform7| from the
|
|
command line. |-log list| prints a list of the aspects.
|
|
|
|
(3) 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 notation |X =14 Y| (or
|
|
some other number) means that the assertion-maker is coupling the tree nodes |X|
|
|
and |Y| together in Case 14 of the //assertions: Assertions// algorithm. For
|
|
example, in the log output from the following, you'll
|
|
see |[ball/PROPER_NOUN_NT] =36 [is on/RELATIONSHIP_NT]|:
|
|
= (text as Inform 7)
|
|
The French Kitchen is a room. An oak table is in the Kitchen.
|
|
*. A ball is on the oak table. *.
|
|
=
|
|
|
|
(4) 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:
|
|
= (text as Inform 7)
|
|
To consider (N - a number):
|
|
***;
|
|
showme N times N;
|
|
***.
|
|
=
|
|
|
|
(5) It is also possible to write |*** "predicate calculus";| to switch on just
|
|
a single named debugging aspect, so:
|
|
= (text as Inform 7)
|
|
Every turn:
|
|
*** "predicate calculus";
|
|
now the rock is in the bucket;
|
|
***.
|
|
=
|
|
|
|
@ 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,
|
|
//inter//) to write out the entire compiled Inter tree, in its textual form, into
|
|
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.
|
|
=
|
|
|
|
@ 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:
|
|
= (text as Inform 7)
|
|
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:
|
|
= (text as Inform 7)
|
|
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:
|
|
= (text)
|
|
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 the |Mp| (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 the |with ...| text
|
|
is |1|, |2|, |3| or |4|.
|
|
|
|
(*) |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 the |with ...| text begins with |list| then a list of
|
|
actions is expected, and if not, then the text should describe a single action
|
|
(though possibly quite vaguely). For example:
|
|
= (text as Inform 7)
|
|
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 like |description|, but expects a whole
|
|
sentence, that is, something with no free variables rather than one:
|
|
= (text)
|
|
1. everyone likes Peter
|
|
<< ForAll x IN< person(x) IN> : amity(x, 'peter') >>
|
|
x - object.
|
|
=
|
|
|
|
@ 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
|
|
//intest// manual is a good place to begin. A test suite to be operated by
|
|
//intest// is configured by a |*.intest| file, and the full details of how 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 Basic| are Basic Inform tests, run
|
|
through the compiler in |-basic| mode, 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 Cases| are 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/Examples| are 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 name |Candle|. But you can use
|
|
intest's |-find| feature if stuck:
|
|
= (text as ConsoleText)
|
|
$ ../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 Internals| run internal test cases (see
|
|
above).
|
|
|
|
(*) Those in |inform/inform7/Tests/Test Makes| are 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
|
|
//inform7: Calling Inform from C//.
|
|
|
|
(*) Those in |inform/inform7/Tests/Test Problems| are 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 Releases| are for testing the release
|
|
instructions which //inform7// generates and supplies to //inblorb//. (Note that
|
|
//inblorb// itself is not run in these tests: only the instructions are checked.
|
|
But //inblorb// has its own test suite, in which of course it is run.)
|
|
|
|
@ 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 the |dumb-glulxe| interpreter.
|
|
|
|
(*) 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-frotz| interpreter.
|
|
|
|
(*) Names beginning |BIP-| are Basic Inform tests which systematically verify
|
|
that language features work. These tests mostly come in all three flavours, so
|
|
for example |BIP-Let|, |BIP-Let-G| and |BIP-Let-C| check 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_ConflictedReturnKinds| or |PM_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.
|
|
|
|
@ 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|, and |problems| catch 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:
|
|
= (text as ConsoleText)
|
|
$ ../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.
|
|
|
|
@ 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:
|
|
= (text as Inform 7)
|
|
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.)
|
|
|
|
@ 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:
|
|
= (text as ConsoleText)
|
|
$ ../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:
|
|
= (text as ConsoleText)
|
|
$ ../intest/Tangled/intest inform7 -debug temp
|
|
=
|
|
does the trick. But be sure not to add this temporary file to the repository.
|