[IMP] update py.parse to 0.2
bzr revid: xmo@openerp.com-20110929135449-ssommvnyof6auy29
This commit is contained in:
parent
c94872fc02
commit
d32d0c5637
|
@ -0,0 +1,5 @@
|
|||
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
|
||||
node: 87fb1b67d6a13f10a1a328104ee4d4b2c36801ec
|
||||
branch: default
|
||||
latesttag: 0.2
|
||||
latesttagdistance: 1
|
|
@ -0,0 +1 @@
|
|||
Parser and evaluator of Python expressions
|
|
@ -0,0 +1,14 @@
|
|||
* 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)
|
|
@ -5,7 +5,7 @@ var py = {};
|
|||
NAME = /^[a-zA-Z0-9_]$/;
|
||||
|
||||
var create = function (o, props) {
|
||||
function F() {};
|
||||
function F() {}
|
||||
F.prototype = o;
|
||||
var inst = new F;
|
||||
for(var name in props) {
|
||||
|
@ -13,7 +13,7 @@ var py = {};
|
|||
inst[name] = props[name];
|
||||
}
|
||||
return inst;
|
||||
}
|
||||
};
|
||||
|
||||
var symbols = {};
|
||||
var comparators = {};
|
||||
|
@ -26,17 +26,17 @@ var py = {};
|
|||
} else if (this.id === '(end)') {
|
||||
return '(end)';
|
||||
} else if (this.id === '(comparator)' ) {
|
||||
var out = ['(comparator', this.expressions[0]];
|
||||
var repr = ['(comparator', this.expressions[0]];
|
||||
for (var i=0;i<this.operators.length; ++i) {
|
||||
out.push(this.operators[i], this.expressions[i+1]);
|
||||
repr.push(this.operators[i], this.expressions[i+1]);
|
||||
}
|
||||
return out.join(' ') + ')';
|
||||
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];
|
||||
|
@ -57,7 +57,7 @@ var py = {};
|
|||
this.value = id;
|
||||
return this;
|
||||
};
|
||||
};
|
||||
}
|
||||
function prefix(id, bp, nud) {
|
||||
symbol(id).nud = nud || function () {
|
||||
this.first = expression(bp);
|
||||
|
@ -149,7 +149,7 @@ var py = {};
|
|||
infix('*', 120); infix('/', 120);
|
||||
infix('//', 120), infix('%', 120);
|
||||
|
||||
prefix('-', 130); prefix('+', 130); prefix('~', 130)
|
||||
prefix('-', 130); prefix('+', 130); prefix('~', 130);
|
||||
|
||||
infixr('**', 140);
|
||||
|
||||
|
@ -170,7 +170,7 @@ var py = {};
|
|||
if (token.id === ')') {
|
||||
break;
|
||||
}
|
||||
this.first.push(expression())
|
||||
this.first.push(expression());
|
||||
if (token.id !== ',') {
|
||||
break;
|
||||
}
|
||||
|
@ -202,11 +202,11 @@ var py = {};
|
|||
|
||||
};
|
||||
infix('[', 150, function (left) {
|
||||
this.first = left
|
||||
this.second = expression()
|
||||
advance("]")
|
||||
this.first = left;
|
||||
this.second = expression();
|
||||
advance("]");
|
||||
return this;
|
||||
})
|
||||
});
|
||||
symbol('[').nud = function () {
|
||||
this.first = [];
|
||||
if (token.id !== ']') {
|
||||
|
@ -253,8 +253,8 @@ var py = {};
|
|||
'!': ['='],
|
||||
'=': ['='],
|
||||
'/': ['/']
|
||||
}
|
||||
function Tokenizer(str) {
|
||||
};
|
||||
function Tokenizer() {
|
||||
this.states = ['initial'];
|
||||
this.tokens = [];
|
||||
}
|
||||
|
@ -369,17 +369,18 @@ var py = {};
|
|||
this.builder().push(character);
|
||||
return index + 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.tokenize = function tokenize(str) {
|
||||
var index = 0,
|
||||
str = str + '\0',
|
||||
tokenizer = new Tokenizer(str);
|
||||
str += '\0';
|
||||
|
||||
do {
|
||||
index = tokenizer.feed(str, index);
|
||||
} while (index !== str.length)
|
||||
} while (index !== str.length);
|
||||
return tokenizer.tokens;
|
||||
}
|
||||
};
|
||||
|
||||
var token, next;
|
||||
function expression(rbp) {
|
||||
|
@ -403,6 +404,7 @@ var py = {};
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -411,6 +413,15 @@ var py = {};
|
|||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
toJSON: function () {
|
||||
return this.values;
|
||||
}
|
||||
});
|
||||
exports.list = exports.tuple;
|
||||
exports.dict = create(exports.object, {
|
||||
toJSON: function () {
|
||||
return this.values;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -420,7 +431,7 @@ var py = {};
|
|||
next = function () { return toks[++index]; };
|
||||
return expression();
|
||||
};
|
||||
evaluate_operator = function (operator, a, b) {
|
||||
var evaluate_operator = function (operator, a, b) {
|
||||
switch (operator) {
|
||||
case '==': case 'is': return a === b;
|
||||
case '!=': case 'is not': return a !== b;
|
||||
|
@ -428,11 +439,19 @@ var py = {};
|
|||
case '<=': return a <= b;
|
||||
case '>': return a > b;
|
||||
case '>=': return a >= b;
|
||||
case 'in': return b.__contains__(a);
|
||||
case 'not in': return !b.__contains__(a);
|
||||
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)':
|
||||
|
@ -444,6 +463,14 @@ var py = {};
|
|||
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) {
|
||||
|
@ -455,10 +482,12 @@ var py = {};
|
|||
}
|
||||
return true;
|
||||
case '-':
|
||||
if (this.second) {
|
||||
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));
|
||||
|
@ -466,16 +495,43 @@ var py = {};
|
|||
return (exports.evaluate(expr.first, context)
|
||||
|| exports.evaluate(expr.second, context));
|
||||
case '(':
|
||||
if (this.second) {
|
||||
throw new Error('SyntaxError: functions not implemented yet');
|
||||
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 i=0, len=tuple_exprs.length; i<len; ++i) {
|
||||
for (var j=0, len=tuple_exprs.length; j<len; ++j) {
|
||||
tuple_values.push(exports.evaluate(
|
||||
tuple_exprs[i], context));
|
||||
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 + ']]');
|
||||
}
|
||||
|
@ -485,6 +541,6 @@ var py = {};
|
|||
exports.parse(
|
||||
exports.tokenize(
|
||||
str)),
|
||||
context);;
|
||||
context);
|
||||
}
|
||||
})(typeof exports === 'undefined' ? py : exports)
|
||||
})(typeof exports === 'undefined' ? py : exports);
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
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'}));;
|
Loading…
Reference in New Issue