new object $parse_lib: $libraries;

var $parse_lib boolean_strs = [["yes", "true", "1", "on"], ["no", "false", "0", "off"]];
var $root inited = 1;

public method ._range() {
    arg str;
    
    if (str.is_numeric()) {
        return toint(str);
    } else {
        switch (str[1]) {
            case "$":
                return 'end;
            case ".":
                return 'current;
            case "^":
                return 'start;
            default:
                throw(~range, "Invalid range reference.");
        }
    }
};

public method ._traceback() {
    arg what, [more];
    var line;
    
    if (more) {
        if (more[1] == more[2])
            return more[1] + "." + what + "() line " + more[3];
        else
            return more[2] + "." + what + "() (" + more[1] + ") line " + more[3];
    } else {
        return what;
    }
};

public method .boolean() {
    arg str;
    
    if (str in boolean_strs[1])
        return 1;
    else if (str in boolean_strs[2])
        return 0;
    else
        throw(~unknown, "Boolean flag not recognized.");
};

public method .get_name() {
    arg obj;
    var name;
    
    if (!valid(obj))
        return ("** Invalid " + toliteral(obj)) + " **";
    return obj.objname();
};

public method .getopt() {
    arg line, [defaults];
    var out, newlist, part, v, opt, t, templates, keys, key, l, x;
    
    // submit: [["template", value], [...]];
    // => if value is 1, it will take the next part of the string
    // receive: [["template", "flag", bool, value]], [...]]; 
    line = line.explode_quoted();
    out = [];
    newlist = [];
    defaults = (| defaults[1] |) || [];
    templates = defaults.slice(1);
    x = 1;
    l = line.length();
    while (1) {
        if (x > l)
            break;
        if (line[x][1] in ["-", "+"]) {
            opt = 0;
            v = "";
            part = line[x].subrange(2);
            for t in [1 .. templates.length()] {
                if ("=" in part) {
                    part = part.explode("=");
                    v = (| part[2] |) || "";
                    part = part[1];
                }
                if (part.match_template(templates[t])) {
                    opt = [templates[t], part, line[x][1] == "+"];
                    if ((| defaults[t][2] |) && !v) {
                        if (x + 1 <= l) {
                            x = x + 1;
                            if (line[x] == "=") {
                                if (x + 1 <= l)
                                    x = x + 1;
                            }
                            v = line[x];
                        }
                    }
                    opt = opt + [v];
                }
            }
            if (!opt)
                opt = [0, part, line[x][1] == "+", ""];
            out = out + [opt];
        } else {
            newlist = newlist + [line[x]];
        }
        x = x + 1;
    }
    return [newlist, out];
};

public method .range() {
    arg str;
    var out;
    
    out = str.split(" *- *");
    if (out.length() == 1) {
        if ("," in str)
            return ['specific, str];
        out = [(> ._range(str) <), 'single];
    } else if (out.length() == 2) {
        out = out.replace(1, (> ._range(out[1]) <));
        out = out.replace(2, (> ._range(out[2]) <));
    } else {
        throw(~range, "Invalid range reference.");
    }
    return out;
};

public method .ref() {
    arg str, [args];
    var def, me, obj, reg, member, match, type, second;
    
    me = [@args, sender()][1];
    if (args.length() > 1)
        match = args[2];
    else
        match = [me, 'match_environment, []];
    if (str == ".") {
        // shortcut
        obj = (> match[1].(match[2])("", @match[3]) <);
        return ['object, obj, obj, 0, 0];
    }
    if ((reg = regexp(str, "^(.*)<([^>]*)>(.*)$"))) {
        def = (> match[1].(match[2])(reg[2], @match[3]) <);
        str = reg[1] + reg[3];
    }
    if ((reg = regexp(str, "([^\.,]*)([\.,]+)([^\( ]*)"))) {
        obj = reg[1];
        member = reg[3];
        type = reg[2];
        if (type.length() > 1 && type[1] == "." && !obj) {
            type = type.subrange(2);
            obj = (> match[1].(match[2])("", @match[3]) <);
        } else {
            obj = obj ? (> match[1].(match[2])(obj, @match[3]) <) : me;
        }
        if ("." in type) {
            if ("," in type)
                second = 'variable;
            type = 'method;
        } else {
            type = 'variable;
        }
    } else {
        obj = (> match[1].(match[2])(str, @match[3]) <);
        type = 'object;
    }
    return [type, obj, def || obj, member, second];
};

public method .traceback() {
    arg traceback, [args];
    var line, out, pre, lines, cur, x, error;
    
    // $parse_lib.traceback(traceback(), lines, pre);
    // -1 lines represents the full error
    // pre is set to "! " unless otherwise specified.
    lines = [@args, -1][1];
    pre = [@args, "! ", "! "][2];
    error = [@args, 0, 0, 0][3];
    out = [pre + "=> " + traceback[1][2]];
    pre = pre + "   ";
    if (error == 0)
        out = [@out, pre + "Thrown by " + ._traceback(@traceback[2].subrange(2))];
    else
        out = [@out, pre + "Error " + error + " caused by " + ._traceback(@traceback[2].subrange(2))];
    for x in [1 .. traceback.length() - 2] {
        if (x <= lines || lines == -1) {
            line = traceback[x + 2][1] + ": ";
            line = line + ._traceback(@traceback[x + 2].subrange(2));
            out = [@out, pre + line];
        }
    }
    return out;
};