Public methods:

        verb_templates()                        Get verb templates
        verb_info(template)                     Get {method, remote}
        local_verb_info(template)               Get {method, remote} locally

    Owner methods:

        add_verb(template, method, remote)      Add a verb
        del_verb(method)                        Remove a verb

parent root
object has_verbs

var root name 'has_verbs
var has_verbs verbs 0

method init_has_verbs
    if (caller() != $root)
        throw(~perm, "Caller is not $root.");
    verbs = #[];
.

method uninit_has_verbs
    var template;

    if (caller() != $root)
        throw(~perm, "Caller is not $root.");
    verbs = 0;
    for template in (dict_keys(verbs))
        $verb_cache.object_undefined_verb(template);
.

eval
    .initialize();
.

method verb_templates
    return dict_keys(verbs);
.

method add_verb
    arg template, method, remote;
    var words, pos;

    if (!.is_owned_by(sender()))
        throw(~perm, "Sender is not an owner.");

    // Check argument types.
    if (type(template) != 'string)
        throw(~type, "Template is not a string.");
    if (type(method) != 'symbol)
        throw(~type, "Method name is not a symbol.");
    if (remote != 'remote && remote != 'noremote)
        throw(~type, "Remote specifier is neither 'remote nor 'noremote");

    // Make sure there's exactly one "%this" in the template, as a word by
    // itself.
    words = explode(template);
    if (words[1] == "%this" || words[1] == "*" || words[1] == "*=*")
        throw(~template, "Template begins with wildcard.");
    pos = "%this" in words;
    if (!pos || "%this" in sublist(words, pos + 1))
        throw(~template, "Template does not contain exactly one \"%this\".");

    // Add the verbs to the dictionary, and notify $verb_cache.
    verbs = dict_add(verbs, template, [method, remote]);
    $verb_cache.object_defined_verb(template);
.

method del_verb
    arg template;
    var verb_info;

    if (!.is_owned_by(sender()))
        throw(~perm, "Sender is not an owner.");
    catch ~keynf {
        verb_info = verbs[template];
        verbs = dict_del(verbs, template);
        $verb_cache.object_undefined_verb(template);
    } with handler {
        throw(~verbnf, "No verb with template " + tostr(template) + ".");
    }
.

method verb_info
    disallow_overrides;
    arg template;
    var verb_info, anc;

    if (dict_contains(verbs, template))
        return verbs[template];
    for anc in (ancestors()) {
        verb_info = (| anc.local_verb_info(template) |);
        if (verb_info)
            return verb_info;
    }
    throw(~verbnf, "No verb with template " + template);
.

method local_verb_info
    disallow_overrides;
    arg template;

    catch ~keynf {
        return verbs[template];
    } with handler {
        throw(~verbnf, "No verb with template " + template);
    }
.