A parse_command tutorial

Written by Matroid of OuterSpace Mud
dean@stack.nl

1. Introduction
2. Parsing simple text
3. Parsing objects
4. Using AFTER_PARSE checks
5. Examples

1. Introduction

In the more traditional LP mudlibs like the one used in OuterSpace, commands are often defined locally by add_action(). This function associates a player command with a routine to validate the input and act accordingly. We will call these command routines command functions.

In some cases it will suffice to check that the input matches a simple string. For example, if you add a command "open" to open a gate, the code could be as simple as:


int
do_open(string str) {
	if (str != "gate")
		return notify_fail("Open what?\n");
	/* ... rest of function ... */
	return 1;
}

However, if you want to allow alternatives like "the gate", or "door", it gets more ugly and ugly:

int
do_open(string str) {
	if (str != "gate" && str != "the gate" && str != "door" &&
			str != "the door")
		return notify_fail("Open what?\n");
	/* ... rest of function ... */
	return 1;
}

One could use the following, but only slightly neater formulation:

int
do_open(string str) {
	if (member_array(str, ({ "gate", "the gate", "door",
			"the door" })) < 0)
		return notify_fail("Open what?\n");
	/* ... rest of function ... */
	return 1;
}

It isn't very pleasing, is it? Fortunately, there is a special function for matching this kind of input in a more natural way. This is the parse_command() function. Do not worry if you don't understand the code below, I'll explain it later in this tutorial:

int do_open(string str) {
	if (!str || !parse_command(str, ({}), "[the] 'door' / 'gate'))
		return notify_fail("Open what?\n");
	/* ... rest of function ... */
	return 1;
}

Maybe this doesn't sound too much of an improvement... you need to add an additional check (the !str part), but if you look at it more closely, you can see there is no longer a need to summarize all possible inputs. You can specify it in a single line of text. This is one of the powers of parse_command().

Parsing text like this is very useful, but there are more complicated scenarios. Let's suppose you've written a silver statuette, as well as a special command, "polish". Now the code for the command function do_polish could look like this:

int
do_polish(string str) {
	if (str != "statuette" && str != "the statuette") 
		return notify_fail("You fail to polish this.\n");
	if (is_polished)
		return notify_fail("There is no need to polish the "+
			"statuette.\n");
	/* ... */
	is_polished = 1;
	return 1;
}

Yes, you could change the first line to use parse_command like in the previous example. But there are more things wrong with this code, that aren't fixed by just using parse_command() like we did before.

For starters, if the player has two statuettes, there is no way that the player can state something like "polish second statuette" so that it works on the second statuette... in fact, the player will see "You fail to polish this.". The player will even get that message twice!

Here parse_command() will show its true power: it can actually handle input like "second vase", "the broken handle", and even "five bananas".

Another problem with the example code above is that it does not check for anything like the object's environment. In the case of polish, that may not matter much, but for commands like "eat" or "wield", it is normal that it is checked whether the player has the object in his inventory. This kind of situations will be discussed in the section Matching objects. It is too detailed to go into here. Other important checks aren't performed either, like whether the player can actually see the object. It may be hidden, or the player could be in a dark room. These checks will be studied in more detail in the section Using AFTER_PARSE checks.

I think the examples above show some good reasons why simple text matching is not adequate and more powerful solutions are needed. parse_command() provides in this need. I hope this tutorial will be helpful to you in how to make proper use of it.

Matroid, January 2002

2. Parsing simple text

In this section we will have a closer look at how to parse simple text that is not really associated with objects. There is no reason to add a 'gate' object to your room, checking for names like we did in the Introduction does the job quite well.

We'll begin with looking more closely at the syntax of parse_command:

int parse_command(string command, object env | object *oblist,
		string pattern, mixed arg, ...)

The first argument, command, is the player's input, with the exception of the first word, the verb. If there is a need to know the verb, you can get it by calling query_verb. We'll show an example of using query_verb below. Note that the command argument is just the argument supplied to the command function.


int do_open(string str) {
	if (!str || !parse_command(str, ({}), "[the] 'gate'"))
		return notify_fail(capitalize(query_verb()) +
			" what?\n");
	/* ... */
	return 1;
}

void init() {
	::init();
	add_action(do_open, "open");
	add_action(do_open, "push");
}

If the player specifies only a verb, but nothing else, the argument str is set to the integer 0. Because parse_command expects a real string, we always provide the !str check to see if a real string was specified. If you forget this check and the player specifies nothing but the verb, parse_command will error.

Note in the above example as well how we use query_verb. Both the "open" and "push" commands call the do_open function. If the player gives illegal arguments, he'll get "Push what?" if he used "push", and "Open what?" if he used "open".

The second argument of parse_command is either an object or an array of objects. This argument is of great importance when parsing objects, but not interesting for text matches. We therefore always specifiy an empty array, ({}) as the second argument of parse_command when matching text only.

The simple text parsing capability is all about the third argument: the pattern. (The other arguments are not needed for this kind of parsing). The pattern is given as a string of words, separated by spaces. parse_command knows two kinds of words: optional words and required words. The meaning of this is as it seems: optional words can be left out.

Required words are surrounded by single (right) quotes ('), just left of the Enter/Return key on most keyboards. Optional words are surrounded with brackets ([ and ]). Note that the spaces are supposed to be *outside* the quotes/brackets. An example:

	"[the] 'gate'"

matches both "the gate" and "gate". Similarly, to match both "the door" and "door", you would write

	"[the] 'door'"

Now this wouldn't be very powerful if there was no way to combine these two patterns into one. The pattern string can have a special symbol, the slash (/) which is used as a marker for an alternative word. Between the slash and other words there must be a space. Examples:

	"[the] 'gate' / 'door'"

matches "the gate", "gate", "the door" and "door".

	"[on] [the] 'gate' / 'door' 'of' 'evil'"

matches "on the gate of evil", "the door of evil", but not "on the door of" or "door". It is important to note that the alternative-marker works on a word-by-word basis.

Sometimes practicality comes into place as well. If we have a gate of evil in the room, you may want to allow "on the gate" as well as "on the gate of evil". Personally, I would use the following as my pattern string then:

	"[on] [the] 'gate' / 'door' [of] [evil]"

This allows what we want, but it also allows factually incorrect input like "on the gate of" and "gate evil". I wouldn't mind these for practical reasons.

"But how about the fourth argument and the rest?", you may wonder. Well, these are used if we use other special symbols in our pattern string. We'll get to these next. But first a more complete example of what we've seen so far. We use one other feature.

In the above code, when an error occurs, we often do:

	return notify_fail("Error text\n");

This doesn't return an error message. Instead, it returns with the return value of notify_fail. This function is used to set a fail message. The following code would do the same:

	notify_fail("Error text\n");
	return 0;

The fail message is only displayed if we return 0, if we return a nonzero value, the command is successful and no message is displayed. If we don't call notify_fail manually, the default message "What?\n" is used.

The advantage of calling the notify_fail() function seperately, is that we don't have to repeat the same error text all the time. Compare the next two skeletons:

int
do_func(string str) {
	if (some check here)
		return notify_fail("Some error text here.\n");
	if (some other check here)
		return notify_fail("Some error text here.\n");
	if (yet another check here) {
		do_something_boring();
		if (you get the idea)
			return notify_fail("Some error text here.\n");
		...
	}
	return 1;
}

and

int
do_func(string str) {
	notify_fail("Some error text here.\n");
	if (some check here)
		return 0;
	if (some other check here)
		return 0;
	if (yet another check here) {
		do_something_boring();
		if (you get the idea)
			return 0;
		...
	}
	return 1;
}

It saves you from duplicating the same fail message all the time. Now let's see some real code:

int
do_press(string str) {
	notify_fail(capitalize(query_verb()) + " what?\n");
	if (!str)
		return 0;
	if (!parse_command(str, ({}),
		"[the] [red] 'button' / 'knob' [on] [the] [wall]"))
		return 0;
	write("You " + query_verb() + " the button on the wall.\n");
	tell_room(this_object(), QCTNAME(TP) + " query_verb() +
		"es the button on the wall.\n", ({ TP }));
	return 1;
}

void
init() {
	::init();
	add_action(do_press, "press");
	add_action(do_press, "push");
}

3. Parsing objects

4. Using AFTER_PARSE checks

5. Examples

TEMPORARY STUFF

Using parse_command for non-environment objects

Until now, we have retricted the object / environment list argument to parse_command to the environment of the object, or objects in that environment: the room the player is in, the contents of a bag the player is carrying, the livings in the room.

However, this object list need not be restricted to objects in our proximity. We can use a list of arbitrary objects. We'll demonstrate this by an example. A player has the powers to summon and rule creatures. Now suppose the player controls a 'red beast' and a 'blue beast', and pointers to the creature objects are kept in the array creatures. The player can use the command 'call' to teleport one of his creatures to him. Since we'll have to select a creature from the creatures array, we'll just pass it to parse_command():

#include <cmdparse.h>

object *creatures;

int
do_call(string str) {
	object *results;

	notify_fail(capitalize(query_verb()) + " whom?\n");
	if (!strlen(str))
		return 0;
	// remove destroyed objects from the array
	creatures -= ({ 0 });
	if (!creatures || !sizeof(creatures))
		return notify_fail("You don't control any creatures.\n");
	if (!parse_command(str, creatures, "%i", results))
		return 0;
	results = AFTER_PARSE(results);
	if (sizeof(results) > 1)
		return notify_fail("You can " + query_verb() +
			" only one creature at a time.\n");
	results[0]->move_living("X", environment(this_player()), 1, 0);
	return 1;
}