08fd851489
Change fonts; make use of the new feature to allow fonts to be specified using property from dialog xml. Ensure that all fonts are defined within the style.
492 lines
13 KiB
Text
492 lines
13 KiB
Text
var iscntrl = func(c) c >= 1 and c <= 31 or c == 127;
|
|
var isascii = func(c) c >= 0 and c <= 127;
|
|
var isupper = func(c) c >= `A` and c <= `Z`;
|
|
var islower = func(c) c >= `a` and c <= `z`;
|
|
var isdigit = func(c) c >= `0` and c <= `9`;
|
|
var isblank = func(c) c == ` ` or c == `\t`;
|
|
var ispunct = func(c) c >= `!` and c <= `/` or c >= `:` and c <= `@`
|
|
or c >= `[` and c <= `\`` or c >= `{` and c <= `~`;
|
|
|
|
var isxdigit = func(c) isdigit(c) or c >= `a` and c <= `f` or c >= `A` and c <= `F`;
|
|
var isspace = func(c) c == ` ` or c >= `\t` and c <= `\r`;
|
|
var isalpha = func(c) isupper(c) or islower(c);
|
|
var isalnum = func(c) isalpha(c) or isdigit(c);
|
|
var isgraph = func(c) isalnum(c) or ispunct(c);
|
|
var isprint = func(c) isgraph(c) or c == ` `;
|
|
|
|
var toupper = func(c) islower(c) ? c + `A` - `a` : c;
|
|
var tolower = func(c) isupper(c) ? c + `a` - `A` : c;
|
|
|
|
var isxspace = func(c) isspace(c) or c == `\n`;
|
|
|
|
|
|
##
|
|
# trim spaces at the left (lr < 0), at the right (lr > 0), or both (lr = 0)
|
|
# An optional function argument defines which characters should be trimmed:
|
|
#
|
|
# string.trim(a); # trim spaces
|
|
# string.trim(a, 1, string.isdigit); # trim digits at the right
|
|
# string.trim(a, 0, func(c) c == `\\` or c == `/`); # trim slashes/backslashes
|
|
#
|
|
var trim = func(s, lr = 0, istrim = nil) {
|
|
if (istrim == nil)
|
|
istrim = isspace;
|
|
var l = 0;
|
|
if (lr <= 0)
|
|
for (; l < size(s); l += 1)
|
|
if (!istrim(s[l]))
|
|
break;
|
|
var r = size(s) - 1;
|
|
if (lr >= 0)
|
|
for (; r >= 0; r -= 1)
|
|
if (!istrim(s[r]))
|
|
break;
|
|
return r < l ? "" : substr(s, l, r - l + 1);
|
|
}
|
|
|
|
##
|
|
# truncate at the first match
|
|
#
|
|
# string.truncateAt("file.xml", ".xml"); # "file.xml" -> "file"
|
|
# string.truncateAt("file.xml", ".txt"); # "file.xml" -> "file.xml"
|
|
#
|
|
var truncateAt = func(src, match){
|
|
var pos = find(match,src);
|
|
if (pos>=0)
|
|
return substr(src,0,pos);
|
|
return src;
|
|
}
|
|
|
|
##
|
|
# return string converted to lower case letters
|
|
#
|
|
var lc = func(str) {
|
|
var s = "";
|
|
for (var i = 0; i < size(str); i += 1)
|
|
s ~= chr(tolower(str[i]));
|
|
return s;
|
|
}
|
|
|
|
|
|
##
|
|
# return string converted to upper case letters
|
|
#
|
|
var uc = func(str) {
|
|
var s = "";
|
|
for (var i = 0; i < size(str); i += 1)
|
|
s ~= chr(toupper(str[i]));
|
|
return s;
|
|
}
|
|
|
|
|
|
##
|
|
# case insensitive string compare and match functions
|
|
# (not very efficient -- converting the array to be sorted
|
|
# first is faster)
|
|
#
|
|
var icmp = func(a, b) cmp(lc(a), lc(b));
|
|
var imatch = func(a, b) match(lc(a), lc(b));
|
|
|
|
|
|
##
|
|
# check if string <str> matches shell style pattern <patt>
|
|
#
|
|
# Rules:
|
|
# ? stands for any single character
|
|
# * stands for any number (including zero) of arbitrary characters
|
|
# \ escapes the next character and makes it stand for itself; that is:
|
|
# \? stands for a question mark (not the "any single character" placeholder)
|
|
# [] stands for a group of characters:
|
|
# [abc] stands for letters a, b or c
|
|
# [^abc] stands for any character but a, b, and c (^ as first character -> inversion)
|
|
# [1-4] stands for digits 1 to 4 (1, 2, 3, 4)
|
|
# [1-4-] stands for digits 1 to 4, and the minus
|
|
# [-1-4] same as above
|
|
# [1-3-6] stands for digits 1 to 3, minus, and 6
|
|
# [1-3-6-9] stands for digits 1 to 3, minus, and 6 to 9
|
|
# [][] stands for the closing and the opening bracket (']' must be first!)
|
|
# [^^] stands for all characters but the caret symbol
|
|
# [\/] stands for a backslash or a slash (the backslash isn't an
|
|
# escape character in a [] character group)
|
|
#
|
|
# Note that a minus can't be a range delimiter, as in [a--e],
|
|
# which would be interpreted as any of a, e, or minus.
|
|
#
|
|
# Example:
|
|
# string.match(name, "*[0-9].xml"); ... true if 'name' ends with digit followed by ".xml"
|
|
#
|
|
match = func(str, patt) {
|
|
var s = 0;
|
|
for (var p = 0; p < size(patt) and s < size(str); ) {
|
|
if (patt[p] == `\\`) {
|
|
if ((p += 1) >= size(patt))
|
|
return 0; # pattern ends with backslash
|
|
|
|
} elsif (patt[p] == `?`) {
|
|
s += 1;
|
|
p += 1;
|
|
continue;
|
|
|
|
} elsif (patt[p] == `*`) {
|
|
for (; p < size(patt); p += 1)
|
|
if (patt[p] != `*`)
|
|
break;
|
|
if (p >= size(patt))
|
|
return 1;
|
|
|
|
for (; s < size(str); s += 1)
|
|
if (caller(0)[1](substr(str, s), substr(patt, p)))
|
|
return 1;
|
|
continue;
|
|
|
|
} elsif (patt[p] == `[`) {
|
|
setsize(var x = [], 256);
|
|
var invert = 0;
|
|
if ((p += 1) < size(patt) and patt[p] == `^`) {
|
|
p += 1;
|
|
invert = 1;
|
|
}
|
|
for (var i = 0; p < size(patt); p += 1) {
|
|
if (patt[p] == `]` and i)
|
|
break;
|
|
x[patt[p]] = 1;
|
|
i += 1;
|
|
|
|
if (p + 2 < patt[p] and patt[p] != `-` and patt[p + 1] == `-`
|
|
and patt[p + 2] != `]` and patt[p + 2] != `-`) {
|
|
var from = patt[p];
|
|
var to = patt[p += 2];
|
|
for (var c = from; c <= to; c += 1)
|
|
x[c] = 1;
|
|
}
|
|
}
|
|
if (invert ? !!x[str[s]] : !x[str[s]])
|
|
return 0;
|
|
s += 1;
|
|
p += 1;
|
|
continue;
|
|
}
|
|
|
|
if (str[s] != patt[p])
|
|
return 0;
|
|
s += 1;
|
|
p += 1;
|
|
}
|
|
return s == size(str) and p == size(patt);
|
|
}
|
|
|
|
|
|
##
|
|
# Removes superfluous slashes, empty and "." elements,
|
|
# expands all ".." elements keeping relative paths,
|
|
# and turns all backslashes into slashes.
|
|
# The result will start with a slash if it started with a slash or backslash,
|
|
# it will end without slash.
|
|
#
|
|
normpath = func(path) {
|
|
path = replace(path, "\\", "/");
|
|
var prefix = size(path) and path[0] == `/` ? "/" : "";
|
|
|
|
var stack = [];
|
|
var relative = 1;
|
|
|
|
foreach (var e; split("/", path)) {
|
|
if (e == "." or e == "")
|
|
continue;
|
|
elsif (e == ".." and !relative)
|
|
pop(stack);
|
|
else {
|
|
append(stack, e);
|
|
relative = 0;
|
|
}
|
|
}
|
|
return size(stack) ? prefix ~ join("/", stack) : "/";
|
|
}
|
|
|
|
|
|
##
|
|
# Join all elements of a list inserting a separator between every two of them.
|
|
#
|
|
join = func(sep, list) {
|
|
if (!size(list))
|
|
return "";
|
|
var str = list[0];
|
|
foreach (var s; subvec(list, 1))
|
|
str ~= sep ~ s;
|
|
return str;
|
|
}
|
|
|
|
|
|
##
|
|
# Replace all occurrences of 'old' by 'new'.
|
|
#
|
|
replace = func(str, old, new) {
|
|
return join(new, split(old, str));
|
|
}
|
|
|
|
|
|
##
|
|
# Get a function out of a string template for fast insertion of template
|
|
# parameters. This allows to use the same templates as with most available tile
|
|
# mapping engines (eg. Leaflet, Polymaps). Return a callable function object on
|
|
# success, and nil if parsing the templated fails. See string._template_getargs
|
|
# for more on calling a compile object.
|
|
#
|
|
# Example (Build MapQuest tile url):
|
|
#
|
|
# var makeUrl = string.compileTemplate(
|
|
# "http://otile1.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.jpg"
|
|
# );
|
|
# print( makeUrl({x: 5, y: 4, z: 3}) );
|
|
#
|
|
# Output:
|
|
#
|
|
# http://otile1.mqcdn.com/tiles/1.0.0/map/3/5/4.jpg
|
|
#
|
|
var compileTemplate = func(template, type=nil) {
|
|
var code = 'func(__ENV=nil) { string._template_getargs();';
|
|
var len = size(template);
|
|
var start = 0;
|
|
var end = 0;
|
|
if (type == nil or type == "simple_names") {
|
|
# See http://james.padolsey.com/javascript/straight-up-interpolation/
|
|
while( (start = template.find('{', end)) >= 0 )
|
|
{
|
|
if( end > 0 )
|
|
code ~= '~';
|
|
code ~= '"' ~ substr(template, end, start - end) ~ '"';
|
|
if( (end = template.find('}', start)) < 0 )
|
|
{
|
|
debug.warn("string.compileTemplate: unclosed brace pair (" ~ template ~ ")");
|
|
return nil;
|
|
}
|
|
|
|
code ~= '~__ENV["' ~ substr(template, start + 1, end - start - 1) ~ '"]';
|
|
end += 1;
|
|
}
|
|
if( end < len )
|
|
code ~= '~"' ~ substr(template, end, len - end) ~ '"';
|
|
} elsif (type == "nasal_expression") {
|
|
var level = 0;
|
|
for (var i=0; i<len; i+=1) {
|
|
if (template[i] != `{`) continue;
|
|
start = i;
|
|
if ( end > 0 )
|
|
code ~= "~";
|
|
code ~= '"' ~ substr(template, end, start - end) ~ '"';
|
|
level = 1; var skip = 0;
|
|
for (var j=i+1; j<len and level > 0; j+=1)
|
|
if (template[j] == `{`) level += 1;
|
|
elsif (template[j] == `}`) level -= 1;
|
|
elsif (template[j] == `"`)
|
|
if (skip == `"`) skip = 0;
|
|
elsif (skip != `'`) skip = `"`;
|
|
elsif (template[j] == `'`)
|
|
if (skip == `'`) skip = 0;
|
|
elsif (skip != '"') skip = `'`;
|
|
elsif (skip)
|
|
if ((skip == `'` or skip == `"`) and template[j] == `\\`)
|
|
skip = `\\`;
|
|
elsif (skip == `\\` and template[j] == `\\`)
|
|
skip = skip;
|
|
else
|
|
skip = 0;
|
|
if (level)
|
|
die("string.compileTemplate: unclosed brace pair (" ~ template ~ ")");
|
|
end = j;
|
|
code ~= '~(' ~ substr(template, start + 1, end - start - 1)~")";
|
|
end += 1;
|
|
i = end;
|
|
}
|
|
if( end < len )
|
|
code ~= '~"' ~ substr(template, end, len - end) ~ '"';
|
|
}
|
|
code ~= "}";
|
|
var fn = compile(code)(); # get the inside function with the argument __ENV=nil
|
|
var (ns,fn1) = caller(1);
|
|
return bind(fn, ns, fn1);
|
|
}
|
|
|
|
##
|
|
# Private function used by string.naCompileTemplate. Expands any __ENV parameter
|
|
# into the locals of the caller. This allows both named arguments and manual hash
|
|
# arguments via __ENV.
|
|
#
|
|
# Examples using (format = func(__ENV) {string._template_getargs()}):
|
|
#
|
|
# Pass arguments as hash:
|
|
# format({a: 1, "b ":2});
|
|
# Or:
|
|
# format(__ENV:{a: 1, "b ":2});
|
|
# Pass arguments as named:
|
|
# format(a: 1, "b ":2);
|
|
# Pass arguments as both named and hash, using
|
|
# __ENV to specify the latter:
|
|
# format(a: 1, __ENV:{"b ": 2});
|
|
#
|
|
var _template_getargs = func() {
|
|
var ns = caller(1)[0];
|
|
if (contains(ns, "__ENV")) {
|
|
var __ENV = ns.__ENV;
|
|
if (__ENV != nil)
|
|
foreach (var k; keys(__ENV))
|
|
ns[k] = __ENV[k];
|
|
}
|
|
ns.__ENV = ns;
|
|
}
|
|
|
|
|
|
##
|
|
# Simple scanf function. Takes an input string, a pattern, and a
|
|
# vector. It returns 0 if the format didn't match, and appends
|
|
# all found elements to the given vector. Return values:
|
|
#
|
|
# -1 string matched format ending with % (i.e. more chars than format cared about)
|
|
# 0 string didn't match format
|
|
# 1 string matched, but would still match if the right chars were added
|
|
# 2 string matched, and would not if any character would be added
|
|
#
|
|
# var r = string.scanf("comm3freq123.456", "comm%ufreq%f", var result = []);
|
|
#
|
|
# The result vector will be set to [3, 123.456].
|
|
#
|
|
var Scan = {
|
|
new : func(s) {{ str: s, pos: 0, parents: [Scan] }},
|
|
getc : func {
|
|
if (me.pos >= size(me.str))
|
|
return nil;
|
|
var c = me.str[me.pos];
|
|
me.pos += 1;
|
|
return c;
|
|
},
|
|
ungetc : func { me.pos -= 1 },
|
|
rest : func { substr(me.str, me.pos) },
|
|
};
|
|
|
|
|
|
var scanf = func(test, format, result) {
|
|
if (find("%", format) < 0)
|
|
return cmp(test, format) ? 0 : 2;
|
|
|
|
var success = 0;
|
|
var str = Scan.new(test);
|
|
var format = Scan.new(format);
|
|
|
|
while (1) {
|
|
var f = format.getc();
|
|
if (f == nil) {
|
|
break;
|
|
|
|
} elsif (f == `%`) {
|
|
success = 1; # unsafe match
|
|
f = format.getc();
|
|
if (f == nil)
|
|
return -1; # format ended with %
|
|
if (f == `%`) {
|
|
if (str.getc() != `%`)
|
|
return 0;
|
|
success = 2;
|
|
continue;
|
|
}
|
|
|
|
if (isdigit(f)) {
|
|
var fnum = f - `0`;
|
|
while ((f = format.getc()) != nil and isdigit(f))
|
|
fnum = fnum * 10 + f - `0`;
|
|
} else {
|
|
var fnum = -2; # because we add one if !prefix
|
|
}
|
|
|
|
var scanstr = "";
|
|
var prefix = 0;
|
|
var sign = 1;
|
|
if (f == `d` or f == `f` or f == `u`) {
|
|
var c = str.getc();
|
|
if (c == nil) {
|
|
return 0;
|
|
} elsif (c == `+`) {
|
|
prefix = 1;
|
|
} elsif (c == `-`) {
|
|
if (f == `u`)
|
|
return 0;
|
|
(prefix, sign) = (1, -1);
|
|
} else {
|
|
str.ungetc();
|
|
}
|
|
if (!prefix)
|
|
fnum += 1;
|
|
|
|
while ((var c = str.getc()) != nil and (fnum -= 1)) {
|
|
if (f != `f` and c == `.`)
|
|
break;
|
|
elsif (num(scanstr ~ chr(c) ~ '0') != nil) # append 0 to digest e/E
|
|
scanstr ~= chr(c);
|
|
else
|
|
break;
|
|
}
|
|
if (c != nil)
|
|
str.ungetc();
|
|
if (num(scanstr) == nil)
|
|
return 0;
|
|
if (!size(scanstr) and prefix)
|
|
return 0;
|
|
append(result, sign * num(scanstr));
|
|
|
|
} elsif (f == `s`) {
|
|
fnum += 1;
|
|
while ((var c = str.getc()) != nil and c != ` ` and (fnum -= 1))
|
|
scanstr ~= chr(c);
|
|
|
|
if (c != nil)
|
|
str.ungetc();
|
|
if (!size(scanstr))
|
|
return 0;
|
|
|
|
append(result, scanstr);
|
|
|
|
} else {
|
|
die("scanf: bad format element %" ~ chr(f));
|
|
}
|
|
|
|
|
|
} elsif (isspace(f)) {
|
|
while ((var c = str.getc()) != nil and isspace(c))
|
|
nil;
|
|
if (c != nil)
|
|
str.ungetc();
|
|
|
|
} elsif (f != (var c = str.getc())) {
|
|
return 0;
|
|
|
|
} else {
|
|
success = 2; # safe match
|
|
}
|
|
}
|
|
return str.getc() == nil and format.getc() == nil ? success : 0;
|
|
}
|
|
|
|
|
|
##
|
|
# ANSI colors (see $ man console_codes)
|
|
#
|
|
var setcolors = func(enabled) {
|
|
color_enabled = (enabled and getprop("/sim/startup/stderr-to-terminal"));
|
|
}
|
|
var color = func(color, s, enabled=nil) {
|
|
if (enabled == nil) enabled = color_enabled;
|
|
return enabled ? "\x1b[" ~ color ~ "m" ~ s ~ "\x1b[m" : s;
|
|
}
|
|
|
|
|
|
##
|
|
# Add ANSI color codes to string, if terminal-ansi-colors are enabled and
|
|
# stderr prints to a terminal. Example:
|
|
#
|
|
# print(string.color("31;1", "this is red"));
|
|
#
|
|
var color_enabled = 0;
|
|
_setlistener("/sim/signals/nasal-dir-initialized", func {
|
|
setlistener("/sim/startup/terminal-ansi-colors", func(n) setcolors(n.getBoolValue()), 1, 0);
|
|
});
|
|
|
|
|