wrog/lambdamoo — pull request #16 by ojacobson (2012-09-15 21:55:05+00:00)
Title: Use #0:do_match to resolve nouns in commands

This change permits in-DB code to override the server's noun-to-object matching by providing a hook (`do_match`) on the player's connection handler. As with other in-server command parsing hooks (`do_command`, `do_login_command`), the handler is controlled using the `listen` built-in.

`do_match` hooks are called with one argument (the string to be matched to a noun), and can either return non-object values to instruct the server to continue with built-in matching or return any object number to indicate matching success. To indicate a failed match, return `#-1` (`$nothing` in LambdaMOO), `#-2` (`$ambiguous_match`), or `#-3` (`$failed_match`) as appropriate, _not_ `0`. Raising an exception, suspending, or stopping to read input will also cause built-in noun matching to proceed.

The `do_match` hooks are invoked before `do_command`, and share a taskid with the rest of the input task pipeline (`do_command`, command verbs, and `huh` calls).

This was motivated by a few scenarios:

* In "game"-like rather than social MOOs, the built-in noun matching may be inappropriate: it permits references to bare object numbers, requiring every command verb to check that its direct or indirect objects are actually reachable. Putting this logic into `$do_match` makes this kind of checking more consistent and streamlines error handling (returning `$nothing` rather than invoking `huh` directly, for example).
* The built-in matching is inextensible without server patches. For example, LambdaMOO's `*name` convention for mailing lists is handled by special-case matching in mailing list commands, which have to reside on players or in the player's feature chain rather than on the mailing list object itself. Matching `*name` to mailing lists early in command parsing means mailing list verbs can live on the mailing lists themselves, and that other commands can interact with mailing lists consistently.
* Compound noun matching requires an in-DB copy of the server's noun matching for consistency. For example, in `@verb Bob:look_self tnt rxd`, `Bob:look_self` is a "compound" noun containing both the location of the verb (`Bob`) and the name of the verb. Matching the location to an object has to be done in-DB, as there's no way to invoke the server's matcher. Having the server also use an in-DB matcher means it's easier to get this kind of object matching to work consistently with command verbs than it would be by reimplementing the stock matcher in MOO. (If you think this is easy, then, without looking, do you know if `$name` strings are supported? Do you know if room contents are matched before or after player inventories?)

This change also restructures the `run_server_task…` family of functions a bit: the `_setting_id` function is gone, replaced with a `start_new_task()` function in `tasks.c`, and a new `_in_current_id` function permits hook calls to reuse the ongoing input task ID. `run_server_task` itself still starts a new taskid before calling any verbs.

An appropriate sample implementation for LambdaMOO might be

@verb #0:do_match tnt rxd
@program #0:do_match
{name} = args;
return $string_utils:match_object(name, player, player.location);
.

*It is possible to render your database unusable with this hook.* The `do_match` hook is also invoked for the `.program` builtin and for the emergency wizard mode recovery commands. If your `do_match` hook renders its location unresolvable by anyone, the following `eval` command may be able to help you recover:

;; info = verb_info(#0, "do_match"); info[3] = "do_match_disabled"; set_verb_info(#0, "do_match", info);

*It is possible to make command-handling very slow with this hook.* The hook is invoked for each of the direct and indirect objects with a full execution quota each time. This means that under the default settings, commands that need to resolve both objects can add up to ten seconds to command handling. This is unlikely, but it's a thing to be aware of when implementing this hook. The `do_match` hook is also evaluated _before_ `do_command` if both are present.