summaryrefslogtreecommitdiff
path: root/addons/web/static/lib/py.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/lib/py.js')
-rw-r--r--addons/web/static/lib/py.js/.hg_archival.txt5
-rw-r--r--addons/web/static/lib/py.js/LICENSE13
-rw-r--r--addons/web/static/lib/py.js/README.rst197
-rw-r--r--addons/web/static/lib/py.js/TODO.rst47
-rw-r--r--addons/web/static/lib/py.js/lib/py.js905
-rw-r--r--addons/web/static/lib/py.js/test/parser.js132
-rw-r--r--addons/web/static/lib/py.js/test/test.js379
7 files changed, 1678 insertions, 0 deletions
diff --git a/addons/web/static/lib/py.js/.hg_archival.txt b/addons/web/static/lib/py.js/.hg_archival.txt
new file mode 100644
index 00000000000..f5355e28a12
--- /dev/null
+++ b/addons/web/static/lib/py.js/.hg_archival.txt
@@ -0,0 +1,5 @@
+repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
+node: 5adb2d9c89e53a6445e3799f9c4dc9110458c149
+branch: default
+latesttag: 0.5
+latesttagdistance: 9
diff --git a/addons/web/static/lib/py.js/LICENSE b/addons/web/static/lib/py.js/LICENSE
new file mode 100644
index 00000000000..01733ddac98
--- /dev/null
+++ b/addons/web/static/lib/py.js/LICENSE
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2012
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/addons/web/static/lib/py.js/README.rst b/addons/web/static/lib/py.js/README.rst
new file mode 100644
index 00000000000..046db7153d1
--- /dev/null
+++ b/addons/web/static/lib/py.js/README.rst
@@ -0,0 +1,197 @@
+What
+====
+
+``py.js`` is a parser and evaluator of Python expressions, written in
+pure javascript.
+
+``py.js`` is not intended to implement a full Python interpreter
+(although it could be used for such an effort later on), its
+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
+--------
+
+``py.js`` currently implements the following builtins:
+
+``type``
+ Restricted to creating new types, can't be used to get an object's
+ type (yet)
+
+``None``
+
+``True``
+
+``False``
+
+``NotImplemented``
+ Returned from rich comparison methods when the comparison is not
+ implemented for this combination of operands. In ``py.js``, this
+ is also the default implementation for all rich comparison methods.
+
+``issubclass``
+
+``object``
+
+``bool``
+ Does not inherit from ``int``, since ``int`` is not currently
+ implemented.
+
+``float``
+
+``str``
+
+``tuple``
+ Constructor/coercer is not implemented, only handles literals
+
+``list``
+ Same as tuple (``list`` is currently an alias for ``tuple``)
+
+``dict``
+ Implements just about nothing
+
+Note that most methods are probably missing from all of these.
+
+Data model protocols
+--------------------
+
+``py.js`` currently implements the following protocols (or
+sub-protocols) of the `Python 2.7 data model
+<http://docs.python.org/reference/datamodel.html>`_:
+
+Rich comparisons
+ Roughly complete implementation but for two limits: ``__eq__`` and
+ ``__ne__`` can't return ``NotImplemented`` (well they can but it's
+ not going to work right), and the behavior is undefined if a
+ rich-comparison operation does not return a ``py.bool``.
+
+ Also, a ``NotImplemented`` result does not try the reverse
+ operation, not sure if it's supposed to. It directly falls back to
+ comparing type names.
+
+Boolean conversion
+ Implementing ``__nonzero__`` should work.
+
+Customizing attribute access
+ Protocols for getting and setting attributes (including new-style
+ extension) fully implemented but for ``__delattr__`` (since
+ ``del`` is a statement)
+
+Descriptor protocol
+ As with attributes, ``__delete__`` is not implemented.
+
+Callable objects
+
+Collections Abstract Base Classes
+ Container is the only implemented ABC protocol (ABCs themselves
+ are not currently implemented) (well technically Callable and
+ Hashable are kind-of implemented as well)
+
+Numeric type emulation
+ 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
+---------
+
+``py.js`` also provides (and exposes) a few utilities for "userland"
+implementation:
+
+``def``
+ Wraps a native javascript function into a ``py.js`` function, so
+ that it can be called from native expressions.
+
+ Does not ensure the return types are type-compatible with
+ ``py.js`` types.
+
+ When accessing instance methods, ``py.js`` automatically wraps
+ these in a variant of ``py.def`` automatically, to behave as
+ Python's (bound) methods.
+
+Why
+===
+
+Originally, to learn about Pratt parsers (which are very, very good at
+parsing expressions with lots of infix or mixfix symbols). The
+evaluator part came because "why not" and because I work on a product
+with the "feature" of transmitting Python expressions (over the wire)
+which the client is supposed to evaluate.
+
+How
+===
+
+At this point, only three steps exist in ``py.js``: tokenizing,
+parsing and evaluation. It is possible that a compilation step be
+added later (for performance reasons).
+
+To evaluate a Python expression, the caller merely needs to call
+`py.eval`_. `py.eval`_ takes a mandatory Python
+expression to evaluate (as a string) and an optional context, for the
+substitution of the free variables in the expression::
+
+ > py.eval("type in ('a', 'b', 'c') and foo", {type: 'c', foo: true});
+ true
+
+This is great for one-shot evaluation of expressions. If the
+expression will need to be repeatedly evaluated with the same
+parameters, the various parsing and evaluation steps can be performed
+separately: `py.eval`_ is really a shortcut for sequentially calling
+`py.tokenize`_, `py.parse`_ and `py.evaluate`_.
+
+API
+===
+
+.. _py.eval:
+
+``py.eval(expr[, context])``
+ "Do everything" function, to use for one-shot evaluation of a
+ Python expression: it will internally handle the tokenizing,
+ parsing and actual evaluation of the Python expression without
+ having to perform these separately.
+
+ ``expr``
+ Python expression to evaluate
+ ``context``
+ context dictionary holding the substitutions for the free
+ variables in the expression
+
+.. _py.tokenize:
+
+``py.tokenize(expr)``
+ ``expr``
+ Python expression to tokenize
+
+.. _py.parse:
+
+``py.parse(tokens)``
+ Parses a token stream and returns an abstract syntax tree of the
+ expression (if the token stream represents a valid Python
+ expression).
+
+ A parse tree is stateless and can be memoized and used multiple
+ times in separate evaluations.
+
+ ``tokens``
+ stream of tokens returned by `py.tokenize`_
+
+.. _py.evaluate:
+
+``py.evaluate(ast[, context])``
+ ``ast``
+ The output of `py.parse`_
+ ``context``
+ The evaluation context for the Python expression.
diff --git a/addons/web/static/lib/py.js/TODO.rst b/addons/web/static/lib/py.js/TODO.rst
new file mode 100644
index 00000000000..a1309bd3fc5
--- /dev/null
+++ b/addons/web/static/lib/py.js/TODO.rst
@@ -0,0 +1,47 @@
+* Parser
+ since parsing expressions, try with a pratt parser
+ http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
+ http://effbot.org/zone/simple-top-down-parsing.htm
+
+Evaluator
+---------
+
+* Builtins should be built-in, there should be no need to add e.g. ``py.bool`` to the evaluation context (?)
+* Stop busyworking trivial binary operator
+* Make it *trivial* to build Python type-wrappers
+* Implement Python's `data model protocols
+ <http://docs.python.org/reference/datamodel.html#basic-customization>`_
+ for *all* supported operations, optimizations can come later
+* Automatically type-wrap everything (for now anyway)
+
+Base type requirements:
+***********************
+
+* int
+* float
+* --str-- unicode
+* bool
+* dict
+* tuple
+* list
+* ?module
+* ?object
+* datetime.time
+* datetime.timedelta
+* NotImplementedType
+
+Base methods requirement
+************************
+
+* ``__getattr__``
+* ``dict.get``
+* ``__len__``
+
+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
new file mode 100644
index 00000000000..3c0b13d0717
--- /dev/null
+++ b/addons/web/static/lib/py.js/lib/py.js
@@ -0,0 +1,905 @@
+var py = {};
+(function (py) {
+ var create = function (o, props) {
+ function F() {}
+ F.prototype = o;
+ var inst = new F;
+ if (props) {
+ for(var name in props) {
+ if(!props.hasOwnProperty(name)) { continue; }
+ inst[name] = props[name];
+ }
+ }
+ return inst;
+ };
+
+ var symbols = {};
+ var comparators = {};
+ var Base = {
+ nud: function () { throw new Error(this.id + " undefined as prefix"); },
+ led: function (led) { throw new Error(this.id + " undefined as infix"); },
+ toString: function () {
+ if (this.id === '(constant)' || this.id === '(number)' || this.id === '(name)' || this.id === '(string)') {
+ return [this.id.slice(0, this.id.length-1), ' ', this.value, ')'].join('');
+ } else if (this.id === '(end)') {
+ return '(end)';
+ } else if (this.id === '(comparator)' ) {
+ var repr = ['(comparator', this.expressions[0]];
+ for (var i=0;i<this.operators.length; ++i) {
+ repr.push(this.operators[i], this.expressions[i+1]);
+ }
+ return repr.join(' ') + ')';
+ }
+ var out = [this.id, this.first, this.second, this.third]
+ .filter(function (r){return r}).join(' ');
+ return '(' + out + ')';
+ }
+ };
+ function symbol(id, bp) {
+ bp = bp || 0;
+ var s = symbols[id];
+ if (s) {
+ if (bp > s.lbp) {
+ s.lbp = bp;
+ }
+ return s;
+ }
+ return symbols[id] = create(Base, {
+ id: id,
+ lbp: bp
+ });
+ }
+ function constant(id) {
+ var s = symbol(id);
+ s.id = '(constant)';
+ s.value = id;
+ s.nud = function () {
+ return this;
+ };
+ }
+ function prefix(id, bp, nud) {
+ symbol(id).nud = nud || function () {
+ this.first = expression(bp);
+ return this
+ }
+ }
+ function infix(id, bp, led) {
+ symbol(id, bp).led = led || function (left) {
+ this.first = left;
+ this.second = expression(bp);
+ return this;
+ }
+ }
+ function infixr(id, bp) {
+ symbol(id, bp).led = function (left) {
+ this.first = left;
+ this.second = expression(bp - 1);
+ return this;
+ }
+ }
+ function comparator(id) {
+ comparators[id] = true;
+ var bp = 60;
+ infix(id, bp, function (left) {
+ this.id = '(comparator)';
+ this.operators = [id];
+ this.expressions = [left, expression(bp)];
+ while (token.id in comparators) {
+ this.operators.push(token.id);
+ advance();
+ this.expressions.push(
+ expression(bp));
+ }
+ return this;
+ });
+ }
+
+ constant('None'); constant('False'); constant('True');
+
+ symbol('(number)').nud = function () { return this; };
+ symbol('(name)').nud = function () { return this; };
+ symbol('(string)').nud = function () { return this; };
+ symbol('(end)');
+
+ 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 !== ':') {
+ for(;;) {
+ if (token.id !== '(name)') {
+ throw new Error('Excepted an argument name');
+ }
+ this.first.push(token);
+ advance();
+ if (token.id !== ',') {
+ break;
+ }
+ advance(',');
+ }
+ }
+ advance(':');
+ this.second = expression();
+ return this;
+ };
+ infix('if', 20, function (left) {
+ this.first = left;
+ this.second = expression();
+ advance('else');
+ this.third = expression();
+ return this;
+ });
+
+ infixr('or', 30); infixr('and', 40); prefix('not', 50);
+
+ comparator('in'); comparator('not in');
+ comparator('is'); comparator('is not');
+ comparator('<'); comparator('<=');
+ comparator('>'); comparator('>=');
+ comparator('<>'); comparator('!='); comparator('==');
+
+ infix('|', 70); infix('^', 80), infix('&', 90);
+
+ infix('<<', 100); infix('>>', 100);
+
+ infix('+', 110); infix('-', 110);
+
+ infix('*', 120); infix('/', 120);
+ infix('//', 120), infix('%', 120);
+
+ prefix('-', 130); prefix('+', 130); prefix('~', 130);
+
+ infixr('**', 140);
+
+ infix('.', 150, function (left) {
+ if (token.id !== '(name)') {
+ throw new Error('Expected attribute name, got ' + token.id);
+ }
+ this.first = left;
+ this.second = token;
+ advance();
+ return this;
+ });
+ symbol('(', 150).nud = function () {
+ this.first = [];
+ var comma = false;
+ if (token.id !== ')') {
+ while (true) {
+ if (token.id === ')') {
+ break;
+ }
+ this.first.push(expression());
+ if (token.id !== ',') {
+ break;
+ }
+ comma = true;
+ advance(',');
+ }
+ }
+ advance(')');
+ if (!this.first.length || comma) {
+ return this;
+ } else {
+ return this.first[0];
+ }
+ };
+ symbol('(').led = function (left) {
+ this.first = left;
+ this.second = [];
+ if (token.id !== ")") {
+ for(;;) {
+ this.second.push(expression());
+ if (token.id !== ',') {
+ break;
+ }
+ advance(',');
+ }
+ }
+ advance(")");
+ return this;
+
+ };
+ infix('[', 150, function (left) {
+ this.first = left;
+ this.second = expression();
+ advance("]");
+ return this;
+ });
+ symbol('[').nud = function () {
+ this.first = [];
+ if (token.id !== ']') {
+ for (;;) {
+ if (token.id === ']') {
+ break;
+ }
+ this.first.push(expression());
+ if (token.id !== ',') {
+ break;
+ }
+ advance(',');
+ }
+ }
+ advance(']');
+ return this;
+ };
+
+ symbol('{').nud = function () {
+ this.first = [];
+ if (token.id !== '}') {
+ for(;;) {
+ if (token.id === '}') {
+ break;
+ }
+ var key = expression();
+ advance(':');
+ var value = expression();
+ this.first.push([key, value]);
+ if (token.id !== ',') {
+ break;
+ }
+ advance(',');
+ }
+ }
+ advance('}');
+ return this;
+ };
+
+ py.tokenize = (function () {
+ function group() { return '(' + Array.prototype.join.call(arguments, '|') + ')'; }
+
+ var Whitespace = '[ \\f\\t]*';
+
+ var Name = '[a-zA-Z_]\\w*';
+
+ var DecNumber = '\\d+';
+ var IntNumber = DecNumber;
+ var PointFloat = group('\\d+\\.\\d*', '\\.\\d+');
+ var FloatNumber = PointFloat;
+ var Number = group(FloatNumber, IntNumber);
+
+ var Operator = group("\\*\\*=?", ">>=?", "<<=?", "<>", "!=",
+ "//=?", "[+\\-*/%&|^=<>]=?", "~");
+ var Bracket = '[\\[\\]\\(\\)\\{\\}]';
+ var Special = '[:;.,`@]';
+ var Funny = group(Operator, Bracket, Special);
+
+ var ContStr = group("'[^']*'", '"[^"]*"');
+
+ var PseudoToken = Whitespace + group(Number, Funny, ContStr, Name);
+
+ return function tokenize(s) {
+ var max=s.length, tokens = [];
+ // /g flag makes repeated exec() have memory
+ var pseudoprog = new RegExp(PseudoToken, 'g');
+
+ while(pseudoprog.lastIndex < max) {
+ var pseudomatch = pseudoprog.exec(s);
+ if (!pseudomatch) {
+ // if match failed on trailing whitespace, end tokenizing
+ if (/^\s+$/.test(s.slice(end))) {
+ break;
+ }
+ throw new Error('Failed to tokenize <<' + s
+ + '>> at index ' + (end || 0)
+ + '; parsed so far: ' + tokens);
+ }
+
+ var start = pseudomatch.index, end = pseudoprog.lastIndex;
+ // strip leading space caught by Whitespace
+ var token = s.slice(start, end).replace(new RegExp('^' + Whitespace), '');
+ var initial = token[0];
+
+ if (/\d/.test(initial) || (initial === '.' && token !== '.')) {
+ tokens.push(create(symbols['(number)'], {
+ value: parseFloat(token)
+ }));
+ } else if (/'|"/.test(initial)) {
+ tokens.push(create(symbols['(string)'], {
+ value: token.slice(1, -1)
+ }));
+ } else if (token in symbols) {
+ var symbol;
+ // transform 'not in' and 'is not' in a single token
+ if (token === 'in' && tokens[tokens.length-1].id === 'not') {
+ symbol = symbols['not in'];
+ tokens.pop();
+ } else if (token === 'not' && tokens[tokens.length-1].id === 'is') {
+ symbol = symbols['is not'];
+ tokens.pop();
+ } else {
+ symbol = symbols[token];
+ }
+ tokens.push(create(symbol));
+ } else if (/[_a-zA-Z]/.test(initial)) {
+ tokens.push(create(symbols['(name)'], {
+ value: token
+ }));
+ } else {
+ throw new Error("Tokenizing failure of <<" + s + ">> at index " + start
+ + " for token [[" + token + "]]"
+ + "; parsed so far: " + tokens);
+
+ }
+ }
+ tokens.push(create(symbols['(end)']));
+ return tokens;
+ }
+ })();
+
+ var token, next;
+ function expression(rbp) {
+ rbp = rbp || 0;
+ var t = token;
+ token = next();
+ var left = t.nud();
+ while (rbp < token.lbp) {
+ t = token;
+ token = next();
+ left = t.led(left);
+ }
+ return left;
+ }
+ function advance(id) {
+ if (id && token.id !== id) {
+ throw new Error(
+ 'Expected "' + id + '", got "' + token.id + '"');
+ }
+ token = next();
+ }
+
+ function PY_ensurepy(val, name) {
+ switch (val) {
+ case undefined:
+ throw new Error("NameError: name '" + name + "' is not defined");
+ case null:
+ return py.None;
+ case true:
+ return py.True;
+ case false:
+ return py.False;
+ }
+
+ if (val instanceof py.object
+ || val === py.object
+ || py.issubclass.__call__([val, py.object]) === py.True) {
+ return val;
+ }
+
+ switch (typeof val) {
+ case 'number':
+ return new py.float(val);
+ case 'string':
+ return new py.str(val);
+ case 'function':
+ return new py.def(val);
+ }
+
+ throw new Error("Could not convert " + val + " to a pyval");
+ }
+ // Builtins
+ py.type = function type(constructor, base, dict) {
+ var proto;
+ if (!base) {
+ base = py.object;
+ }
+ proto = constructor.prototype = create(base.prototype);
+ proto.constructor = constructor;
+ if (dict) {
+ for(var k in dict) {
+ if (!dict.hasOwnProperty(k)) { continue; }
+ proto[k] = dict[k];
+ }
+ }
+ constructor.__call__ = function () {
+ // create equivalent type with same prototype
+ var instance = create(proto);
+ // call actual constructor
+ var res = constructor.apply(instance, arguments);
+ // return result of constructor if any, otherwise instance
+ return res || instance;
+ };
+ return constructor;
+ };
+
+ var hash_counter = 0;
+ py.object = py.type(function object() {}, {}, {
+ // Basic customization
+ __hash__: function () {
+ if (this._hash) {
+ return this._hash;
+ }
+ return this._hash = hash_counter++;
+ },
+ __eq__: function (other) {
+ return (this === other) ? py.True : py.False;
+ },
+ __ne__: function (other) {
+ if (this.__eq__(other) === py.True) {
+ return py.False;
+ } else {
+ return py.True;
+ }
+ },
+ __lt__: function () { return py.NotImplemented; },
+ __le__: function () { return py.NotImplemented; },
+ __ge__: function () { return py.NotImplemented; },
+ __gt__: function () { return py.NotImplemented; },
+ __str__: function () {
+ return this.__unicode__();
+ },
+ __unicode__: function () {
+ // TODO: return python string
+ return '<object ' + this.constructor.name + '>';
+ },
+ __nonzero__: function () {
+ return py.True;
+ },
+ // Attribute access
+ __getattribute__: function (name) {
+ if (name in this) {
+ var val = this[name];
+ if ('__get__' in val) {
+ // TODO: second argument should be class
+ return val.__get__(this);
+ }
+ if (typeof val === 'function' && !this.hasOwnProperty(name)) {
+ // val is a method from the class
+ return new PY_instancemethod(val, this);
+ }
+ return PY_ensurepy(val);
+ }
+ if ('__getattr__' in this) {
+ return this.__getattr__(name);
+ }
+ throw new Error("AttributeError: object has no attribute '" + name +"'");
+ },
+ __setattr__: function (name, value) {
+ if (name in this && '__set__' in this[name]) {
+ this[name].__set__(this, value);
+ }
+ this[name] = value;
+ },
+ // no delattr, because no 'del' statement
+
+ // Conversion
+ toJSON: function () {
+ throw new Error(this.constructor.name + ' can not be converted to JSON');
+ }
+ });
+ var NoneType = py.type(function NoneType() {}, py.object, {
+ __nonzero__: function () { return py.False; },
+ toJSON: function () { return null; }
+ });
+ py.None = new NoneType();
+ var NotImplementedType = py.type(function NotImplementedType(){});
+ 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
+ // return either py.True or py.False.
+ if (!booleans_initialized) {
+ return;
+ }
+ if (value === undefined) { return py.False; }
+ return value.__nonzero__() === py.True ? py.True : py.False;
+ }, py.object, {
+ __nonzero__: function () { return this; },
+ toJSON: function () { return this === py.True; }
+ });
+ py.True = new py.bool();
+ py.False = new py.bool();
+ booleans_initialized = true;
+ py.float = py.type(function float(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;
+ },
+ __lt__: function (other) {
+ if (!(other instanceof py.float)) { return py.NotImplemented; }
+ return this._value < other._value ? py.True : py.False;
+ },
+ __le__: function (other) {
+ if (!(other instanceof py.float)) { return py.NotImplemented; }
+ return this._value <= other._value ? py.True : py.False;
+ },
+ __gt__: function (other) {
+ if (!(other instanceof py.float)) { return py.NotImplemented; }
+ return this._value > other._value ? py.True : py.False;
+ },
+ __ge__: function (other) {
+ 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;
+ },
+ toJSON: function () {
+ return this._value;
+ }
+ });
+ py.str = py.type(function str(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) {
+ return py.True;
+ }
+ return py.False;
+ },
+ __lt__: function (other) {
+ if (!(other instanceof py.str)) { return py.NotImplemented; }
+ return this._value < other._value ? py.True : py.False;
+ },
+ __le__: function (other) {
+ if (!(other instanceof py.str)) { return py.NotImplemented; }
+ return this._value <= other._value ? py.True : py.False;
+ },
+ __gt__: function (other) {
+ if (!(other instanceof py.str)) { return py.NotImplemented; }
+ return this._value > other._value ? py.True : py.False;
+ },
+ __ge__: function (other) {
+ 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;
+ },
+ __contains__: function (s) {
+ return (this._value.indexOf(s._value) !== -1) ? py.True : py.False;
+ },
+ toJSON: function () {
+ return this._value;
+ }
+ });
+ py.tuple = py.type(function tuple() {}, null, {
+ __contains__: function (value) {
+ for(var i=0, len=this.values.length; i<len; ++i) {
+ if (this.values[i].__eq__(value) === py.True) {
+ return py.True;
+ }
+ }
+ return py.False;
+ },
+ toJSON: function () {
+ var out = [];
+ for (var i=0; i<this.values.length; ++i) {
+ out.push(this.values[i].toJSON());
+ }
+ return out;
+ }
+ });
+ py.list = py.tuple;
+ py.dict = py.type(function dict() {
+ this._store = {};
+ }, py.object, {
+ __setitem__: function (key, value) {
+ this._store[key.__hash__()] = [key, value];
+ },
+ toJSON: function () {
+ var out = {};
+ for(var k in this._store) {
+ var item = this._store[k];
+ out[item[0].toJSON()] = item[1].toJSON();
+ }
+ return out;
+ }
+ });
+ py.def = py.type(function def(nativefunc) {
+ this._inst = null;
+ this._func = nativefunc;
+ }, py.object, {
+ __call__: function (args, kwargs) {
+ // don't want to rewrite __call__ for instancemethod
+ return this._func.call(this._inst, args, kwargs);
+ },
+ toJSON: function () {
+ return this._func;
+ }
+ });
+ var PY_instancemethod = py.type(function instancemethod(nativefunc, instance, _cls) {
+ // could also use bind?
+ this._inst = instance;
+ this._func = nativefunc;
+ }, py.def, {});
+
+ 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,
+
+ None: py.None,
+ True: py.True,
+ False: py.False,
+ NotImplemented: py.NotImplemented,
+
+ object: py.object,
+ bool: py.bool,
+ float: py.float,
+ tuple: py.tuple,
+ list: py.list,
+ dict: py.dict,
+ issubclass: py.issubclass
+ };
+
+ py.parse = function (toks) {
+ var index = 0;
+ token = toks[0];
+ next = function () { return toks[++index]; };
+ return expression();
+ };
+ var evaluate_operator = function (operator, a, b) {
+ var v;
+ switch (operator) {
+ case 'is': return a === b ? py.True : py.False;
+ case 'is not': return a !== b ? py.True : py.False;
+ 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 + ']]');
+ };
+ py.evaluate = function (expr, context) {
+ context = context || {};
+ switch (expr.id) {
+ case '(name)':
+ var val = context[expr.value];
+ if (val === undefined && expr.value in PY_builtins) {
+ return PY_builtins[expr.value];
+ }
+ return PY_ensurepy(val, expr.value);
+ case '(string)':
+ return new py.str(expr.value);
+ case '(number)':
+ return new py.float(expr.value);
+ case '(constant)':
+ switch (expr.value) {
+ case 'None': return py.None;
+ case 'False': return py.False;
+ case 'True': return py.True;
+ }
+ throw new Error("SyntaxError: unknown constant '" + expr.value + "'");
+ case '(comparator)':
+ var result, left = py.evaluate(expr.expressions[0], context);
+ for(var i=0; i<expr.operators.length; ++i) {
+ result = evaluate_operator(
+ expr.operators[i],
+ left,
+ left = py.evaluate(expr.expressions[i+1], context));
+ if (result === py.False) { return py.False; }
+ }
+ return py.True;
+ case 'not':
+ return py.evaluate(expr.first, context).__nonzero__() === py.True
+ ? py.False
+ : py.True;
+ case 'and':
+ var and_first = py.evaluate(expr.first, context);
+ if (and_first.__nonzero__() === py.True) {
+ return py.evaluate(expr.second, context);
+ }
+ return and_first;
+ case 'or':
+ var or_first = py.evaluate(expr.first, context);
+ if (or_first.__nonzero__() === py.True) {
+ return or_first
+ }
+ return py.evaluate(expr.second, context);
+ case '(':
+ if (expr.second) {
+ var callable = py.evaluate(expr.first, context);
+ var args = [], kwargs = {};
+ for (var jj=0; jj<expr.second.length; ++jj) {
+ 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__(args, kwargs);
+ }
+ var tuple_exprs = expr.first,
+ tuple_values = [];
+ for (var j=0, len=tuple_exprs.length; j<len; ++j) {
+ tuple_values.push(py.evaluate(
+ tuple_exprs[j], context));
+ }
+ var t = new py.tuple();
+ t.values = tuple_values;
+ return t;
+ case '[':
+ if (expr.second) {
+ 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) {
+ list_values.push(py.evaluate(
+ list_exprs[k], context));
+ }
+ var l = new py.list();
+ l.values = list_values;
+ return l;
+ case '{':
+ var dict_exprs = expr.first, dict = new py.dict;
+ for(var l=0; l<dict_exprs.length; ++l) {
+ dict.__setitem__(
+ py.evaluate(dict_exprs[l][0], context),
+ py.evaluate(dict_exprs[l][1], context));
+ }
+ return dict;
+ case '.':
+ if (expr.second.id !== '(name)') {
+ throw new Error('SyntaxError: ' + expr);
+ }
+ 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 + ']]');
+ }
+ };
+ py.eval = function (str, context) {
+ return py.evaluate(
+ py.parse(
+ py.tokenize(
+ str)),
+ context).toJSON();
+ }
+})(typeof exports === 'undefined' ? py : exports);
diff --git a/addons/web/static/lib/py.js/test/parser.js b/addons/web/static/lib/py.js/test/parser.js
new file mode 100644
index 00000000000..bbbd5d7663d
--- /dev/null
+++ b/addons/web/static/lib/py.js/test/parser.js
@@ -0,0 +1,132 @@
+var py = require('../lib/py.js'),
+ expect = require('expect.js');
+
+expect.Assertion.prototype.tokens = function (n) {
+ var length = this.obj.length;
+ this.assert(length === n + 1,
+ 'expected ' + this.obj + ' to have ' + n + ' tokens',
+ 'expected ' + this.obj + ' to not have ' + n + ' tokens');
+ this.assert(this.obj[length-1].id === '(end)',
+ 'expected ' + this.obj + ' to have and end token',
+ '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',
+ 'expected ' + this.obj + ' not to be a constant 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.number = function (value) {
+ this.assert(this.obj.id === '(number)',
+ 'expected ' + this.obj + ' to be a number token',
+ 'expected ' + this.obj + ' not to be a number 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.string = function (value) {
+ this.assert(this.obj.id === '(string)',
+ 'expected ' + this.obj + ' to be a string token',
+ 'expected ' + this.obj + ' not to be a string token');
+ this.assert(this.obj.value === value,
+ 'expected ' + this.obj + ' to have tokenized ' + value,
+ 'expected ' + this.obj + ' not to have tokenized ' + value);
+};
+
+describe('Tokenizer', function () {
+ describe('simple literals', function () {
+ it('tokenizes numbers', function () {
+ var toks = py.tokenize('1');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.number(1);
+
+ var toks = py.tokenize('-1');
+ expect(toks).to.have.tokens(2);
+ expect(toks[0].id).to.be('-');
+ expect(toks[1]).to.be.number(1);
+
+ var toks = py.tokenize('1.2');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.number(1.2);
+
+ var toks = py.tokenize('.42');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.number(0.42);
+ });
+ it('tokenizes strings', function () {
+ var toks = py.tokenize('"foo"');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.string('foo');
+
+ var toks = py.tokenize("'foo'");
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.string('foo');
+ });
+ it('tokenizes bare names', function () {
+ var toks = py.tokenize('foo');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0].id).to.be('(name)');
+ expect(toks[0].value).to.be('foo');
+ });
+ it('tokenizes constants', function () {
+ var toks = py.tokenize('None');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.constant('None');
+
+ var toks = py.tokenize('True');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.constant('True');
+
+ var toks = py.tokenize('False');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.constant('False');
+ });
+ it('does not fuck up on trailing spaces', function () {
+ var toks = py.tokenize('None ');
+ expect(toks).to.have.tokens(1);
+ expect(toks[0]).to.be.constant('None');
+ });
+ });
+ describe('collections', function () {
+ it('tokenizes opening and closing symbols', function () {
+ var toks = py.tokenize('()');
+ expect(toks).to.have.tokens(2);
+ expect(toks[0].id).to.be('(');
+ 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
new file mode 100644
index 00000000000..31fccc23240
--- /dev/null
+++ b/addons/web/static/lib/py.js/test/test.js
@@ -0,0 +1,379 @@
+var py = require('../lib/py.js'),
+ expect = require('expect.js');
+
+var ev = function (str, context) {
+ return py.evaluate(py.parse(py.tokenize(str)), context);
+};
+
+describe('Literals', function () {
+ describe('Number', function () {
+ it('should have the right type', function () {
+ expect(ev('1')).to.be.a(py.float);
+ });
+ it('should yield the corresponding JS value', function () {
+ expect(py.eval('1')).to.be(1);
+ expect(py.eval('42')).to.be(42);
+ expect(py.eval('9999')).to.be(9999);
+ });
+ it('should correctly handle negative literals', function () {
+ expect(py.eval('-1')).to.be(-1);
+ expect(py.eval('-42')).to.be(-42);
+ expect(py.eval('-9999')).to.be(-9999);
+ });
+ it('should correctly handle float literals', function () {
+ expect(py.eval('.42')).to.be(0.42);
+ expect(py.eval('1.2')).to.be(1.2);
+ });
+ });
+ describe('Booleans', function () {
+ it('should have the right type', function () {
+ expect(ev('False')).to.be.a(py.bool);
+ expect(ev('True')).to.be.a(py.bool);
+ });
+ it('should yield the corresponding JS value', function () {
+ expect(py.eval('False')).to.be(false);
+ expect(py.eval('True')).to.be(true);
+ });
+ });
+ describe('None', function () {
+ it('should have the right type', function () {
+ expect(ev('None')).to.be.a(py.object)
+ });
+ it('should yield a JS null', function () {
+ expect(py.eval('None')).to.be(null);
+ });
+ });
+ describe('String', function () {
+ it('should have the right type', function () {
+ expect(ev('"foo"')).to.be.a(py.str);
+ expect(ev("'foo'")).to.be.a(py.str);
+ });
+ it('should yield the corresponding JS string', function () {
+ expect(py.eval('"somestring"')).to.be('somestring');
+ expect(py.eval("'somestring'")).to.be('somestring');
+ });
+ });
+ describe('Tuple', function () {
+ it('shoud have the right type', function () {
+ expect(ev('()')).to.be.a(py.tuple);
+ });
+ it('should map to a JS array', function () {
+ expect(py.eval('()')).to.eql([]);
+ expect(py.eval('(1, 2, 3)')).to.eql([1, 2, 3]);
+ });
+ });
+ describe('List', function () {
+ it('shoud have the right type', function () {
+ expect(ev('[]')).to.be.a(py.list);
+ });
+ it('should map to a JS array', function () {
+ expect(py.eval('[]')).to.eql([]);
+ expect(py.eval('[1, 2, 3]')).to.eql([1, 2, 3]);
+ });
+ });
+ describe('Dict', function () {
+ it('shoud have the right type', function () {
+ expect(ev('{}')).to.be.a(py.dict);
+ });
+ it('should map to a JS object', function () {
+ expect(py.eval("{}")).to.eql({});
+ expect(py.eval("{'foo': 1, 'bar': 2}"))
+ .to.eql({foo: 1, bar: 2});
+ });
+ });
+});
+describe('Free variables', function () {
+ it('should return its identity', function () {
+ expect(py.eval('foo', {foo: 1})).to.be(1);
+ expect(py.eval('foo', {foo: true})).to.be(true);
+ expect(py.eval('foo', {foo: false})).to.be(false);
+ expect(py.eval('foo', {foo: null})).to.be(null);
+ expect(py.eval('foo', {foo: 'bar'})).to.be('bar');
+ });
+});
+describe('Comparisons', function () {
+ describe('equality', function () {
+ it('should work with literals', function () {
+ expect(py.eval('1 == 1')).to.be(true);
+ expect(py.eval('"foo" == "foo"')).to.be(true);
+ expect(py.eval('"foo" == "bar"')).to.be(false);
+ });
+ it('should work with free variables', function () {
+ expect(py.eval('1 == a', {a: 1})).to.be(true);
+ expect(py.eval('foo == "bar"', {foo: 'bar'})).to.be(true);
+ expect(py.eval('foo == "bar"', {foo: 'qux'})).to.be(false);
+ });
+ });
+ describe('inequality', function () {
+ it('should work with literals', function () {
+ expect(py.eval('1 != 2')).to.be(true);
+ expect(py.eval('"foo" != "foo"')).to.be(false);
+ expect(py.eval('"foo" != "bar"')).to.be(true);
+ });
+ it('should work with free variables', function () {
+ expect(py.eval('1 != a', {a: 42})).to.be(true);
+ expect(py.eval('foo != "bar"', {foo: 'bar'})).to.be(false);
+ expect(py.eval('foo != "bar"', {foo: 'qux'})).to.be(true);
+ expect(py.eval('foo != bar', {foo: 'qux', bar: 'quux'}))
+ .to.be(true);
+ });
+ });
+ describe('rich comparisons', function () {
+ it('should work with numbers', function () {
+ expect(py.eval('3 < 5')).to.be(true);
+ expect(py.eval('5 >= 3')).to.be(true);
+ expect(py.eval('3 >= 3')).to.be(true);
+ expect(py.eval('3 > 5')).to.be(false);
+ });
+ it('should support comparison chains', function () {
+ expect(py.eval('1 < 3 < 5')).to.be(true);
+ expect(py.eval('5 > 3 > 1')).to.be(true);
+ expect(py.eval('1 < 3 > 2 == 2 > -2')).to.be(true);
+ });
+ it('should compare strings', function () {
+ expect(py.eval('date >= current',
+ {date: '2010-06-08', current: '2010-06-05'}))
+ .to.be(true);
+ expect(py.eval('state == "cancel"', {state: 'cancel'}))
+ .to.be(true);
+ expect(py.eval('state == "cancel"', {state: 'open'}))
+ .to.be(false);
+ });
+ });
+ describe('missing eq/neq', function () {
+ it('should fall back on identity', function () {
+ var typ = new py.type(function MyType() {});
+ expect(py.eval('MyType() == MyType()', {MyType: typ})).to.be(false);
+ });
+ });
+ describe('un-comparable types', function () {
+ it('should default to type-name ordering', function () {
+ var t1 = new py.type(function Type1() {});
+ var t2 = new py.type(function Type2() {});
+ expect(py.eval('T1() < T2()', {T1: t1, T2: t2})).to.be(true);
+ expect(py.eval('T1() > T2()', {T1: t1, T2: t2})).to.be(false);
+ });
+ it('should handle native stuff', function () {
+ expect(py.eval('None < 42')).to.be(true);
+ expect(py.eval('42 > None')).to.be(true);
+ expect(py.eval('None > 42')).to.be(false);
+
+ expect(py.eval('None < False')).to.be(true);
+ expect(py.eval('None < True')).to.be(true);
+ expect(py.eval('False > None')).to.be(true);
+ expect(py.eval('True > None')).to.be(true);
+ expect(py.eval('None > False')).to.be(false);
+ expect(py.eval('None > True')).to.be(false);
+
+ expect(py.eval('False < ""')).to.be(true);
+ expect(py.eval('"" > False')).to.be(true);
+ expect(py.eval('False > ""')).to.be(false);
+ });
+ });
+});
+describe('Boolean operators', function () {
+ it('should work', function () {
+ expect(py.eval("foo == 'foo' or foo == 'bar'",
+ {foo: 'bar'}))
+ .to.be(true);;
+ expect(py.eval("foo == 'foo' and bar == 'bar'",
+ {foo: 'foo', bar: 'bar'}))
+ .to.be(true);;
+ });
+ it('should be lazy', function () {
+ // second clause should nameerror if evaluated
+ expect(py.eval("foo == 'foo' or bar == 'bar'",
+ {foo: 'foo'}))
+ .to.be(true);;
+ expect(py.eval("foo == 'foo' and bar == 'bar'",
+ {foo: 'bar'}))
+ .to.be(false);;
+ });
+ it('should return the actual object', function () {
+ expect(py.eval('"foo" or "bar"')).to.be('foo');
+ expect(py.eval('None or "bar"')).to.be('bar');
+ expect(py.eval('False or None')).to.be(null);
+ expect(py.eval('0 or 1')).to.be(1);
+ });
+});
+describe('Containment', function () {
+ describe('in sequences', function () {
+ it('should match collection items', function () {
+ expect(py.eval("'bar' in ('foo', 'bar')"))
+ .to.be(true);
+ expect(py.eval('1 in (1, 2, 3, 4)'))
+ .to.be(true);;
+ expect(py.eval('1 in (2, 3, 4)'))
+ .to.be(false);;
+ expect(py.eval('"url" in ("url",)'))
+ .to.be(true);
+ expect(py.eval('"foo" in ["foo", "bar"]'))
+ .to.be(true);
+ });
+ it('should not be recursive', function () {
+ expect(py.eval('"ur" in ("url",)'))
+ .to.be(false);;
+ });
+ it('should be negatable', function () {
+ expect(py.eval('1 not in (2, 3, 4)')).to.be(true);
+ expect(py.eval('"ur" not in ("url",)')).to.be(true);
+ expect(py.eval('-2 not in (1, 2, 3)')).to.be(true);
+ });
+ });
+ describe('in dict', function () {
+ // TODO
+ });
+ describe('in strings', function () {
+ it('should match the whole string', function () {
+ expect(py.eval('"view" in "view"')).to.be(true);
+ expect(py.eval('"bob" in "view"')).to.be(false);
+ });
+ it('should match substrings', function () {
+ expect(py.eval('"ur" in "url"')).to.be(true);
+ });
+ });
+});
+describe('Conversions', function () {
+ describe('to bool', function () {
+ describe('strings', function () {
+ it('should be true if non-empty', function () {
+ expect(py.eval('bool(date_deadline)',
+ {date_deadline: '2008'}))
+ .to.be(true);
+ });
+ it('should be false if empty', function () {
+ expect(py.eval('bool(s)', {s: ''})) .to.be(false);
+ });
+ });
+ });
+});
+describe('Attribute access', function () {
+ it("should return the attribute's value", function () {
+ var o = new py.object();
+ o.bar = py.True;
+ expect(py.eval('foo.bar', {foo: o})).to.be(true);
+ o.bar = py.False;
+ expect(py.eval('foo.bar', {foo: o})).to.be(false);
+ });
+ it("should work with functions", function () {
+ var o = new py.object();
+ o.bar = new py.def(function () {
+ return new py.str("ok");
+ });
+ 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);
+ }, py.object, {});
+ expect(py.eval('MyType().attr', {MyType: typ})).to.be(3);
+ });
+ it('should work on class attributes', function () {
+ var typ = py.type(function MyType() {}, py.object, {
+ attr: new py.float(3)
+ });
+ expect(py.eval('MyType().attr', {MyType: typ})).to.be(3);
+ });
+ it('should work with methods', function () {
+ var typ = py.type(function MyType() {
+ this.attr = new py.float(3);
+ }, py.object, {
+ some_method: function () { return new py.str('ok'); },
+ get_attr: function () { return this.attr; }
+ });
+ expect(py.eval('MyType().some_method()', {MyType: typ})).to.be('ok');
+ expect(py.eval('MyType().get_attr()', {MyType: typ})).to.be(3);
+ });
+});
+describe('Callables', function () {
+ it('should wrap JS functions', function () {
+ expect(py.eval('foo()', {foo: function foo() { return new py.float(3); }}))
+ .to.be(3);
+ });
+ it('should work on custom types', function () {
+ var typ = py.type(function MyType() {}, py.object, {
+ toJSON: function () { return true; }
+ });
+ 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())
+ .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())
+ .to.be(true);
+ });
+});
+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