You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

983 lines
35 KiB

"""
PScript standard functions.
Functions are declared as ... functions. Methods are written as methods
(using this), but declared as functions, and then "apply()-ed" to the
instance of interest. Declaring methods on Object is a bad idea (breaks
Bokeh, jquery).
"""
import re
# Functions not covered by this lib:
# isinstance, issubclass, print, len, max, min, callable, chr, ord
FUNCTIONS = {}
METHODS = {}
FUNCTION_PREFIX = '_pyfunc_'
METHOD_PREFIX = '_pymeth_'
def get_std_info(code):
""" Given the JS code for a std function or method, determine the
number of arguments, function_deps and method_deps.
"""
_, _, nargs = code.splitlines()[0].partition('nargs:')
nargs = [int(i.strip()) for i in nargs.strip().replace(',', ' ').split(' ') if i]
# Collect dependencies on other funcs/methods
sep = FUNCTION_PREFIX
function_deps = [part.split('(')[0].strip() for part in code.split(sep)[1:]]
sep = METHOD_PREFIX
method_deps = [part.split('.')[0].strip() for part in code.split(sep)[1:]]
# Reduce and sort
function_deps = sorted(set(function_deps))
method_deps = sorted(set(method_deps))
# Filter
function_deps = [dep for dep in function_deps if dep not in method_deps]
function_deps = set([dep for dep in function_deps if dep in FUNCTIONS])
method_deps = set([dep for dep in method_deps if dep in METHODS])
# Recurse
for dep in list(function_deps):
_update_deps(FUNCTIONS[dep], function_deps, method_deps)
for dep in list(method_deps):
_update_deps(METHODS[dep], function_deps, method_deps)
return nargs, sorted(function_deps), sorted(method_deps)
def _update_deps(code, function_deps, method_deps):
""" Given the code of a dependency, recursively resolve additional dependencies.
"""
# Collect deps
sep = FUNCTION_PREFIX
new_function_deps = [part.split('(')[0].strip() for part in code.split(sep)[1:]]
sep = METHOD_PREFIX
new_method_deps = [part.split('.')[0].strip() for part in code.split(sep)[1:]]
# Update
new_function_deps = set(new_function_deps).difference(function_deps)
new_method_deps = set(new_method_deps).difference(method_deps)
function_deps.update(new_function_deps)
method_deps.update(new_method_deps)
# Recurse
for dep in new_function_deps:
_update_deps(FUNCTIONS[dep], function_deps, method_deps)
for dep in new_method_deps:
_update_deps(METHODS[dep], function_deps, method_deps)
return function_deps, method_deps
def get_partial_std_lib(func_names, method_names, indent=0,
func_prefix=None, method_prefix=None):
""" Get the code for the PScript standard library consisting of
the given function and method names. The given indent specifies how
many sets of 4 spaces to prepend.
"""
func_prefix = 'var ' + FUNCTION_PREFIX if (func_prefix is None) else func_prefix
method_prefix = 'var ' + METHOD_PREFIX if (method_prefix is None) else method_prefix
lines = []
for name in sorted(func_names):
code = FUNCTIONS[name].strip()
if '\n' not in code:
code = code.rsplit('//', 1)[0].rstrip() # strip comment from one-liners
lines.append('%s%s = %s;' % (func_prefix, name, code))
for name in sorted(method_names):
code = METHODS[name].strip()
# lines.append('Object.prototype.%s%s = %s;' % (METHOD_PREFIX, name, code))
lines.append('%s%s = %s;' % (method_prefix, name, code))
code = '\n'.join(lines)
if indent:
lines = [' '*indent + line for line in code.splitlines()]
code = '\n'.join(lines)
return code
def get_full_std_lib(indent=0):
""" Get the code for the full PScript standard library.
The given indent specifies how many sets of 4 spaces to prepend.
If the full stdlib is made available in JavaScript, multiple
snippets of code can be transpiled without inlined stdlib parts by
using ``py2js(..., inline_stdlib=False)``.
"""
return get_partial_std_lib(FUNCTIONS.keys(), METHODS.keys(), indent)
# todo: now that we have modules, we can have shorter/no prefixes, right?
# -> though maybe we use them for string replacement somewhere?
def get_all_std_names():
""" Get list if function names and methods names in std lib.
"""
return ([FUNCTION_PREFIX + f for f in FUNCTIONS],
[METHOD_PREFIX + f for f in METHODS])
## ----- Functions
## Special functions: not really in builtins, but important enough to support
FUNCTIONS['perf_counter'] = """function() { // nargs: 0
if (typeof(process) === "undefined"){return performance.now()*1e-3;}
else {var t = process.hrtime(); return t[0] + t[1]*1e-9;}
}""" # Work in nodejs and browser
FUNCTIONS['time'] = """function () {return Date.now() / 1000;} // nargs: 0"""
## Hardcore functions
FUNCTIONS['op_instantiate'] = """function (ob, args) { // nargs: 2
if ((typeof ob === "undefined") ||
(typeof window !== "undefined" && window === ob) ||
(typeof global !== "undefined" && global === ob))
{throw "Class constructor is called as a function.";}
for (var name in ob) {
if (Object[name] === undefined &&
typeof ob[name] === 'function' && !ob[name].nobind) {
ob[name] = ob[name].bind(ob);
ob[name].__name__ = name;
}
}
if (ob.__init__) {
ob.__init__.apply(ob, args);
}
}"""
FUNCTIONS['create_dict'] = """function () {
var d = {};
for (var i=0; i<arguments.length; i+=2) { d[arguments[i]] = arguments[i+1]; }
return d;
}"""
FUNCTIONS['merge_dicts'] = """function () {
var res = {};
for (var i=0; i<arguments.length; i++) {
var d = arguments[i];
var key, keys = Object.keys(d);
for (var j=0; j<keys.length; j++) { key = keys[j]; res[key] = d[key]; }
}
return res;
}"""
# args is a list of (name, default) tuples, and is overwritten with names from kwargs
FUNCTIONS['op_parse_kwargs'] = """
function (arg_names, arg_values, kwargs, strict) { // nargs: 3
for (var i=0; i<arg_values.length; i++) {
var name = arg_names[i];
if (kwargs[name] !== undefined) {
arg_values[i] = kwargs[name];
delete kwargs[name];
}
}
if (strict && Object.keys(kwargs).length > 0) {
throw FUNCTION_PREFIXop_error('TypeError',
'Function ' + strict + ' does not accept **kwargs.');
}
return kwargs;
}""".lstrip()
FUNCTIONS['op_error'] = """function (etype, msg) { // nargs: 2
var e = new Error(etype + ': ' + msg);
e.name = etype
return e;
}"""
FUNCTIONS['hasattr'] = """function (ob, name) { // nargs: 2
return (ob !== undefined) && (ob !== null) && (ob[name] !== undefined);
}"""
FUNCTIONS['getattr'] = """function (ob, name, deflt) { // nargs: 2 3
var has_attr = ob !== undefined && ob !== null && ob[name] !== undefined;
if (has_attr) {return ob[name];}
else if (arguments.length == 3) {return deflt;}
else {var e = Error(name); e.name='AttributeError'; throw e;}
}"""
FUNCTIONS['setattr'] = """function (ob, name, value) { // nargs: 3
ob[name] = value;
}"""
FUNCTIONS['delattr'] = """function (ob, name) { // nargs: 2
delete ob[name];
}"""
FUNCTIONS['dict'] = """function (x) {
var t, i, keys, r={};
if (Array.isArray(x)) {
for (i=0; i<x.length; i++) {
t=x[i]; r[t[0]] = t[1];
}
} else {
keys = Object.keys(x);
for (i=0; i<keys.length; i++) {
t=keys[i]; r[t] = x[t];
}
}
return r;
}"""
FUNCTIONS['list'] = """function (x) {
var r=[];
if (typeof x==="object" && !Array.isArray(x)) {x = Object.keys(x)}
for (var i=0; i<x.length; i++) {
r.push(x[i]);
}
return r;
}"""
FUNCTIONS['range'] = """function (start, end, step) {
var i, res = [];
var val = start;
var n = (end - start) / step;
for (i=0; i<n; i++) {
res.push(val);
val += step;
}
return res;
}"""
FUNCTIONS['format'] = """function (v, fmt) { // nargs: 2
fmt = fmt.toLowerCase();
var s = String(v);
if (fmt.indexOf('!r') >= 0) {
try { s = JSON.stringify(v); } catch (e) { s = undefined; }
if (typeof s === 'undefined') { s = v._IS_COMPONENT ? v.id : String(v); }
}
var fmt_type = '';
if (fmt.slice(-1) == 'i' || fmt.slice(-1) == 'f' ||
fmt.slice(-1) == 'e' || fmt.slice(-1) == 'g') {
fmt_type = fmt[fmt.length-1]; fmt = fmt.slice(0, fmt.length-1);
}
var i0 = fmt.indexOf(':');
var i1 = fmt.indexOf('.');
var spec1 = '', spec2 = ''; // before and after dot
if (i0 >= 0) {
if (i1 > i0) { spec1 = fmt.slice(i0+1, i1); spec2 = fmt.slice(i1+1); }
else { spec1 = fmt.slice(i0+1); }
}
// Format numbers
if (fmt_type == '') {
} else if (fmt_type == 'i') { // integer formatting, for %i
s = parseInt(v).toFixed(0);
} else if (fmt_type == 'f') { // float formatting
v = parseFloat(v);
var decimals = spec2 ? Number(spec2) : 6;
s = v.toFixed(decimals);
} else if (fmt_type == 'e') { // exp formatting
v = parseFloat(v);
var precision = (spec2 ? Number(spec2) : 6) || 1;
s = v.toExponential(precision);
} else if (fmt_type == 'g') { // "general" formatting
v = parseFloat(v);
var precision = (spec2 ? Number(spec2) : 6) || 1;
// Exp or decimal?
s = v.toExponential(precision-1);
var s1 = s.slice(0, s.indexOf('e')), s2 = s.slice(s.indexOf('e'));
if (s2.length == 3) { s2 = 'e' + s2[1] + '0' + s2[2]; }
var exp = Number(s2.slice(1));
if (exp >= -4 && exp < precision) { s1=v.toPrecision(precision); s2=''; }
// Skip trailing zeros and dot
var j = s1.length-1;
while (j>0 && s1[j] == '0') { j-=1; }
s1 = s1.slice(0, j+1);
if (s1.slice(-1) == '.') { s1 = s1.slice(0, s1.length-1); }
s = s1 + s2;
}
// prefix/padding
var prefix = '';
if (spec1) {
if (spec1[0] == '+' && v > 0) { prefix = '+'; spec1 = spec1.slice(1); }
else if (spec1[0] == ' ' && v > 0) { prefix = ' '; spec1 = spec1.slice(1); }
}
if (spec1 && spec1[0] == '0') {
var padding = Number(spec1.slice(1)) - (s.length + prefix.length);
s = '0'.repeat(Math.max(0, padding)) + s;
}
return prefix + s;
}"""
## Normal functions
FUNCTIONS['pow'] = 'Math.pow // nargs: 2'
FUNCTIONS['sum'] = """function (x) { // nargs: 1
return x.reduce(function(a, b) {return a + b;});
}"""
FUNCTIONS['round'] = 'Math.round // nargs: 1'
FUNCTIONS['int'] = """function (x, base) { // nargs: 1 2
if(base !== undefined) return parseInt(x, base);
return x<0 ? Math.ceil(x): Math.floor(x);
}"""
FUNCTIONS['float'] = 'Number // nargs: 1'
FUNCTIONS['str'] = 'String // nargs: 0 1'
# Note use of "_IS_COMPONENT" to check for flexx.app component classes.
FUNCTIONS['repr'] = """function (x) { // nargs: 1
var res; try { res = JSON.stringify(x); } catch (e) { res = undefined; }
if (typeof res === 'undefined') { res = x._IS_COMPONENT ? x.id : String(x); }
return res;
}"""
FUNCTIONS['bool'] = """function (x) { // nargs: 1
return Boolean(FUNCTION_PREFIXtruthy(x));
}"""
FUNCTIONS['abs'] = 'Math.abs // nargs: 1'
FUNCTIONS['divmod'] = """function (x, y) { // nargs: 2
var m = x % y; return [(x-m)/y, m];
}"""
FUNCTIONS['all'] = """function (x) { // nargs: 1
for (var i=0; i<x.length; i++) {
if (!FUNCTION_PREFIXtruthy(x[i])){return false;}
} return true;
}"""
FUNCTIONS['any'] = """function (x) { // nargs: 1
for (var i=0; i<x.length; i++) {
if (FUNCTION_PREFIXtruthy(x[i])){return true;}
} return false;
}"""
FUNCTIONS['enumerate'] = """function (iter) { // nargs: 1
var i, res=[];
if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);}
for (i=0; i<iter.length; i++) {res.push([i, iter[i]]);}
return res;
}"""
FUNCTIONS['zip'] = """function () { // nargs: 2 3 4 5 6 7 8 9
var i, j, tup, arg, args = [], res = [], len = 1e20;
for (i=0; i<arguments.length; i++) {
arg = arguments[i];
if ((typeof arg==="object") && (!Array.isArray(arg))) {arg = Object.keys(arg);}
args.push(arg);
len = Math.min(len, arg.length);
}
for (j=0; j<len; j++) {
tup = []
for (i=0; i<args.length; i++) {tup.push(args[i][j]);}
res.push(tup);
}
return res;
}"""
FUNCTIONS['reversed'] = """function (iter) { // nargs: 1
if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);}
return iter.slice().reverse();
}"""
FUNCTIONS['sorted'] = """function (iter, key, reverse) { // nargs: 1 2 3
if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);}
var comp = function (a, b) {a = key(a); b = key(b);
if (a<b) {return -1;} if (a>b) {return 1;} return 0;};
comp = Boolean(key) ? comp : undefined;
iter = iter.slice().sort(comp);
if (reverse) iter.reverse();
return iter;
}"""
FUNCTIONS['filter'] = """function (func, iter) { // nargs: 2
if (typeof func === "undefined" || func === null) {func = function(x) {return x;}}
if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);}
return iter.filter(func);
}"""
FUNCTIONS['map'] = """function (func, iter) { // nargs: 2
if (typeof func === "undefined" || func === null) {func = function(x) {return x;}}
if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);}
return iter.map(func);
}"""
## Other / Helper functions
FUNCTIONS['truthy'] = """function (v) {
if (v === null || typeof v !== "object") {return v;}
else if (v.length !== undefined) {return v.length ? v : false;}
else if (v.byteLength !== undefined) {return v.byteLength ? v : false;}
else if (v.constructor !== Object) {return true;}
else {return Object.getOwnPropertyNames(v).length ? v : false;}
}"""
FUNCTIONS['op_equals'] = """function op_equals (a, b) { // nargs: 2
var a_type = typeof a;
// If a (or b actually) is of type string, number or boolean, we don't need
// to do all the other type checking below.
if (a_type === "string" || a_type === "boolean" || a_type === "number") {
return a == b;
}
if (a == null || b == null) {
} else if (Array.isArray(a) && Array.isArray(b)) {
var i = 0, iseq = a.length == b.length;
while (iseq && i < a.length) {iseq = op_equals(a[i], b[i]); i+=1;}
return iseq;
} else if (a.constructor === Object && b.constructor === Object) {
var akeys = Object.keys(a), bkeys = Object.keys(b);
akeys.sort(); bkeys.sort();
var i=0, k, iseq = op_equals(akeys, bkeys);
while (iseq && i < akeys.length)
{k=akeys[i]; iseq = op_equals(a[k], b[k]); i+=1;}
return iseq;
} return a == b;
}"""
FUNCTIONS['op_contains'] = """function op_contains (a, b) { // nargs: 2
if (b == null) {
} else if (Array.isArray(b)) {
for (var i=0; i<b.length; i++) {if (FUNCTION_PREFIXop_equals(a, b[i]))
return true;}
return false;
} else if (b.constructor === Object) {
for (var k in b) {if (a == k) return true;}
return false;
} else if (b.constructor == String) {
return b.indexOf(a) >= 0;
} var e = Error('Not a container: ' + b); e.name='TypeError'; throw e;
}"""
FUNCTIONS['op_add'] = """function (a, b) { // nargs: 2
if (Array.isArray(a) && Array.isArray(b)) {
return a.concat(b);
} return a + b;
}"""
FUNCTIONS['op_mult'] = """function (a, b) { // nargs: 2
if ((typeof a === 'number') + (typeof b === 'number') === 1) {
if (a.constructor === String) return METHOD_PREFIXrepeat(a, b);
if (b.constructor === String) return METHOD_PREFIXrepeat(b, a);
if (Array.isArray(b)) {var t=a; a=b; b=t;}
if (Array.isArray(a)) {
var res = []; for (var i=0; i<b; i++) res = res.concat(a);
return res;
}
} return a * b;
}"""
## ----- Methods
## List only
METHODS['append'] = """function (x) { // nargs: 1
if (!Array.isArray(this)) return this.KEY.apply(this, arguments);
this.push(x);
}"""
METHODS['extend'] = """function (x) { // nargs: 1
if (!Array.isArray(this)) return this.KEY.apply(this, arguments);
this.push.apply(this, x);
}"""
METHODS['insert'] = """function (i, x) { // nargs: 2
if (!Array.isArray(this)) return this.KEY.apply(this, arguments);
i = (i < 0) ? this.length + i : i;
this.splice(i, 0, x);
}"""
METHODS['remove'] = """function (x) { // nargs: 1
if (!Array.isArray(this)) return this.KEY.apply(this, arguments);
for (var i=0; i<this.length; i++) {
if (FUNCTION_PREFIXop_equals(this[i], x)) {this.splice(i, 1); return;}
}
var e = Error(x); e.name='ValueError'; throw e;
}"""
METHODS['reverse'] = """function () { // nargs: 0
this.reverse();
}"""
METHODS['sort'] = """function (key, reverse) { // nargs: 0 1 2
if (!Array.isArray(this)) return this.KEY.apply(this, arguments);
var comp = function (a, b) {a = key(a); b = key(b);
if (a<b) {return -1;} if (a>b) {return 1;} return 0;};
comp = Boolean(key) ? comp : undefined;
this.sort(comp);
if (reverse) this.reverse();
}"""
## List and dict
METHODS['clear'] = """function () { // nargs: 0
if (Array.isArray(this)) {
this.splice(0, this.length);
} else if (this.constructor === Object) {
var keys = Object.keys(this);
for (var i=0; i<keys.length; i++) delete this[keys[i]];
} else return this.KEY.apply(this, arguments);
}"""
METHODS['copy'] = """function () { // nargs: 0
if (Array.isArray(this)) {
return this.slice(0);
} else if (this.constructor === Object) {
var key, keys = Object.keys(this), res = {};
for (var i=0; i<keys.length; i++) {key = keys[i]; res[key] = this[key];}
return res;
} else return this.KEY.apply(this, arguments);
}"""
METHODS['pop'] = """function (i, d) { // nargs: 1 2
if (Array.isArray(this)) {
i = (i === undefined) ? -1 : i;
i = (i < 0) ? (this.length + i) : i;
var popped = this.splice(i, 1);
if (popped.length) return popped[0];
var e = Error(i); e.name='IndexError'; throw e;
} else if (this.constructor === Object) {
var res = this[i]
if (res !== undefined) {delete this[i]; return res;}
else if (d !== undefined) return d;
var e = Error(i); e.name='KeyError'; throw e;
} else return this.KEY.apply(this, arguments);
}"""
## List and str
# start and stop nor supported for list on Python, but for simplicity, we do
METHODS['count'] = """function (x, start, stop) { // nargs: 1 2 3
start = (start === undefined) ? 0 : start;
stop = (stop === undefined) ? this.length : stop;
start = Math.max(0, ((start < 0) ? this.length + start : start));
stop = Math.min(this.length, ((stop < 0) ? this.length + stop : stop));
if (Array.isArray(this)) {
var count = 0;
for (var i=0; i<this.length; i++) {
if (FUNCTION_PREFIXop_equals(this[i], x)) {count+=1;}
} return count;
} else if (this.constructor == String) {
var count = 0, i = start;
while (i >= 0 && i < stop) {
i = this.indexOf(x, i);
if (i < 0) break;
count += 1;
i += Math.max(1, x.length);
} return count;
} else return this.KEY.apply(this, arguments);
}"""
METHODS['index'] = """function (x, start, stop) { // nargs: 1 2 3
start = (start === undefined) ? 0 : start;
stop = (stop === undefined) ? this.length : stop;
start = Math.max(0, ((start < 0) ? this.length + start : start));
stop = Math.min(this.length, ((stop < 0) ? this.length + stop : stop));
if (Array.isArray(this)) {
for (var i=start; i<stop; i++) {
if (FUNCTION_PREFIXop_equals(this[i], x)) {return i;} // indexOf cant
}
} else if (this.constructor === String) {
var i = this.slice(start, stop).indexOf(x);
if (i >= 0) return i + start;
} else return this.KEY.apply(this, arguments);
var e = Error(x); e.name='ValueError'; throw e;
}"""
## Dict only
# note: fromkeys is a classmethod, and we dont support it.
METHODS['get'] = """function (key, d) { // nargs: 1 2
if (this.constructor !== Object) return this.KEY.apply(this, arguments);
if (this[key] !== undefined) {return this[key];}
else if (d !== undefined) {return d;}
else {return null;}
}"""
METHODS['items'] = """function () { // nargs: 0
if (this.constructor !== Object) return this.KEY.apply(this, arguments);
var key, keys = Object.keys(this), res = []
for (var i=0; i<keys.length; i++) {key = keys[i]; res.push([key, this[key]]);}
return res;
}"""
METHODS['keys'] = """function () { // nargs: 0
if (typeof this['KEY'] === 'function') return this.KEY.apply(this, arguments);
return Object.keys(this);
}"""
METHODS['popitem'] = """function () { // nargs: 0
if (this.constructor !== Object) return this.KEY.apply(this, arguments);
var keys, key, val;
keys = Object.keys(this);
if (keys.length == 0) {var e = Error(); e.name='KeyError'; throw e;}
key = keys[0]; val = this[key]; delete this[key];
return [key, val];
}"""
METHODS['setdefault'] = """function (key, d) { // nargs: 1 2
if (this.constructor !== Object) return this.KEY.apply(this, arguments);
if (this[key] !== undefined) {return this[key];}
else if (d !== undefined) { this[key] = d; return d;}
else {return null;}
}"""
METHODS['update'] = """function (other) { // nargs: 1
if (this.constructor !== Object) return this.KEY.apply(this, arguments);
var key, keys = Object.keys(other);
for (var i=0; i<keys.length; i++) {key = keys[i]; this[key] = other[key];}
return null;
}"""
METHODS['values'] = """function () { // nargs: 0
if (this.constructor !== Object) return this.KEY.apply(this, arguments);
var key, keys = Object.keys(this), res = [];
for (var i=0; i<keys.length; i++) {key = keys[i]; res.push(this[key]);}
return res;
}"""
## String only
# ignores: encode, decode, format_map, isprintable, maketrans
# Not a Python method, but a method that we need, and is only ECMA 6
# http://stackoverflow.com/a/5450113/2271927
METHODS['repeat'] = """function(count) { // nargs: 0
if (this.repeat) return this.repeat(count);
if (count < 1) return '';
var result = '', pattern = this.valueOf();
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
return result + pattern;
}"""
METHODS['capitalize'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return this.slice(0, 1).toUpperCase() + this.slice(1).toLowerCase();
}"""
METHODS['casefold'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return this.toLowerCase();
}"""
METHODS['center'] = """function (w, fill) { // nargs: 1 2
if (this.constructor !== String) return this.KEY.apply(this, arguments);
fill = (fill === undefined) ? ' ' : fill;
var tofill = Math.max(0, w - this.length);
var left = Math.ceil(tofill / 2);
var right = tofill - left;
return METHOD_PREFIXrepeat(fill, left) + this + METHOD_PREFIXrepeat(fill, right);
}"""
METHODS['endswith'] = """function (x) { // nargs: 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var last_index = this.lastIndexOf(x);
return last_index == this.length - x.length && last_index >= 0;
}"""
METHODS['expandtabs'] = """function (tabsize) { // nargs: 0 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
tabsize = (tabsize === undefined) ? 8 : tabsize;
return this.replace(/\\t/g, METHOD_PREFIXrepeat(' ', tabsize));
}"""
METHODS['find'] = """function (x, start, stop) { // nargs: 1 2 3
if (this.constructor !== String) return this.KEY.apply(this, arguments);
start = (start === undefined) ? 0 : start;
stop = (stop === undefined) ? this.length : stop;
start = Math.max(0, ((start < 0) ? this.length + start : start));
stop = Math.min(this.length, ((stop < 0) ? this.length + stop : stop));
var i = this.slice(start, stop).indexOf(x);
if (i >= 0) return i + start;
return -1;
}"""
METHODS['format'] = """function () {
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var parts = [], i = 0, i1, i2;
var itemnr = -1;
while (i < this.length) {
// find opening
i1 = this.indexOf('{', i);
if (i1 < 0 || i1 == this.length-1) { break; }
if (this[i1+1] == '{') {parts.push(this.slice(i, i1+1)); i = i1 + 2; continue;}
// find closing
i2 = this.indexOf('}', i1);
if (i2 < 0) { break; }
// parse
itemnr += 1;
var fmt = this.slice(i1+1, i2);
var index = fmt.split(':')[0].split('!')[0];
index = index? Number(index) : itemnr
var s = FUNCTION_PREFIXformat(arguments[index], fmt);
parts.push(this.slice(i, i1), s);
i = i2 + 1;
}
parts.push(this.slice(i));
return parts.join('');
}"""
METHODS['isalnum'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return Boolean(/^[A-Za-z0-9]+$/.test(this));
}"""
METHODS['isalpha'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return Boolean(/^[A-Za-z]+$/.test(this));
}"""
METHODS['isidentifier'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return Boolean(/^[A-Za-z_][A-Za-z0-9_]*$/.test(this));
}"""
METHODS['islower'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var low = this.toLowerCase(), high = this.toUpperCase();
return low != high && low == this;
}"""
METHODS['isdecimal'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return Boolean(/^[0-9]+$/.test(this));
}"""
# The thing about isdecimal, isdigit and isnumeric.
# https://stackoverflow.com/a/36800319/2271927
#
# * isdecimal() (Only Decimal Numbers)
# * str.isdigit() (Decimals, Subscripts, Superscripts)
# * isnumeric() (Digits, Vulgar Fractions, Subscripts, Superscripts,
# Roman Numerals, Currency Numerators)
#
# In other words, isdecimal is the most strict. We used to have
# isnumeric with isdecimal's implementation, so we provide isnumeric
# and isdigit as aliases for now.
METHODS['isnumeric'] = METHODS['isdigit'] = METHODS['isdecimal']
METHODS['isspace'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return Boolean(/^\\s+$/.test(this));
}"""
METHODS['istitle'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var low = this.toLowerCase(), title = METHOD_PREFIXtitle(this);
return low != title && title == this;
}"""
METHODS['isupper'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var low = this.toLowerCase(), high = this.toUpperCase();
return low != high && high == this;
}"""
METHODS['join'] = """function (x) { // nargs: 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return x.join(this); // call join on the list instead of the string.
}"""
METHODS['ljust'] = """function (w, fill) { // nargs: 1 2
if (this.constructor !== String) return this.KEY.apply(this, arguments);
fill = (fill === undefined) ? ' ' : fill;
var tofill = Math.max(0, w - this.length);
return this + METHOD_PREFIXrepeat(fill, tofill);
}"""
METHODS['lower'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return this.toLowerCase();
}"""
METHODS['lstrip'] = """function (chars) { // nargs: 0 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
chars = (chars === undefined) ? ' \\t\\r\\n' : chars;
for (var i=0; i<this.length; i++) {
if (chars.indexOf(this[i]) < 0) return this.slice(i);
} return '';
}"""
METHODS['partition'] = """function (sep) { // nargs: 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
if (sep === '') {var e = Error('empty sep'); e.name='ValueError'; throw e;}
var i1 = this.indexOf(sep);
if (i1 < 0) return [this.slice(0), '', '']
var i2 = i1 + sep.length;
return [this.slice(0, i1), this.slice(i1, i2), this.slice(i2)];
}"""
METHODS['replace'] = """function (s1, s2, count) { // nargs: 2 3
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var i = 0, i2, parts = [];
count = (count === undefined) ? 1e20 : count;
while (count > 0) {
i2 = this.indexOf(s1, i);
if (i2 >= 0) {
parts.push(this.slice(i, i2));
parts.push(s2);
i = i2 + s1.length;
count -= 1;
} else break;
}
parts.push(this.slice(i));
return parts.join('');
}"""
METHODS['rfind'] = """function (x, start, stop) { // nargs: 1 2 3
if (this.constructor !== String) return this.KEY.apply(this, arguments);
start = (start === undefined) ? 0 : start;
stop = (stop === undefined) ? this.length : stop;
start = Math.max(0, ((start < 0) ? this.length + start : start));
stop = Math.min(this.length, ((stop < 0) ? this.length + stop : stop));
var i = this.slice(start, stop).lastIndexOf(x);
if (i >= 0) return i + start;
return -1;
}"""
METHODS['rindex'] = """function (x, start, stop) { // nargs: 1 2 3
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var i = METHOD_PREFIXrfind(this, x, start, stop);
if (i >= 0) return i;
var e = Error(x); e.name='ValueError'; throw e;
}"""
METHODS['rjust'] = """function (w, fill) { // nargs: 1 2
if (this.constructor !== String) return this.KEY.apply(this, arguments);
fill = (fill === undefined) ? ' ' : fill;
var tofill = Math.max(0, w - this.length);
return METHOD_PREFIXrepeat(fill, tofill) + this;
}"""
METHODS['rpartition'] = """function (sep) { // nargs: 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
if (sep === '') {var e = Error('empty sep'); e.name='ValueError'; throw e;}
var i1 = this.lastIndexOf(sep);
if (i1 < 0) return ['', '', this.slice(0)]
var i2 = i1 + sep.length;
return [this.slice(0, i1), this.slice(i1, i2), this.slice(i2)];
}"""
METHODS['rsplit'] = """function (sep, count) { // nargs: 1 2
if (this.constructor !== String) return this.KEY.apply(this, arguments);
sep = (sep === undefined) ? /\\s/ : sep;
count = Math.max(0, (count === undefined) ? 1e20 : count);
var parts = this.split(sep);
var limit = Math.max(0, parts.length-count);
var res = parts.slice(limit);
if (count < parts.length) res.splice(0, 0, parts.slice(0, limit).join(sep));
return res;
}"""
METHODS['rstrip'] = """function (chars) { // nargs: 0 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
chars = (chars === undefined) ? ' \\t\\r\\n' : chars;
for (var i=this.length-1; i>=0; i--) {
if (chars.indexOf(this[i]) < 0) return this.slice(0, i+1);
} return '';
}"""
METHODS['split'] = """function (sep, count) { // nargs: 0, 1 2
if (this.constructor !== String) return this.KEY.apply(this, arguments);
if (sep === '') {var e = Error('empty sep'); e.name='ValueError'; throw e;}
sep = (sep === undefined) ? /\\s/ : sep;
if (count === undefined) { return this.split(sep); }
var res = [], i = 0, index1 = 0, index2 = 0;
while (i < count && index1 < this.length) {
index2 = this.indexOf(sep, index1);
if (index2 < 0) { break; }
res.push(this.slice(index1, index2));
index1 = index2 + sep.length || 1;
i += 1;
}
res.push(this.slice(index1));
return res;
}"""
METHODS['splitlines'] = """function (keepends) { // nargs: 0 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
keepends = keepends ? 1 : 0
var finder = /\\r\\n|\\r|\\n/g;
var i = 0, i2, isrn, parts = [];
while (finder.exec(this) !== null) {
i2 = finder.lastIndex -1;
isrn = i2 > 0 && this[i2-1] == '\\r' && this[i2] == '\\n';
if (keepends) parts.push(this.slice(i, finder.lastIndex));
else parts.push(this.slice(i, i2 - isrn));
i = finder.lastIndex;
}
if (i < this.length) parts.push(this.slice(i));
else if (!parts.length) parts.push('');
return parts;
}"""
METHODS['startswith'] = """function (x) { // nargs: 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return this.indexOf(x) == 0;
}"""
METHODS['strip'] = """function (chars) { // nargs: 0 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
chars = (chars === undefined) ? ' \\t\\r\\n' : chars;
var i, s1 = this, s2 = '', s3 = '';
for (i=0; i<s1.length; i++) {
if (chars.indexOf(s1[i]) < 0) {s2 = s1.slice(i); break;}
} for (i=s2.length-1; i>=0; i--) {
if (chars.indexOf(s2[i]) < 0) {s3 = s2.slice(0, i+1); break;}
} return s3;
}"""
METHODS['swapcase'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var c, res = [];
for (var i=0; i<this.length; i++) {
c = this[i];
if (c.toUpperCase() == c) res.push(c.toLowerCase());
else res.push(c.toUpperCase());
} return res.join('');
}"""
METHODS['title'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var i0, res = [], tester = /^[^A-Za-z]?[A-Za-z]$/;
for (var i=0; i<this.length; i++) {
i0 = Math.max(0, i-1);
if (tester.test(this.slice(i0, i+1))) res.push(this[i].toUpperCase());
else res.push(this[i].toLowerCase());
} return res.join('');
}"""
METHODS['translate'] = """function (table) { // nargs: 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
var c, res = [];
for (var i=0; i<this.length; i++) {
c = table[this[i]];
if (c === undefined) res.push(this[i]);
else if (c !== null) res.push(c);
} return res.join('');
}"""
METHODS['upper'] = """function () { // nargs: 0
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return this.toUpperCase();
}"""
METHODS['zfill'] = """function (width) { // nargs: 1
if (this.constructor !== String) return this.KEY.apply(this, arguments);
return METHOD_PREFIXrjust(this, width, '0');
}"""
for key in METHODS:
METHODS[key] = re.subn(r'METHOD_PREFIX(.+?)\(',
r'METHOD_PREFIX\1.call(', METHODS[key])[0]
METHODS[key] = METHODS[key].replace(
'KEY', key).replace(
'FUNCTION_PREFIX', FUNCTION_PREFIX).replace(
'METHOD_PREFIX', METHOD_PREFIX).replace(
', )', ')')
for key in FUNCTIONS:
FUNCTIONS[key] = re.subn(r'METHOD_PREFIX(.+?)\(',
r'METHOD_PREFIX\1.call(', FUNCTIONS[key])[0]
FUNCTIONS[key] = FUNCTIONS[key].replace(
'KEY', key).replace(
'FUNCTION_PREFIX', FUNCTION_PREFIX).replace(
'METHOD_PREFIX', METHOD_PREFIX)