| ||||||||
Contents Back Forward |
| |||||||
The parser's fundamental method is simple. Given a stream of text like saint / peter / , / take / the / keys / from / paulit first calls the entry point BeforeParsing (in case you want to meddle with the text stream before it gets underway). It 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 (anyone animate or anything talkable will do): in this case St Peter. This person is called the "actor'', since he is going to perform the action, and is usually the player himself (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 global variables actor and verb_word while working. (In the example above, their values would be the St Peter object and 'take', respectively.) | ||||||||
| ||||||||
It isn't quite that simple: names of direction objects are treated as implicit "go'' commands, so that "n'' is acceptable as an alternative to "go north''. There are also "again'', "oops'' and "undo'' to grapple with. | ||||||||
| ||||||||
Also, a major feature (the grammar property for the person being addressed) has been missed out of this description: see the latter half of Section 16 for details. | ||||||||
Teaching the parser a new synonym is easy. Like all of the directives in this section, the following must appear after the inclusion of the library file Grammar: Verb "steal" "acquire" "grab" = "take";This creates another three synonyms for "take''.
| ||||||||
One can also prise synonyms apart, as will appear later. | ||||||||
The parser is now up to word 5; i.e., it has "the keys from paul'' left to understand. Apart from a list of English verb-words which refer to it, an Inform verb also has a "grammar''. This is a list of 1 or more "lines'', each 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 itself 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. For instance, the line of 3 tokens <a noun> <the word from> <a noun>matches the text. Each line has an action attached, which in this case is Remove: so the parser has ground up the original text into just four numbers, ending up with actor = st_peter action = Remove noun = gold_keys second = st_paulWhat happens then is that the St Peter's orders routine (if any) is sent the action, and may if it wishes cooperate. If the actor had been the player, then the action would have been processed in the usual way.
| ||||||||
The action for the line which is 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 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 Section 30 for details.) 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 bananaand so on. A full list of tokens will be given later: briefly,
| ||||||||
Since this book was first written, the library has been improved so that "take'' and "get'' each have their own independent grammars. But for the sake of example, suppose they share the grammar written out above. Sometimes this has odd results: "get in bed" is correctly understood as a request to enter the bed, "take in washing" is misunderstood as a request to enter the washing. You might avoid this by using Extend only to separate them into different grammars, or you could fix the Enter action to see if the variable verb_word=='take' or 'get'. | ||||||||
| ||||||||
Some verbs are meta - they are not really part of the game:
for example, "save'', "score'' and "quit''. These are declared using
Verb meta, as in
Verb meta "score" * -> Score;ninepoint 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 and after 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 and 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 very often need to alter these, usually to add extra lines of grammar but sometimes to remove existing ones. For example, consider an array of 676 labelled buttons, any of which could be pushed: it's hardly convenient to define 676 button objects. It would be more sensible to create a grammar line which understands things like "button j16", "d11", "a5 button"(it's easy enough to write code for a token to do this), and then to add it to the grammar for the "press'' verb. The Extend directive is provided for exactly this purpose: Extend "push" * Button -> PushButton;The point of Extend is that it is against the spirit of the Library to alter the standard library files -- including the grammar table -- unless absolutely necessary.
| ||||||||
Another method would be to create a single button object with a parse_name routine which carefully remembers what it was last called, so that the object always knows which button it represents. See 'Balances' for an example. | ||||||||
Normally, extra lines of grammar are added at the bottom of those already there. This may not be what you want. For instance, "take" has a grammar line * multi -> Takequite early on. So if you want to add a grammar line which diverts "take something-edible" to a different action, like so: * edible -> Eat(
Extend "take" first * edible -> Eat;You might even want to over-ride the old grammar completely, not just add a line or two. For this, use Extend "push" replace * Button -> PushButton;and now "push" can be used only in this way. To sum up, Extend can take three keywords:
with last being the default (which doesn't need to be said explicitly).
| ||||||||
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 "get" * "with" "it" -> Sing;makes "get" behave exactly like "take" (as usual) except that it also recognises "with it", so that "get with it" makes the player sing but "take with it" 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 extension made to one will not be made to the other. | ||||||||
| ||||||||
There are (a few) times when verb definition commands are not
enough. For example, in the original 'Advent' (or 'Colossal Cave'),
the player could type the name of a not-too-distant place which had
previously been visited, and be taken there. There are several ways to code
this -- say, with 60 rather similar verb definitions, or with a single
"travel" verb which has 60 synonyms, whose action routine looks at the
parser's verb_word variable to see which one was typed, or even by restocking
the compass object with new directions in each room -- but here's
another. The library will call the UnknownVerb 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 a foolish example:
[ UnknownVerb w; if (w=='shazam') { print "Shazam!^"; return 'inventory'; } rfalse; ];which responds to the magic word "shazam" by printing Shazam! and then, rather disappointingly, taking the player's inventory. But in the example above, it could be used to look for the word w through the locations of the game, store the place away in some global variable, and then return 'go'. The GoSub routine could then be fixed to look at this variable. | ||||||||
| ||||||||
EXERCISE 65: (link to the answer) | ||||||||
Why is it usually a bad idea to print text out in an
UnknownVerb routine?
| ||||||||
If you allow a flexible collection of verbs (say, names of spells or places) then you may want a single 'dummy' verb to stand for whichever is being typed. This may make the parser produce strange questions because it is unable to sensibly print the verb back at the player, but you can fix this using the PrintVerb entry point. | ||||||||
| ||||||||
EXERCISE 66: (link to the answer) | ||||||||
Implement the Crowther and Woods feature
of moving from one room to another by typing its name, using a
dummy verb.
| ||||||||
EXERCISE 67: (link to the answer) | ||||||||
Implement a lamp which, when rubbed, produces a
genie who casts a spell over the player to make him confuse the
words "white'' and "black''.
| ||||||||
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. |