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.
986 lines
35 KiB
986 lines
35 KiB
# -*- coding: utf-8 -*- |
|
""" |
|
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). |
|
|
|
""" |
|
|
|
from __future__ import print_function, absolute_import, with_statement, unicode_literals, division |
|
|
|
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)
|
|
|