“…I can see that the grammar gets tucked into the tales and poetry as one gives pills in jelly.”
— Louisa May Alcott (1832–1888), Little Women
Here is how the parser reads in a whole command. Given a stream of text like
saint / peter / , / take / the / keys / from / paul
it first breaks it into words, as shown, and then
calls the entry point routine BeforeParsing
(which you
can provide, if you want to, in order to meddle with the text stream
before parsing gets underway). The parser then works out who is being
addressed, if anyone, by looking for a comma, and trying out the text
up to there as a noun matching an animate
or
talkable
object: in this case St Peter. This person is
called the “actor”, since he or she is going to perform
the action, and is most often the player (thus, typing “myself,
go north” is equivalent to typing “go north”).
The next word, in this case 'take'
, is the “verb
word”. An Inform verb usually has several English verb words
attached, which are called synonyms of each other: for instance, the
library is set up with
“take” = “carry” = “hold”
all referring to the same Inform verb.
▲
The parser sets up variables actor
and verb_word
while working. (In the example above, their values would be the St Peter
object and 'take'
, respectively.)
▲ This brief discussion is simplified in two ways. Firstly, it leaves out directions, because Inform considers that the name of a direction-object implies “go”: thus “north” means “go north”. Secondly, it misses out the grammar property described in §18, which can cause different actors to recognise different grammars.
•▲
EXERCISE 81
Use BeforeParsing
to implement a lamp which, when rubbed,
produces a genie who casts a spell to make the player confuse the
words “white” and “black”.
· · · · ·
This section is about verbs, which are defined
with “grammar”, meaning usages of the directives Verb
and Extend
. The library contains a substantial amount
of grammar as it is, and this forms (most of) the library file
"Grammar.h". Grammar defined in your own code can
either build on this or selectively knock it down, but either way it
should be made after the inclusion of "Grammar.h".
For instance, making a new synonym for an existing verb is easy:
Verb 'steal' 'acquire' 'grab' = 'take';
Now “steal”, “acquire” and “grab” are synonyms for “take”.
▲ One can also prise synonyms apart, as will appear later.
· · · · ·
To return to the text above, the parser has now recognised the English word “take” as one of those which can refer to a particular Inform verb. It has reached word 5 and still has “the keys from paul” left to understand.
Every Inform verb has a “grammar” which consists of a list of one or more “grammar lines”, each of them a pattern which the rest of the text might match. The parser tries the first, then the second and so on, and accepts the earliest one that matches, without ever considering later ones.
A line is a row of “tokens”. Typical
tokens might mean ‘the name of a nearby object’, ‘the
word 'from'
’ or ‘somebody's name’. To
match a line, the parser must match against each token in sequence.
Continuing the example, the parser accepts the line of three tokens
‹one or more nouns› ‹the word from› ‹a noun›
as matching “the keys from paul”.
Every grammar line has the name of an action attached,
and in this case it is Remove
: so the parser has ground
up the original text into just four quantities, ending up with
actor = StPeter action = Remove noun = gold_keys second = StPaul
The parser's job is now complete, and the rest of the Inform library can get on with processing the action or, as in this case, an order being addressed to somebody other than the player.
▲
The action for the line currently being worked through is stored in
the variable action_to_be
; or, at earlier stages when
the verb hasn't been deciphered yet, it holds the value NULL
.
· · · · ·
The Verb
directive creates Inform
verbs, giving them some English verb words and a grammar. The library's
"Grammar.h" file consists almost exclusively of
Verb
directives: here is an example simplified from one
of them.
Verb 'take' 'get' 'carry' 'hold' * 'out' -> Exit * multi -> Take * multiinside 'from' noun -> Remove * 'in' noun -> Enter * multiinside 'off' noun -> Remove * 'off' held -> Disrobe * 'inventory' -> Inv;
(You can look at the grammar being used in a game
with the debugging verb “showverb”: see
§7.) Each line of grammar begins with a
*
, gives a list of tokens as far as ->
and
then the action which the line produces. The first line can only be
matched by something like “get out”, the second might
be matched by
“take the banana”
“get all the fruit except the apple”
and so on. A full list of tokens will be given
later: briefly, 'out'
means the literal word “out”, multi
means one or more objects nearby, noun
means just one and multiinside
means one or more objects inside the second noun. In this book, grammar
tokens are written in the style noun
to prevent confusion (as there is also a variable called noun
).
▲
Some verbs are marked as meta
– these are the verbs
leading to Group 1 actions, those which are not really part of the
game's world: for example, “save”, “score”
and “quit”. For example:
Verb meta 'score' * -> Score;
and any debugging verbs you create would probably work better this way, since meta-verbs are protected from interference by the game and take up no game time.
· · · · ·
After the ->
in each line is the
name of an action. Giving a name in this way is what creates an action,
and if you give the name of one which doesn't already
exist then you
must also write a routine to execute the action, even if it's one which
doesn't do very much. The name of the routine is always the name of
the action with Sub
appended. For instance:
[ XyzzySub; "Nothing happens."; ]; Verb 'xyzzy' * -> Xyzzy;
will make a new magic-word verb “xyzzy”,
which always says “Nothing happens” – always, that
is, unless some before rule gets there first, as it might do in
certain magic places. Xyzzy
is now an action just as
good as all the standard ones: ##Xyzzy
gives its action
number, and you can write before
rules for it in
Xyzzy:
fields just as you would for, say, Take
.
▲
Finally, the line can end with the word reverse
. This
is only useful if there are objects or numbers in the line which
occur in the wrong order. An example from the library's grammar:
Verb 'show' 'present' 'display' * creature held -> Show reverse * held 'to' creature -> Show;
The point is that the Show
action expects
the first parameter to be an item, and the second to be a person.
When the text “show him the shield” is typed in, the
parser must reverse the two parameters “him” and
“the shield” before causing a Show
action.
On the other hand, in “show the shield to him” the
parameters are in the right order already.
· · · · ·
The library defines grammars for the 100 or so English
verbs most often used by adventure games. However, in practice you
quite often need to alter these, usually to add extra lines of grammar
but sometimes to remove existing ones. For instance, suppose you
would like “drop charges” to be a command in a
detection game (or a naval warfare game). This means adding a new
grammar line to the “drop” verb. The Extend
directive is provided for exactly this purpose:
Extend 'drop' * 'charges' -> DropCharges;
Normally, extra lines of grammar are added at the bottom of those already there, so that this will be the very last grammar line tested by the parser. This may not be what you want. For instance, “take” has a grammar line reading
* multi -> Take
quite early on. So if you want to add a grammar line diverting “take ‹food›” to a different action, like so:
* edible -> Eat
(edible
being a token matching anything which has the attribute edible
)
then it's no good adding this at the bottom of the Take
grammar, because the earlier line will always be matched first. What you
need is for the new line to go in at the top, not the bottom:
Extend 'take' first * edible -> Eat;
You might even want to throw away the old grammar completely, not just add a line or two. For this, use
Extend 'press' replace * 'charges' -> PressCharges;
and now the verb “press” has no other
sense but this, and can't be used in the sense of pressing down on
objects any more, because those grammar lines are gone. To sum
up, Extend
can optionally take one of three keywords:
replace | replace the old grammar with this one; |
first | insert the new grammar at the top; |
last | insert the new grammar at the bottom; |
with last
being the default.
▲ In library grammar, some verbs have many synonyms: for instance,
'attack' 'break' 'smash' 'hit' 'fight' 'wreck' 'crack' 'destroy' 'murder' 'kill' 'torture' 'punch' 'thump'
are all treated as identical. But you might want to distinguish between murder and lesser crimes. For this, try
Extend only 'murder' 'kill' replace * animate -> Murder;
The keyword only tells Inform to extract the two
verbs “murder" and “kill". These then become
a new verb which is initially an identical copy of the old one, but
then replace
tells Inform to throw that away in favour of
an entirely new grammar. Similarly,
Extend only 'run' * 'program' -> Compute;
makes “run" behave exactly like “go" and “walk”, as these three words are ordinarily synonymous to the library, except that it also recognises “program", so that “run program" activates a computer but “walk program" doesn't. Other good pairs to separate might be “cross” and “enter”, “drop” and “throw”, “give” and “feed”, “swim” and “dive”, “kiss” and “hug”, “cut” and “prune”. Bear in mind that once a pair has been split apart like this, any subsequent change to one will not also change the other.
▲▲
Occasionally verb definition commands are not enough. For example,
in the original ‘Advent’, the player could type the name
of an adjacent place which had previously been visited, and be taken
there. (This feature isn't included in the Inform example version
of the game in order to keep the source code as simple as possible.)
There are several laborious ways to code this, but here's a concise
way. The library calls the UnknownVerb
entry point routine
(if you provide one) when the parser can't even get past the first
word. This has two options: it can return false
, in which
case the parser just goes on to complain as it would have done anyway.
Otherwise, it can return a verb word which is substituted for what
the player actually typed. Here is one way the ‘Advent’
room-naming might work. Suppose that every room has been given a
property called go_verb
listing the words which refer
to it, so for instance the well house might be defined along these
lines:
AboveGround Inside_Building "Inside Building" with description "You are inside a building, a well house for a large spring.", go_verb 'well' 'house' 'inside' 'building', ...
The UnknownVerb
routine then looks
through the possible compass directions for already-visited
rooms, checking against words stored in this new property:
Global go_verb_direction; [ UnknownVerb word room direction adjacent; room = real_location; objectloop (direction in compass) { adjacent = room.(direction.door_dir); if (adjacent ofclass Object && adjacent has visited && adjacent provides go_verb && WordInProperty(word, adjacent, go_verb)) { go_verb_direction = direction; return 'go.verb'; } } if (room provides go_verb && WordInProperty(word, room, go_verb)) { go_verb_direction = "You're already there!"; return 'go.verb'; } objectloop (room provides go_verb && room has visited && WordInProperty(word, room, go_verb)) { go_verb_direction = "You can't get there from here!"; return 'go.verb'; } objectloop (room provides go_verb && room hasnt visited && WordInProperty(word, room, go_verb)) { go_verb_direction = "But you don't know the way there!"; return 'go.verb'; } rfalse; ];
When successful, this routine stores either a compass
direction (an object belonging to the compass) in the variable
go_verb_direction
, or else a string to print. (Note that
an UnknownVerb
routine shouldn't print anything itself,
as this might be inappropriate in view of subsequent parsing, or if
the actor isn't the player.) The routine then tells the parser to
treat the verb as if it were 'go.verb'
, and as this doesn't
exist yet, we must define it:
[ Go_VerbSub; if (go_verb_direction ofclass String) print_ret (string) go_verb_direction; <<Go go_verb_direction>>; ]; Verb 'go.verb' * -> Go_Verb;
•▲▲
EXERCISE 82
A minor deficiency with the above system is that the parser may print
out strange responses like “I only understood you as far as
wanting to go.verb.” if the player types something odd
like “bedquilt the nugget”. How can we ensure that the
parser will always say something like “I only understood you
as far as wanting to go to Bedquilt.”?
•
REFERENCES
‘Advent’ makes a string of simple Verb
definitions;
‘Alice Through the Looking-Glass’ uses Extend
a
little.
•‘Balances’ has a
large extra grammar and also uses the UnknownVerb
and
PrintVerb
entry points.
•Irene Callaci's "AskTellOrder.h"
library extension file makes an elegant use of BeforeParsing
to
convert commands in the form “ask mr darcy to dance” or
“tell jack to go north” to Inform's preferred form
“mr darcy, dance” and “jack, go north”.