/
CDC-1.1/
parent $builder
object $programmer

var $root child_index 33
var $root owners [$programmer]
var $root fertile 0
var $has_commands commands [["@show *", 'show_cmd], ["@list|@nlist *", 'list_cmd], ["@program *", 'program_cmd], ["@d?isplay *", 'display_cmd], ["@dump *", 'dump_cmd], ["@id *", 'id_cmd], ["@go *", 'go_cmd], ["@mcom?mands *", 'mcommands_cmd], ["@info *", 'info_cmd], ["@descend?ants *", 'descendants_cmd], ["@which *", 'which_cmd], ["@mv|@move|@cp|@copy *", 'copy_move_cmd], ["@del-m?ethod|@delm?ethod|@dm *", 'del_method_cmd], ["@add-m?ethod|@addm?ethod|@am *", 'add_method_cmd], ["@teval *", 'tick_eval_cmd], ["@eval *", 'eval_cmd], ["@ancestor?s|@ascend *", 'ancestors_cmd], ["@add-c?ommand|@ac *", 'add_command_cmd], ["@del-c?ommand|@dc *", 'del_command_cmd], ["@as * eval *", 'eval_as_cmd], ["@def?iner * this|as * eval *", 'eval_as_to_cmd]]
var $has_commands shortcuts [[";*", 'eval_cmd, ["eval", 1]]]
var $has_verbs verbs #[]
var $location contents []
var $located location $nowhere
var $user password "*"
var $user connected_at 0
var $user last_command_at 0
var $user connections []
var $root inited 1
var $root owned [$programmer]
var $programmer eval_prefix 0
var $command_aliases command_aliases []
var $user modes #[]
var $gendered gender $gender_neuter
var $located obvious 1
var $described prose #[]
var $user prompt ""
var $root manager $programmer
var $root writable [$programmer]
var $root readable ['parameters, 'methods, 'code]
var $user parsers [$command_parser, $verb_parser]
var $user tell_traceback ['brief, 0]
var $user context #[]
var $root quota 75000
var $root dbref 'programmer
var $named name ['uniq, "Generic Programmer"]
var $named name_aliases []
var $user_data user_data #[['real_name, [1, "???"]], ['email, [1, "???"]]]
var $has_settings setting_templates #[["@list-options", 'string]]
var $programmer eval_tick_offset 0

method init_programmer
    .perms(caller(), $root);
    .set_tell_traceback('verbose, 4);
.

method uninit_programmer
    .perms(caller(), $root);
.

method eval_cmd
    arg com, str;
    var result, vars, pkeys, v, evalp, adjust, ticks, time;
    
    .perms(sender(), 'this);
    evalp = .eval_prefix();
    pkeys = dict_keys(evalp);
    adjust = !str;
    vars = ("var " + ($list.to_string(pkeys, ", "))) + "; ";
    for v in (pkeys)
        vars = (vars + (evalp[v])) + "; ";
    
    // perform escape substitution
    str = .eval_subs(str);
    
    // Evaluate the line.
    if (str && ((str[1]) == ";"))
        str = substr(str, 2);
    else
        str = ((vars + " return ") + str) + ";";
    time = time();
    ticks = tick();
    result = .eval([str]);
    ticks = (tick() - ticks) - eval_tick_offset;
    time = time() - time;
    if (adjust && ((result[1]) != 'errors)) {
        eval_tick_offset = eval_tick_offset + ticks;
        .tell(("Eval tick offset adjusted by " + tostr(ticks)) + " ticks.");
        return;
    }
    
    // Display the errors, or the result.
    if ((result[1]) == 'errors) {
        .tell(result[2]);
    } else {
        .tell("=> " + ($data.unparse(result[2], 'full)));
        .tell(((("[ ticks: " + tostr(ticks)) + " seconds: ") + tostr(time)) + " ]");
    }
.

method program_cmd
    arg com, [args];
    var syntax, obj, ref, add_hist, why, x;
    
    .perms(sender(), 'this);
    syntax = "Syntax: `@program <obj>.<method>`";
    args = explode(args[1]);
    if (!args)
        $parse.tell_error("You must give an object and method to program", syntax);
    ref = $parse.reference(args[1]);
    obj = .match_env_nice(ref[1], syntax);
    ref = (ref[2]) || ($parse.tell_error("No method name given.", syntax));
    why = "";
    add_hist = 1;
    for x in [2 .. listlen(args)] {
        if (match_begin("-edited", args[x])) {
            if (!($sys.is_admin(this())))
                .tell("!  Only administrators can compile without history comments.");
            else
                add_hist = 0;
        } else {
            why = (why + " ") + (args[x]);
        }
    }
    args = ['compile, obj, tosym(ref), add_hist, why];
    if (!(obj.is_writable_by(this()))) {
        .tell("!  You cannot program that object; ignoring code.");
        args = replace(args, 1, 'ignore);
    }
    .read('programming_done, @args);
    .tell(((("-- Enter text for " + (obj.dbref())) + ".") + ref) + " --");
.

method show_cmd
    arg com, name;
    var obj;
    
    .perms(sender(), 'this);
    if (!name)
        $parse.tell_error("Must specify an object to show.", com + " <object>");
    obj = .match_env_nice(name);
    if (!(| .tell(obj.show()) |))
        .tell("Permission denied.");
.

method _move_method
    arg syn, ref1, ref2, [opts];
    var line, code;
    
    .perms(sender(), 'this);
    catch any {
        ref1 = $parse.reference(ref1);
        ref2 = $parse.reference(ref2);
        if ((!(ref1[2])) || (!(ref2[2])))
            $parse.tell_error("You must define both a method to be copied from, and one to be copied to.", syn);
        ref1 = replace(ref1, 1, .match_env_nice(ref1[1]));
        ref1 = replace(ref1, 2, tosym(ref1[2]));
        ref2 = replace(ref2, 1, .match_env_nice(ref2[1]));
        ref2 = replace(ref2, 2, tosym(ref2[2]));
        code = (ref1[1]).list_method(ref1[2]);
    
        // Parse Options
        if (opts && ("-edited" in ($list.to_string(opts[1])))) {
            if (!($sys.is_system(sender())))
                .tell("!  Sorry, only admins can turn off the edited messages.");
        } else {
            line = (" // " + ($time.ldate('mdy, 'dash))) + "/";
            line = ((line + ($time.ltime('24hr))) + " ") + (.namef('ref));
            line = (((line + ", moved from ") + ((ref1[1]).dbref())) + ".") + tostr(ref1[2]);
            code = [@code, line];
        }
    
        // delete the old method, compile the new one.
        (ref1[1]).del_method(ref1[2]);
        (ref2[1]).compile(code, ref2[2]);
    
        // cool, we made it.
        line = ((("Method " + ((ref1[1]).dbref())) + ".") + tostr(ref1[2])) + " moved to ";
        line = (((line + ((ref2[1]).dbref())) + ".") + tostr(ref2[2])) + ".";
        .tell(line);
    } with handler {
        switch (error()) {
            case ~methodnf:
                line = (traceback()[1])[2];
                line = substr(line, 1, strlen(line) - 1);
                $parse.tell_error(((line + " on ") + ((ref1[1]).namef('ref))) + ".", syn);
            case ~perm:
                $parse.tell_error("You cannot write on that object.", syn);
            case ~stop:
                rethrow(error());
            default:
                // they are men, they can deal with the tracebacks
                $parse.tell_error([(traceback()[1])[2], traceback()[2], traceback()[3]], syn);
        }
    }
.

method del_method_cmd
    arg com, what;
    var syntax, obj, ref, res;
    
    .perms(sender(), 'this);
    syntax = ("Syntax: `" + com) + " <obj>.<method>`";
    what = explode(what);
    if (!what)
        $parse.tell_error("You must give an object and a method to delete", syntax);
    ref = $parse.reference(what[1]);
    obj = .match_env_nice(ref[1], syntax);
    ref = (ref[2]) || ($parse.tell_error("No method name given.", syntax));
    res = (| obj.del_method(tosym(ref)) |);
    if (type(res) == 'error) {
        switch (res) {
            case ~methodnf:
                .tell("!  No such method");
            default:
                .tell("!  You do not have the perms to modify that object");
        }
    } else {
        .tell("Method removed.");
    }
.

method move_cmd
    arg com, [args];
    var syn;
    
    .perms(sender(), 'this);
    syn = ("`" + com) + " [from] <$obj.ref> [to] <$obj.ref>`";
    
    // figure up args
    args = explode(@args);
    if (args && ((args[1]) == "from"))
        args = delete(args, 1);
    if ((listlen(args) > 1) && ((args[2]) == "to"))
        args = delete(args, 2);
    if (listlen(args) < 2)
        $parse.tell_error("Not enough arguments sent.", syn);
    
    // what type is it (method/parameter)
    if (("." in (args[1])) && ("." in (args[2])))
        return ._move_method(syn, @args);
    else if (("," in (args[1])) && ("," in (args[2])))
        return ._move_parameter(syn, @args);
    else
        $parse.tell_error("You must define both methods, or both parameters.", syn);
.

method list_cmd
    arg com, str;
    var code, nums, line, ref, syntax, ancestor, opt;
    
    .perms(sender(), 'this);
    nums = com == "@nlist";
    syntax = com + " <object>.<method>";
    
    // figure out the object/reference
    catch any {
        ref = $parse.full_reference(str);
        if ((ref[1]) != 'method)
            $parse.tell_error("You must submit an object.method() reference", syntax);
        ancestor = (ref[2]).find_method(ref[3]);
        code = ancestor.list_method(ref[3]);
        if (nums) {
            code = $list.numbered_text(code);
        } else {
            for line in [1 .. listlen(code)]
                code = replace(code, line, "  " + (code[line]));
        }
    } with handler {
        switch (error()) {
            case ~methodnf:
                line = ((((ref[2]).dbref()) + ".") + tostr(ref[3])) + "()";
                $parse.tell_error(line + " not found.", syntax);
            default:
                $parse.tell_error((traceback()[1])[2], syntax);
        }
        $parse.tell_error((traceback()[1])[2], syntax);
    }
    opt = (.setting("@list-options")) || "";
    line = ((((ancestor.dbref()) + ".") + tostr(ref[3])) + " ") + opt;
    if (nums)
        .tell("--- Method " + line);
    else
        .tell("@program " + line);
    .tell(code);
    .tell(".");
.

method copy_move_cmd
    arg com, [args];
    var syn, how, line;
    
    .perms(sender(), 'this);
    syn = ("`" + com) + " [from] <$obj.ref> [to] <$obj.ref>`";
    
    // figure up args
    args = explode(@args);
    args = [@args, ""];
    args = setremove(args, "from");
    args = setremove(args, "to");
    if (listlen(args) != 3)
        $parse.tell_error("Send two object references.", syn);
    how = (com in ["@mv", "@move"]) ? 'move | 'copy;
    catch ~namenf {
        args = [$parse.full_reference(args[1], sender()), args[2], args[3]];
        args = [args[1], $parse.full_reference(args[2], sender()), args[3]];
        if ((((args[1])[1]) != ((args[2])[1])) && (((args[2])[1]) != 'unknown))
            $parse.tell_error(((((("Cannot " + tostr(how)) + ((((args[1])[1]) == 'unknown) ? " an object" | ("from a " + tostr((args[1])[1])))) + " to ") + "a ") + tostr((args[2])[1])) + ".", syn);
        else if (((args[1])[1]) == 'method)
            ._copy_move_method(syn, how, @args);
        else if (((args[1])[1]) == 'parameter)
            .tell("Parameters are currently unsupported, sorry!");
        else
            $parse.tell("You must specify a full reference for the source object.", syn);
    } with handler {
        $parse.tell_error((traceback()[1])[2], syn);
    }
.

method display_cmd
    arg com, disp_ref;
    var to_show;
    
    // display method/parameter info for an object
    // to_show is a list as follows:
    // [$object, 'param | 'meth | 'none, "pattern", [opts]]
    .perms(sender(), 'this);
    to_show = ._parse_disp_ref(disp_ref);
    if (to_show) {
        if ((to_show[4])['header])
            ._disp_obj_header(to_show[1], to_show[4]);
        ._disp_obj_detail(@to_show);
        .tell($string.center(" + Finis + ", .linelen(), "-"));
    }
.

method id_cmd
    arg verb, obj;
    
    .perms(sender(), 'this);
    obj = .match_env_nice(obj);
    .tell((((((((obj.namef('xref)) + " ") + ($object.see_perms(obj))) + " ") + ($data.unparse(obj.parents()))) + " ") + tostr(obj.size())) + " bytes");
.

method dump_cmd
    arg com, args;
    var opts, obj, dbref, x, pdbref, par, data, readable, me, code, syn, tail;
    
    .perms(sender(), 'this);
    syn = [["Options can be:", "    +/-n     -- nice formatting", "    +/-t     -- textdump formatting", "    +/-m     -- show/don't show methods", "    +/-p     -- show/don't show parameters", "Flags default to \"-n +m +p\""], com + " [options] <object>"];
    opts = #[["n", [0, 0]], ["t", [1, 0]], ["m", [1, 0]], ["p", [1, 0]], ["e", [0, 0]]];
    args = $parse.options(args, opts);
    if (!(args[1]))
        $parse.tell_error(@syn);
    obj = .match_env_nice((args[1])[1]);
    me = this();
    dbref = obj.dbref();
    opts = args[2];
    readable = obj.is_readable_by(me);
    data = (| obj.data() |);
    if ((opts["p"])[1]) {
        if (!data) {
            .tell(" *** Parameters are unreadable by you ***");
        } else {
            if ((opts["n"])[1]) {
                .tell("Object: " + (obj.namef('ref)));
                .tell("Parents: " + ($list.to_english($list.map(obj.parents(), 'namef))));
                .tell(("Size: " + tostr(obj.size())) + " bytes");
            } else {
                // I'm not sure how the latest drivers handle dbrefs vs objnums
                for x in (obj.parents())
                    .tell("parent " + (x.dbref()));
                .tell("object " + dbref);
                .tell("");
            }
            for par in (data) {
                if (type(par[2]) == 'string) {
                    .tell((" *** " + ((par[1]).dbref())) + "'s parameters are unreadable by you ***");
                } else {
                    pdbref = (| (par[1]).dbref() |) || toliteral(par[1]);
                    for x in (par[2])
                        .tell((((("var " + pdbref) + " ") + tostr(x[1])) + " ") + ($data.unparse(x[2])));
                }
            }
        }
    }
    if ((opts["m"])[1]) {
        .tell("");
        if (!('code in readable)) {
            .tell((" *** " + dbref) + "'s methods are unreadable by you ***");
        } else {
            if ((opts["e"])[1])
                tail = " -edited";
            else
                tail = "";
            for x in (obj.methods()) {
                code = obj.list_method(x);
                if ((opts["n"])[1])
                    .tell(((("@program " + dbref) + ".") + tostr(x)) + tail);
                else
                    .tell("method " + tostr(x));
                for x in [1 .. listlen(code)]
                    code = replace(code, x, "    " + (code[x]));
                .tell(code);
                .tell([".", ""]);
    
                // this can get long
                pause();
            }
        }
    }
.

method add_method_cmd
    arg com, what;
    var syntax, obj, ref, res;
    
    .perms(sender(), 'this);
    syntax = "Syntax: `@add-method <obj>.<method>`";
    what = explode(what);
    if (!what)
        $parse.tell_error("You must give an object and a method to add", syntax);
    ref = $parse.reference(what[1]);
    obj = .match_env_nice(ref[1], syntax);
    ref = (ref[2]) || ($parse.tell_error("No method name given.", syntax));
    res = (| obj.compile([""], tosym(ref)) |);
    if (res != [])
        .tell("!  You do not have the perms to modify that object");
    else
        .tell("Method added.");
.

method descendants_cmd
    arg verb, what;
    var thing;
    
    thing = .match_env_nice(what);
    if (!thing)
        return;
    .tell(("Descendants of " + (thing.namef('ref))) + ":");
    .tell(thing._display_descendants());
    .tell("---");
.

method ancestors_cmd
    arg verb, what;
    var thing;
    
    thing = .match_env_nice(what);
    if (!thing)
        return;
    .tell(("Ancestors of " + (thing.namef('ref))) + ":");
    .tell(thing._display_ancestors());
    .tell("---");
.

method _parse_disp_ref
    arg disp_ref;
    var sep_pos, sep, obj, obj_name, to_show, opts, ref, pattern, which_det;
    
    opts = explode(disp_ref);
    if (!opts)
        return [];
    ref = opts[1];
    opts = $list.to_string(sublist(opts, 2));
    sep_pos = (":" in ref) || (("." in ref) || ((";" in ref) || ("," in ref)));
    if (sep_pos) {
        obj_name = substr(ref, 1, sep_pos - 1) || (.namef('ref));
        sep = substr(ref, sep_pos, 1);
        pattern = substr(ref, sep_pos + 1) || "*";
        obj = .match_env_nice(obj_name);
        if (sep in [";", ":"]) {
            opts = ($string.trim(opts)) + " anc:";
            if ((opts[1]) == "$") {
                opts = opts + (explode(opts)[1]);
                opts = substr(opts, strlen(explode(opts)[1]) + 1);
            } else {
                opts = opts + ",";
            }
        }
        if ((pattern[1]) == "_")
            opts = "pri" + opts;
        which_det = #[[":", 'meth], [".", 'meth], [";", 'param], [",", 'param]][sep];
        opts = $display_opts.from_str(opts, obj, $list.last(obj.ancestors()));
        return [obj, which_det, pattern, opts];
    } else {
        obj = .match_env_nice(ref);
        opts = $display_opts.from_str(opts, obj, $list.last(obj.ancestors()));
        return [obj, 'none, "", opts];
    }
.

method _disp_obj_detail
    arg obj, which_det, pattern, opts;
    var details, info, detail, len, show_private, param;
    
    .perms(sender(), 'this);
    len = .linelen();
    if (which_det == 'meth) {
        info = obj.method_info(opts['max_parent], $misc, '_display_filter, [pattern], (opts['show_private]) ? [] | ["_*"]);
        details = [["Methods:", ""], ["Lines $parent.method(args)", "First comment or return value"]];
        for detail in (info)
            details = [@details, [(((((((((detail[4]) ? "+" | " ") + ($string.right(tostr(detail[5]), 4))) + " ") + (((detail[1]) != obj) ? (detail[1]).id() | "")) + ".") + tostr(detail[2])) + "(") + (detail[3])) + ")", detail[6]]];
        for detail in (details)
            .tell(pad((pad(detail[1], len / 2) + " ") + (detail[2]), len));
    } else if (which_det == 'param) {
        info = obj.parameter_info(opts['max_parent]);
        details = ["Parameter(s)"];
        for detail in (info) {
            param = tostr(detail[2]);
            if (match_pattern(pattern, param) != 0) {
                detail = (((("  " + (((detail[1]) != obj) ? (detail[1]).namef('id) | "")) + ",") + param) + ": ") + ($data.unparse(detail[3]));
                if ((opts['chop_props]) && (strlen(detail) > (len - 1)))
                    detail = $string.chop(detail, len);
                details = [@details, detail];
            }
        }
        .tell(details);
    }
.

method mcommands_cmd
    arg cmd, what;
    var obj, coms, c, len, line, emote, opts;
    
    // returns all commands in a nice format.
    // god this is getting ugly.
    .perms(sender(), 'this);
    
    // options
    opts = explode(what);
    what = opts[1];
    opts = sublist(opts, 2);
    if (opts && match_begin("-emote", "-e"))
        emote = 1;
    
    //
    if (what == "")
        what = .ancestors();
    else
        what = [.match_env_nice(what)];
    for obj in (what) {
        if ((obj.has_ancestor($has_commands)) || (obj.has_ancestor($has_verbs))) {
            .tell(("Commands on " + (obj.namef('ref))) + ":");
            coms = [];
            coms = coms + ((| obj.shortcuts() |) || []);
            coms = coms + ((| obj.commands() |) || []);
            len = ((.linelen()) - 5) / 2;
            if (!emote) {
                for c in (coms) {
                    line = ("  " + ($string.left(("\"" + (c[1])) + "\"", len))) + "  ";
                    .tell(line + ($data.unparse(c[2])));
                }
            } else {
                for c in (coms) {
                    // they aren't always a _cmd but fk.
                    .tell((((";" + (.dbref())) + ".del_command(") + ($data.unparse(c[2]))) + ")");
                    line = (((";" + (.dbref())) + ".add_command(\"") + (c[1])) + "\", ";
                    .tell((line + ($data.unparse(c[2]))) + ")");
                }
            }
            if ((| obj.verbs() |)) {
                for c in ($dict.to_list(obj.verbs())) {
                    line = ("  " + ($string.left(("\"" + (c[1])) + "\"", len))) + " ";
                    line = line + ((((c[2])[2]) == 'remote) ? "rmt " | "    ");
                    line = line + ($data.unparse((c[2])[1]));
                    .tell(line);
                }
            }
            if (obj == $has_commands)
                break;
        } else {
            .tell(("Object " + (obj.namef('ref))) + " has no commands.");
        }
    }
.

method _disp_obj_header
    arg obj, opts;
    var line, lines, objs, line_len, extra_str;
    
    // Display header info for obj
    .perms(sender(), 'this);
    line_len = .linelen();
    
    // Object:
    line = "Object: " + (obj.namef('ref));
    if (obj.fertile())
        line = line + " (Fertile)";
    .tell(line);
    
    // Owners:
    if (opts['header]) {
        objs = obj.owners();
        line = $list.to_english($list.map(objs, 'namef, 'ref));
        .tell(("Owner" + ((listlen(objs) > 1) ? "s: " | ": ")) + line);
        .tell(("Size: " + ($integer.to_english(obj.size()))) + " bytes");
    
        // Parents: [parents]
        objs = obj.parents();
        if (objs) {
            line = $list.to_english($list.map(objs, 'namef, 'ref));
            if (!(opts['chop_head_data]))
                line = $string.chop(line, line_len - 9);
            .tell(("Parent" + ((listlen(objs) > 1) ? "s: " | ": ")) + line);
        }
        objs = obj.children();
        if (objs) {
            line = $list.to_english($list.map(objs, 'namef, 'ref));
            if (!(opts['chop_head_data]))
                line = $string.chop(line, line_len - 10);
            .tell(((listlen(objs) > 1) ? "Children: " | "Child: ") + line);
        }
        if (obj.has_ancestor($located))
            .tell("Location: " + ((obj.location()).namef('ref)));
    }
.

method go_cmd
    arg com, where;
    var loc, r;
    
    if (sender() != this())
        throw(~perm, "Sender not this");
    loc = (| .match_environment(where) |);
    if (!loc) {
        catch any {
            loc = $room_db.match_room(where);
        } with handler {
            switch (error()) {
                case ~ambig:
                    // check aliases for the hell of it
                    for r in ((traceback()[1])[3]) {
                        if (r.name_match(where)) {
                            loc = r;
                            break;
                        }
                    }
                    if (!loc)
                        $parse.tell_error("Several rooms match that name: " + ($list.to_english($list.map((traceback()[1])[3], 'namef))));
                default:
                    $parse.tell_error((traceback()[1])[2]);
            }
        }
    }
    (.location()).announce((.namef()) + " @go'd elsewhere", this());
    .move_to(loc);
    (.location()).announce((.namef()) + " @go'd here", this());
    .tell("You @go'd");
.

method info_cmd
    arg com, obj;
    var info;
    
    .perms(sender(), 'this);
    obj = .match_env_nice(obj);
    info = obj.info();
    if (!info)
        return .tell("No information about " + (obj.namef('ref)));
    .tell("-----");
    .tell(info);
    .tell("-----");
.

method eval_subs
    arg code;
    var idx, ret_code, sub;
    
    ret_code = "";
    while (code) {
        idx = "^" in code;
        if (!idx) {
            return ret_code + code;
        } else if ((idx == strlen(code)) || (substr(code, idx + 1, 1) == "^")) {
            ret_code = ret_code + substr(code, 1, idx);
            code = substr(code, idx + 1);
            if (code && ((code[1]) == "^"))
                code = substr(code, 2);
        } else {
            if (idx > 1) {
                ret_code = ret_code + substr(code, 1, idx - 1);
                code = substr(code, idx + 1);
            } else {
                code = substr(code, 2);
            }
            idx = 1;
            while ((idx <= strlen(code)) && (!((code[idx]) in " =.()[]=<>?|&!*+-/';\"")))
                idx = idx + 1;
            sub = .match_env_nice(substr(code, 1, idx - 1));
            ret_code = ret_code + (sub.dbref());
            code = substr(code, idx);
        }
    }
    return ret_code;
.

method eval_prefix
    .perms(sender(), 'this);
    return $dict.union(#[["me", "me = this();"], ["here", "here = me.location()"]], eval_prefix || #[]);
.

method del_command_cmd
    arg cmd, str;
    var syn, cmdref, ref;
    
    .perms(sender(), 'this);
    syn = ("`" + cmd) + " <command reference> [from] <object>`";
    if (!str)
        $parse.tell_error("No arguments specified.", syn);
    if ((str[1]) == "\"") {
        str = substr(str, 2);
        cmdref = substr(str, 1, ("\"" in str) - 1);
        str = explode(substr(str, ("\"" in str) + 1));
    } else if ((str[1]) == "'") {
        str = explode(str);
        cmdref = tosym(substr(str[1], 2));
    }
    if (!cmdref)
        $parse.tell_error("Command references can either be templates or symbols.", syn);
    if (!str)
        $parse.tell_error("Invalid object reference.", syn);
    ref = .match_env_nice(str[listlen(str)]);
    catch any {
        ref.del_command(cmdref);
    } with handler {
        switch (error()) {
            case ~perm:
                $parse.tell_error(strsub((traceback()[1])[2], "%O", ((traceback()[1])[3]).namef('xref)), syn);
            default:
                $parse.tell_error((traceback()[1])[2], syn);
        }
    }
    .tell(((("Command " + ((type(cmdref) == 'symbol) ? "for method " | "with template ")) + toliteral(cmdref)) + " deleted from ") + (ref.namef('xref)));
.

method programming_done
    arg code, status, obj, meth, hist, why;
    var last_edit, errors;
    
    .perms(caller(), $input);
    if (status == 'ignore)
        return .tell("Finished ignoring input.");
    if (hist) {
        last_edit = (" // " + ($time.ldate('mdy, 'dash))) + "/";
        last_edit = (last_edit + ($time.ltime('24hr))) + " ";
        last_edit = last_edit + (.namef('ref));
        if (why)
            last_edit = (last_edit + ":") + why;
        code = code + [last_edit];
    }
    catch any {
        errors = obj.compile(code, meth);
        if (errors)
            .tell(errors);
        else
            .tell("Method compiled.");
    } with handler {
        switch (error()) {
            case ~perm:
                $parse.tell_error("!  You cannot program that object.");
            default:
                $parse.tell_error("!  " + ((traceback()[1])[2]));
        }
    }
.

method tick_eval_cmd
    arg com, str;
    
    .eval_cmd(com, (";var ticks,x,y,z; ticks = ticks_left(); " + str) + " ; return ticks-ticks_left()-4;");
.

method which_cmd
    arg which, cmdstr;
    var cmd, cmds, parent, pcmds, matches, smatches, template, syn, len, line;
    
    // searches all commands for str and returns obj.method for said command
    .perms(sender(), 'this);
    syn = ("`" + which) + " <template>`";
    if (!cmdstr)
        $parse.tell_error("No template given.", syn);
    matches = [];
    smatches = [];
    template = explode(cmdstr)[1];
    for parent in (.ancestors()) {
        pcmds = (| parent.commands() |);
        if (pcmds) {
            for cmd in (pcmds) {
                if (cmdstr in ($string.strip(cmd[1], "?")))
                    matches = [@matches, [cmd[1], parent, cmd[2]]];
    
                //            else if (match_begin(cmd[1], template))
                //                 matches = [@matches, [cmd[1], parent, cmd[2]]];
            }
        }
    
        // do shortcuts seperately so we can note the difference
        pcmds = (| parent.shortcuts() |);
        if (pcmds) {
            for cmd in (pcmds) {
                if (cmdstr in ($string.strip(cmd[1], "?")))
                    smatches = [@smatches, [cmd[1], parent, cmd[2], cmd[3]]];
    
                //            else if (match_begin(cmd[1], template))
                //                smatches = [@smatches, [cmd[1], parent, cmd[2], cmd[3]]];
            }
        }
        if (parent == $has_commands)
            break;
    }
    if ((!matches) && (!smatches)) {
        .tell(("No commands found matching the template \"" + cmdstr) + "\".");
        return;
    }
    len = (.linelen()) / 2;
    .tell(("Commands matching the template \"" + cmdstr) + "\":");
    for cmd in (matches) {
        line = ("  " + pad($code.unparse_command(delete(cmd, 2)), len)) + "  ";
        .tell((((line + ((cmd[2]).dbref())) + ".") + tostr(cmd[3])) + "()");
    }
    for cmd in (smatches) {
        line = ("  " + pad($code.unparse_command(delete(cmd, 2)), len)) + "  ";
        .tell((((line + ((cmd[2]).dbref())) + ".") + tostr(cmd[3])) + "()");
    }
.

method _copy_move_method
    arg syn, how, ref1, ref2, opts;
    var line, code, type;
    
    .perms(sender(), 'this);
    catch any {
        code = (ref1[2]).list_method(ref1[3]);
        if (opts && ("-edited" in opts)) {
            if (!($sys.is_system(sender())))
                .tell("!  Only admins can turn off the edited messages.");
        } else {
            line = (" // " + ($time.ldate('mdy, 'dash))) + "/";
            line = ((line + ($time.ltime('24hr))) + " ") + (.namef('ref));
            line = (((line + ", moved from ") + ((ref1[2]).dbref())) + ".") + tostr(ref1[3]);
            code = [@code, line];
        }
        if (how == 'move)
            (ref1[2]).del_method(ref1[3]);
        (ref2[2]).compile(code, ref2[3]);
        line = ($string.capitalize(tostr(ref1[1]))) + " ";
        line = (((line + ((ref1[2]).dbref())) + ".") + tostr(ref1[3])) + " ";
        line = (line + ((how == 'move) ? "moved" | "copied")) + " ";
        line = (((line + ((ref2[2]).dbref())) + ".") + tostr(ref2[3])) + ".";
        .tell(line);
    } with handler {
        switch (error()) {
            case ~methodnf:
                line = (traceback()[1])[2];
                line = substr(line, 1, strlen(line) - 1);
                $parse.tell_error(((line + " on ") + ((ref1[1]).namef('ref))) + ".", syn);
            case ~perm:
                $parse.tell_error("You cannot write on that object.", syn);
            case ~stop:
                rethrow(error());
            default:
                // they are men, they can deal with the tracebacks
                .tell("Error encountered:");
                .tell_traceback(traceback());
        }
    }
.

method add_command_cmd
    arg cmd, str;
    var syn, sref, ref, template;
    
    .perms(sender(), 'this);
    syn = ("`" + cmd) + " <template> [for] <object.method>`";
    if (!str)
        $parse.tell_error("No arguments specified.", syn);
    if ((str[1]) != "\"")
        $parse.tell_error("No template given (must be enclosed in quotes).", syn);
    str = substr(str, 2);
    sref = explode(substr(str, ("\"" in str) + 1));
    template = substr(str, 1, ("\"" in str) - 1);
    sref = sref[listlen(sref)];
    ref = (| $parse.full_reference(sref, this()) |);
    if (!ref)
        $parse.tell_error(("\"" + sref) + "\" is an invalid object.method() reference.", syn);
    if (!(ref[3]))
        $parse.tell_error(("No method specified on " + ((ref[2]).dbref())) + ".", syn);
    if (!template)
        $parse.tell_error("No template specified.", syn);
    catch any {
        (ref[2]).add_command(template, ref[3]);
    } with handler {
        .tell_traceback(traceback());
        switch (error()) {
            case ~perm:
                $parse.tell_error(strsub((traceback()[1])[2], "%O", ((traceback()[1])[3]).namef('xref)), syn);
            default:
                $parse.tell_error((traceback()[1])[2], syn);
        }
    }
    .tell(((((("Command " + template) + " added for the method ") + toliteral(ref[3])) + " on ") + ((ref[2]).namef('xref))) + ".");
.

method eval_as_cmd
    arg cmd, objname, prep, line;
    var obj, result;
    
    (> .perms(sender(), 'this) <);
    line = line + ";";
    obj = .match_env_nice(objname);
    result = obj.eval([line]);
    if ((result[1]) == 'errors)
        .tell(result[2]);
    else
        .tell((("eval as " + (obj.dbref())) + " => ") + ($data.unparse(result[2])));
.

method eval_as_to_cmd
    arg cmd, objname, this, targetname, eval, line;
    var obj, target, result;
    
    (> .perms(sender(), 'this) <);
    line = line + ";";
    obj = .match_env_nice(objname);
    target = .match_env_nice(targetname);
    result = obj.eval([line], target);
    if ((result[1]) == 'errors)
        .tell(result[2]);
    else
        .tell((((((((cmd + " ") + objname) + " ") + this) + " ") + targetname) + " eval => ") + ($data.unparse(result[2])));
.