ColdWeb-3.1/
ColdWeb-3.1/src/
new object $libraries: $root;
var $root inited = 1;

new object $network: $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 .filter_text() {
    arg text;
    var x, line;

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

    return line.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
};

public method .explode_url() {
    arg line;
    var out, args, i;
  
    i = "?" in line;
    if (i) {
        args = substr(line, i + 1);
        line = substr(line, 1, i - 1);
    }
    if (args)
        out = #[['path, explode(line, "/")], ['args, .break_fields(args)]];
    else
        out = #[['path, explode(line, "/")], ['args, #[]]];
    return out;
};

public method .left() {
    arg str, width, [fchar];
    
    // will NOT chop off 'str' if it is longer than width, use pad() for that.
    if (fchar)
        return str + (str.length() < width ? "".pad(width - str.length(), fchar[1]) : "");
    else
        return str + (str.length() < width ? "".pad(width - str.length()) : "");
};

public method .fill() {
    arg n, [args];
    var fill, x;
    
    // same as pad("", n, [args]);
    fill = [@args, " "][1];
    return "".pad(n, fill);
};

public method .center() {
    arg text, len, [args];
    var lfill, rfill, textlen, padlen;
    
    // args[1] == string to center
    // args[2] == integer of width to center in
    // args[3] <op> == what to fill the left|right side with.
    // args[4] <op> == what to fill the right side with.
    lfill = args.length() >= 1 && args[1] || " ";
    rfill = args.length() >= 2 ? args[2] : lfill == " " ? "" : lfill;
    textlen = text.length();
    padlen = (len - textlen) / 2;
    if (textlen < len)
        return .fill(padlen, lfill) + text + (rfill ? .fill(len - textlen - padlen, rfill) : "");
    else
        return len > 0 ? text : text.pad(len);
};

public method .to_list() {
    arg str, [sep];
    var result, list;
    
    // separate a string into a list of strings, breaking wherever 'sep' appears.
    // if not provided, sep defaults to a comma.
    // One word of warning.  sep should not contain an asterisk.  If it does,
    // this routine will separate the string oddly, most likely losing bits.
    if (!str)
        return [];
    sep = "*" + (sep ? sep[1] : ",") + "*";
    list = [];
    while (1) {
        result = str.match_pattern(sep);
        if (result) {
            list = list + [result[1]];
            str = result[2];
        } else {
            return list + [str];
        }
    }
};

public method .right() {
    arg str, width, [fchar];
    
    // will not chop off 'str' if it is longer than width (unlike pad())
    if (fchar)
        return "".pad(width - str.length(), fchar[1]) + str;
    else
        return "".pad(width - str.length()) + str;
};

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

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

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;
    
    new_str = "";
    if (!strip)
        strip = "!@#$%^&*()_+-=~`'{}[]|/?\"\,.<>;: ";
    else
        strip = strip[1];
    for char in [1 .. strlen(string)] {
        if (!(string[char] in strip))
            new_str = new_str + string[char];
    }
    return new_str;
};

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

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;
    
    if (!line)
        return [];
    
    // explodes an english list ("foo, bar and zoo").
    line = line.explode(",");
    output = [];
    for x in (line) {
        x = .trim(x);
        if ((| x.subrange(1, 4) |) == "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 = $list.join(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 .explode_delimited() {
    arg str, left, right;
    var pattern, parsed, matched, match_num, match_result;
    
    // parse str looking for anything surrounded by left and right
    // ;$string.explode_delimited("foo%[bar]baz", "%[", "]")
    // => [["foo", 1, "baz"], ["bar"]]
    pattern = "*" + left + "*" + right + "*";
    parsed = [];
    matched = [];
    match_num = 0;
    while (str) {
        match_result = str.match_pattern(pattern);
        if (match_result) {
            match_num = match_num + 1;
            parsed = [@parsed, match_result[1], match_num];
            matched = [@matched, match_result[2]];
            str = match_result[3];
        } else {
            parsed = [@parsed, str];
            str = "";
        }
    }
    return [parsed, matched];
};

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), " ");
        if (cutoff <= prefix.length()) {
            output += "\n" + string.subrange(1, length);
            string = prefix + string.subrange(length + 1);
        } else {
            output += "\n" + string.subrange(1, cutoff - 1);
            string = prefix + string.subrange(cutoff + 1);
        }
    }
    return (output ? output.subrange(3) + "\n" : "") + string;
};

public method .rindex() {
    arg string, index;
    var loc, rest;
    
    // returns the first occurance of index starting from the end of the string,
    // and moving to the beginning.
    loc = index in string;
    rest = loc && string.subrange(loc + 1);
    while (loc && index in rest) {
        loc = loc + (index in rest);
        rest = loc && string.subrange(loc + 1);
    }
    return loc;
};

public method .match_sub_tag() {
    arg string, tag;
    var x, expl, output, match, matches;
    
    // matches a string between 'tag' and " " in a larger string against
    // the sender's environment.  If a match is found it subs the match.name
    // with the string, otherwize it lets it pass through with the tag, ie:
    // .match_sub_tag("this test #of something #note or other");
    // => "this test #of something Note of sorts or other"
    // where the note is in the sender's environment.
    expl = .explode_delimited(string + " ", tag, " ");
    matches = expl[2];
    expl = expl[1];
    output = "";
    for x in (expl) {
        if (type(x) == 'integer) {
            match = (| sender().match_environment(matches[x]) |);
            if (match)
                output = output + match.name() + " ";
            else
                output = output + tag + matches[x] + " ";
        } else {
            output = output + x;
        }
    }
    return output.subrange(1, output.length() - 1);
};

public method .search_pat() {
    arg pat, text, [start_at];
    var line, match_result, type;
    
    line = 1;
    type = [@start_at, 'pattern, 'pattern][2] == 'pattern ? 'match_pattern : 'match_regexp;
    if (start_at) {
        line = start_at[1];
        start_at = [@start_at, 1, 1][2];
        match_result = pat.(type)(text[line].subrange(line));
        if (match_result != 0) {
            if (type == 'match_pattern) {
                pat = $string.pat_sub(pat, match_result);
                return [line, start_at + pat in text[line].subrange(start_at)];
            } else {
                return [line, start_at + match_result[1][1]];
            }
        }
        line = line + 1;
    }
    while (line <= text.length()) {
        match_result = pat.(type)(text[line]);
        if (match_result != 0) {
            if (type == 'pattern) {
                pat = $string.pat_sub(pat, match_result);
                return [line, pat in text[line]];
            } else {
                return [line, match_result[1][1]];
            }
        }
        line = line + 1;
    }
    throw(~strnf, "String not found in text.");
};

public method .pat_sub() {
    arg pat, subs;
    var wc_idx;
    
    // wc_idx == wildcard index
    while (subs) {
        wc_idx = "*" in pat;
        if (wc_idx == 1)
            pat = subs[1] + pat.subrange(2);
        else if (wc_idx == pat.length())
            pat = pat.subrange(1, wc_idx - 1) + subs[1];
        else
            pat = pat.subrange(1, wc_idx - 1) + subs[1] + pat.subrange(wc_idx + 1);
        subs = subs.delete(1);
    }
    return pat;
};

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

public method .parse_template() {
    arg str;
    var index, out;
    
    out = (str.explode(" *"))[1];
    
    // index = "?" in str;
    // if (index) {
    //     out = uppercase(str.subrange(1, index - 1));
    //     out = out + "?" + str.subrange(index + 1);
    // } else {
    //     out = uppercase(out);
    // }
    return out;
};

public method .repeat() {
    arg string, times;
    var t, out;
    
    // repeats <string> <times> times
    if (type(string) != 'string)
        throw(~type, "The first agrument must be a string.");
    if (type(times) != 'integer || times < 0)
        throw(~type, "The second agrument must be a non-negatiive integer.");
    out = "";
    for t in [1 .. times]
        out = out + string;
    return out;
};

public method .find_next() {
    arg str, choices;
    var t, first, pos;
    
    //Returns the index of the first string in choices to appear.
    //Returns str.length() if none are in str.
    first = str.length() + 1;
    for t in (choices) {
        pos = t in str;
        if (pos && pos < first)
            first = pos;
    }
    return first;
};

public method .split_on_next() {
    arg str, choices;
    var pos, pre, post;
    
    // splits str around whichever choice appears first.
    pos = $string.find_next(str, choices);
    pre = (| str.subrange(1, pos - 1) |) || "";
    post = (| str.subrange(pos + 1) |) || "";
    return [pre, (| str[pos] |) || "", post];
};

public method .explode_template_word() {
    arg template;
    var t, x, idx, out, new;
    
    // this only explodes single word templates
    template = template.explode("|");
    out = [];
    for t in (template) {
        idx = "?" in t;
        if (idx) {
            t = t.strip("?");
            new = t.subrange(1, idx - 1);
            out = [@out, new];
            for x in [idx .. t.length()] {
                new = new + t[x];
                out = [@out, new];
            }
        } else {
            out = [@out, t];
        }
    }
    return out;
};

public method .to_number() {
    arg str;
    
    if (str.is_numeric())
        return toint(str);
    throw(~nonum, "\"" + str + "\" is not a number.");
};

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 .onespace() {
    arg str;
    var sub;
    
    if ((sub = "  +".sed(str, " ", "g")))
        str = sub;
    return str;
};

public method .find_next_escaped() {
    arg str, choices;
    var t, first, pos, good, p, start;
    
    //Returns the index of the first string in choices to appear.
    //If 
    //Returns str.length() if none are in str.
    first = str.length() + 1;
    for t in (choices) {
        pos = str.find_escaped(t);
        if (pos < first)
            first = pos;
    }
    return first;
};

public method .split_on_next_escaped() {
    arg str, choices;
    var pos, pre, post;
    
    // splits str around whichever choice appears first.
    pos = str.find_next_escaped(choices);
    pre = (| str.subrange(1, pos - 1) |) || "";
    post = (| str.subrange(pos + 1) |) || "";
    return [pre, (| str[pos] |) || "", post];
};

public method .find_escaped() {
    arg str, char;
    var good, start, pos, p;
    
    good = 0;
    start = 0;
    while (!good && start < str.length()) {
        pos = (char in str.subrange(start + 1)) + start;
        good = 1;
        if (pos > start) {
            p = pos - 1;
            while (p > 0 && str[p] == "\\") {
                good = good ? 0 : 1;
                p = p - 1;
            }
        }
        if (good)
            return pos;
        else
            start = pos;
    }
};

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 .strip_article() {
    arg str;
    
    return strsed(str, "^(an|a|the)  *", "", "g");
};

public method .rindexc() {
    arg str, c;
    var i;
    
    // same as rindex, but only with a single character, faster.
    i = str.length();
    while (i) {
        if (str[i] == c)
            return i;
        i = i - 1;
    }
    return 0;
};

public method .echo() {
    arg str;
    
    return str;
};

public method .sub() {
    arg regexp, string;
    var match, m, complete, out;
    
    match = string.match_regexp(regexp);
    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 .unquote() {
    arg str;
    
    if (str && str[1] == "\"" && str[str.length()] == "\"")
        return str.subrange(2, str.length() - 2);
    return str;
};

public method .to_buffer() {
    arg string;
    
    return (> $buffer.from_string(string) <);
};

public method .valid_method_name() {
    arg str;
    
    return .strip_others(str, alphabet + numbers + "_").length() == str.length();
};

public method .strip_others() {
    arg string, valid;
    var new_str, char;
    
    // strips all but "strip" characters from the string
    new_str = "";
    for char in [1 .. string.length()] {
        if (string[char] in valid)
            new_str = new_str + string[char];
    }
    return new_str;
};

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), " ");
        if (cutoff <= prefix.length()) {
            output = [@output, string.subrange(1, length)];
            string = prefix + string.subrange(length + 1);
        } else {
            output = [@output, string.subrange(1, cutoff - 1)];
            string = prefix + string.subrange(cutoff + 1);
        }
    }
    return [@output, string];
};

public method .to_symbol() {
    arg str;
    
    str = str.strip();
    return (> tosym(str) <);
};

public method .valid_ident() {
    arg str;
    
    return (| tosym(str) |) || 0;
};

new object $list: $libraries;
var $root inited = 1;

public method .tb_to_text() {
    arg [args];

    return (> $parse_lib.traceback(@args) <);
};

public method .tb_to_html() {
    arg [args];

    return (> $parse_lib.html_traceback(@args) <);
};

public method .to_english() {
    arg list, [options];
    var empty, and, sep;
    
    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 join(list.delete(list.length()), sep) + and + tostr(list[list.length()]);
};

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

public method .mfilter() {
    arg list, method, [args];
    var x;
    
    // similar to .mmap, but returns a list of objects which returned a
    // true value from 'method.
    return filter x in (list) where (x.method(@args));
};

public method .sort() {
    arg list, [sortby];
    
    // calls ._sort().  Does not set an element to sort by yet, but should
    // eventually will have to fix.
    return ._sort(list, 1, list.length());
};

public method ._sort() {
    arg lst, x, y;
    var p, i, j;
    
    switch (y - x + 1) {
        case 0, 1:
            return lst;
        case 2:
            if (lst[x] <= lst[y])
                return lst;
            p = lst[x];
            lst = lst.replace(x, lst[y]);
            lst = lst.replace(y, p);
            return lst;
        case 3:
            if (lst[x] <= lst[x + 1]) {
                if (lst[x + 1] <= lst[y]) {
                    ;
                } else if (lst[x] <= lst[y]) {
                    p = lst[x + 1];
                    lst = lst.replace(x + 1, lst[y]);
                    lst = lst.replace(y, p);
                } else {
                    p = lst[x];
                    lst = lst.replace(x, lst[y]);
                    lst = lst.replace(y, lst[x + 1]);
                    lst = lst.replace(x + 1, p);
                }
            } else if (lst[x] <= lst[y]) {
                p = lst[x];
                lst = lst.replace(x, lst[x + 1]);
                lst = lst.replace(x + 1, p);
            } else if (lst[x + 1] <= lst[y]) {
                p = lst[x];
                lst = lst.replace(x, lst[x + 1]);
                lst = lst.replace(x + 1, lst[y]);
                lst = lst.replace(y, p);
            } else {
                p = lst[x];
                lst = lst.replace(x, lst[y]);
                lst = lst.replace(y, p);
            }
            return lst;
    }
    p = lst[x];
    i = x;
    j = y;
    while (1) {
        while (i < j && p <= lst[j])
            j = j - 1;
        if (i == j)
            break;
        lst = lst.replace(i, lst[j]);
        i = i + 1;
        while (i < j && p >= lst[i])
            i = i + 1;
        if (i == j)
            break;
        lst = lst.replace(j, lst[i]);
        j = j - 1;
    }
    lst = lst.replace(i, p);
    lst = ._sort(lst, x, i - 1);
    lst = ._sort(lst, i + 1, y);
    return lst;
};

public method .columnize() {
    arg list, cols, [rest];
    var width, lines, line, separator, linelength, curcol;
    
    // turn [...] into ".   .   ."
    // rest[1]==separator; rest[2]==linelength
    separator = [@rest, "   "][1];
    linelength = [@rest, 78, 78][2];
    width = linelength / cols - separator.length();
    lines = [];
    while (list) {
        line = list[1].pad(width);
        list = list.subrange(2);
        for curcol in [2 .. cols] {
            if (list) {
                line = line + separator + list[1].pad(width);
                list = list.subrange(2);
            }
        }
        lines = [@lines, line];
    }
    return lines;
};

public method .reverse() {
    arg list;
    var i, len;
    
    // .reverse(list)
    // -> list with its elements reversed
    len = list.length() + 1;
    return map i in [1 .. len - 1] to (list[len - i]);
};

public method .compress() {
    arg list;
    var x;
    
    // [a,a,b,b,c,c,d,d] => [a,b,c,d]
    // removes duplicate entries in a list
    return hash x in (list) to ([x, 1]).keys();
};

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

public method .count() {
    arg list, elem;
    var count;
    
    // count of elem in list
    while (elem in list) {
        count = count + 1;
        list = list.subrange((elem in list) + 1);
    }
    return count;
};

public method .element_maxlength() {
    arg list;
    var elm;
    
    return map elm in (list) to (tostr(elm).length()).max();
};

public method .nth_element_maxlength() {
    arg lists, element;
    var list;
    
    // Returns longest string whose index is element in one of the lists in
    // lists.
    if (type(element) != 'integer)
        throw(~type, "Second argument is not an integer");
    if (type(lists) != 'list)
        throw(~type, "First argument is not a list");
    return map list in (lists) to (tostr(list[element]).length()).max();
};

public method .numbered_text() {
    arg text;
    var line;
    
    // receives a list of strings, returns that list with line numbers
    // prepended
    return map line in [1 .. text.length()] to ("%3r: %l".format(line, text[line]));
};

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

public method .swap() {
    arg list, a, b;
    var holder;
    
    // swap elements at indexes a and b
    holder = (> list[a] <);
    list = (> list.replace(a, list[b]) <);
    list = list.replace(b, holder);
    return list;
};

public method .max() {
    arg list;
    
    return (| max(@list) |) || 0;
};

public method .min() {
    arg list;
    
    return (| min(@list) |) || 0;
};

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 .swapsort() {
    arg list, [sort_by];
    var bot_elem, cur_elem, elem, compare;
    
    // note: iterative implementation allows sorts of extra-long lists
    elem = [@sort_by, 1];
    compare = [@sort_by, 'gt, 'lt][2];
    for bot_elem in [1 .. list.length()] {
        for cur_elem in [bot_elem + 1 .. list.length()] {
            if (._swap_compare(list[bot_elem], list[cur_elem], compare, elem))
                list = $list.swap(list, bot_elem, cur_elem);
        }
    }
    return list;
};

public method ._swap_compare() {
    arg elem1, elem2, compare, [elem];
    
    elem = [@elem, 1][1];
    switch (compare) {
        case 'lt:
            return elem1[elem] < elem2[elem];
        case 'gt:
            return elem1[elem] > elem2[elem];
        default:
            return 0;
    }
};

public method .heapsort() {
    arg list, [sort_by];
    var heap, sort_type, sort_elem;
    
    sort_elem = [@sort_by, 1][1];
    sort_type = [@sort_by, 'gt, 'gt][2];
    switch (sort_type) {
        case 'gt:
            heap = $small_first_heap_class.new(list, sort_elem);
        default:
            return list;
    }
    list = [];
    while (heap.length()) {
        list = heap.element(1);
        heap = heap.del(1);
    }
    return list;
};

public method .map_to_english() {
    arg list, method, [args];
    
    // because I (Lynx) am lazy
    return .to_english(.mmap(list, method, @args));
};

public method .map_to_string() {
    arg list, method, [args];
    
    // because I (Lynx) am lazy
    return .join(.mmap(list, method, @args));
};

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

public method .sum() {
    arg data;
    var ret, i;
    
    // returns a sum of each element in the list.
    ret = data[1];
    for i in (data.subrange(2))
        ret += i;
    return ret;
};

public method .non_alphanumeric() {
    // returns nun-alphanumeric in a list of characters
    return ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "-", "=", "~", "`", "'", "{", "}", "[", "]", "|", "/", "?", "\"", "\\", ",", ".", "<", ">", ";", ":", " "];
};

public method .numbers() {
    // returns a list of numbers
    return ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
};

public method .alphabet() {
    //returns the alphabet in a list
    return ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
};

public method .center_lines() {
    arg lines, width, [args];
    var line;
    
    return map line in (lines) to ($string.center(line, width, @args));
};

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

public method .chop() {
    arg list, [count];
    
    // chops the last <count> elements off the list.
    // return [] if count is longer then the list.
    count = count || 1;
    return (| list.subrange(1, list.length() - count) |) || [];
};

public method .lmap() {
    arg list, method, [args];
    var x, s;
    
    //call methods for each thing in list on sender()
    s = sender();
    return map x in (list) to (s.(method)(x, @args));
};

public method .omap() {
    arg list, object, method, [args];
    var obj;
    
    // calls object.method(obj, @args) for each obj in list
    return map obj in (list) to (object.(method)(obj, @args));
};

public method .del() {
    arg list, element;
    
    return (> list.setremove(element) <);
};

public method .add() {
    arg list, element;
    
    return (> list.setadd(element) <);
};

public method .set_difference() {
    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 .set_contains() {
    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 .set_equal() {
    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
    switch (list.length()) {
        case 0:
            return 0;
        case 1:
            return list[1];
    }
    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];
};

private 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 elem;
    
    return map elem in (list) to (prefix + elem);
};

public method .valid_objects() {
    arg list;
    var obj;
    
    return filter obj in (list) where (valid(obj));
};

public method .grep() {
    arg lines, regexp;
    var line, result, out, reg;
    
    out = [];
    for line in [1 .. lines.length()] {
        reg = lines[line].match_regexp(regexp);
        if (reg)
            out = out + [[line, reg, lines[line]]];
    }
    return out;
};

public method .vcolumnize() {
    arg list, cols, [rest];
    var linelength, sep, width, lines, i, j, line, outlist;
    
    linelength = [@rest, (| sender().linelen() |) || 79][1];
    sep = [@rest, " ", " "][2];
    lines = list.length() / cols + (list.length() % cols ? 1 : 0);
    width = linelength / cols;
    return ._vcolumnize(list, lines, cols, width, sep);
};

public method .make() {
    arg n, [elt];
    var i;
    
    elt = [@elt, 0][1];
    return map i in [1 .. n] to (elt);
};

public method ._vcolumnize() {
    arg list, lines, cols, width, [other];
    var outlist, line, i, j, sep;
    
    sep = [@other, " "][1];
    width -= sep.length();
    lines = lines > list.length() ? list.length() : lines;
    outlist = [];
    for i in [1 .. lines] {
        line = list[i].pad(width);
        for j in [1 .. cols]
            (| (line = line + sep + list[i + j * lines].pad(width)) |);
        outlist = [@outlist, line];
    }
    return outlist;
};

public method .affix() {
    arg l1, l2;
    var last, first;
    
    // Combines l1 and l2 by appending the first element of l2 to the last
    // of l1.
    if (type(l2) != 'list)
        l2 = [l2];
    last = (| l1.last() |) || "";
    first = (| l2[1] |) || "";
    l1 = [@l1.chop(), last + first];
    if (l2.length() > 1)
        l1 = l1 + l2.subrange(2);
    return l1;
};

public method .addkey() {
    arg l, key, val;
    var i;
    
    find i in [1 .. l.length()] where (l[i][1] == key);
    return i ? l.replace(i, [key, val]) : [@l, [key, val]];
};

public method .delkey() {
    arg l, key;
    var i;
    
    find i in [1 .. l.length()] where (l[i][1] == key);
    return i ? l.delete(i) : l;
};

public method .getkey() {
    arg l, key;
    var i;
    
    find i in [1 .. l.length()] where (l[i][1] == key) || throw(~keynf, "Key not found.");
    return l[i][2];
};

public method .getkey_index() {
    arg l, key;
    var i;
    
    find i in [1 .. l.length()] where (l[i][1] == key) || throw(~keynf, "Key not found.");
    return i;
};

public method .setremove_all() {
    arg list, remove;
    
    while (remove in list)
        list = .setremove(list, remove);
    return list;
};

public method .vcolumnize2() {
    arg list, lines, [rest];
    var linelength, sep, cols, width, i, j, line, outlist;
    
    linelength = [@rest, (| sender().linelen() |) || 79][1];
    sep = [@rest, " ", " "][2];
    cols = list.length() / lines + (list.length() % lines ? 1 : 0);
    width = linelength / cols;
    return ._vcolumnize(list, lines, cols, width, sep);
};

public method .vcolumnize3() {
    arg list, lines, [rest];
    var linelength, cols, width, i, j, line, outlist;
    
    linelength = [@rest, (| sender().linelen() |) || 79][1];
    cols = list.length() / lines + (list.length() % lines ? 1 : 0);
    width = linelength / cols;
    outlist = [];
    for i in [1 .. lines] {
        line = "";
        for j in [0 .. cols]
            (| (line = line + list[i + j * lines].pad(width)) |);
        outlist = [@outlist, line];
    }
    return outlist;
};

public method .vcolumnize4() {
    arg list, [args];
    var linelength, sep, lines, cols, width, max;
    
    linelength = [@args, (| sender().linelen() |) || 79][1];
    sep = [@args, "", ""][2];
    max = .element_maxlength(list) + sep.length();
    cols = linelength > max ? linelength / max : 1;
    width = linelength / cols;
    lines = list.length() / cols + (list.length() % cols ? 1 : 0);
    return ._vcolumnize(list, lines, cols, width, sep);
};

public method .random() {
    arg list;
    
    return list[random(listlen(list))];
};

new object $dictionary: $libraries;
var $root inited = 1;

public method .map_method() {
    arg ls, what;
    var x, dict;
    
    // args[1] == list of objects
    // args[2] == symbol for method on objects
    // it will create a dictionary out of the two.
    dict = #[];
    
    // Get list's method(whatever) and add it to the dictionary
    for x in [1 .. ls.length()]
        dict = dict.add(ls[x], ls[x].(what)());
    return dict;
};

public method .merge() {
    arg [args];
    var x, dict, z, tule, dz, axz, keys;
    
    // merges all dictionaries into a single one, if they have the same key's --
    // basing off of args[1] (this should be the longest list (i know, bad Lynx).
    dict = args[1];
    keys = args[1].keys();
    for x in [2 .. args.length()] {
        for z in (keys) {
            dz = dict[z];
            axz = args[x][z];
            if (type(dict[z]) == 'list)
                tule = dz;
            else
                tule = [dz];
            tule = [@tule, axz];
            dict = dict.add(z, tule);
        }
    }
    return dict;
};

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 .merge_to_list() {
    arg [args];
    var x, dict, z, tule, dz, axz, keys, list;
    
    // merges all dictionaries into a single list, where each related key
    // is merged with all it's other values as a subrange
    // basing off of args[1] (this should be the longest list (i know, bad Lynx).
    dict = .merge(@args);
    list = [];
    for z in (dict.keys())
        list = [@list, dict[z]];
    return list;
};

public method .nunion() {
    arg dict1, dict2;
    var key;
    
    // like union() but for dictionaries.  adds any keys from dict2 that don't
    // already exist in dict1 to dict1 and returns the result.  Order of keys in
    // result is not guaranteed.
    if (dict1.keys().length() < dict2.keys().length()) {
        for key in (dict1)
            dict2 = dict2.add(key[1], key[2]);
        return dict2;
    } else {
        for key in (dict2)
            dict1 = dict1.add(key[1], key[2]);
        return dict1;
    }
};

public method .replace() {
    arg dict, key, value;
    
    dict = (> dict.del(key) <);
    dict = (> dict.add(key, value) <);
    return dict;
};

public method .apply() {
    arg tdict, list;
    var x;
    
    // Apply a translation-dict to a list
    for x in [1 .. list.length()] {
        catch ~keynf
            list = list.replace(x, tdict[list[x]]);
    }
    return list;
};

public method .apply_to_keys() {
    arg tdict, dict;
    var x, newdict;
    
    // Apply a t-dict to the keys of a dict
    newdict = #[];
    for x in (dict) {
        catch ~keynf
            x = x.replace(1, tdict[x[1]]);
        newdict = newdict.add(@x);
    }
    return newdict;
};

public method .apply_to_values() {
    arg tdict, dict;
    var x, newdict;
    
    // Apply a t-dict to the values of a dict
    newdict = #[];
    for x in (dict) {
        catch ~keynf
            x = x.replace(2, tdict[x[2]]);
        newdict = newdict.add(@x);
    }
    return newdict;
};

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 " + toliteral(key) + " (" + toliteral(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 " + toliteral(key) + " (" + toliteral(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 = setadd(value, elem);
    else
        value = [elem];
    return dict_add(dict, key, value);
};

public method .to_trie() {
    arg dict;
    var i, trie;
    
    trie = (<$trie, [0, ""]>);
    for i in (dict) {
        trie = trie.add(i[1], i[2]);
        refresh();
    }
    return trie;
};

public method .ordered_union() {
    arg dict1, dict2;
    var key;
    
    // like union() but for dictionaries.  adds any keys from dict2 that don't
    // already exist in dict1 to dict1 and returns the result.  
    // This one is guaranteed to pass the keys from argument1 to the argument2. It is more suitable for argument parsing.
    for key in (dict1)
        dict2 = dict2.add(key[1], key[2]);
    return dict2;
    
    // $#Copied 05 Jul 96 00:58 from $dictionary.union() by $jenner
};

public method .setadd_elem() {
    arg dict, key, elem;
    var value;
    
    value = (| dict[key] |);
    if (value)
        value = setadd(value, elem);
    else
        value = [elem];
    return dict_add(dict, key, value);
};

new object $math: $libraries;
var $root inited = 1;

var $math pi = 3.14159;
var $math pi2 = 6.28318;
var $math origin_2d = [0.0, 0.0];
var $math origin_3d = [0.0, 0.0, 0.0];
var $math transmat_2d = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
var $math transmat_3d = [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]];

public method $math.polar_rectangular() {
    arg coords;
    
    return [coords[1] * cos(coords[2]), coords[1] * sin(coords[2])];
};

public method $math.rectangular_polar() {
    arg coords;
    var a;
    
    a = atan2(coords[2], coords[1]);
    if (a < 0)
        a += pi2;
    return [.distance(coords, origin_2d), a];
};

public method $math.pi() {
    return pi;
};

public method $math.pi2() {
    return pi2;
};

public method $math.deg_rad() {
    arg angle;
    
    return angle / 57.2958;
};

public method $math.rad_deg() {
    arg angle;
    
    return angle * 57.2958;
};

public method $math.matrix_add() {
    arg m1, m2;
    var i;
    
    return map i in [1 .. m1.length()] to (.add(m1[i], m2[i]));
};

public method $math.matrix_sub() {
    arg m1, m2;
    var i;
    
    return map i in [1 .. m2.length()] to (.sub(m1[i], m2[i]));
};

public method $math.matrix_mul() {
    arg m1, m2;
    var x, y;
    
    m2 = .transpose(m2);
    return map x in (m1) to (map y in (m2) to (.dot(x, y)));
};

public method $math.spherical_rectangular() {
    arg coords;
    var r, phi, theta, r1;
    
    r = coords[1];
    phi = coords[2];
    theta = coords[3];
    r1 = r * cos(theta);
    return [r1 * cos(phi), r1 * sin(phi), r * sin(theta)];
};

public method $math.rectangular_spherical() {
    arg coords;
    var a, d;
    
    a = atan2(coords[2], coords[1]);
    if (a < 0)
        a += pi2;
    return [(d = .distance(coords, origin_3d)), a, atan2(coords[3], .distance(coords.subrange(1, 2), origin_2d))];
};

public method $math.ident_mat() {
    arg n;
    var x, y;
    
    return map x in [1 .. n] to (map y in [1 .. n] to (x == y ? 1.0 : 0.0));
};

public method $math.translation_mat() {
    arg vector;
    var x, y;
    
    if (vector.length() == 2)
        return [@transmat_2d, [@vector, 1.0]];
    else
        return [@transmat_3d, [@vector, 1.0]];
};

public method $math.rectangular_cylindrical() {
    arg coords;
    var a;
    
    a = atan2(coords[2], coords[1]);
    if (a < 0)
        a += pi2;
    return [.distance(coords, origin_2d), a, coords[3]];
};

public method $math.cylindrical_rectangular() {
    arg coords;
    
    return [coords[1] * cos(coords[2]), coords[1] * sin(coords[2]), coords[3]];
};

public method $math.matrix_scale() {
    arg s, m;
    var x;
    
    return map x in (m) to (.scale(s, x));
};

public method $math.tensor() {
    arg v1, v2;
    var x, y;
    
    return map x in (v1) to (map y in (v2) to (x * y));
};

public method $math.skew() {
    arg v;
    
    return [[0.0, v[3], -v[2]], [-v[3], 0.0, v[1]], [v[2], -v[1], 0.0]];
};

public method $math.rotation_mat_3d() {
    arg axis, angle;
    var s, c, m, tens;
    
    s = sin(angle);
    c = cos(angle);
    if (type(axis) == 'list) {
        axis = .scale(1.0 / .distance(axis, origin_3d), axis);
        tens = .tensor(axis, axis);
        m = .matrix_add(tens, .matrix_add(.matrix_scale(s, .skew(axis)), .matrix_scale(c, .matrix_sub(.ident_mat(3), tens))));
        return [[@m[1], 0.0], [@m[2], 0.0], [@m[3], 0.0], [0.0, 0.0, 0.0, 1.0]];
    } else {
        switch (axis) {
            case 'z:
                return [[c, s, 0.0, 0.0], [-s, c, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]];
            case 'y:
                return [[c, 0.0, -s, 0.0], [0.0, 1.0, 0.0, 0.0], [s, 0.0, c, 0.0], [0.0, 0.0, 0.0, 1.0]];
            case 'x:
                return [[1.0, 0.0, 0.0, 0.0], [0.0, c, s, 0.0], [0.0, -s, c, 0.0], [0.0, 0.0, 0.0, 1.0]];
        }
    }
};

public method $math.transform_vect() {
    arg m, v;
    var x, outvect, flag;
    
    if (m.length() == v.length() + 1) {
        v = [@v, 1.0];
        flag = 1;
    }
    outvect = map x in (m) to (.dot(x, v));
    return flag ? outvect.subrange(1, outvect.length() - 1) : outvect;
};

public method $math.rotation_mat_2d() {
    arg angle;
    var s, c;
    
    s = sin(angle);
    c = cos(angle);
    return [[c, s, 0.0], [-s, c, 0.0], [0.0, 0.0, 1.0]];
};

public method $math.scale_mat() {
    arg scale;
    
    if (scale.length() == 2)
        return [[scale[1], 0.0, 0.0], [0, scale[2], 0.0], [0.0, 0.0, 1.0]];
    else
        return [[scale[1], 0.0, 0.0, 0.0], [0.0, scale[2], 0.0, 0.0], [0.0, 0.0, scale[3], 0.0], [0.0, 0.0, 0.0, 1]];
};


new object $parse_lib: $libraries;

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

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

public method $parse_lib.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 $parse_lib.html_traceback() {
    arg t, status;
    var line, out, x;
  
    out = "<h2>" + t[1][2].filter_line() + "</h2>";
    out += "<i><b>Thrown by " + ._html_traceback(@t[2]) + "</b></i>\n<p>";
    for x in [3 .. listlen(t)]
        out += "<code><i>" + t[x][1] + "</i>: " + ._traceback(@t[x]) +
               "</code><br>";
    return $sys.response(status, out + "</p>");
};

private method ._html_traceback() {
    arg type, what, [more];
    var line;

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

public method $parse_lib.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;
};

public method $parse_lib._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 $parse_lib.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 $parse_lib._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 $parse_lib.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 $parse_lib.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];
};


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;
var $root inited = 1;

new object $integer: $libraries;
var $root inited = 1;

new object $http: $libraries;
var $root inited = 1;