""" 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 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= 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; ib) {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= 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; ib) {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= 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= 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 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 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=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