/
MinimalCore-3.2/
MinimalCore-3.2/src/
new object $libraries: $root;

var $root inited = 1;


new object $string: $libraries;

var $root inited = 1;
var $string alphabet = "abcdefghijklmnopqrstuvwxyz";
var $string numbers = "1234567890";
var $string non_alphanumeric = "!@#$%^&*()_+-=~`'{}[]|/?\",.<>;: ";

public method .trim() {
    arg string, [dir];
    var reg, range;
    
    // trim whitespace from string, args can be 'left or 'right
    if (type(string) != 'string)
        throw(~invarg, "Argument must be a string.");
    dir = [@dir, 'right][1];
    if (dir == 'left) {
        range = "[^ ] *$".match_regexp(string);
        if (!range)
            return string;
        range = [1, (range[1])[2]];
    } else {
        range = "[^ ]".match_regexp(string);
        if (!range)
            return string;
        range = [(range[1])[1]];
    }
    return .subrange(@range);
};

public method .alphabet() {
    return alphabet;
};

public method .numbers() {
    return numbers;
};

public method .non_alphanumeric() {
    return non_alphanumeric;
};

public method .is_numeric() {
    arg string;
    
    return toint(string) || (string == "0");
};

public method .a_or_an() {
    arg string;
    
    if (((string[1]).lowercase()) in "aeiou")
        return "an";
    return "a";
};

public method .strip() {
    arg string, strip;
    var new_str, char;
    
    // strips all of "strip" characters from the string
    // if "strip" is -1 it will use .non_alphanumeric()
    if ((type(string) != 'string) || ((type(strip) != 'string) && (strip != (-1))))
        throw(~type, "First argument must be a string, second can be -1");
    new_str = "";
    if (strip == (-1))
        new_str = non_alphanumeric;
    for char in [1 .. strlen(string)] {
        if (!((string[char]) in strip))
            new_str = new_str + (string[char]);
    }
    return new_str;
};

public method .chop() {
    arg str, len, [end];
    
    // chops string off end.length() characters before len and appends len
    end = [@end, "..."][1];
    if ((str.length()) < len)
        return str;
    if ((str.length()) < (end.length()))
        return str;
    return (str.pad(len - (end.length()))) + end;
};

public method .explode_english_list() {
    arg line, [opts];
    var x, output, tmp;
    
    // explodes an english list ("foo, bar and zoo").
    line = line.explode(",");
    output = [];
    for x in (line) {
        x = .trim(x);
        if ((| x.subrange(1, 3) |) == "and")
            output = [@output, .trim(x.subrange(4))];
        else
            output = [@output, x];
    }
    
    // check the last element, if they didn't specify 'noand
    if (!('noand in opts)) {
        line = (output[output.length()]).explode();
        tmp = "";
        for x in [1 .. line.length()] {
            if ((line[x]) == "and") {
                output = output.delete(output.length());
                if (tmp)
                    output = [@output, tmp];
                tmp = .to_string(line.subrange(x + 1));
                if (tmp)
                    output = [@output, tmp];
    
                // only bother with the first "and"
                break;
            }
            tmp = (tmp + (tmp ? " " | "")) + (line[x]);
        }
    }
    return output;
};

public method .wrap_lines() {
    arg string, length, [stuff];
    var output, cutoff, firstline, prefix;
    
    // takes string and wraps it by words, compared to length, returns a list.
    prefix = [@stuff, ""][1];
    firstline = [@stuff, 0, 0][2];
    output = [];
    if (firstline)
        string = prefix + string;
    while ((string.length()) > length) {
        cutoff = .rindex(string.subrange(1, length), " ");
        output = [@output, string.subrange(1, cutoff - 1)];
        string = prefix + (string.subrange(cutoff + 1));
    }
    return [@output, string];
};

public method .wrap_line() {
    arg string, length, [stuff];
    var output, cutoff, firstline, prefix;
    
    // takes string and wraps it by words, compared to length, breaks with \n
    prefix = [@stuff, ""][1];
    firstline = [@stuff, 0, 0][2];
    output = "";
    if (firstline)
        string = prefix + string;
    while ((string.length()) > length) {
        cutoff = .rindex(string.subrange(1, length), " ");
        output = (output + "\n") + (string.subrange(1, cutoff - 1));
        string = prefix + (string.subrange(cutoff + 1));
    }
    return (output + "\n") + string;
};

public method .rindex() {
    arg str, c;
    var i;
    
    i = strlen(str);
    while (i) {
        if ((str[i]) == c)
            return i;
        i = i - 1;
    }
    return 0;
};

public method .is_boolean() {
    arg str;
    
    if ((.match_begin("yes", str)) || ((.match_begin("true", str)) || (str == "1")))
        return 1;
    else if ((.match_begin("no", str)) || ((.match_begin("false", str)) || (str == "0")))
        return 0;
    else
        return -1;
};

public method .toliteral() {
    arg [args];
    
    return (> toliteral(@args) <);
};

public method .last() {
    arg str;
    
    return str[str.length()];
};

public method .explode_list() {
    arg str;
    
    if ("," in str)
        return str.explode_english_list();
    else
        return str.explode();
};

public method .explode_quoted() {
    arg str;
    var out, result;
    
    out = [];
    while (str) {
        result = .match_pattern("*\"*\"*", str);
        if (result) {
            out = [@out, @(result[1]).explode(), (result[2]).trim()];
            str = result[3];
        } else {
            out = [@out, @str.explode()];
            str = "";
        }
    }
    return out;
};

public method .regexp() {
    arg regexp, string;
    var match, m, complete, out;
    
    match = $string.match_regexp(regexp, string);
    if (!match)
        return 0;
    complete = match[1];
    out = [];
    for m in (match.delete(1)) {
        if (!(m[1]))
            break;
        out = out + [string.subrange(@m)];
    }
    return out || (string.subrange(@complete));
};

public method .to_symbol() {
    arg [args];
    
    return (> tosym(@args) <);
};

new object $list: $libraries;

var $root inited = 1;

public method .to_string() {
    arg list, [sep];
    var str, part;
    
    // uses $parse.unparse() rather than tostr()
    if (!list)
        return "";
    sep = [@sep, " "][1];
    str = tostr(list[1]);
    for part in (list.delete(1))
        str = (str + sep) + tostr(part);
    return str;
};

public method .to_english() {
    arg list, [options];
    var empty, and, sep;
    
    // uses $parse.unparse() rather than tostr()
    empty = [@options, "nothing"][1];
    switch (list.length()) {
        case 0:
            return empty;
        case 1:
            return tostr(list[1]);
    }
    and = [@options, " and ", " and "][2];
    sep = [@options, ", ", ", ", ", "][3];
    return ((.to_string(list.delete(list.length()), sep)) + and) + tostr(list[list.length()]);
};

public method .map() {
    arg list, method, [args];
    var out, x;
    
    // call 'method on each object, return results.
    out = [];
    for x in (list)
        out = [@out, x.(method)(@args)];
    return out;
};

public method .reverse() {
    arg list;
    var elm, reversed;
    
    // .reverse(list)
    // -> list with its elements reversed
    reversed = [];
    for elm in (list)
        reversed = [elm, @reversed];
    return reversed;
};

public method .compress() {
    arg list;
    var out, last, x;
    
    // [a,a,b,b,c,c,d,d] => [a,b,c,d]
    // removes duplicate entries in a list
    out = [];
    for x in (list) {
        if (!(x in out))
            out = [@out, x];
    }
    return out;
};

public method .last() {
    arg list;
    
    return list[list.length()];
};

public method .slice() {
    arg big_list, element;
    var list, ret_list;
    
    // Return elementh' element of all lists in big_list
    // No type or length checking done for speed purposes.
    ret_list = [];
    for list in (big_list)
        ret_list = [@ret_list, list[element]];
    return ret_list;
};

public method .max() {
    arg list;
    
    // return greatest element of list (no type checking performed)
    // if list is [], returns 0
    while ((list.length()) > 1) {
        if ((list[1]) > (list[2]))
            list = list.delete(2);
        else
            list = list.delete(1);
    }
    return [@list, 0][1];
};

public method .min() {
    arg list;
    
    // return least element of list (no type checking performed)
    // if list is [], returns 0
    while ((list.length()) > 1) {
        if ((list[1]) < (list[2]))
            list = list.delete(2);
        else
            list = list.delete(1);
    }
    return [@list, 0][1];
};

public method .lcolumnize() {
    arg list, [args];
    var line, part, lines, max, cols, col, width, len, sep;
    
    len = [@args, (| sender().linelen() |) || 79][1];
    sep = [@args, "", ""][2];
    lines = [];
    line = "";
    max = (.element_maxlength(list)) + (sep.length());
    cols = (len > max) ? len / max | 1;
    width = (len / cols) - (sep.length());
    col = cols;
    for part in (list) {
        col = col - 1;
        if (!col) {
            lines = lines + [line + part];
            line = "";
            col = cols;
            continue;
        }
        line = line + (part.pad(width));
    }
    if (line)
        return lines + [line];
    return lines;
};

public method .flatten() {
    arg list;
    var toret, elem;
    
    // [[[x], x], x]   =>   [x, x, x]
    toret = [];
    for elem in (list) {
        if (type(elem) == 'list)
            toret = toret + (.flatten(elem));
        else
            toret = toret + [elem];
    }
    return toret;
};

public method .to_buffer() {
    arg [args];
    
    return (> $buffer.from_strings(@args) <);
};

public method .setdifference() {
    arg [args];
    var set, list, element;
    
    // Usage:  diff(set 1, set 2, ..., set n)
    // Returns all elements of set 1 that are not in sets 2..n
    if (!args)
        return [];
    set = args[1];
    for list in (args.delete(1)) {
        for element in (list)
            set = set.setremove(element);
    }
    return set;
};

public method .setcontains() {
    arg [args];
    var super, list, element;
    
    // True if the first list given is a superset of all subsequent lists.
    // False otherwise.  [] is a superset of [] and nothing else; anything is
    // a superset of [].  If only one list is given, return true.
    super = args ? args[1] | [];
    for list in (args.delete(1)) {
        for element in (list) {
            if (!(element in super))
                return 0;
        }
    }
    return 1;
};

public method .setequal() {
    arg set1, set2;
    var element;
    
    // True if the two lists given contain the same elements.
    // False otherwise.
    while (set1) {
        element = set1[1];
        if ((!element) in set2)
            return 0;
        while (element in set2)
            set2 = set2.setremove(element);
        while (element in set1)
            set1 = set1.setremove(element);
    }
    return set2 == [];
};

public method .fold() {
    arg list, object, method, [args];
    var i, out;
    
    // apply object.method to a current result and the next element, return the
    // result
    if (list == [])
        return 0;
    out = list[1];
    for i in (list.subrange(2, (list.length()) - 1))
        out = object.(method)(out, i, @args);
    return out;
};

public method .intersection() {
    arg l1, l2;
    var i, out;
    
    // set intersection if the arguments
    out = [];
    for i in (l1) {
        if (i in l2)
            out = out.setadd(i);
    }
    return out;
};

public method .msort() {
    arg list, [keys];
    
    keys = keys ? keys[1] | list;
    if (listlen(list) != listlen(keys))
        throw(~invarg, "Invalid key list - the list lengths must be the same.");
    if (!list)
        return [];
    return (._merge_sort(list, keys))[1];
    
    // 9-25-95/21:26 Jenner ($jenner), moved from $jenner.msort
};

public method ._merge_sort() {
    arg list, keys;
    var i, j, l1, k1, l2, k2, n1, n2, n;
    
    n = listlen(list);
    if (n == 1)
        return [list, keys];
    n1 = n / 2;
    n2 = n - n1;
    l1 = ._merge_sort(list.subrange(1, n1), keys.subrange(1, n1));
    k1 = l1[2];
    l1 = l1[1];
    l2 = ._merge_sort(list.subrange(n1 + 1, n2), keys.subrange(n1 + 1, n2));
    k2 = l2[2];
    l2 = l2[1];
    list = [];
    keys = [];
    i = 1;
    j = 1;
    if (n > 30)
        pause();
    while ((i <= n1) && (j <= n2)) {
        if ((k1[i]) <= (k2[j])) {
            list = [@list, l1[i]];
            keys = [@keys, k1[i]];
            i = i + 1;
        } else {
            list = [@list, l2[j]];
            keys = [@keys, k2[j]];
            j = j + 1;
        }
    }
    return [[@list, @l1.subrange(i), @l2.subrange(j)], [@keys, @k1.subrange(i), @k2.subrange(j)]];
};

public method .prefix() {
    arg list, prefix;
    var out, elem;
    
    out = [];
    for elem in (list)
        out = out + [prefix + elem];
    return out;
};

public method .valid_objects() {
    arg list;
    var obj;
    
    for obj in (list) {
        if (!valid(obj))
            list = list.setremove(obj);
    }
    return list;
};


new object $parse: $libraries;

var $root inited = 1;

public method .html_traceback() {
    arg status, t;
    var line, out, x;
    
    out = ("<h2>" + ((t[1])[2])) + "</h2>";
    out = [out, ("<i><b>Thrown by " + (._html_traceback(@t[2]))) + "</b></i>", "<p>"];
    for x in [3 .. listlen(t)] {
        line = ("<code><i>" + ($parse.unparse((t[x])[1]))) + "</i>: ";
        out = out + [(line + (._traceback(@t[x]))) + "</code><br>"];
    }
    return .response(status, [@out, "</p>"]);
};

public method .traceback() {
    arg t;
    var line, out, x;
    
    out = "=> " + ((t[1])[2]);
    out = [out, "Thrown by " + (._traceback(@t[2]))];
    for x in [3 .. listlen(t)] {
        line = ($parse.unparse((t[x])[1])) + ": ";
        line = line + (._traceback(@t[x]));
        out = [@out, line];
    }
    return out;
};

public method ._html_traceback() {
    arg type, what, [more];
    var line;
    
    switch (type) {
        case 'function:
            return ("function <tt>" + tostr(what)) + "()</tt>";
        case 'opcode:
            return ("operator <tt>" + ($parse.unparse(what))) + "</tt>";
        default:
            line = ((($parse.unparse(more[2])) + ".") + tostr(what)) + "()";
            if ((more[1]) != (more[2]))
                line = ((line + " (") + ($parse.unparse(more[1]))) + ") ";
            line = (line + " line ") + tostr(more[3]);
            return line;
    }
};

public method ._traceback() {
    arg type, what, [more];
    var line;
    
    switch (type) {
        case 'function:
            return ("function " + tostr(what)) + "()";
        case 'opcode:
            return "operator " + ($parse.unparse(what));
        default:
            line = ((($parse.unparse(more[1])) + ".") + tostr(what)) + "()";
            if ((more[1]) != (more[2]))
                line = ((line + " (") + ($parse.unparse(more[2]))) + ") ";
            line = (line + " line ") + tostr(more[3]);
            return line;
    }
};

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

public method .unparse() {
    arg data;
    var str, element, association, pos, method;
    
    switch (type(data)) {
        case 'integer, 'float:
            return tostr(data);
        case 'string, 'symbol, 'error, 'buffer:
            return toliteral(data);
        case 'dbref:
            return .get_name(data);
        case 'list:
            if (!data)
                return "[]";
            str = "[";
            for element in (sublist(data, 1, listlen(data) - 1)) {
                str = str + (.unparse(element));
                str = str + ", ";
            }
            str = str + (.unparse(data[listlen(data)]));
            return str + "]";
        case 'dictionary:
            if (!data)
                return "#[]";
            str = "#[";
            for association in (data) {
                str = str + (.unparse(association));
                str = str + ", ";
            }
            return substr(str, 1, strlen(str) - 2) + "]";
        case 'frob:
            catch any
                return data.unparse();
            with
                return toliteral(data);
            
    }
};

public method .filter_for_html() {
    arg text;
    var x, line;
    
    for x in [1 .. listlen(text)] {
        if (text[x])
            text = replace(text, x, strsub(strsub(strsub(text[x], "&", "&amp;"), "<", "&lt;"), ">", "&gt;"));
    }
    return text;
};

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 ($string.match_template(templates[t], part)) {
                    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];
};


new object $dictionary: $libraries;

var $root inited = 1;

public method .to_list() {
    arg dict;
    var list, x, k;
    
    // merges into an associated list.
    k = dict.keys();
    list = [];
    for x in (k)
        list = [@list, [x, dict[x]]];
    return list;
};

public method .union() {
    arg dict1, dict2;
    var key;
    
    for key in (dict1)
        dict2 = dict2.add(key[1], key[2]);
    return dict2;
};

public method .values() {
    arg dict;
    var list, x, k;
    
    // returns values same as dict_keys() returns keys.
    k = dict.keys();
    list = [];
    for x in (k)
        list = [@list, dict[x]];
    return list;
};

public method .invert() {
    arg dict;
    var inverted, x;
    
    // Invert a dict (keys<->values)
    inverted = #[];
    for x in (dict.keys())
        inverted = inverted.add(dict[x], x);
    return inverted;
};

public method .add_elem() {
    arg dict, key, elem;
    var value;
    
    // same as old dict_add_elem
    value = (| dict[key] |);
    if ((type(value) != 'list) && (type(value) != 'error))
        throw(~type, ((("Value for key " + ($parse.unparse(key))) + " (") + ($parse.unparse(value))) + ") is not a list.");
    if (value)
        value = [@value, elem];
    else
        value = [elem];
    return dict.add(key, value);
};

public method .del_elem() {
    arg dict, key, elem;
    var value;
    
    value = (| dict[key] |);
    if ((type(value) != 'list) && (type(value) != 'error))
        throw(~type, ((("Value for key " + ($parse.unparse(key))) + " (") + ($parse.unparse(value))) + ") is not a list.");
    value = value.setremove(elem);
    if (!value)
        return dict.del(key);
    return dict.add(key, value);
};

public method .add_elem_union() {
    arg dict, key, elem;
    var value;
    
    value = (| dict[key] |);
    if (value)
        value = value.union([elem]);
    else
        value = [elem];
    return dict.add(key, value);
};


new object $buffer: $libraries;

var $root inited = 1;

public method .to_list() {
    arg buf;
    var idx, list;
    
    list = [];
    for idx in [1 .. buf.length()]
        list = list + [buf[idx]];
    return list;
};

public method .from_list() {
    arg list;
    var buf, x;
    
    buf = `[];
    for x in [1 .. list.length()]
        buf = buf.add(list[x]);
    return buf;
};

new object $time: $libraries;