Chapter 9: Understanding player input

Actions originate from the parser, a central part of the standard library. The detailed inner workings of the parser are beyond the scope of this manual (but interested readers are encouraged to look at the source code of the standard library). What story authors should know, though, is that the parser makes queries to understand-predicates, which are defined by the standard library and the story author together. These predicates can make queries to the parser in turn. To see how this back-and-forth is supposed to work, we must first introduce a new data structure:

Complex actions

During parsing, the standard library works with an intermediate representation of actions, called complex actions. Like regular actions, complex actions are lists of dictionary words and objects, but the following subexpressions are also allowed in them:

[+ #object1 #object2 ...]

The player referred to multiple objects here.

[a #object]

The player referred to a non-specific object that should be printed with “a” rather than “the”. [a ...] subexpressions may be nested inside [+ ...] subexpressions.

[]

The input contained one or more words that couldn't be parsed. When the complex action is printed, this part will appear as “something”.

[1]

The input contained one or more words that couldn't be parsed, and an animate object was expected. When the complex action is printed, this part will appear as “someone”.

[,]

The input contained multiple objects in an illegal place.

[all]

The input contained an ALL-expression in an illegal place.

Thus, a complex action might be:

[give [+ [a #apple] #peeler] to [1]]
[Copy to clipboard]

and its printed representation makes an appearance in the following message:

(I only understood you as far as wanting to give an apple and the peeler to
someone.)

The parsing process

The following chart illustrates the overall parsing process, starting with the player input as a list of words, and ending with a set of actions. The list of words is first split into a sequence of sublists by the word THEN or the full stop. If such a sublist cannot be parsed, it is in turn split by the first AND or comma. This allows the player to type multiple commands on one line, such as: N, U THEN DROP ALL, D.

Parsing actions

When the library needs to parse an action, it makes a multi-query to the predicate:

(understand $Words as $ComplexAction)
[Copy to clipboard]

A multi-query is made in order to collect every possible interpretation of the player's input, which could be ambiguous.

The first parameter is the input: a list of dictionary words. The second parameter is the output: a complex action.

Story authors can easily add rule definitions to this predicate, in order to add support for new verbs or set phrases:

(understand [take a break] as [wait])
[Copy to clipboard]

Note that the multi-query to (understand $ as $) may backtrack over several possible interpretations, e.g. [wait] and [take #break] if an object called “break” is in scope.

Understand-rules may of course have rule bodies:

(understand [who am i] as [examine $Player])
(current player $Player)
[Copy to clipboard]

Defining new actions

(understand [yodel] as [yodel])
[Copy to clipboard]

Here the story author has introduced a new action, [yodel]. When the name of the action is identical to the matched input, as in this particular case, a short form can be used instead:

(understand [yodel])
[Copy to clipboard]

So far, there is no code to handle the [yodel] action. The fallback implementations of the five action-handling predicates will cause the action to succeed without printing anything at all. This is a no-no in parser games, so the story author should at least add a perform-rule:

(perform [yodel])
Your voice soars over the mountain tops, bringing tears to many eyes.
[Copy to clipboard]

The standard library might have to print the name of an action, perhaps as part of a disambiguation question, or during debugging if the ACTIONS ON command has been issued. This is done with a query to the predicate (describe action $). The parameter is a complex action, so it may contain [+ ...] and other special subexpressions. Usually, there is no need to add an explicit rule for describing every new action; the default implementation of (describe action $) simply visits each element of the action (which is a list) in turn, printing dictionary words verbatim, and calling (the full $) for anything else.

Commands are system-level actions, such as SAVE or TRANSCRIPT OFF, that do not consume any time in the game world. The predicate (command $) decides whether an action is a command or not. Thus, to define a new command called HINT, we could write:

(understand [hint])
(command [hint])
(perform [hint])
Try yodeling a lot.
[Copy to clipboard]

There is also a short form that combines the first two rule definitions into one:

(understand command [hint])
(perform [hint])
Try yodeling a lot.
[Copy to clipboard]

Parsing object names

Many actions involve objects. The rule for understanding such an action will typically query a library-provided predicate for parsing a list of words as an object. There are several to choose from, but the most basic one is (understand $Words as non-all object $Object), which can be used like this:

(understand [transmogrify | $Words] as [transmogrify $Object])
*(understand $Words as non-all object $Object)
[Copy to clipboard]

Note that a multi-query must be used, because the words may be ambiguous. Suppose a red box and a blue box are in scope. TRANSMOGRIFY BOX will cause the above rule header to match, binding $Words to the single-element list [box]. Since there are two boxes, (understand [box] as non-all object $Object) will return twice, binding $Object to #redbox the first time, and to #bluebox the second time. Consequently, the rule for understanding the action will return twice, binding its output parameter to [transmogrify #redbox] the first time, and [transmogrify #bluebox] the second time.

Some actions involve two (or even more) objects, usually separated by a keyword such as a preposition. Dialog provides a handy built-in predicate for searching a list for a set of keywords, and splitting the list at the position where a match was found. Consider the following example, where a new “read something to somebody” action is created:

#book
(proper *)
(name *)To Kill A Mockingbird
#bird
(animate *)
(name *)mockingbird
(understand [read | $Words] as [read $Object to $Person])
*(split $Words by [to] into $Left and $Right)
*(understand $Left as non-all object $Object)
*(understand $Right as non-all object $Person)
[Copy to clipboard]

Again, the consistent use of multi-queries helps with disambiguation. If the player attempts to READ TO KILL A MOCKINGBIRD TO MOCKINGBIRD, $Words will be bound to [to kill a mockingbird to mockingbird]. The split-predicate first separates it into [] and [kill a mockingbird to mockingbird]. The empty list is not a valid object name, so the subsequent query to (understand $Left ...) fails, and the split-predicate proceeds with the next occurrence of the keyword: Now it separates $Words into [to kill a mockingbird] and [mockingbird], which makes the rest of the rule body succeed.

Still, the name of the second object (MOCKINGBIRD) is ambiguous, so the final invocation of *(understand $Right ...) returns twice. The parser will end up asking the player whether they wanted to read the book to the bird, or the book to the book. One way to address this problem is to indicate that the second noun is supposed to be animate:

(understand [read | $Words] as [read $Object to $Person])
*(split $Words by [to] into $Left and $Right)
*(understand $Left as non-all object $Object)
*(understand $Right as single object $Person preferably animate)
[Copy to clipboard]

The library provides a set of object-parsing predicates that favour objects with certain common traits, or limit the selection in some other way. The object-parsing predicates are:

• A predicate that accepts multiple objects, but not the word ALL:

(understand $Words as non-all object $Output)

• Predicates that accept multiple objects, including ALL:

(understand $Words as object $Output preferably held)
(understand $Words as object $Output preferably held excluding $ExcludeObj)
(understand $Words as object $Output preferably worn)
(understand $Words as object $Output preferably takable)
(understand $Words as object $Output preferably child of $Parent)

• Predicates that only accept a single object (possibly implied by ALL):

(understand $Words as single object $Output)
(understand $Words as single object $Output preferably held)
(understand $Words as single object $Output preferably animate)
(understand $Words as single object $Output preferably supporter)
(understand $Words as single object $Output preferably container)

Some of the variants above are primarily there to provide context for the word ALL. For instance, TAKE ALL should only select takable objects (items not already held), while DROP ALL should operate on held objects. But the preferably specifier is also used to carry out some initial disambiguation, so that e.g. FEED BIRD might be understood as an intention to feed the bird (animate), but not the bird cage.

Two further variants allow the story author to specify an arbitrary condition using a closure:

(understand $Words as object $Obj preferably $Closure)
(understand $Words as single object $Obj preferably $Closure)

The closure takes a candidate object as parameter. Here is an example of how to parse an object name while favouring objects that can be picked up, but not eaten:

(understand $Words as object $Obj preferably {
(item $_)
~(edible $_)
})
[Copy to clipboard]

The output of all of these object-parsing predicates is either an object or a list that represents a complex object (e.g. [+ $Obj1 $Obj2 $Obj3], or [a $Obj1], or []) For the as single object rules, the output is guaranteed to be either an object or one of the values that indicate a parse error.

Directions and numbers

Some actions involve a named direction, such as SOUTHWEST, OUT, or UP. To parse a direction, use the predicate:

(understand $Words as direction $Dir)
[Copy to clipboard]

As when parsing objects, $Dir is potentially a complex expression: When the player types PUSH CART SOUTHWEST, OUT AND UP, the words [southwest , out and up] will be understood as the complex direction [+ #southwest #out #up].

To parse a number, typed using decimal digits or spelled out as a word, use:

(understand $Words as number $N)
[Copy to clipboard]

The output parameter $N is a number, and thus limited to the range 0–16383.

To go the other way, printing a numeric value with words, use:

(spell out $N)
[Copy to clipboard]

Topics

Some actions, e.g. [ask $ about $] and [tell $ about $], involve topics of conversation. As we saw in the Standard actions chapter, topics can be regular objects, but sometimes it makes more sense to use dictionary words to represent abstract concepts. To parse a topic, use the predicate:

(understand $Words as topic $Topic)
[Copy to clipboard]

It is possible to add rules to that predicate in order to add new topics to a game:

(understand [my childhood] as topic @childhood)
(understand [growing up on planet zyx] as topic @childhood)
[Copy to clipboard]

However, that would result in a parser that is very picky about the exact wording of ask/tell commands, so it is not generally recommended. A better (but potentially slower) approach is to look for keywords or key phrases like this:

(understand $Words as topic @childhood)
($Words contains sublist [growing up])
(or) ($Words contains one of [childhood planet zyx])
[Copy to clipboard]

The default implementation of (understand $ as topic $) tries to strike a balance between performance and flexibility by using a system of simple keywords. Keywords are defined with (topic keyword $ implies $):

(topic keyword @childhood implies @childhood)
(topic keyword @zyx implies @childhood)
[Copy to clipboard]

A short form is available when the keyword equals the topic value:

(topic keyword @childhood)
[Copy to clipboard]

All of these variants can of course be combined. For instance, the keyword approach could be employed as a fall-back that often works well enough, and specific understand-rules could use the (just) keyword to overrule the keyword system when it would otherwise misfire:

(understand [your childhood] as topic #doctor)
(just)
(topic keyword @childhood)
(topic keyword @yourself implies #doctor)
[Copy to clipboard]

The (just) keyword can also be used to selectively disable the behaviour where objects in scope are understood as topics. For instance, in an aquarium, the word FISH might be accepted as a synonym for every individual fish in the room. But suppose we want the last word of ASK CLERK ABOUT FISH to be understood unambiguously as being in reference to the general subject of fish. That is, suppose we don't want the game to ask the player if they meant to ask about fish in general, the zebrafish, or the neon tetra. To obtain the desired behaviour, we just have to:

(understand [fish] as topic @fish)
(current room #aquarium)
(just)
[Copy to clipboard]

Printed representation

Topics are supposed to have a printed representation, accessible via the (describe topic $) predicate. The default implementation of this predicate delegates to (the full $) when the topic is an object; otherwise it just prints the word “something”. Story authors are strongly recommended to override this predicate for non-object topics:

(describe topic @childhood)
your childhood
[Copy to clipboard]

When you add new actions that involve topics, remember to add corresponding (describe action $) rules as well. That's because the default implementation of (describe action $) is rather crude: It looks at each element of the action list, printing full descriptions of any objects, and printing dictionary words as they appear. But if the dictionary word happens to be a topic, the proper thing to do is to query (describe topic $) to print it. Thus:

(understand [complain about | $Words] as [complain about $Topic])
*(understand $Words as topic $Topic)
(describe action [complain about $Topic])
complain about (describe topic $Topic)
[Copy to clipboard]

Adjusting the likelihood of actions

When the player's input can be understood in multiple ways, it is up to the library to weigh the different interpretetions against each other, and select the one most probably intended by the player. This is achieved by looking at the actions from a semantical point of view, and discarding the unlikely ones, as determined by the predicate (unlikely $):

(unlikely [open $Object])
~(openable $Object)
(unlikely [open $Object])
($Object is open)
[Copy to clipboard]

If that's not enough, and several equally likely (or unlikely) interpretations remain, the library will ask the player a disambiguating question.

Thus, if the player is located in a room with a wooden door (open), and holds a wooden box (closed), and attempts to OPEN WOODEN, that will be understood as a request to [open #woodenbox]. The alternative, [open #woodendoor], gets discarded due to the second rule above. But if both the door and the box are open, both actions are deemed equally unlikely, and the game resorts to asking the player what they meant.

Story authors may override (unlikely $) to influence this proceduce. For instance, if a room contains a red lever and a red indicator light, it's up to the author to specify:

(unlikely [pull #redlight])
[Copy to clipboard]

which makes PULL RED do the expected thing.

Sometimes it is necessary to override (unlikely $) with a negated rule, when a more general rule would identify it as unlikely by default. For instance, suppose a location contains a wall-mounted ladder, and the story author wants the game to understand CLIMB LADDER as going up. The functionality itself is implemented by redirecting [climb #ladder] to [go #up]:

(instead of [climb #ladder])
(current room #ladderroom)
(try [go #up])
[Copy to clipboard]

But [climb #ladder] is still considered unlikely by the parser, because (we assume) the ladder is not an actor supporter, i.e. it is not possible to be located #on the ladder. Now, if the player were to attempt to CLIMB LADDER while also holding the ladder instruction manual, the game would ask which one of the objects to climb. To prevent that slightly surreal question, a negated rule can be defined:

~(unlikely [climb #ladder])  %% Climbing the ladder is not unlikely after all.
[Copy to clipboard]

In Dialog, the current room and its neighbours are in scope by default. But rooms are often named by some conspicuous object contained inside them, so that e.g. an engine might be located in the engine room. To avoid a lot of disambiguating questions, any action that expilitly mentions a room is considered unlikely by default. This is implemented in the library using a catch-all rule for (unlikely $Action) that delegates to (unlikely due to room $Action). The latter checks that this isn't one of the few actions that are likely to involve rooms ([enter $Room], [leave $Room], and [go to $Room]). If it isn't, and if the action contains a room object, the predicate succeeds and the action is considered unlikely.

When the player's input can be understood as several actions, but all of those actions are deemed unlikely, there's still a chance to avoid a disambiguating question: In these situations, the library invokes (unlikely due to room $) a second time, and discards any actions that were deemed unlikely due to rooms; they are considered extra unlikely.

For this reason, if you ever add a new action that involves a room object directly, make sure to override (unlikely due to room $) instead of just (unlikely $).

Synonyms

Before the player's input is handed to the action-parsing predicate (understand $ as $), it undergoes rewriting: The predicate (rewrite $Input into $Output) is queried once (i.e. neither iteratively nor with a multi-query), and may transform the list of words in any way it sees fit before parsing. This can be used to define multi-word synonyms for verbs:

(rewrite [pick up | $Words] into [take | $Words])
(rewrite [let go of | $Words] into [drop | $Words])
[Copy to clipboard]

Word-for-word replacements should preferably be handled directly in the heads of the understand-rules, using slash expressions:

(understand [drink/sip/quaff | $Words] as [drink $Obj])
*(understand $Words as non-all object $Obj)
[Copy to clipboard]

Asking for clarification

Some actions are designed to require objects, but it makes grammatical sense to use the verb alone (intransitively), or with fewer objects than the author had in mind. For instance, an understand-rule could be added to recognize PLAY VIOLIN WITH BOW as the action [play #violin with #bow]:

(understand [play | $Words] as [play $Obj1 with $Obj2])
*(split $Words by [with using] into $Left and $Right)
*(understand $Left as single object $Obj1)
*(understand $Right as single object $Obj2 preferably held)
[Copy to clipboard]

But now, players who enter PLAY VIOLIN (or just PLAY) will be met by an unhelpful message about not understanding what they wanted to do. In this case, it's a good idea to add partial actions that nudge the player towards the full sentence. These actions can ask the player for clarification, and set up an implicit action using one of the predicates (asking for object in $) and (asking for direction in $). The parameter is a simple action, with an empty list [] marking the position of a blank slot. If the player now types in the name of an object (or, optionally, USE followed by the name of an object), this will be understood as the implicit action, with that object in the slot. Thus:

(understand [play | $Words] as [play $Obj])
*(understand $Words as single object $Obj)
(perform [play $Obj])
With what?
(asking for object in [play $Obj with []])
(understand [play])
(perform [play])
Play what?
(asking for object in [play []])
[Copy to clipboard]

Note that (asking for object in $) and (asking for direction in $) will automatically invoke (stop) to prevent any subsequent actions: We've asked the player a question, so we have to give them an opportunity to respond.

Of course, it is also possible to override your own action-handling rules for this kind of intermediate actions, in specific situations where no additional object is required:

(perform [play #piano])
You plink away at the Maple Leaf Rag, only to get stuck in the trio.
[Copy to clipboard]

Just remember that rules are tried in program order, so the rule for playing the piano must appear before the generic perform-rule that asks for a second object. One approach is to organize the story file as a large bulk of object-specific rule definitions, followed by a smaller section at the end where new actions are defined.

Rule order

The order of understand-rules has another, more subtle implication. The library usually parses actions in a strict mode, where every word of the player's input must be accounted for. If that fails, the library sets a global flag that makes the understand ... as object rules more lenient, and tries to parse the input a second time. Now, unparseable input is represented by special markers ([], [1], [,], or [all]) in the resulting complex action, and a description of that incomplete action is printed. Thus, PLAY SPICCATO WITH BOW might result in the response “I only understood you as far as wanting to play something with the bow”. But here's the subtlety: Only the first incomplete action will be described in that message. So if the rules were defined in this order:

(understand [play | $Words] as [play $Obj])
*(understand $Words as single object $Obj)
(understand [play | $Words] as [play $Obj1 with $Obj2])
*(split $Words by [with using] into $Left and $Right)
*(understand $Left as single object $Obj1)
*(understand $Right as single object $Obj2 preferably held)
[Copy to clipboard]

then the entire subexpression SPICCATO WITH BOW would be interpreted as a badly formed object reference, and the error message would just be: “I only understood you as far as wanting to play something.

Thus, in the interest of providing as informative error messages as possible to the player, authors are encouraged to always put the longest understand-rule first, i.e. the one that involves the most objects.

Example: Defining a new action

Story-specific actions are typically defined towards the end of the source code file. This allows object-specific rules, defined earlier in the file, to override them.

In this example, we first split the player's input by either of the keywords WITH and USING. Then we parse the right-hand side first, obtaining a tool object. We then exclude the tool when parsing the left-hand side: If the player types PEEL ALL WITH PEELER, the ALL will expand to every held object except the peeler. Note that this may still not be what the player intended (because in addition to fruit, they might be holding a brass key and a lamp), but at this stage we are primarily interested in grammar, not semantics.

Remember, put the longest understand-rule first:

(understand [peel | $Words] as [peel $Object with $Tool])
*(split $Words by [with using] into $Left and $Right)
*(understand $Right as single object $Tool preferably held)
*(understand $Left as object $Object preferably held excluding $Tool)
(understand [peel | $Words] as [peel $Object])
*(understand $Words as object $Object preferably held)
(perform [peel $Obj])
With what?
(asking for object in [peel $Obj with []])
[Copy to clipboard]

Either variant is deemed unlikely for non-edible objects:

(unlikely [peel $Obj | $])  %% Match both variants.
~(edible $Obj)
[Copy to clipboard]

The likelihood of an action helps resolve ambiguities, but it won't prevent the action from being attempted: If the player unambiguously tries to peel the kitchen floor, that request is going to go through, unlikely or not. Thus we also need:

(prevent [peel $Obj | $])
~(edible $Obj)
That's not something you can peel.
[Copy to clipboard]

Likewise, because we specified preferably held, the library will try to satisfy the parser rules using objects that are held by the player, so that e.g. PEEL FRUIT will prioritize held fruit over non-held fruit. But an unambiguous PEEL BANANA will be understood even when the banana isn't held.

Thus, we need a rule to prevent the peeling of a non-held object. The standard library provides a number of handy when-predicates that check for common conditions, and print appropriate responses when the conditions are met.

(prevent [peel $Obj | $])
(when $Obj isn't directly held)
(prevent [peel $ with $Obj])
(when $Obj isn't directly held)
[Copy to clipboard]

But, out of the kindness of our hearts, we might decide to pick up the mentioned objects automatically before attempting the peel action:

(before [peel $Obj | $])
%% This will invoke (first try [take $Obj]) if necessary:
(ensure $Obj is held)
(before [peel $ with $Obj])
(ensure $Obj is held)
[Copy to clipboard]

Finally, there needs to be a default response for the [peel $ with $] action (we already have one for the [peel $] action):

(perform [peel $Obj with $Tool])
After an extended period of fumbling, you conclude that you don't know
how to peel (the $Obj) with (the $Tool).
(tick) (stop)
[Copy to clipboard]

Of course, the story should also contain a couple of objects that would make the peel action succeed. The following object-specific rules must be defined before the generic rules described above, otherwise they will never match:

(edible #apple)
(edible #peeled-apple)
(perform [peel #apple with #peeler])
You peel the apple without cutting yourself even once.
(#apple is $Rel $Loc)
(now) (#apple is nowhere)
(now) (#peeled-apple is $Rel $Loc)
[Copy to clipboard]

Finally, we could smoothen gameplay by implicitly assuming that if the player is holding the peeler, that's probably their tool of choice for peeling:

(instead of [peel $Obj])
(current player $Player)
(#peeler is #heldby $Player)
\( with the peeler \) (line)  %% Tell the player what's going on.
(try [peel $Obj with #peeler])
[Copy to clipboard]

Hyperlinks

When a Dialog program is compiled to run on the Å-machine, the text may contain clickable links that resolve into text input. Selecting a link has the same effect as typing the words of the link target and pressing return. This can simplify text entry on mobile devices.

Links are created using the special (link $) ... syntax:

In the bowl a (link [red marble]) { red marble } glistens in the sunlight.
[Copy to clipboard]

Often, as above, we want both the link target and the clickable text to be the printed name of an object. The standard library provides a tersely named predicate, ($), for this case:

In the bowl a (#redmarble) glistens in the sunlight.
[Copy to clipboard]

The same predicate is useful for describing exits:

An exit leads (#north).
[Copy to clipboard]

To use the printed name of an object as a link target, but supply a different text, use the predicate ($ $). The first parameter is an object, and the second is a closure:

In the bowl (#redmarble {something red}) glistens in the sunlight.
[Copy to clipboard]

Be aware, however, that hyperlinks are an optional feature of the Å-machine, and not every interpreter will support them. While it can be tempting to create a jarring effect by having links resolve into unexpected input text, some players will simply not see it.

By default, library-generated messages never contain hyperlinks. The behaviour of the library should be consistent with the rest of the story, and whether or not to sprinkle room descriptions with clickable links is a decision best left to the author.

To enable clickable links in disambiguation messages, the game over menu, default appearances, and queries to (a $) and (A $), add the following rule definition to the source code:

(library links enabled)
[Copy to clipboard]

Since link targets are appended to the current line of input, readers who are playing on a touchscreen device can type a verb using the on-screen keyboard, and then complete the sentence by tapping on a recently printed object name. Compass directions can of course be used directly as commands.

However, when we turn our nouns into hyperlinks, players will (understandably) attempt to click on them without first typing a verb. To handle this situation, the standard library provides an optional feature called default actions. It is enabled like this:

(default actions enabled)
[Copy to clipboard]

When this feature is enabled, the parser will understand noun-only input as a request for the default action, which is examine. The default action can be changed, and may depend on the object, as in the following example:

(default action (animate $Obj) [talk to $Obj])
[Copy to clipboard]