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("&", "&").replace("<", "<").replace(">", ">");
text = replace(text, x, line);
}
}
return text;
};
public method .filter_line() {
arg line;
return line.replace("&", "&").replace("<", "<").replace(">", ">");
};
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;