In the second post, we covered noun phrases -- phrases like "all cubes except red" and "lamp, food, and bottle" that the player can type to refer to objects.
We also covered how the parser recognizes them and how it represents them in memory: the
PARSE-NOUN-PHRASEroutine scans the player's command and fills in a
NOUN-PHRASEstructure, which holds a list of adjective/noun pairs called
But how does the parser identify which objects the player is referring to?
MATCH-SYNTAXfinds a syntax line that matches the player's command, the
FIND-OBJECTSroutine combines the noun phrases with the find flag and search options from the syntax line, and decides what needs to be done for each object required by the syntax line.
There are a few possibilities, depending on what the player typed. It can:
- match a noun phrase,
- expand a pronoun,
- supply a missing object,
- ask the player to clarify,
- or fail, printing an error message.
Matching a noun phrase
The most obvious choice: if the player typed "shiny lamp", we want to find an object whose
SYNONYMproperties contain the words
LAMP, somewhere within the player's reach.
MATCH-NOUN-PHRASEdoes just that. It runs through the
OBJSPECs included by the noun phrase -- called
YSPECs, "Y" for "yes" -- and for each one, it uses a
MAP-SCOPEloop to search for objects that match the
YSPECand aren't excluded by any
NSPECs ("except" clauses). The objects that match are written into a table (
P-PRSIS), where they'll eventually be used to perform the action.
MAP-SCOPEis a powerful, flexible loop statement, which chooses a set of scope stages (from scope.zil) and runs the loop body for each object encountered in those stages.
In part 1, we saw that the syntax line contains search options telling the parser where to look for the direct and indirect objects. Those options correspond to scope stages: if we give
MAP-SCOPEa set of search options, it'll only use the stages corresponding to those options.
When it comes to interpreting a player's command, though, the search options are really only guidelines. If the player types "pick up axe" when there are two axes available, they probably don't mean the axe they're holding, so we only want to match the one they aren't holding. But if there's only one axe and they're holding it, we want to settle for that one, so we can come up with a better error message than "You don't see that here."
So the parser might need to do more than one search for the same
YSPEC: once with the scopes suggested by the syntax line, and again with a wider set of scopes if it didn't match any objects the first time.
(That's an oversimplification. The way it actually works is... complicated... in order to address some special cases. It seemed like a good idea at the time.)
Match quality and
OBJSPECs contain an adjective and a noun, and they can match an object using either or both. In fact, since a word can be both a noun and an adjective, an
OBJSPECthat only contains a noun can match an object's adjective.
If the player types "get polish" when there's a Polish sausage and a can of shoe polish available, we assume they mean the can of shoe polish, because the command makes more grammatical sense with a noun than an adjective. But if only the sausage is available, we accept a match using only the adjective.
That logic is in the
REFERS?routine, which checks an
OBJSPECagainst an object's
SYNONYMproperties and returns a match score: 0 for no match, 1 for adjective-only, 2 for noun-only, and 3 if the adjective and noun both matched. Only the highest-scoring set of objects are kept as matches: a single noun match takes precedence over all the adjective-only matches.
As a special case, objects with the
INVISIBLEflag can never match. The parser skips them without even calling
OBJSPECs that don't match
Usually, it's an error if any of the
OBJSPECs in a
NOUN-PHRASEreturns no matches: if the player types "get lamp and sword" when there's no sword, we assume they won't settle for just the lamp.
But there's an exception for "except". If every object matched by a
YSPECis excluded by one of the
NSPECs, the parser lets it slide, so "get all swords and shields except rusty" can succeed even if every sword available is rusty. If every shield is rusty too, though, the parser will complain "There are none at all available!"
GENERIC-OBJECTS and pseudo-objects
GLOBAL-OBJECTS, but for objects that should only match as a last resort. Typically those are concepts like
NUMBER, or conversation topics, or placeholders for objects the player can refer to when they're not present (like an NPC they might want to follow).
The parser never considers
YSPECfails to match in all the other scopes, at which point it enables "ludicrous scope" and starts over.
ALL by itself
Commands with no
YSPECs, like "take all" and "drop all but lamp", form a special case that skips most of the steps above.
In this case, the parser calls
MAP-SCOPEto find everything in the player's reach, but instead of
REFERS?, it calls
ALL-INCLUDES?to filter out objects that "all" shouldn't apply to. That means
INVISIBLEobjects and the
WINNERfor sure. For
DROP, it also skips any objects that need special care to pick up (ones with
TRYTAKEBIT) or can't be picked up at all (ones missing
Also, in this case,
GENERIC-OBJECTSare always skipped. We assume the player means all the nearby objects, not their hands, the sun, or conversation topics.
LOCAL-GLOBALScan still match.
Returning the matches
If an error occurs in any step above,
MATCH-NOUN-PHRASEreturns zero after printing an error message.
If a single object was matched, it returns that object.
If multiple objects were matched, a few things can happen:
- If the mode is "all" or if the player clearly asked for more than one object (by providing multiple
OBJSPECs), it returns the placeholder
MANY-OBJECTS, which tells
PERFORMto repeat the action for all the objects in
- If the mode is "any", it picks a random object from the set of matches, prints a clarifying message, and returns that object.
- If any of the matched objects have a
GENERICfunction, it calls them all until one of them returns an object.
- If none of that worked, it asks the player to clarify which matched object they mean, and puts the command on hold until they answer. (This is what most languages call disambiguation. In ZIL, it's called orphaning, and we'll cover it in more detail in a later post.)
That's all for
MATCH-NOUN-PHRASE. But what if the player didn't name an object at all?
Expanding a pronoun
A noun phrase can also just be a pronoun. Before
EXPAND-PRONOUNgets a chance to recognize the pronoun and load the appropriate list of objects.
The pronouns are defined in pronouns.zil, and the list of objects for each pronoun is saved either by the parser (which calls
SET-PRONOUNSafter a successful parse), or by explicitly calling
CONTENTS-ARE-ITto make the pronouns refer to something the game mentioned.
Since the objects or the player may have moved since the list was written,
EXPAND-PRONOUNalso makes sure the player can still see them.
Supplying a missing object
Sometimes the syntax line that comes closest to matching the player's command still isn't a perfect match, because the command is incomplete.
For example, if the player types "enter" by itself, the only syntax line for that verb requires an object, so the parser needs to find an object to put there:
<SYNTAX ENTER OBJECT (FIND DOORBIT) (IN-ROOM) = V-ENTER>
The find flag is optional, but important. If it's omitted,
GWIMroutine ("Get What I Mean") uses the find flag and search options to look for a single object that matches. If there's exactly one object with that flag (
DOORBIT) in that scope (
GWIMprints a clarifying message and uses that object as the missing noun. Otherwise, it fails, and
FIND-OBJECTSorphans the command after asking "What do you want to enter?"
The find flag is optional, but important. If it's omitted,
GWIMcan only succeed if there's only one object at all in that scope.
GWIMis also called for perfectly good commands, when they happen to end with a preposition.
Recall from part 1 that every preposition in a syntax line has to be associated with a noun phrase. According to ZIL's primitive concept of sentences, "turn lamp off" isn't a valid sentence, and neither is "take inventory" (since "inventory" isn't a noun phrase, it's fixed syntax). But of course we want to support those syntaxes. The solution is to put the extra object slot in the syntax line, and mark it with a special find flag:
Now "turn lamp off" is parsed as if it's missing the second noun phrase, and when
<SYNTAX TURN OBJECT (FIND DEVICEBIT) OFF OBJECT (FIND KLUDGEBIT) = V-TURN-OFF>
GWIMis called for it, it sees the
KLUDGEBITand silently fills in a special object for
ROOMS, which the player could never refer to otherwise.
This doesn't stop the player from providing a second noun phrase if they want to, which can be a mixed blessing. For example, the same syntax line that uses
KLUDGEBITto match "put brick down" could also match "put brick down chute"... but if the verb code isn't written to handle both uses, the second noun will simply be ignored.