summaryrefslogtreecommitdiff
path: root/addons/web/static/lib
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/lib')
-rw-r--r--addons/web/static/lib/py.js/.hg_archival.txt6
-rw-r--r--addons/web/static/lib/py.js/README.rst18
-rw-r--r--addons/web/static/lib/py.js/TODO.rst19
-rw-r--r--addons/web/static/lib/py.js/lib/py.js196
-rw-r--r--addons/web/static/lib/py.js/test/parser.js31
-rw-r--r--addons/web/static/lib/py.js/test/test.js67
6 files changed, 284 insertions, 53 deletions
diff --git a/addons/web/static/lib/py.js/.hg_archival.txt b/addons/web/static/lib/py.js/.hg_archival.txt
index d10d8a6b1cc..f5355e28a12 100644
--- a/addons/web/static/lib/py.js/.hg_archival.txt
+++ b/addons/web/static/lib/py.js/.hg_archival.txt
@@ -1,5 +1,5 @@
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
-node: 7be96c381d9302b4aafaf5eb5381083b0731a9aa
+node: 5adb2d9c89e53a6445e3799f9c4dc9110458c149
branch: default
-latesttag: 0.4
-latesttagdistance: 7
+latesttag: 0.5
+latesttagdistance: 9
diff --git a/addons/web/static/lib/py.js/README.rst b/addons/web/static/lib/py.js/README.rst
index a5aaa289a46..046db7153d1 100644
--- a/addons/web/static/lib/py.js/README.rst
+++ b/addons/web/static/lib/py.js/README.rst
@@ -10,6 +10,17 @@ specification document is the `Python 2.7 Expressions spec
<http://docs.python.org/reference/expressions.html>`_ (along with the
lexical analysis part).
+Syntax
+------
+
+* Lambdas and ternaries should be parsed but are not implemented (in
+ the evaluator)
+* Only floats are implemented, ``int`` literals are parsed as floats.
+* Octal and hexadecimal literals are not implemented
+* Srings are backed by JavaScript strings and probably behave like
+ ``unicode`` more than like ``str``
+* Slices don't work
+
Builtins
--------
@@ -89,9 +100,10 @@ Collections Abstract Base Classes
Hashable are kind-of implemented as well)
Numeric type emulation
- Basically not implemented, the only part of it which is
- implemented is the unary ``-`` (because it's used to create
- negative floats, they're parsed as a negated positive number)
+ Operators are implemented (but not tested), ``abs``, ``divmod``
+ and ``pow`` builtins are not implemented yet. Neither are ``oct``
+ and ``hex`` but I'm not sure we care (I'm not sure we care about
+ ``pow`` or even ``divmod`` either, for that matter)
Utilities
---------
diff --git a/addons/web/static/lib/py.js/TODO.rst b/addons/web/static/lib/py.js/TODO.rst
index f80520944d5..a1309bd3fc5 100644
--- a/addons/web/static/lib/py.js/TODO.rst
+++ b/addons/web/static/lib/py.js/TODO.rst
@@ -34,15 +34,14 @@ Base methods requirement
************************
* ``__getattr__``
-* ? ``__getitem``
-* ``__call__``
-* ``or``
-* ``toJS`` / ``toJSON``
* ``dict.get``
-* ``datetime.time.today``
-* ``datetime.time.strftime``
-* ``time.strftime``
-* ``__add__`` / ``__radd__``
-* ``__sub__`` / ``__rsub__``
* ``__len__``
-* ``__nonzero__``
+
+In datamodel, not implemented in any type, untested
+***************************************************
+
+* a[b]
+
+* a + b, a - b, a * b, ...
+
+* +a, ~a
diff --git a/addons/web/static/lib/py.js/lib/py.js b/addons/web/static/lib/py.js/lib/py.js
index ec6b9182543..3c0b13d0717 100644
--- a/addons/web/static/lib/py.js/lib/py.js
+++ b/addons/web/static/lib/py.js/lib/py.js
@@ -104,6 +104,15 @@ var py = {};
symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
symbol('else');
+ infix('=', 10, function (left) {
+ if (left.id !== '(name)') {
+ throw new Error("Expected keyword argument name, got " + token.id);
+ }
+ this.first = left;
+ this.second = expression();
+ return this;
+ });
+
symbol('lambda', 20).nud = function () {
this.first = [];
if (token.id !== ':') {
@@ -362,7 +371,7 @@ var py = {};
if (val instanceof py.object
|| val === py.object
- || py.issubclass.__call__(val, py.object) === py.True) {
+ || py.issubclass.__call__([val, py.object]) === py.True) {
return val;
}
@@ -443,7 +452,7 @@ var py = {};
// TODO: second argument should be class
return val.__get__(this);
}
- if (typeof val === 'function' && !this.hasOwnProperty(val)) {
+ if (typeof val === 'function' && !this.hasOwnProperty(name)) {
// val is a method from the class
return new PY_instancemethod(val, this);
}
@@ -476,6 +485,7 @@ var py = {};
py.NotImplemented = new NotImplementedType();
var booleans_initialized = false;
py.bool = py.type(function bool(value) {
+ value = (value instanceof Array) ? value[0] : value;
// The only actual instance of py.bool should be py.True
// and py.False. Return the new instance of py.bool if we
// are initializing py.True and py.False, otherwise always
@@ -493,7 +503,26 @@ var py = {};
py.False = new py.bool();
booleans_initialized = true;
py.float = py.type(function float(value) {
- this._value = value;
+ value = (value instanceof Array) ? value[0] : value;
+ if (value === undefined) { this._value = 0; return; }
+ if (value instanceof py.float) { return value; }
+ if (typeof value === 'number' || value instanceof Number) {
+ this._value = value;
+ return;
+ }
+ if (typeof value === 'string' || value instanceof String) {
+ this._value = parseFloat(value);
+ return;
+ }
+ if (value instanceof py.object && '__float__' in value) {
+ var res = value.__float__();
+ if (res instanceof py.float) {
+ return res;
+ }
+ throw new Error('TypeError: __float__ returned non-float (type ' +
+ res.constructor.name + ')');
+ }
+ throw new Error('TypeError: float() argument must be a string or a number');
}, py.object, {
__eq__: function (other) {
return this._value === other._value ? py.True : py.False;
@@ -514,9 +543,25 @@ var py = {};
if (!(other instanceof py.float)) { return py.NotImplemented; }
return this._value >= other._value ? py.True : py.False;
},
+ __add__: function (other) {
+ if (!(other instanceof py.float)) { return py.NotImplemented; }
+ return new py.float(this._value + other._value);
+ },
__neg__: function () {
return new py.float(-this._value);
},
+ __sub__: function (other) {
+ if (!(other instanceof py.float)) { return py.NotImplemented; }
+ return new py.float(this._value - other._value);
+ },
+ __mul__: function (other) {
+ if (!(other instanceof py.float)) { return py.NotImplemented; }
+ return new py.float(this._value * other._value);
+ },
+ __div__: function (other) {
+ if (!(other instanceof py.float)) { return py.NotImplemented; }
+ return new py.float(this._value / other._value);
+ },
__nonzero__: function () {
return this._value ? py.True : py.False;
},
@@ -525,7 +570,17 @@ var py = {};
}
});
py.str = py.type(function str(s) {
- this._value = s;
+ s = (s instanceof Array) ? s[0] : s;
+ if (s === undefined) { this._value = ''; return; }
+ if (s instanceof py.str) { return s; }
+ if (typeof s === 'string' || s instanceof String) {
+ this._value = s;
+ return;
+ }
+ var v = s.__str__();
+ if (v instanceof py.str) { return v; }
+ throw new Error('TypeError: __str__ returned non-string (type ' +
+ v.constructor.name + ')');
}, py.object, {
__eq__: function (other) {
if (other instanceof py.str && this._value === other._value) {
@@ -549,6 +604,10 @@ var py = {};
if (!(other instanceof py.str)) { return py.NotImplemented; }
return this._value >= other._value ? py.True : py.False;
},
+ __add__: function (other) {
+ if (!(other instanceof py.str)) { return py.NotImplemented; }
+ return new py.str(this._value + other._value);
+ },
__nonzero__: function () {
return this._value.length ? py.True : py.False;
},
@@ -596,9 +655,9 @@ var py = {};
this._inst = null;
this._func = nativefunc;
}, py.object, {
- __call__: function () {
+ __call__: function (args, kwargs) {
// don't want to rewrite __call__ for instancemethod
- return this._func.apply(this._inst, arguments);
+ return this._func.call(this._inst, args, kwargs);
},
toJSON: function () {
return this._func;
@@ -610,13 +669,69 @@ var py = {};
this._func = nativefunc;
}, py.def, {});
- py.issubclass = new py.def(function issubclass(derived, parent) {
+ py.issubclass = new py.def(function issubclass(args) {
+ var derived = args[0], parent = args[1];
// still hurts my brain that this can work
return derived.prototype instanceof py.object
? py.True
: py.False;
});
+
+ // All binary operators with fallbacks, so they can be applied generically
+ var PY_operators = {
+ '==': ['eq', 'eq', function (a, b) { return a === b; }],
+ '!=': ['ne', 'ne', function (a, b) { return a !== b; }],
+ '<': ['lt', 'gt', function (a, b) {return a.constructor.name < b.constructor.name;}],
+ '<=': ['le', 'ge', function (a, b) {return a.constructor.name <= b.constructor.name;}],
+ '>': ['gt', 'lt', function (a, b) {return a.constructor.name > b.constructor.name;}],
+ '>=': ['ge', 'le', function (a, b) {return a.constructor.name >= b.constructor.name;}],
+
+ '+': ['add', 'radd'],
+ '-': ['sub', 'rsub'],
+ '*': ['mul', 'rmul'],
+ '/': ['div', 'rdiv'],
+ '//': ['floordiv', 'rfloordiv'],
+ '%': ['mod', 'rmod'],
+ '**': ['pow', 'rpow'],
+ '<<': ['lshift', 'rlshift'],
+ '>>': ['rshift', 'rrshift'],
+ '&': ['and', 'rand'],
+ '^': ['xor', 'rxor'],
+ '|': ['or', 'ror']
+ };
+ /**
+ * Implements operator fallback/reflection.
+ *
+ * First two arguments are the objects to apply the operator on,
+ * in their actual order (ltr).
+ *
+ * Third argument is the actual operator.
+ *
+ * If the operator methods raise exceptions, those exceptions are
+ * not intercepted.
+ */
+ var PY_op = function (o1, o2, op) {
+ var r;
+ var methods = PY_operators[op];
+ var forward = '__' + methods[0] + '__', reverse = '__' + methods[1] + '__';
+ var otherwise = methods[2];
+
+ if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) {
+ return r;
+ }
+ if (reverse in o2 && (r = o2[reverse](o1)) !== py.NotImplemented) {
+ return r;
+ }
+ if (otherwise) {
+ return PY_ensurepy(otherwise(o1, o2));
+ }
+ throw new Error(
+ "TypeError: unsupported operand type(s) for " + op + ": '"
+ + o1.constructor.name + "' and '"
+ + o2.constructor.name + "'");
+ };
+
var PY_builtins = {
type: py.type,
@@ -643,30 +758,16 @@ var py = {};
var evaluate_operator = function (operator, a, b) {
var v;
switch (operator) {
- case '==': return a.__eq__(b);
case 'is': return a === b ? py.True : py.False;
- case '!=': return a.__ne__(b);
case 'is not': return a !== b ? py.True : py.False;
- case '<':
- v = a.__lt__(b);
- if (v !== py.NotImplemented) { return v; }
- return PY_ensurepy(a.constructor.name < b.constructor.name);
- case '<=':
- v = a.__le__(b);
- if (v !== py.NotImplemented) { return v; }
- return PY_ensurepy(a.constructor.name <= b.constructor.name);
- case '>':
- v = a.__gt__(b);
- if (v !== py.NotImplemented) { return v; }
- return PY_ensurepy(a.constructor.name > b.constructor.name);
- case '>=':
- v = a.__ge__(b);
- if (v !== py.NotImplemented) { return v; }
- return PY_ensurepy(a.constructor.name >= b.constructor.name);
case 'in':
return b.__contains__(a);
case 'not in':
return b.__contains__(a) === py.True ? py.False : py.True;
+ case '==': case '!=':
+ case '<': case '<=':
+ case '>': case '>=':
+ return PY_op(a, b, operator);
}
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
};
@@ -700,11 +801,6 @@ var py = {};
if (result === py.False) { return py.False; }
}
return py.True;
- case '-':
- if (expr.second) {
- throw new Error('SyntaxError: binary [-] not implemented yet');
- }
- return (py.evaluate(expr.first, context)).__neg__();
case 'not':
return py.evaluate(expr.first, context).__nonzero__() === py.True
? py.False
@@ -723,12 +819,20 @@ var py = {};
return py.evaluate(expr.second, context);
case '(':
if (expr.second) {
- var callable = py.evaluate(expr.first, context), args=[];
+ var callable = py.evaluate(expr.first, context);
+ var args = [], kwargs = {};
for (var jj=0; jj<expr.second.length; ++jj) {
- args.push(py.evaluate(
- expr.second[jj], context));
+ var arg = expr.second[jj];
+ if (arg.id !== '=') {
+ // arg
+ args.push(py.evaluate(arg, context));
+ } else {
+ // kwarg
+ kwargs[arg.first.value] =
+ py.evaluate(arg.second, context);
+ }
}
- return callable.__call__.apply(callable, args);
+ return callable.__call__(args, kwargs);
}
var tuple_exprs = expr.first,
tuple_values = [];
@@ -741,7 +845,8 @@ var py = {};
return t;
case '[':
if (expr.second) {
- throw new Error('SyntaxError: indexing not implemented yet');
+ return py.evaluate(expr.first, context)
+ .__getitem__(expr.evaluate(expr.second, context));
}
var list_exprs = expr.first, list_values = [];
for (var k=0; k<list_exprs.length; ++k) {
@@ -765,6 +870,27 @@ var py = {};
}
return py.evaluate(expr.first, context)
.__getattribute__(expr.second.value);
+ // numerical operators
+ case '~':
+ return (py.evaluate(expr.first, context)).__invert__();
+ case '+':
+ if (!expr.second) {
+ return (py.evaluate(expr.first, context)).__pos__();
+ }
+ case '-':
+ if (!expr.second) {
+ return (py.evaluate(expr.first, context)).__neg__();
+ }
+ case '*': case '/': case '//':
+ case '%':
+ case '**':
+ case '<<': case '>>':
+ case '&': case '^': case '|':
+ return PY_op(
+ py.evaluate(expr.first, context),
+ py.evaluate(expr.second, context),
+ expr.id);
+
default:
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
}
diff --git a/addons/web/static/lib/py.js/test/parser.js b/addons/web/static/lib/py.js/test/parser.js
index ca74d2bebc8..bbbd5d7663d 100644
--- a/addons/web/static/lib/py.js/test/parser.js
+++ b/addons/web/static/lib/py.js/test/parser.js
@@ -11,6 +11,14 @@ expect.Assertion.prototype.tokens = function (n) {
'expected ' + this.obj + ' to not have an end token');
};
+expect.Assertion.prototype.named = function (value) {
+ this.assert(this.obj.id === '(name)',
+ 'expected ' + this.obj + ' to be a name token',
+ 'expected ' + this.obj + ' not to be a name token');
+ this.assert(this.obj.value === value,
+ 'expected ' + this.obj + ' to have tokenized ' + value,
+ 'expected ' + this.obj + ' not to have tokenized ' + value);
+};
expect.Assertion.prototype.constant = function (value) {
this.assert(this.obj.id === '(constant)',
'expected ' + this.obj + ' to be a constant token',
@@ -98,4 +106,27 @@ describe('Tokenizer', function () {
expect(toks[1].id).to.be(')');
});
});
+ describe('functions', function () {
+ it('tokenizes kwargs', function () {
+ var toks = py.tokenize('foo(bar=3, qux=4)');
+ expect(toks).to.have.tokens(10);
+ });
+ });
+});
+
+describe('Parser', function () {
+ describe('functions', function () {
+ var ast = py.parse(py.tokenize('foo(bar=3, qux=4)'));
+ expect(ast.id).to.be('(');
+ expect(ast.first).to.be.named('foo');
+
+ args = ast.second;
+ expect(args[0].id).to.be('=');
+ expect(args[0].first).to.be.named('bar');
+ expect(args[0].second).to.be.number(3);
+
+ expect(args[1].id).to.be('=');
+ expect(args[1].first).to.be.named('qux');
+ expect(args[1].second).to.be.number(4);
+ });
}); \ No newline at end of file
diff --git a/addons/web/static/lib/py.js/test/test.js b/addons/web/static/lib/py.js/test/test.js
index 6c53bcfad85..31fccc23240 100644
--- a/addons/web/static/lib/py.js/test/test.js
+++ b/addons/web/static/lib/py.js/test/test.js
@@ -262,6 +262,15 @@ describe('Attribute access', function () {
});
expect(py.eval('foo.bar()', {foo: o})).to.be('ok');
});
+ it('should not convert function attributes into methods', function () {
+ var o = new py.object();
+ o.bar = new py.type(function bar() {});
+ o.bar.__getattribute__ = function () {
+ return o.bar.baz;
+ }
+ o.bar.baz = py.True;
+ expect(py.eval('foo.bar.baz', {foo: o})).to.be(true);
+ });
it('should work on instance attributes', function () {
var typ = py.type(function MyType() {
this.attr = new py.float(3);
@@ -296,16 +305,37 @@ describe('Callables', function () {
});
expect(py.eval('MyType()', {MyType: typ})).to.be(true);
});
+ it('should accept kwargs', function () {
+ expect(py.eval('foo(ok=True)', {
+ foo: function foo() { return py.True; }
+ })).to.be(true);
+ });
+ it('should be able to get its kwargs', function () {
+ expect(py.eval('foo(ok=True)', {
+ foo: function foo(args, kwargs) { return kwargs.ok; }
+ })).to.be(true);
+ });
+ it('should be able to have both args and kwargs', function () {
+ expect(py.eval('foo(1, 2, 3, ok=True, nok=False)', {
+ foo: function (args, kwargs) {
+ expect(args).to.have.length(3);
+ expect(args[0].toJSON()).to.be(1);
+ expect(kwargs).to.only.have.keys('ok', 'nok')
+ expect(kwargs.nok.toJSON()).to.be(false);
+ return kwargs.ok;
+ }
+ })).to.be(true);
+ });
});
describe('issubclass', function () {
it('should say a type is its own subclass', function () {
- expect(py.issubclass.__call__(py.dict, py.dict).toJSON())
+ expect(py.issubclass.__call__([py.dict, py.dict]).toJSON())
.to.be(true);
expect(py.eval('issubclass(dict, dict)'))
.to.be(true);
});
it('should work with subtypes', function () {
- expect(py.issubclass.__call__(py.bool, py.object).toJSON())
+ expect(py.issubclass.__call__([py.bool, py.object]).toJSON())
.to.be(true);
});
});
@@ -313,4 +343,37 @@ describe('builtins', function () {
it('should aways be available', function () {
expect(py.eval('bool("foo")')).to.be(true);
});
+});
+
+describe('numerical protocols', function () {
+ describe('True numbers (float)', function () {
+ describe('Basic arithmetic', function () {
+ it('can be added', function () {
+ expect(py.eval('1 + 1')).to.be(2);
+ expect(py.eval('1.5 + 2')).to.be(3.5);
+ expect(py.eval('1 + -1')).to.be(0);
+ });
+ it('can be subtracted', function () {
+ expect(py.eval('1 - 1')).to.be(0);
+ expect(py.eval('1.5 - 2')).to.be(-0.5);
+ expect(py.eval('2 - 1.5')).to.be(0.5);
+ });
+ it('can be multiplied', function () {
+ expect(py.eval('1 * 3')).to.be(3);
+ expect(py.eval('0 * 5')).to.be(0);
+ expect(py.eval('42 * -2')).to.be(-84);
+ });
+ it('can be divided', function () {
+ expect(py.eval('1 / 2')).to.be(0.5);
+ expect(py.eval('2 / 1')).to.be(2);
+ });
+ });
+ });
+ describe('Strings', function () {
+ describe('Basic arithmetics operators', function () {
+ it('can be added (concatenation)', function () {
+ expect(py.eval('"foo" + "bar"')).to.be('foobar');
+ });
+ });
+ });
}); \ No newline at end of file