odoo/addons/web/static/lib/py.js/lib/py.js

751 lines
24 KiB
JavaScript

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);