[UP] py.parse 0.2 to py.js 0.4 ~
should probably go through a testing phase (checking its result against that of eval_domain_and_context) for a while, but apart from that it's supposed to work. 'stdlib' stuff (datetime.date.today, datetime.timedelta, time.localtime, time.time, time.strftime) still need to be written. Isn't there also a dateutil.relativedeleta somewhere? Anyway that needs to be done and the various evaluation contexts need to be defined, but the valuator itself should mostly work. bzr revid: xmo@openerp.com-20120227073721-nkgeiqacbzch8xev
This commit is contained in:
parent
f47e37ce35
commit
739f719ad6
|
@ -35,7 +35,7 @@
|
|||
"static/lib/underscore/underscore.js",
|
||||
"static/lib/underscore/underscore.string.js",
|
||||
"static/lib/labjs/LAB.src.js",
|
||||
"static/lib/py.parse/lib/py.js",
|
||||
"static/lib/py.js/lib/py.js",
|
||||
"static/src/js/boot.js",
|
||||
"static/src/js/core.js",
|
||||
"static/src/js/dates.js",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
|
||||
node: a2f78eecb94af8e38839e47b3a5b2ca21f1ed6c3
|
||||
branch: default
|
||||
tag: 0.4
|
|
@ -0,0 +1,85 @@
|
|||
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).
|
||||
|
||||
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.
|
|
@ -0,0 +1,48 @@
|
|||
* 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__``
|
||||
* ? ``__getitem``
|
||||
* ``__call__``
|
||||
* ``or``
|
||||
* ``toJS`` / ``toJSON``
|
||||
* ``dict.get``
|
||||
* ``datetime.time.today``
|
||||
* ``datetime.time.strftime``
|
||||
* ``time.strftime``
|
||||
* ``__add__`` / ``__radd__``
|
||||
* ``__sub__`` / ``__rsub__``
|
||||
* ``__len__``
|
||||
* ``__nonzero__``
|
|
@ -0,0 +1,750 @@
|
|||
var py = {};
|
||||
(function (py) {
|
||||
var NUMBER = /^\d$/,
|
||||
NAME_FIRST = /^[a-zA-Z_]$/,
|
||||
NAME = /^[a-zA-Z0-9_]$/;
|
||||
|
||||
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');
|
||||
|
||||
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) {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
},
|
||||
__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(val)) {
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
NoneType = py.type(function () {}, py.object, {
|
||||
__nonzero__: function () { return py.False; },
|
||||
toJSON: function () { return null; }
|
||||
});
|
||||
py.None = new NoneType();
|
||||
var booleans_initialized = false;
|
||||
py.bool = py.type(function bool(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) {
|
||||
this._value = value;
|
||||
}, py.object, {
|
||||
__eq__: function (other) {
|
||||
return this._value === other._value ? py.True : py.False;
|
||||
},
|
||||
__lt__: function (other) {
|
||||
return this._value < other._value ? py.True : py.False;
|
||||
},
|
||||
__le__: function (other) {
|
||||
return this._value <= other._value ? py.True : py.False;
|
||||
},
|
||||
__gt__: function (other) {
|
||||
return this._value > other._value ? py.True : py.False;
|
||||
},
|
||||
__ge__: function (other) {
|
||||
return this._value >= other._value ? py.True : py.False;
|
||||
},
|
||||
__neg__: function () {
|
||||
return new py.float(-this._value);
|
||||
},
|
||||
__nonzero__: function () {
|
||||
return this._value ? py.True : py.False;
|
||||
},
|
||||
toJSON: function () {
|
||||
return this._value;
|
||||
}
|
||||
});
|
||||
py.str = py.type(function str(s) {
|
||||
this._value = s;
|
||||
}, py.object, {
|
||||
__eq__: function (other) {
|
||||
if (other instanceof py.str && this._value === other._value) {
|
||||
return py.True;
|
||||
}
|
||||
return py.False;
|
||||
},
|
||||
__lt__: function (other) {
|
||||
return this._value < other._value ? py.True : py.False;
|
||||
},
|
||||
__le__: function (other) {
|
||||
return this._value <= other._value ? py.True : py.False;
|
||||
},
|
||||
__gt__: function (other) {
|
||||
return this._value > other._value ? py.True : py.False;
|
||||
},
|
||||
__ge__: function (other) {
|
||||
return this._value >= other._value ? py.True : py.False;
|
||||
},
|
||||
__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 () {
|
||||
// don't want to rewrite __call__ for instancemethod
|
||||
return this._func.apply(this._inst, arguments);
|
||||
},
|
||||
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(derived, parent) {
|
||||
// still hurts my brain that this can work
|
||||
return derived.prototype instanceof py.object
|
||||
? py.True
|
||||
: py.False;
|
||||
});
|
||||
|
||||
var PY_builtins = {
|
||||
type: py.type,
|
||||
|
||||
None: py.None,
|
||||
True: py.True,
|
||||
False: py.False,
|
||||
|
||||
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) {
|
||||
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 '<': return a.__lt__(b);
|
||||
case '<=': return a.__le__(b);
|
||||
case '>': return a.__gt__(b);
|
||||
case '>=': return a.__ge__(b);
|
||||
case 'in':
|
||||
return b.__contains__(a);
|
||||
case 'not in':
|
||||
return b.__contains__(a) === py.True ? py.False : py.True;
|
||||
}
|
||||
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 '-':
|
||||
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
|
||||
: 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), args=[];
|
||||
for (var jj=0; jj<expr.second.length; ++jj) {
|
||||
args.push(py.evaluate(
|
||||
expr.second[jj], context));
|
||||
}
|
||||
return callable.__call__.apply(callable, args);
|
||||
}
|
||||
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) {
|
||||
throw new Error('SyntaxError: indexing not implemented yet');
|
||||
}
|
||||
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);
|
||||
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);
|
|
@ -0,0 +1,96 @@
|
|||
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.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');
|
||||
});
|
||||
});
|
||||
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(')');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,283 @@
|
|||
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);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
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 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);
|
||||
});
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
|
||||
node: 87fb1b67d6a13f10a1a328104ee4d4b2c36801ec
|
||||
branch: default
|
||||
latesttag: 0.2
|
||||
latesttagdistance: 1
|
|
@ -1 +0,0 @@
|
|||
Parser and evaluator of Python expressions
|
|
@ -1,14 +0,0 @@
|
|||
* 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
|
||||
---------
|
||||
|
||||
* 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)
|
|
@ -1,546 +0,0 @@
|
|||
var py = {};
|
||||
(function (exports) {
|
||||
var NUMBER = /^\d$/,
|
||||
NAME_FIRST = /^[a-zA-Z_]$/,
|
||||
NAME = /^[a-zA-Z0-9_]$/;
|
||||
|
||||
var create = function (o, props) {
|
||||
function F() {}
|
||||
F.prototype = o;
|
||||
var inst = new F;
|
||||
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) {
|
||||
symbol(id).nud = function () {
|
||||
this.id = "(constant)";
|
||||
this.value = id;
|
||||
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');
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
var longops = {
|
||||
'*': ['*'],
|
||||
'<': ['<', '=', '>'],
|
||||
'>': ['=', '>'],
|
||||
'!': ['='],
|
||||
'=': ['='],
|
||||
'/': ['/']
|
||||
};
|
||||
function Tokenizer() {
|
||||
this.states = ['initial'];
|
||||
this.tokens = [];
|
||||
}
|
||||
Tokenizer.prototype = {
|
||||
builder: function (empty) {
|
||||
var key = this.states[0] + '_builder';
|
||||
if (empty) {
|
||||
var value = this[key];
|
||||
delete this[key];
|
||||
return value;
|
||||
} else {
|
||||
return this[key] = this[key] || [];
|
||||
}
|
||||
},
|
||||
simple: function (type) {
|
||||
this.tokens.push({type: type});
|
||||
},
|
||||
push: function (new_state) {
|
||||
this.states.push(new_state);
|
||||
},
|
||||
pop: function () {
|
||||
this.states.pop();
|
||||
},
|
||||
|
||||
feed: function (str, index) {
|
||||
var s = this.states;
|
||||
return this[s[s.length - 1]](str, index);
|
||||
},
|
||||
|
||||
initial: function (str, index) {
|
||||
var character = str[index];
|
||||
|
||||
if (character in longops) {
|
||||
var follow = longops[character];
|
||||
for(var i=0, len=follow.length; i<len; ++i) {
|
||||
if (str[index+1] === follow[i]) {
|
||||
character += follow[i];
|
||||
index++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character === ' ') {
|
||||
return index+1;
|
||||
} else if (character === '\0') {
|
||||
this.tokens.push(symbols['(end)']);
|
||||
return index + 1
|
||||
} else if (character === '"' || character === "'") {
|
||||
this.push('string');
|
||||
return index + 1;
|
||||
} else if (NUMBER.test(character)) {
|
||||
this.push('number');
|
||||
return index;
|
||||
} else if (NAME_FIRST.test(character)) {
|
||||
this.push('name');
|
||||
return index;
|
||||
} else if (character in symbols) {
|
||||
this.tokens.push(create(symbols[character]));
|
||||
return index + 1;
|
||||
}
|
||||
throw new Error("Tokenizing failure of <<" + str + ">> at index " + index
|
||||
+ ", character [[" + character + "]]"
|
||||
+ "; parsed so far: " + this.tokens);
|
||||
},
|
||||
string: function (str, index) {
|
||||
var character = str[index];
|
||||
if (character === '"' || character === "'") {
|
||||
this.tokens.push(create(symbols['(string)'], {
|
||||
value: this.builder(true).join('')
|
||||
}));
|
||||
this.pop();
|
||||
return index + 1;
|
||||
}
|
||||
this.builder().push(character);
|
||||
return index + 1;
|
||||
},
|
||||
number: function (str, index) {
|
||||
var character = str[index];
|
||||
if (!(character == '.' || NUMBER.test(character))) {
|
||||
this.tokens.push(create(symbols['(number)'], {
|
||||
value: parseFloat(this.builder(true).join(''))
|
||||
}));
|
||||
this.pop();
|
||||
return index;
|
||||
}
|
||||
this.builder().push(character);
|
||||
return index + 1;
|
||||
},
|
||||
name: function (str, index) {
|
||||
var character = str[index];
|
||||
if (!NAME.test(character)) {
|
||||
var name = this.builder(true).join('');
|
||||
var symbol = symbols[name];
|
||||
if (symbol) {
|
||||
if (name === 'in' && this.tokens[this.tokens.length-1].id === 'not') {
|
||||
symbol = symbols['not in'];
|
||||
this.tokens.pop();
|
||||
} else if (name === 'not' && this.tokens[this.tokens.length-1].id === 'is') {
|
||||
symbol = symbols['is not'];
|
||||
this.tokens.pop();
|
||||
}
|
||||
this.tokens.push(create(symbol));
|
||||
} else {
|
||||
this.tokens.push(create(symbols['(name)'], {
|
||||
value: name
|
||||
}));
|
||||
}
|
||||
this.pop();
|
||||
return index;
|
||||
}
|
||||
this.builder().push(character);
|
||||
return index + 1;
|
||||
}
|
||||
};
|
||||
|
||||
exports.tokenize = function tokenize(str) {
|
||||
var index = 0,
|
||||
tokenizer = new Tokenizer(str);
|
||||
str += '\0';
|
||||
|
||||
do {
|
||||
index = tokenizer.feed(str, index);
|
||||
} while (index !== str.length);
|
||||
return tokenizer.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();
|
||||
}
|
||||
|
||||
exports.object = create({}, {});
|
||||
exports.bool = function (arg) { return !!arg; };
|
||||
exports.tuple = create(exports.object, {
|
||||
__contains__: function (value) {
|
||||
for(var i=0, len=this.values.length; i<len; ++i) {
|
||||
if (this.values[i] === value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
toJSON: function () {
|
||||
return this.values;
|
||||
}
|
||||
});
|
||||
exports.list = exports.tuple;
|
||||
exports.dict = create(exports.object, {
|
||||
toJSON: function () {
|
||||
return this.values;
|
||||
}
|
||||
});
|
||||
|
||||
exports.parse = function (toks) {
|
||||
var index = 0;
|
||||
token = toks[0];
|
||||
next = function () { return toks[++index]; };
|
||||
return expression();
|
||||
};
|
||||
var evaluate_operator = function (operator, a, b) {
|
||||
switch (operator) {
|
||||
case '==': case 'is': return a === b;
|
||||
case '!=': case 'is not': return a !== b;
|
||||
case '<': return a < b;
|
||||
case '<=': return a <= b;
|
||||
case '>': return a > b;
|
||||
case '>=': return a >= b;
|
||||
case 'in':
|
||||
if (typeof b === 'string') {
|
||||
return b.indexOf(a) !== -1;
|
||||
}
|
||||
return b.__contains__(a);
|
||||
case 'not in':
|
||||
if (typeof b === 'string') {
|
||||
return b.indexOf(a) === -1;
|
||||
}
|
||||
return !b.__contains__(a);
|
||||
}
|
||||
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
|
||||
};
|
||||
exports.evaluate = function (expr, context) {
|
||||
switch (expr.id) {
|
||||
case '(name)':
|
||||
var val = context[expr.value];
|
||||
if (val === undefined) {
|
||||
throw new Error("NameError: name '" + expr.value + "' is not defined");
|
||||
}
|
||||
return val;
|
||||
case '(string)':
|
||||
case '(number)':
|
||||
return expr.value;
|
||||
case '(constant)':
|
||||
if (expr.value === 'None')
|
||||
return null;
|
||||
else if (expr.value === 'False')
|
||||
return false;
|
||||
else if (expr.value === 'True')
|
||||
return true;
|
||||
throw new Error("SyntaxError: unknown constant '" + expr.value + "'");
|
||||
case '(comparator)':
|
||||
var result, left = exports.evaluate(expr.expressions[0], context);
|
||||
for(var i=0; i<expr.operators.length; ++i) {
|
||||
result = evaluate_operator(
|
||||
expr.operators[i],
|
||||
left,
|
||||
left = exports.evaluate(expr.expressions[i+1], context));
|
||||
if (!result) { return false; }
|
||||
}
|
||||
return true;
|
||||
case '-':
|
||||
if (expr.second) {
|
||||
throw new Error('SyntaxError: binary [-] not implemented yet');
|
||||
}
|
||||
return -(exports.evaluate(expr.first, context));
|
||||
case 'not':
|
||||
return !(exports.evaluate(expr.first, context));
|
||||
case 'and':
|
||||
return (exports.evaluate(expr.first, context)
|
||||
&& exports.evaluate(expr.second, context));
|
||||
case 'or':
|
||||
return (exports.evaluate(expr.first, context)
|
||||
|| exports.evaluate(expr.second, context));
|
||||
case '(':
|
||||
if (expr.second) {
|
||||
var fn = exports.evaluate(expr.first, context), args=[];
|
||||
for (var jj=0; jj<expr.second.length; ++jj) {
|
||||
args.push(exports.evaluate(
|
||||
expr.second[jj], context));
|
||||
}
|
||||
return fn.apply(null, args);
|
||||
}
|
||||
var tuple_exprs = expr.first,
|
||||
tuple_values = [];
|
||||
for (var j=0, len=tuple_exprs.length; j<len; ++j) {
|
||||
tuple_values.push(exports.evaluate(
|
||||
tuple_exprs[j], context));
|
||||
}
|
||||
return create(exports.tuple, {values: tuple_values});
|
||||
case '[':
|
||||
if (expr.second) {
|
||||
throw new Error('SyntaxError: indexing not implemented yet');
|
||||
}
|
||||
var list_exprs = expr.first, list_values = [];
|
||||
for (var k=0; k<list_exprs.length; ++k) {
|
||||
list_values.push(exports.evaluate(
|
||||
list_exprs[k], context));
|
||||
}
|
||||
return create(exports.list, {values: list_values});
|
||||
case '{':
|
||||
var dict_exprs = expr.first, dict_values = {};
|
||||
for(var l=0; l<dict_exprs.length; ++l) {
|
||||
dict_values[exports.evaluate(dict_exprs[l][0], context)] =
|
||||
exports.evaluate(dict_exprs[l][1], context);
|
||||
}
|
||||
return create(exports.dict, {values: dict_values});
|
||||
case '.':
|
||||
if (expr.second.id !== '(name)') {
|
||||
throw new Error('SyntaxError: ' + expr);
|
||||
}
|
||||
return exports.evaluate(expr.first, context)[expr.second.value];
|
||||
default:
|
||||
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
|
||||
}
|
||||
};
|
||||
exports.eval = function (str, context) {
|
||||
return exports.evaluate(
|
||||
exports.parse(
|
||||
exports.tokenize(
|
||||
str)),
|
||||
context);
|
||||
}
|
||||
})(typeof exports === 'undefined' ? py : exports);
|
|
@ -1,90 +0,0 @@
|
|||
var py = require('../lib/py.js'),
|
||||
assert = require('assert');
|
||||
|
||||
// Literals
|
||||
assert.strictEqual(py.eval('1'), 1);
|
||||
assert.strictEqual(py.eval('None'), null);
|
||||
assert.strictEqual(py.eval('False'), false);
|
||||
assert.strictEqual(py.eval('True'), true);
|
||||
assert.strictEqual(py.eval('"somestring"'), 'somestring');
|
||||
assert.strictEqual(py.eval("'somestring'"), 'somestring');
|
||||
assert.deepEqual(py.eval("()").toJSON(), []);
|
||||
assert.deepEqual(py.eval("[]").toJSON(), []);
|
||||
assert.deepEqual(py.eval("{}").toJSON(), {});
|
||||
assert.deepEqual(py.eval("(None, True, False, 0, 1, 'foo')").toJSON(),
|
||||
[null, true, false, 0, 1, 'foo']);
|
||||
assert.deepEqual(py.eval("[None, True, False, 0, 1, 'foo']").toJSON(),
|
||||
[null, true, false, 0, 1, 'foo']);
|
||||
assert.deepEqual(py.eval("{'foo': 1, foo: 2}", {foo: 'bar'}).toJSON(),
|
||||
{foo: 1, bar: 2});
|
||||
|
||||
// Equality tests
|
||||
assert.ok(py.eval(
|
||||
"foo == 'foo'", {foo: 'foo'}));
|
||||
// Inequality
|
||||
assert.ok(py.eval(
|
||||
"foo != bar", {foo: 'foo', bar: 'bar'}));
|
||||
|
||||
// Comparisons
|
||||
assert.ok(py.eval('3 < 5'));
|
||||
assert.ok(py.eval('5 >= 3'));
|
||||
assert.ok(py.eval('3 >= 3'));
|
||||
assert.ok(!py.eval('5 < 3'));
|
||||
assert.ok(py.eval('1 < 3 < 5'));
|
||||
assert.ok(py.eval('5 > 3 > 1'));
|
||||
assert.ok(py.eval('1 < 3 > 2 == 2 > -2 not in (0, 1, 2)'));
|
||||
// string rich comparisons
|
||||
assert.ok(py.eval(
|
||||
'date >= current', {date: '2010-06-08', current: '2010-06-05'}));
|
||||
|
||||
// Boolean operators
|
||||
assert.ok(py.eval(
|
||||
"foo == 'foo' or foo == 'bar'", {foo: 'bar'}));
|
||||
assert.ok(py.eval(
|
||||
"foo == 'foo' and bar == 'bar'", {foo: 'foo', bar: 'bar'}));
|
||||
// - lazyness, second clauses NameError if not short-circuited
|
||||
assert.ok(py.eval(
|
||||
"foo == 'foo' or bar == 'bar'", {foo: 'foo'}));
|
||||
assert.ok(!py.eval(
|
||||
"foo == 'foo' and bar == 'bar'", {foo: 'bar'}));
|
||||
|
||||
// contains (in)
|
||||
assert.ok(py.eval(
|
||||
"foo in ('foo', 'bar')", {foo: 'bar'}));
|
||||
assert.ok(py.eval('1 in (1, 2, 3, 4)'));
|
||||
assert.ok(!py.eval('1 in (2, 3, 4)'));
|
||||
assert.ok(py.eval('type in ("url",)', {type: 'url'}));
|
||||
assert.ok(!py.eval('type in ("url",)', {type: 'ur'}));
|
||||
assert.ok(py.eval('1 not in (2, 3, 4)'));
|
||||
assert.ok(py.eval('type not in ("url",)', {type: 'ur'}));
|
||||
|
||||
assert.ok(py.eval(
|
||||
"foo in ['foo', 'bar']", {foo: 'bar'}));
|
||||
// string contains
|
||||
assert.ok(py.eval('type in "view"', {type: 'view'}));
|
||||
assert.ok(!py.eval('type in "view"', {type: 'bob'}));
|
||||
assert.ok(py.eval('type in "url"', {type: 'ur'}));
|
||||
|
||||
// Literals
|
||||
assert.strictEqual(py.eval('False'), false);
|
||||
assert.strictEqual(py.eval('True'), true);
|
||||
assert.strictEqual(py.eval('None'), null);
|
||||
assert.ok(py.eval('foo == False', {foo: false}));
|
||||
assert.ok(!py.eval('foo == False', {foo: true}));
|
||||
|
||||
// conversions
|
||||
assert.strictEqual(
|
||||
py.eval('bool(date_deadline)', {bool: py.bool, date_deadline: '2008'}),
|
||||
true);
|
||||
|
||||
// getattr
|
||||
assert.ok(py.eval('foo.bar', {foo: {bar: true}}));
|
||||
assert.ok(!py.eval('foo.bar', {foo: {bar: false}}));
|
||||
|
||||
// complex expressions
|
||||
assert.ok(py.eval(
|
||||
"state=='pending' and not(date_deadline and (date_deadline < current_date))",
|
||||
{state: 'pending', date_deadline: false}));
|
||||
assert.ok(py.eval(
|
||||
"state=='pending' and not(date_deadline and (date_deadline < current_date))",
|
||||
{state: 'pending', date_deadline: '2010-05-08', current_date: '2010-05-08'}));;
|
|
@ -1237,7 +1237,7 @@ openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.we
|
|||
if (translation_bundle.lang_parameters) {
|
||||
this.parameters = translation_bundle.lang_parameters;
|
||||
this.parameters.grouping = py.eval(
|
||||
this.parameters.grouping).toJSON();
|
||||
this.parameters.grouping);
|
||||
}
|
||||
},
|
||||
add_module_translation: function(mod) {
|
||||
|
|
|
@ -158,7 +158,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
|
|||
var pair = this.colors[i],
|
||||
color = pair[0],
|
||||
expression = pair[1];
|
||||
if (py.evaluate(expression, _.extend({bool: py.bool}, context))) {
|
||||
if (py.evaluate(expression, context).toJSON()) {
|
||||
return 'color: ' + color + ';';
|
||||
}
|
||||
// TODO: handle evaluation errors
|
||||
|
|
|
@ -146,7 +146,7 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView#
|
|||
var pair = this.colors[i],
|
||||
color = pair[0],
|
||||
expression = pair[1];
|
||||
if (py.evaluate(expression, _.extend({bool: py.bool}, context))) {
|
||||
if (py.evaluate(expression, context).toJSON()) {
|
||||
return 'color: ' + color + ';';
|
||||
}
|
||||
// TODO: handle evaluation errors
|
||||
|
|
Loading…
Reference in New Issue