/
MinimalCore-2.0/
MinimalCore-2.0/src/
// ------------------------------------------------------------------
// Just a placeholder

new object $libraries: $root;

var $root inited = 1;
var $root objname = 'libraries;

// ------------------------------------------------------------------
// $string library

new object $string: $libraries;

var $root inited = 1;
var $root objname = 'string;

var alphabet = "abcdefghijklmnopqrstuvwxyz";
var numbers = "1234567890";
var 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 .. string.length()] {
        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 explode {
    arg [args];
    
    return (> explode(@args) <);
};

public method match_template {
    arg template, string;
    
    return (> match_template(template, string) <);
};

public method match_pattern {
    arg pattern, string;
    
    return (> match_pattern(pattern, string) <);
};

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

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 pad {
    arg [args];
    
    return (> pad(@args) <);
};

public method length {
    arg str;
    
    return (> strlen(str) <);
};

public method subrange {
    arg [args];
    
    return (> substr(@args) <);
};

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

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

public method uppercase {
    arg string;
    
    return (> uppercase(string) <);
};

public method lowercase {
    arg string;
    
    return (> lowercase(string) <);
};

public method compare {
    arg str1, str2;
    
    return (> strcmp(str1, str2) <);
};

public method format {
    arg format, [args];
    
    return (> strfmt(format, args) <);
};

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

public method replace {
    arg [args];
    
    return (> strsub(@args) <);
};

// ------------------------------------------------------------------
// $list library

new object $list: $libraries;

var $root inited = 1;
var $root objname = 'list;

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 delete {
    arg [args];
    
    return (> delete(@args) <);
};

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

public method graft {
    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 length {
    arg l;
    
    return (> listlen(l) <);
};

public method union {
    arg [args];
    
    return (> union(@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 setadd {
    arg [args];
    
    return (> setadd(@args) <);
};

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 setremove {
    arg [args];
    
    return (> setremove(@args) <);
};

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

public method subrange {
    arg [args];
    
    return (> sublist(@args) <);
};

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

// ------------------------------------------------------------------
// $parse library

new object $parse: $libraries;

var $root inited = 1;
var $root objname = 'parse;

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 handler {
              return toliteral(data);
          }
  }
};

public method filter_for_html {
    arg text;
    var x, line;

    for x in [1 .. listlen(text)] {
        if (text[x]) {
            line = text[x].replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
            text = text.replace(x, line);
        }
    }
    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];
};

// ------------------------------------------------------------------
// $dictionary library

new object $dictionary: $libraries;

var $root inited = 1;
var $root objname = 'dictionary;

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 {
    arg [args];
    
    return (> dict_add(@args) <);
};

public method del {
    arg [args];
    
    return (> dict_del(@args) <);
};

public method keys {
    arg dict;
    
    return (> dict_keys(dict) <);
};

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

public method contains {
    arg [args];
    
    return (> dict_contains(@args) <);
};

// ------------------------------------------------------------------

new object $buffer: $libraries;

var $root inited = 1;
var $root objname = 'buffer;

public method to_list {
    arg buf;
    var idx, list;
    
    list = [];
    for idx in [1 .. buf.length()]
        list = list + [buf.retrieve(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;
};

public method length {
    arg buffer;
    
    return (> buffer_len(buffer) <);
};

public method retrieve {
    arg buffer, position;
    
    return (> buffer_retrieve(buffer, position) <);
};

public method append {
    arg buffer, buffer_2;
    
    return (> buffer_append(buffer, buffer_2) <);
};

public method replace {
    arg buffer, position, value;
    
    return (> buffer_replace(buffer, position, value) <);
};

public method add {
    arg buffer, value;
    
    return (> buffer_add(buffer, value) <);
};

public method truncate {
    arg buffer, position;
    
    return (> buffer_truncate(buffer, position) <);
};

public method tail {
    arg buffer, position;
    
    return (> buffer_tail(buffer, position) <);
};

public method to_string {
    arg buffer;
    
    return (> buffer_to_string(buffer) <);
};

public method to_strings {
    arg [args];
    
    return (> buffer_to_strings(@args) <);
};

public method from_string {
    arg string;
    
    return (> buffer_from_string(string) <);
};

public method from_strings {
    arg strings, [seperator];
    
    return (> buffer_from_strings(strings, @seperator) <);
};

// ------------------------------------------------------------------
// $time library

new object $time: $libraries;

var $root inited 1
var $root objname 'time

public method format {
    arg [args];

    return (> strftime(@args) <);
};