PScript user guide

This guide demonstrates the features that PScript supports and how it maps to JavaScript. In most cases you can just read what is not supported and the caveats and start coding.

The basics

Most types just work, common Python names are converted to their JavaScript equivalents.

# Simple operations
3 + 4 -1
3 * 7 / 9
5**2
pow(5, 2)
7 // 2
JS
var _pyfunc_pow = Math.pow;
(3 + 4) - 1;
(3 * 7) / 9;
Math.pow(5, 2);
_pyfunc_pow(5, 2);
Math.floor(7/2);
# Basic types
[True, False, None]
JS
[true, false, null];
# Lists and dicts
foo = [1, 2, 3]
bar = {'a': 1, 'b': 2}
JS
var bar, foo;
foo = [1, 2, 3];
bar = ({a: 1, b: 2});

Slicing and subscriping

# Slicing lists
foo = [1, 2, 3, 4, 5]
foo[2:]
foo[2:-2]
JS
var foo;
foo = [1, 2, 3, 4, 5];
foo.slice(2);
foo.slice(2,-2);
# Slicing strings
bar = 'abcdefghij'
bar[2:]
bar[2:-2]
JS
var bar;
bar = "abcdefghij";
bar.slice(2);
bar.slice(2,-2);
# Subscripting
foo = {'bar': 3}
foo['bar']
foo.bar  # Works in JS, but not in Python
JS
var foo;
foo = ({bar: 3});
foo["bar"];
foo.bar;

String formatting

String formatting is supported in various forms.

# Old school
"value: %g" % val
"%s: %0.2f" % (name, val)
JS
var _pyfunc_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;
};
var _pymeth_format = function () {
    if (this.constructor !== String) return this.format.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 = _pyfunc_format(arguments[index], fmt);
        parts.push(this.slice(i, i1), s);
        i = i2 + 1;
    }
    parts.push(this.slice(i));
    return parts.join('');
};
_pymeth_format.call("value: {:g}", val);
_pymeth_format.call("{}: {:0.2f}", name, val);
# Modern
"value: {:g}".format(val)
"{}: {:3.2f}".format(name, val)
JS
var _pyfunc_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;
};
var _pymeth_format = function () {
    if (this.constructor !== String) return this.format.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 = _pyfunc_format(arguments[index], fmt);
        parts.push(this.slice(i, i1), s);
        i = i2 + 1;
    }
    parts.push(this.slice(i));
    return parts.join('');
};
_pymeth_format.call("value: {:g}", val);
_pymeth_format.call("{}: {:3.2f}", name, val);
# F-strings (python 3.6+)
#f"value: {val:g}"
#f"{name}: {val:3.2f}"
JS

# This also works
t = "value: {:g}"
t.format(val)
JS
var _pyfunc_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;
};
var _pymeth_format = function () {
    if (this.constructor !== String) return this.format.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 = _pyfunc_format(arguments[index], fmt);
        parts.push(this.slice(i, i1), s);
        i = i2 + 1;
    }
    parts.push(this.slice(i));
    return parts.join('');
};
var t;
t = "value: {:g}";
_pymeth_format.call(t, val);
# But this does not (because PScript cannot know whether t is str or float)
t = "value: %g"
t % val
JS
var t;
t = "value: %g";
t % val;

Kinds of formatting that is supported:

  • Float, exponential en “general” number formatting.
  • Specifying precision for numbers.
  • Padding of number with “+” or ” “.
  • Repr-formatting.

At the moment, PScript does not support advanced features such as string padding.

Assignments

Declaration of variables is handled automatically. Also support for tuple packing and unpacking (a.k.a. destructuring assignment).

# Declare foo
foo = 3
JS
var foo;
foo = 3;
# But not here
bar.foo = 3
JS
bar.foo = 3;
# Pack items in an array
a = 1, 2, 3
JS
var a;
a = [1, 2, 3];
# And unpack them
a1, a2, a3 = a
JS
var a1, a2, a3, stub1_;
stub1_ = a;
a1 = stub1_[0];a2 = stub1_[1];a3 = stub1_[2];
# Deleting variables
del bar.foo
JS
delete bar.foo;
# Functions starting with a capital letter
# are assumed constructors
foo = Foo()
JS
var foo;
foo = new Foo();

Comparisons

# Identity
foo is bar
JS
foo === bar;
# Equality
foo == bar
JS
var _pyfunc_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;
};
_pyfunc_op_equals(foo, bar);
# But comparisons are deep (unlike JS)
(2, 3, 4) == (2, 3, 4)
(2, 3) in [(1,2), (2,3), (3,4)]
JS
var _pyfunc_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 (_pyfunc_op_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;
};
var _pyfunc_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;
};
_pyfunc_op_equals([2, 3, 4], [2, 3, 4]);
_pyfunc_op_contains([2, 3], ([[1, 2], [2, 3], [3, 4]]));
# Test for null
foo is None
JS
foo === null;
# Test for JS undefined
foo is undefined
JS
foo === undefined;
# Testing for containment
"foo" in "this has foo in it"
3 in [0, 1, 2, 3, 4]
JS
var _pyfunc_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 (_pyfunc_op_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;
};
var _pyfunc_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;
};
_pyfunc_op_contains("foo", "this has foo in it");
_pyfunc_op_contains(3, [0, 1, 2, 3, 4]);

Truthy and Falsy

In JavaScript, an empty array and an empty dict are interpreted as truthy. PScript fixes this, so that you can do if an_array: as usual.

# These evaluate to False:
0
NaN
""  # empty string
None  # JS null
undefined
[]
{}
JS
0;
NaN;
"";
null;
undefined;
[];
({});
# This still works
a = []
a = a or [1]  # a is now [1]
JS
var _pyfunc_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;}
};
var a;
a = [];
a = _pyfunc_truthy(a) || [1];

Function calls

As in Python, the default return value of a function is None (i.e. null in JS).

# Business as usual
foo(a, b)
JS
foo(a, b);
# Support for star args (but not **kwargs)
foo(*a)
JS
foo.apply(null, a);

Imports

Imports are not supported syntax in PScript. Imports “from pscript” and “from __future__” are ignored to help writing hybrid Python/JS modules.

PScript does provide functionality to package code in JS modules, but these follow the require pattern.

If statements

if val > 7:
    result = 42
elif val > 1:
    result = 1
else:
    result = 0
JS
var result;
if ((val > 7)) {
    result = 42;
} else if ((val > 1)) {
    result = 1;
} else {
    result = 0;
}
# One-line if
result = 42 if truth else 0
JS
var _pyfunc_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;}
};
var result;
result = (_pyfunc_truthy(truth))? (42) : (0);

Looping

There is support for while loops and for-loops in several forms. Both support continue, break and the else clause.

While loops map well to JS

val = 0
while val < 10:
    val += 1
JS
var val;
val = 0;
while (val < 10) {
    val += 1;
}

Explicit iterating over arrays (and strings):

# Using range() yields true for-loops
for i in range(10):
    print(i)
JS
var i;
for (i = 0; i < 10; i += 1) {
    console.log(i);
}
for i in range(100, 10, -2):
    print(i)
JS
var i;
for (i = 100; i > 10; i += -2) {
    console.log(i);
}
# One way to iterate over an array
for i in range(len(arr)):
    print(arr[i])
JS
var i;
for (i = 0; i < arr.length; i += 1) {
    console.log(arr[i]);
}
# But this is equally valid (and fast)
for element in arr:
    print(element)
JS
var element, stub1_seq, stub2_itr;
stub1_seq = arr;
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    element = stub1_seq[stub2_itr];
    console.log(element);
}

Iterations over dicts:

# Plain iteration over a dict has a minor overhead
for key in d:
    print(key)
JS
var key, stub1_seq, stub2_itr;
stub1_seq = d;
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    key = stub1_seq[stub2_itr];
    console.log(key);
}
# Which is why we recommend using keys(), values(), or items()
for key in d.keys():
    print(key)
JS
var key, stub1_seq;
stub1_seq = d;
for (key in stub1_seq) {
    if (!stub1_seq.hasOwnProperty(key)){ continue; }
    console.log(key);
}
for val in d.values():
    print(val)
JS
var stub1_seq, val;
stub1_seq = d;
for (val in stub1_seq) {
    if (!stub1_seq.hasOwnProperty(val)){ continue; }
    val = stub1_seq[val];
    console.log(val);
}
for key, val in d.items():
    print(key, val, sep=': ')
JS
var key, stub1_seq, val;
stub1_seq = d;
for (key in stub1_seq) {
    if (!stub1_seq.hasOwnProperty(key)){ continue; }
    val = stub1_seq[key];
    console.log(key + ": " + val);
}

We can iterate over anything:

# Strings
for char in "foo bar":
    print(c)
JS
var char, stub1_seq, stub2_itr;
stub1_seq = "foo bar";
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    char = stub1_seq[stub2_itr];
    console.log(c);
}
# More complex data structes
for i, j in [[1, 2], [3, 4]]:
    print(i+j)
JS
var _pyfunc_op_add = function (a, b) { // nargs: 2
    if (Array.isArray(a) && Array.isArray(b)) {
        return a.concat(b);
    } return a + b;
};
var i, j, stub1_seq, stub2_itr, stub3_tgt;
stub1_seq = [[1, 2], [3, 4]];
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    stub3_tgt = stub1_seq[stub2_itr];
    i = stub3_tgt[0]; j = stub3_tgt[1];
    console.log(_pyfunc_op_add(i, j));
}

Builtin functions intended for iterations are supported too: enumerate, zip, reversed, sorted, filter, map.

for i, x in enumerate(foo):
    pass
JS
var _pyfunc_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;
};
var i, stub1_seq, stub2_itr, stub3_tgt, x;
stub1_seq = _pyfunc_enumerate(foo);
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    stub3_tgt = stub1_seq[stub2_itr];
    i = stub3_tgt[0]; x = stub3_tgt[1];
}
for a, b in zip(foo, bar):
    pass
JS
var _pyfunc_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;
};
var a, b, stub1_seq, stub2_itr, stub3_tgt;
stub1_seq = _pyfunc_zip(foo, bar);
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    stub3_tgt = stub1_seq[stub2_itr];
    a = stub3_tgt[0]; b = stub3_tgt[1];
}
for x in reversed(sorted(foo)):
    pass
JS
var _pyfunc_reversed = function (iter) { // nargs: 1
    if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);}
    return iter.slice().reverse();
};
var _pyfunc_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;
};
var stub1_seq, stub2_itr, x;
stub1_seq = _pyfunc_reversed(_pyfunc_sorted(foo, undefined, false));
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    x = stub1_seq[stub2_itr];
}
for x in map(lambda x: x+1, foo):
    pass
JS
var _pyfunc_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);
};
var stub1_seq, stub2_itr, x;
stub1_seq = _pyfunc_map((function (x) {return x + 1;}), foo);
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    x = stub1_seq[stub2_itr];
}
for x in filter(lambda x: x>0, foo):
    pass
JS
var _pyfunc_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);
};
var stub1_seq, stub2_itr, x;
stub1_seq = _pyfunc_filter((function (x) {return x > 0;}), foo);
if ((typeof stub1_seq === "object") && (!Array.isArray(stub1_seq))) { stub1_seq = Object.keys(stub1_seq);}
for (stub2_itr = 0; stub2_itr < stub1_seq.length; stub2_itr += 1) {
    x = stub1_seq[stub2_itr];
}

Comprehensions

# List comprehensions just work
x = [i*2 for i in some_array if i>0]
y = [i*j for i in a for j in b]
JS
var _pyfunc_op_mult = function (a, b) { // nargs: 2
    if ((typeof a === 'number') + (typeof b === 'number') === 1) {
        if (a.constructor === String) return _pymeth_repeat.call(a, b);
        if (b.constructor === String) return _pymeth_repeat.call(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;
};
var _pymeth_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;
};
var stub1_, stub1_i, stub1_i0, stub1_iter0, stub2_, stub2_i, stub2_i0, stub2_i1, stub2_iter0, stub2_iter1, stub2_j, x, y;
stub1_ = [];stub1_iter0 = some_array;if ((typeof stub1_iter0 === "object") && (!Array.isArray(stub1_iter0))) {stub1_iter0 = Object.keys(stub1_iter0);}for (stub1_i0=0; stub1_i0<stub1_iter0.length; stub1_i0++) {stub1_i = stub1_iter0[stub1_i0];if (!((stub1_i > 0))) {continue;}{stub1_.push(_pyfunc_op_mult(stub1_i, 2));}}
x = stub1_;
stub2_ = [];stub2_iter0 = a;if ((typeof stub2_iter0 === "object") && (!Array.isArray(stub2_iter0))) {stub2_iter0 = Object.keys(stub2_iter0);}for (stub2_i0=0; stub2_i0<stub2_iter0.length; stub2_i0++) {stub2_i = stub2_iter0[stub2_i0];stub2_iter1 = b;if ((typeof stub2_iter1 === "object") && (!Array.isArray(stub2_iter1))) {stub2_iter1 = Object.keys(stub2_iter1);}for (stub2_i1=0; stub2_i1<stub2_iter1.length; stub2_i1++) {stub2_j = stub2_iter1[stub2_i1];{stub2_.push(_pyfunc_op_mult(stub2_i, stub2_j));}}}
y = stub2_;

Defining functions

def display(val):
    print(val)
JS
var display;
display = function flx_display (val) {
    console.log(val);
    return null;
};
# Support for *args
def foo(x, *values):
    bar(x+1, *values)
JS
var foo;
foo = function flx_foo (x) {
    var values;
    values = Array.prototype.slice.call(arguments, 1);
    bar.apply(null, [].concat([x + 1], values));
    return null;
};
# To write the function in raw JS, use the RawJS call
def bar(a, b):
    RawJS('''
    var c = 4;
    return a + b + c;
    ''')
JS
var bar;
bar = function flx_bar (a, b) {
    var c = 4;
    return a + b + c;;
    return null;
};
# Lambda expressions
foo = lambda x: x**2
JS
var foo;
foo = function (x) {return Math.pow(x, 2);};

PScript also supports async functions and await syntax. (These map to async and await in JS, which work in about every browser except IE.):

async def getresult(uri):
    response = await window.fetch(uri)
    return await response.text()
JS
var getresult;
getresult = async function flx_getresult (uri) {
    var response;
    response = await window.fetch(uri);
    return await response.text();
};

Defining classes

Classes are translated to the JavaScript prototypal class paragigm, which means that they should play well with other JS libraries and e.g. instanceof. Inheritance is supported, but not multiple inheritance. Further, super() works just as in Python 3.

class Foo:
    a_class_attribute = 4
    def __init__(self):
        self.x = 3
JS
var _pyfunc_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);
    }
};
var Foo;
Foo = function () {
    _pyfunc_op_instantiate(this, arguments);
}
Foo.prototype._base_class = Object;
Foo.prototype.__name__ = "Foo";

Foo.prototype.a_class_attribute = 4;
Foo.prototype.__init__ = function () {
    this.x = 3;
    return null;
};
class Bar(Foo):
    def __init__(self):
        super.__init__()
        self.x += 1
    def add1(self):
        self.x += 1
JS
var _pyfunc_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);
    }
};
var Bar;
Bar = function () {
    _pyfunc_op_instantiate(this, arguments);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype._base_class = Foo.prototype;
Bar.prototype.__name__ = "Bar";

Bar.prototype.__init__ = function () {
    super.__init__();
    this.x += 1;
    return null;
};

Bar.prototype.add1 = function () {
    this.x += 1;
    return null;
};
# Methods are bound functions, like in Python
b = Bar()
setTimeout(b.add1, 1000)
JS
var b;
b = new Bar();
setTimeout(b.add1, 1000);
# Functions defined in methods (and that do not start with self or this)
# have ``this`` bound the the same object.
class Spam(Bar):
    def add_later(self):
        setTimeout(lambda ev: self.add1(), 1000)
JS
var _pyfunc_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);
    }
};
var Spam;
Spam = function () {
    _pyfunc_op_instantiate(this, arguments);
}
Spam.prototype = Object.create(Bar.prototype);
Spam.prototype._base_class = Bar.prototype;
Spam.prototype.__name__ = "Spam";

Spam.prototype.add_later = function () {
    setTimeout((function (ev) {return this.add1();}).bind(this), 1000);
    return null;
};

Exceptions

Raised exceptions are translated to a JavaScript Error objects, for which the name attribute is set to the type of the exception being raised. When catching exceptions the name attribute is checked (if its an Error object. You can raise strings or any other kind of object, but you can only catch Error objects.

# Throwing/raising exceptions
raise SomeError('asd')
raise AnotherError()
raise "In JS you can throw anything"
raise 4
JS
var _pyfunc_op_error = function (etype, msg) { // nargs: 2
    var e = new Error(etype + ': ' + msg);
    e.name = etype
    return e;
};
var err_0;
throw _pyfunc_op_error('SomeError', "asd");
throw _pyfunc_op_error('AnotherError', "");
throw "In JS you can throw anything";
throw 4;
# Assertions work too
assert foo == 3
assert bar == 4, "bar should be 4"
JS
var _pyfunc_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;
};
var _pyfunc_op_error = function (etype, msg) { // nargs: 2
    var e = new Error(etype + ': ' + msg);
    e.name = etype
    return e;
};
if (!(_pyfunc_op_equals(foo, 3))) { throw _pyfunc_op_error('AssertionError', "_pyfunc_op_equals(foo, 3)");}
if (!(_pyfunc_op_equals(bar, 4))) { throw _pyfunc_op_error('AssertionError', "\"bar should be 4\"");}
# Catching exceptions
try:
    raise IndexError('blabla')
except IndexError as err:
    print(err)
except Exception:
   print('something went wrong')
JS
var _pyfunc_op_error = function (etype, msg) { // nargs: 2
    var e = new Error(etype + ': ' + msg);
    e.name = etype
    return e;
};
var err, err_1;
try {
    throw _pyfunc_op_error('IndexError', "blabla");
} catch(err_1) {
    if (err_1 instanceof Error && err_1.name === "IndexError") {
        err = err_1;
        console.log(err);
    } else {
        console.log("something went wrong");
    }
}

Globals and nonlocal

a = 3
def foo():
    global a
    a = 4
foo()
# a is now 4
JS
var a, foo;
a = 3;
foo = function flx_foo () {
    a = 4;
    return null;
};

foo();

Python Builtins

Most builtin functions (that make sense in JS) are automatically translated to JavaScript: isinstance, issubclass, callable, hasattr, getattr, setattr, delattr, print, len, max, min, chr, ord, dict, list, tuple, range, pow, sum, round, int, float, str, bool, abs, divmod, all, any, enumerate, zip, reversed, sorted, filter, map.

Further all methods for list, dict and str are implemented (except str methods: encode, decode, format_map, isprintable, maketrans).

# "self" is replaced with "this"
self.foo
JS
this.foo;
# Printing just works
print('some test')
print(a, b, c, sep='-')
JS
console.log("some test");
console.log(a + "-" + b + "-" + c);
# Getting the length of a string or array
len(foo)
JS
foo.length;
# Rounding and abs
round(foo)  # round to nearest integer
int(foo)  # round towards 0 as in Python
abs(foo)
JS
var _pyfunc_abs = Math.abs;
var _pyfunc_int = function (x, base) { // nargs: 1 2
    if(base !== undefined) return parseInt(x, base);
    return x<0 ? Math.ceil(x): Math.floor(x);
};
var _pyfunc_round = Math.round;
_pyfunc_round(foo);
_pyfunc_int(foo);
_pyfunc_abs(foo);
# min and max
min(foo)
min(a, b, c)
max(foo)
max(a, b, c)
JS
Math.min.apply(null, foo);
Math.min(a, b, c);
Math.max.apply(null, foo);
Math.max(a, b, c);
# divmod
a, b = divmod(100, 7)  # -> 14, 2
JS
var _pyfunc_divmod = function (x, y) { // nargs: 2
    var m = x % y; return [(x-m)/y, m];
};
var a, b, stub1_;
stub1_ = _pyfunc_divmod(100, 7);
a = stub1_[0];b = stub1_[1];
# Aggregation
sum(foo)
all(foo)
any(foo)
JS
var _pyfunc_all = function (x) { // nargs: 1
    for (var i=0; i<x.length; i++) {
        if (!_pyfunc_truthy(x[i])){return false;}
    } return true;
};
var _pyfunc_any = function (x) { // nargs: 1
    for (var i=0; i<x.length; i++) {
        if (_pyfunc_truthy(x[i])){return true;}
    } return false;
};
var _pyfunc_sum = function (x) {  // nargs: 1
    return x.reduce(function(a, b) {return a + b;});
};
var _pyfunc_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;}
};
_pyfunc_sum(foo);
_pyfunc_all(foo);
_pyfunc_any(foo);
# Turning things into numbers, bools and strings
str(s)
float(x)
bool(y)
int(z)  # this rounds towards zero like in Python
chr(65)  # -> 'A'
ord('A')  # -> 65
JS
var _pyfunc_bool = function (x) { // nargs: 1
    return Boolean(_pyfunc_truthy(x));
};
var _pyfunc_float = Number;
var _pyfunc_int = function (x, base) { // nargs: 1 2
    if(base !== undefined) return parseInt(x, base);
    return x<0 ? Math.ceil(x): Math.floor(x);
};
var _pyfunc_str = String;
var _pyfunc_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;}
};
_pyfunc_str(s);
_pyfunc_float(x);
_pyfunc_bool(y);
_pyfunc_int(z);
String.fromCharCode(65);
"A".charCodeAt(0);
# Turning things into lists and dicts
dict([['foo', 1], ['bar', 2]])  # -> {'foo': 1, 'bar': 2}
list('abc')  # -> ['a', 'b', 'c']
dict(other_dict)  # make a copy
list(other_list)  # make copy
JS
var _pyfunc_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;
};
var _pyfunc_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;
};
_pyfunc_dict(([["foo", 1], ["bar", 2]]));
_pyfunc_list("abc");
_pyfunc_dict(other_dict);
_pyfunc_list(other_list);

The isinstance function (and friends)

The isinstance() function works for all JS primitive types, but also for user-defined classes.

# Basic types
isinstance(3, float)  # in JS there are no ints
isinstance('', str)
isinstance([], list)
isinstance({}, dict)
isinstance(foo, types.FunctionType)
JS
Object.prototype.toString.call(3).slice(8,-1).toLowerCase() === 'number';
Object.prototype.toString.call("").slice(8,-1).toLowerCase() === 'string';
Array.isArray([]);
Object.prototype.toString.call(({})).slice(8,-1).toLowerCase() === 'object';
Object.prototype.toString.call(foo).slice(8,-1).toLowerCase() === 'function';
# Can also use JS strings
isinstance(3, 'number')
isinstance('', 'string')
isinstance([], 'array')
isinstance({}, 'object')
isinstance(foo, 'function')
JS
Object.prototype.toString.call(3).slice(8,-1).toLowerCase() === 'number';
Object.prototype.toString.call("").slice(8,-1).toLowerCase() === 'string';
Array.isArray([]);
Object.prototype.toString.call(({})).slice(8,-1).toLowerCase() === 'object';
Object.prototype.toString.call(foo).slice(8,-1).toLowerCase() === 'function';
# You can use it on your own types too ...
isinstance(x, MyClass)
isinstance(x, 'MyClass')  # equivalent
isinstance(x, 'Object')  # also yields true (subclass of Object)
JS
x instanceof MyClass;
x instanceof MyClass;
Object.prototype.toString.call(x).slice(8,-1).toLowerCase() === 'object';
# issubclass works too
issubclass(Foo, Bar)
JS
(Foo.prototype instanceof Bar);
# As well as callable
callable(foo)
JS
(typeof foo === "function");

hasattr, getattr, setattr and delattr

a = {'foo': 1, 'bar': 2}
JS
var a;
a = ({foo: 1, bar: 2});
hasattr(a, 'foo')  # -> True
hasattr(a, 'fooo')  # -> False
hasattr(null, 'foo')  # -> False
JS
var _pyfunc_hasattr = function (ob, name) { // nargs: 2
    return (ob !== undefined) && (ob !== null) && (ob[name] !== undefined);
};
_pyfunc_hasattr(a, "foo");
_pyfunc_hasattr(a, "fooo");
_pyfunc_hasattr(null, "foo");
getattr(a, 'foo')  # -> 1
getattr(a, 'fooo')  # -> raise AttributeError
getattr(a, 'fooo', 3)  # -> 3
getattr(null, 'foo', 3)  # -> 3
JS
var _pyfunc_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;}
};
_pyfunc_getattr(a, "foo");
_pyfunc_getattr(a, "fooo");
_pyfunc_getattr(a, "fooo", 3);
_pyfunc_getattr(null, "foo", 3);
setattr(a, 'foo', 2)
JS
var _pyfunc_setattr = function (ob, name, value) {  // nargs: 3
    ob[name] = value;
};
_pyfunc_setattr(a, "foo", 2);
delattr(a, 'foo')
JS
var _pyfunc_delattr = function (ob, name) {  // nargs: 2
    delete ob[name];
};
_pyfunc_delattr(a, "foo");

Creating sequences

range(10)
range(2, 10, 2)
range(100, 0, -1)
JS
var _pyfunc_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;
};
_pyfunc_range(0, 10, 1);
_pyfunc_range(2, 10, 2);
_pyfunc_range(100, 0, (-1));
reversed(foo)
sorted(foo)
enumerate(foo)
zip(foo, bar)
JS
var _pyfunc_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;
};
var _pyfunc_reversed = function (iter) { // nargs: 1
    if ((typeof iter==="object") && (!Array.isArray(iter))) {iter = Object.keys(iter);}
    return iter.slice().reverse();
};
var _pyfunc_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;
};
var _pyfunc_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;
};
_pyfunc_reversed(foo);
_pyfunc_sorted(foo, undefined, false);
_pyfunc_enumerate(foo);
_pyfunc_zip(foo, bar);
filter(func, foo)
map(func, foo)
JS
var _pyfunc_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);
};
var _pyfunc_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);
};
_pyfunc_filter(func, foo);
_pyfunc_map(func, foo);

List methods

# Call a.append() if it exists, otherwise a.push()
a.append(x)
JS
var _pymeth_append = function (x) { // nargs: 1
    if (!Array.isArray(this)) return this.append.apply(this, arguments);
    this.push(x);
};
_pymeth_append.call(a, x);
# Similar for remove()
a.remove(x)
JS
var _pyfunc_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;
};
var _pymeth_remove = function (x) { // nargs: 1
    if (!Array.isArray(this)) return this.remove.apply(this, arguments);
    for (var i=0; i<this.length; i++) {
        if (_pyfunc_op_equals(this[i], x)) {this.splice(i, 1); return;}
    }
    var e = Error(x); e.name='ValueError'; throw e;
};
_pymeth_remove.call(a, x);

Dict methods

a = {'foo': 3}
a['foo']
a.get('foo', 0)
a.get('foo')
a.keys()
JS
var _pymeth_get = function (key, d) { // nargs: 1 2
    if (this.constructor !== Object) return this.get.apply(this, arguments);
    if (this[key] !== undefined) {return this[key];}
    else if (d !== undefined) {return d;}
    else {return null;}
};
var _pymeth_keys = function () { // nargs: 0
    if (typeof this['keys'] === 'function') return this.keys.apply(this, arguments);
    return Object.keys(this);
};
var a;
a = ({foo: 3});
a["foo"];
_pymeth_get.call(a, "foo", 0);
_pymeth_get.call(a, "foo");
_pymeth_keys.call(a);

Str methods

"foobar".startswith('foo')
"foobar".replace('foo', 'bar')
"foobar".upper()
JS
var _pymeth_replace = function (s1, s2, count) {  // nargs: 2 3
    if (this.constructor !== String) return this.replace.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('');
};
var _pymeth_startswith = function (x) { // nargs: 1
    if (this.constructor !== String) return this.startswith.apply(this, arguments);
    return this.indexOf(x) == 0;
};
var _pymeth_upper = function () { // nargs: 0
    if (this.constructor !== String) return this.upper.apply(this, arguments);
    return this.toUpperCase();
};
_pymeth_startswith.call("foobar", "foo");
_pymeth_replace.call("foobar", "foo", "bar");
_pymeth_upper.call("foobar");

Using JS specific functionality

When writing PScript inside Python modules, we recommend that where specific JavaScript functionality is used, that the references are prefixed with window. Where window represents the global JS namespace. All global JavaScript objects, functions, and variables automatically become members of the window object. This helps make it clear that the functionality is specific to JS, and also helps static code analysis tools like flake8.

from pscript import window  # this is a stub
def foo(a):
    return window.Math.cos(a)
JS
var foo;
foo = function flx_foo (a) {
    return window.Math.cos(a);
};

Aside from window, pscript also provides undefined, Inifinity, and NaN.