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

    Protected methods:

	verbs_changed_on_leaf()			Verbs changed on leaf object

    Private methods (non-overridable):

	verbs_changed()				Indicates verbs changed

    Verbs:

	list_verb	@list * on %this	List method
	show_verb	@show %this		Show object
	params_verb	@params %this		Show parameters
	methods_verb	@methods %this		Show method names
	verbs_verb	@verbs %this		Show verbs

parent root
object verbs

var verbs verbs 0

method init
    arg ancestors;

    (> pass(ancestors) <);
    if (definer() in ancestors)
	verbs = #[];
.

eval
    .initialize();
    .set_name("Generic verb-containing object");
.

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.
    verbs = dict_add(verbs, template, [method, remote]);
    if (remote == 'remote)
	$sys.new_remote_template(template);
    else
	.verbs_changed();
.

method remove_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);
	if (verb_info[1] == 'remote)
	    $sys.removed_remote_template(template);
	else
	    .verbs_changed();
    } with handler {
	throw(~verbnf, "No verb with template " + tostr(template) + ".");
    }
.

method verbs_changed
    disallow_overrides;
    var loc, p;

    if (sender() != this() || caller() != definer())
	throw(~perm, "Invalid call to private method.");

    // If this object has children, up the system verb consistency clock,
    // forcing all users to update their command lists next time they process
    // a command.
    if (children()) {
	for p in ($sys.connected_users())
	    p.invalidate_verb_cache();
    }

    // The verbs changed on a leaf.  Delegate this to the subclasses; located
    // objects will want to deal with it one way, exits another.
    .verbs_changed_on_leaf();
.

method verbs_changed_on_leaf
    if (sender() != this())
	throw(~perm, "Sender not this.");
.

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);
    }
.