summaryrefslogtreecommitdiff
path: root/addons/web/static/lib/py.parse
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/lib/py.parse')
-rw-r--r--addons/web/static/lib/py.parse/.hg_archival.txt5
-rw-r--r--addons/web/static/lib/py.parse/README1
-rw-r--r--addons/web/static/lib/py.parse/TODO.rst14
-rw-r--r--addons/web/static/lib/py.parse/lib/py.js546
-rw-r--r--addons/web/static/lib/py.parse/test/test.js90
5 files changed, 656 insertions, 0 deletions
diff --git a/addons/web/static/lib/py.parse/.hg_archival.txt b/addons/web/static/lib/py.parse/.hg_archival.txt
new file mode 100644
index 00000000000..43d5d80a9e4
--- /dev/null
+++ b/addons/web/static/lib/py.parse/.hg_archival.txt
@@ -0,0 +1,5 @@
+repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
+node: 87fb1b67d6a13f10a1a328104ee4d4b2c36801ec
+branch: default
+latesttag: 0.2
+latesttagdistance: 1
diff --git a/addons/web/static/lib/py.parse/README b/addons/web/static/lib/py.parse/README
new file mode 100644
index 00000000000..453f001ee15
--- /dev/null
+++ b/addons/web/static/lib/py.parse/README
@@ -0,0 +1 @@
+Parser and evaluator of Python expressions
diff --git a/addons/web/static/lib/py.parse/TODO.rst b/addons/web/static/lib/py.parse/TODO.rst
new file mode 100644
index 00000000000..3638b681ddc
--- /dev/null
+++ b/addons/web/static/lib/py.parse/TODO.rst
@@ -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)
diff --git a/addons/web/static/lib/py.parse/lib/py.js b/addons/web/static/lib/py.parse/lib/py.js
new file mode 100644
index 00000000000..0f2415f1252
--- /dev/null
+++ b/addons/web/static/lib/py.parse/lib/py.js
@@ -0,0 +1,546 @@
+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 (!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);
diff --git a/addons/web/static/lib/py.parse/test/test.js b/addons/web/static/lib/py.parse/test/test.js
new file mode 100644
index 00000000000..0b4ddc1b623
--- /dev/null
+++ b/addons/web/static/lib/py.parse/test/test.js
@@ -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'}));;