[FIX] update py.js for operators &al, implement basic crummy version of relativedelta
bzr revid: xmo@openerp.com-20120305085532-0vcz6j0m985gjuz1
This commit is contained in:
parent
12358af73b
commit
f8a93bc284
|
@ -1,5 +1,5 @@
|
||||||
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
|
repo: 076b192d0d8ab2b92d1dbcfa3da055382f30eaea
|
||||||
node: 7be96c381d9302b4aafaf5eb5381083b0731a9aa
|
node: 5adb2d9c89e53a6445e3799f9c4dc9110458c149
|
||||||
branch: default
|
branch: default
|
||||||
latesttag: 0.4
|
latesttag: 0.5
|
||||||
latesttagdistance: 7
|
latesttagdistance: 9
|
||||||
|
|
|
@ -10,6 +10,17 @@ specification document is the `Python 2.7 Expressions spec
|
||||||
<http://docs.python.org/reference/expressions.html>`_ (along with the
|
<http://docs.python.org/reference/expressions.html>`_ (along with the
|
||||||
lexical analysis part).
|
lexical analysis part).
|
||||||
|
|
||||||
|
Syntax
|
||||||
|
------
|
||||||
|
|
||||||
|
* Lambdas and ternaries should be parsed but are not implemented (in
|
||||||
|
the evaluator)
|
||||||
|
* Only floats are implemented, ``int`` literals are parsed as floats.
|
||||||
|
* Octal and hexadecimal literals are not implemented
|
||||||
|
* Srings are backed by JavaScript strings and probably behave like
|
||||||
|
``unicode`` more than like ``str``
|
||||||
|
* Slices don't work
|
||||||
|
|
||||||
Builtins
|
Builtins
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -89,9 +100,10 @@ Collections Abstract Base Classes
|
||||||
Hashable are kind-of implemented as well)
|
Hashable are kind-of implemented as well)
|
||||||
|
|
||||||
Numeric type emulation
|
Numeric type emulation
|
||||||
Basically not implemented, the only part of it which is
|
Operators are implemented (but not tested), ``abs``, ``divmod``
|
||||||
implemented is the unary ``-`` (because it's used to create
|
and ``pow`` builtins are not implemented yet. Neither are ``oct``
|
||||||
negative floats, they're parsed as a negated positive number)
|
and ``hex`` but I'm not sure we care (I'm not sure we care about
|
||||||
|
``pow`` or even ``divmod`` either, for that matter)
|
||||||
|
|
||||||
Utilities
|
Utilities
|
||||||
---------
|
---------
|
||||||
|
|
|
@ -34,15 +34,14 @@ Base methods requirement
|
||||||
************************
|
************************
|
||||||
|
|
||||||
* ``__getattr__``
|
* ``__getattr__``
|
||||||
* ? ``__getitem``
|
|
||||||
* ``__call__``
|
|
||||||
* ``or``
|
|
||||||
* ``toJS`` / ``toJSON``
|
|
||||||
* ``dict.get``
|
* ``dict.get``
|
||||||
* ``datetime.time.today``
|
|
||||||
* ``datetime.time.strftime``
|
|
||||||
* ``time.strftime``
|
|
||||||
* ``__add__`` / ``__radd__``
|
|
||||||
* ``__sub__`` / ``__rsub__``
|
|
||||||
* ``__len__``
|
* ``__len__``
|
||||||
* ``__nonzero__``
|
|
||||||
|
In datamodel, not implemented in any type, untested
|
||||||
|
***************************************************
|
||||||
|
|
||||||
|
* a[b]
|
||||||
|
|
||||||
|
* a + b, a - b, a * b, ...
|
||||||
|
|
||||||
|
* +a, ~a
|
||||||
|
|
|
@ -104,6 +104,15 @@ var py = {};
|
||||||
symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
|
symbol(':'); symbol(')'); symbol(']'); symbol('}'); symbol(',');
|
||||||
symbol('else');
|
symbol('else');
|
||||||
|
|
||||||
|
infix('=', 10, function (left) {
|
||||||
|
if (left.id !== '(name)') {
|
||||||
|
throw new Error("Expected keyword argument name, got " + token.id);
|
||||||
|
}
|
||||||
|
this.first = left;
|
||||||
|
this.second = expression();
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
|
||||||
symbol('lambda', 20).nud = function () {
|
symbol('lambda', 20).nud = function () {
|
||||||
this.first = [];
|
this.first = [];
|
||||||
if (token.id !== ':') {
|
if (token.id !== ':') {
|
||||||
|
@ -362,7 +371,7 @@ var py = {};
|
||||||
|
|
||||||
if (val instanceof py.object
|
if (val instanceof py.object
|
||||||
|| val === py.object
|
|| val === py.object
|
||||||
|| py.issubclass.__call__(val, py.object) === py.True) {
|
|| py.issubclass.__call__([val, py.object]) === py.True) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,7 +452,7 @@ var py = {};
|
||||||
// TODO: second argument should be class
|
// TODO: second argument should be class
|
||||||
return val.__get__(this);
|
return val.__get__(this);
|
||||||
}
|
}
|
||||||
if (typeof val === 'function' && !this.hasOwnProperty(val)) {
|
if (typeof val === 'function' && !this.hasOwnProperty(name)) {
|
||||||
// val is a method from the class
|
// val is a method from the class
|
||||||
return new PY_instancemethod(val, this);
|
return new PY_instancemethod(val, this);
|
||||||
}
|
}
|
||||||
|
@ -476,6 +485,7 @@ var py = {};
|
||||||
py.NotImplemented = new NotImplementedType();
|
py.NotImplemented = new NotImplementedType();
|
||||||
var booleans_initialized = false;
|
var booleans_initialized = false;
|
||||||
py.bool = py.type(function bool(value) {
|
py.bool = py.type(function bool(value) {
|
||||||
|
value = (value instanceof Array) ? value[0] : value;
|
||||||
// The only actual instance of py.bool should be py.True
|
// The only actual instance of py.bool should be py.True
|
||||||
// and py.False. Return the new instance of py.bool if we
|
// and py.False. Return the new instance of py.bool if we
|
||||||
// are initializing py.True and py.False, otherwise always
|
// are initializing py.True and py.False, otherwise always
|
||||||
|
@ -493,7 +503,26 @@ var py = {};
|
||||||
py.False = new py.bool();
|
py.False = new py.bool();
|
||||||
booleans_initialized = true;
|
booleans_initialized = true;
|
||||||
py.float = py.type(function float(value) {
|
py.float = py.type(function float(value) {
|
||||||
this._value = value;
|
value = (value instanceof Array) ? value[0] : value;
|
||||||
|
if (value === undefined) { this._value = 0; return; }
|
||||||
|
if (value instanceof py.float) { return value; }
|
||||||
|
if (typeof value === 'number' || value instanceof Number) {
|
||||||
|
this._value = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string' || value instanceof String) {
|
||||||
|
this._value = parseFloat(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value instanceof py.object && '__float__' in value) {
|
||||||
|
var res = value.__float__();
|
||||||
|
if (res instanceof py.float) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
throw new Error('TypeError: __float__ returned non-float (type ' +
|
||||||
|
res.constructor.name + ')');
|
||||||
|
}
|
||||||
|
throw new Error('TypeError: float() argument must be a string or a number');
|
||||||
}, py.object, {
|
}, py.object, {
|
||||||
__eq__: function (other) {
|
__eq__: function (other) {
|
||||||
return this._value === other._value ? py.True : py.False;
|
return this._value === other._value ? py.True : py.False;
|
||||||
|
@ -514,9 +543,25 @@ var py = {};
|
||||||
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||||
return this._value >= other._value ? py.True : py.False;
|
return this._value >= other._value ? py.True : py.False;
|
||||||
},
|
},
|
||||||
|
__add__: function (other) {
|
||||||
|
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||||
|
return new py.float(this._value + other._value);
|
||||||
|
},
|
||||||
__neg__: function () {
|
__neg__: function () {
|
||||||
return new py.float(-this._value);
|
return new py.float(-this._value);
|
||||||
},
|
},
|
||||||
|
__sub__: function (other) {
|
||||||
|
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||||
|
return new py.float(this._value - other._value);
|
||||||
|
},
|
||||||
|
__mul__: function (other) {
|
||||||
|
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||||
|
return new py.float(this._value * other._value);
|
||||||
|
},
|
||||||
|
__div__: function (other) {
|
||||||
|
if (!(other instanceof py.float)) { return py.NotImplemented; }
|
||||||
|
return new py.float(this._value / other._value);
|
||||||
|
},
|
||||||
__nonzero__: function () {
|
__nonzero__: function () {
|
||||||
return this._value ? py.True : py.False;
|
return this._value ? py.True : py.False;
|
||||||
},
|
},
|
||||||
|
@ -525,7 +570,17 @@ var py = {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
py.str = py.type(function str(s) {
|
py.str = py.type(function str(s) {
|
||||||
this._value = s;
|
s = (s instanceof Array) ? s[0] : s;
|
||||||
|
if (s === undefined) { this._value = ''; return; }
|
||||||
|
if (s instanceof py.str) { return s; }
|
||||||
|
if (typeof s === 'string' || s instanceof String) {
|
||||||
|
this._value = s;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var v = s.__str__();
|
||||||
|
if (v instanceof py.str) { return v; }
|
||||||
|
throw new Error('TypeError: __str__ returned non-string (type ' +
|
||||||
|
v.constructor.name + ')');
|
||||||
}, py.object, {
|
}, py.object, {
|
||||||
__eq__: function (other) {
|
__eq__: function (other) {
|
||||||
if (other instanceof py.str && this._value === other._value) {
|
if (other instanceof py.str && this._value === other._value) {
|
||||||
|
@ -549,6 +604,10 @@ var py = {};
|
||||||
if (!(other instanceof py.str)) { return py.NotImplemented; }
|
if (!(other instanceof py.str)) { return py.NotImplemented; }
|
||||||
return this._value >= other._value ? py.True : py.False;
|
return this._value >= other._value ? py.True : py.False;
|
||||||
},
|
},
|
||||||
|
__add__: function (other) {
|
||||||
|
if (!(other instanceof py.str)) { return py.NotImplemented; }
|
||||||
|
return new py.str(this._value + other._value);
|
||||||
|
},
|
||||||
__nonzero__: function () {
|
__nonzero__: function () {
|
||||||
return this._value.length ? py.True : py.False;
|
return this._value.length ? py.True : py.False;
|
||||||
},
|
},
|
||||||
|
@ -596,9 +655,9 @@ var py = {};
|
||||||
this._inst = null;
|
this._inst = null;
|
||||||
this._func = nativefunc;
|
this._func = nativefunc;
|
||||||
}, py.object, {
|
}, py.object, {
|
||||||
__call__: function () {
|
__call__: function (args, kwargs) {
|
||||||
// don't want to rewrite __call__ for instancemethod
|
// don't want to rewrite __call__ for instancemethod
|
||||||
return this._func.apply(this._inst, arguments);
|
return this._func.call(this._inst, args, kwargs);
|
||||||
},
|
},
|
||||||
toJSON: function () {
|
toJSON: function () {
|
||||||
return this._func;
|
return this._func;
|
||||||
|
@ -610,13 +669,69 @@ var py = {};
|
||||||
this._func = nativefunc;
|
this._func = nativefunc;
|
||||||
}, py.def, {});
|
}, py.def, {});
|
||||||
|
|
||||||
py.issubclass = new py.def(function issubclass(derived, parent) {
|
py.issubclass = new py.def(function issubclass(args) {
|
||||||
|
var derived = args[0], parent = args[1];
|
||||||
// still hurts my brain that this can work
|
// still hurts my brain that this can work
|
||||||
return derived.prototype instanceof py.object
|
return derived.prototype instanceof py.object
|
||||||
? py.True
|
? py.True
|
||||||
: py.False;
|
: py.False;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// All binary operators with fallbacks, so they can be applied generically
|
||||||
|
var PY_operators = {
|
||||||
|
'==': ['eq', 'eq', function (a, b) { return a === b; }],
|
||||||
|
'!=': ['ne', 'ne', function (a, b) { return a !== b; }],
|
||||||
|
'<': ['lt', 'gt', function (a, b) {return a.constructor.name < b.constructor.name;}],
|
||||||
|
'<=': ['le', 'ge', function (a, b) {return a.constructor.name <= b.constructor.name;}],
|
||||||
|
'>': ['gt', 'lt', function (a, b) {return a.constructor.name > b.constructor.name;}],
|
||||||
|
'>=': ['ge', 'le', function (a, b) {return a.constructor.name >= b.constructor.name;}],
|
||||||
|
|
||||||
|
'+': ['add', 'radd'],
|
||||||
|
'-': ['sub', 'rsub'],
|
||||||
|
'*': ['mul', 'rmul'],
|
||||||
|
'/': ['div', 'rdiv'],
|
||||||
|
'//': ['floordiv', 'rfloordiv'],
|
||||||
|
'%': ['mod', 'rmod'],
|
||||||
|
'**': ['pow', 'rpow'],
|
||||||
|
'<<': ['lshift', 'rlshift'],
|
||||||
|
'>>': ['rshift', 'rrshift'],
|
||||||
|
'&': ['and', 'rand'],
|
||||||
|
'^': ['xor', 'rxor'],
|
||||||
|
'|': ['or', 'ror']
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Implements operator fallback/reflection.
|
||||||
|
*
|
||||||
|
* First two arguments are the objects to apply the operator on,
|
||||||
|
* in their actual order (ltr).
|
||||||
|
*
|
||||||
|
* Third argument is the actual operator.
|
||||||
|
*
|
||||||
|
* If the operator methods raise exceptions, those exceptions are
|
||||||
|
* not intercepted.
|
||||||
|
*/
|
||||||
|
var PY_op = function (o1, o2, op) {
|
||||||
|
var r;
|
||||||
|
var methods = PY_operators[op];
|
||||||
|
var forward = '__' + methods[0] + '__', reverse = '__' + methods[1] + '__';
|
||||||
|
var otherwise = methods[2];
|
||||||
|
|
||||||
|
if (forward in o1 && (r = o1[forward](o2)) !== py.NotImplemented) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if (reverse in o2 && (r = o2[reverse](o1)) !== py.NotImplemented) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
if (otherwise) {
|
||||||
|
return PY_ensurepy(otherwise(o1, o2));
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
"TypeError: unsupported operand type(s) for " + op + ": '"
|
||||||
|
+ o1.constructor.name + "' and '"
|
||||||
|
+ o2.constructor.name + "'");
|
||||||
|
};
|
||||||
|
|
||||||
var PY_builtins = {
|
var PY_builtins = {
|
||||||
type: py.type,
|
type: py.type,
|
||||||
|
|
||||||
|
@ -643,30 +758,16 @@ var py = {};
|
||||||
var evaluate_operator = function (operator, a, b) {
|
var evaluate_operator = function (operator, a, b) {
|
||||||
var v;
|
var v;
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case '==': return a.__eq__(b);
|
|
||||||
case 'is': return a === b ? py.True : py.False;
|
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 'is not': return a !== b ? py.True : py.False;
|
||||||
case '<':
|
|
||||||
v = a.__lt__(b);
|
|
||||||
if (v !== py.NotImplemented) { return v; }
|
|
||||||
return PY_ensurepy(a.constructor.name < b.constructor.name);
|
|
||||||
case '<=':
|
|
||||||
v = a.__le__(b);
|
|
||||||
if (v !== py.NotImplemented) { return v; }
|
|
||||||
return PY_ensurepy(a.constructor.name <= b.constructor.name);
|
|
||||||
case '>':
|
|
||||||
v = a.__gt__(b);
|
|
||||||
if (v !== py.NotImplemented) { return v; }
|
|
||||||
return PY_ensurepy(a.constructor.name > b.constructor.name);
|
|
||||||
case '>=':
|
|
||||||
v = a.__ge__(b);
|
|
||||||
if (v !== py.NotImplemented) { return v; }
|
|
||||||
return PY_ensurepy(a.constructor.name >= b.constructor.name);
|
|
||||||
case 'in':
|
case 'in':
|
||||||
return b.__contains__(a);
|
return b.__contains__(a);
|
||||||
case 'not in':
|
case 'not in':
|
||||||
return b.__contains__(a) === py.True ? py.False : py.True;
|
return b.__contains__(a) === py.True ? py.False : py.True;
|
||||||
|
case '==': case '!=':
|
||||||
|
case '<': case '<=':
|
||||||
|
case '>': case '>=':
|
||||||
|
return PY_op(a, b, operator);
|
||||||
}
|
}
|
||||||
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
|
throw new Error('SyntaxError: unknown comparator [[' + operator + ']]');
|
||||||
};
|
};
|
||||||
|
@ -700,11 +801,6 @@ var py = {};
|
||||||
if (result === py.False) { return py.False; }
|
if (result === py.False) { return py.False; }
|
||||||
}
|
}
|
||||||
return py.True;
|
return py.True;
|
||||||
case '-':
|
|
||||||
if (expr.second) {
|
|
||||||
throw new Error('SyntaxError: binary [-] not implemented yet');
|
|
||||||
}
|
|
||||||
return (py.evaluate(expr.first, context)).__neg__();
|
|
||||||
case 'not':
|
case 'not':
|
||||||
return py.evaluate(expr.first, context).__nonzero__() === py.True
|
return py.evaluate(expr.first, context).__nonzero__() === py.True
|
||||||
? py.False
|
? py.False
|
||||||
|
@ -723,12 +819,20 @@ var py = {};
|
||||||
return py.evaluate(expr.second, context);
|
return py.evaluate(expr.second, context);
|
||||||
case '(':
|
case '(':
|
||||||
if (expr.second) {
|
if (expr.second) {
|
||||||
var callable = py.evaluate(expr.first, context), args=[];
|
var callable = py.evaluate(expr.first, context);
|
||||||
|
var args = [], kwargs = {};
|
||||||
for (var jj=0; jj<expr.second.length; ++jj) {
|
for (var jj=0; jj<expr.second.length; ++jj) {
|
||||||
args.push(py.evaluate(
|
var arg = expr.second[jj];
|
||||||
expr.second[jj], context));
|
if (arg.id !== '=') {
|
||||||
|
// arg
|
||||||
|
args.push(py.evaluate(arg, context));
|
||||||
|
} else {
|
||||||
|
// kwarg
|
||||||
|
kwargs[arg.first.value] =
|
||||||
|
py.evaluate(arg.second, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return callable.__call__.apply(callable, args);
|
return callable.__call__(args, kwargs);
|
||||||
}
|
}
|
||||||
var tuple_exprs = expr.first,
|
var tuple_exprs = expr.first,
|
||||||
tuple_values = [];
|
tuple_values = [];
|
||||||
|
@ -741,7 +845,8 @@ var py = {};
|
||||||
return t;
|
return t;
|
||||||
case '[':
|
case '[':
|
||||||
if (expr.second) {
|
if (expr.second) {
|
||||||
throw new Error('SyntaxError: indexing not implemented yet');
|
return py.evaluate(expr.first, context)
|
||||||
|
.__getitem__(expr.evaluate(expr.second, context));
|
||||||
}
|
}
|
||||||
var list_exprs = expr.first, list_values = [];
|
var list_exprs = expr.first, list_values = [];
|
||||||
for (var k=0; k<list_exprs.length; ++k) {
|
for (var k=0; k<list_exprs.length; ++k) {
|
||||||
|
@ -765,6 +870,27 @@ var py = {};
|
||||||
}
|
}
|
||||||
return py.evaluate(expr.first, context)
|
return py.evaluate(expr.first, context)
|
||||||
.__getattribute__(expr.second.value);
|
.__getattribute__(expr.second.value);
|
||||||
|
// numerical operators
|
||||||
|
case '~':
|
||||||
|
return (py.evaluate(expr.first, context)).__invert__();
|
||||||
|
case '+':
|
||||||
|
if (!expr.second) {
|
||||||
|
return (py.evaluate(expr.first, context)).__pos__();
|
||||||
|
}
|
||||||
|
case '-':
|
||||||
|
if (!expr.second) {
|
||||||
|
return (py.evaluate(expr.first, context)).__neg__();
|
||||||
|
}
|
||||||
|
case '*': case '/': case '//':
|
||||||
|
case '%':
|
||||||
|
case '**':
|
||||||
|
case '<<': case '>>':
|
||||||
|
case '&': case '^': case '|':
|
||||||
|
return PY_op(
|
||||||
|
py.evaluate(expr.first, context),
|
||||||
|
py.evaluate(expr.second, context),
|
||||||
|
expr.id);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
|
throw new Error('SyntaxError: Unknown node [[' + expr.id + ']]');
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,14 @@ expect.Assertion.prototype.tokens = function (n) {
|
||||||
'expected ' + this.obj + ' to not have an end token');
|
'expected ' + this.obj + ' to not have an end token');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
expect.Assertion.prototype.named = function (value) {
|
||||||
|
this.assert(this.obj.id === '(name)',
|
||||||
|
'expected ' + this.obj + ' to be a name token',
|
||||||
|
'expected ' + this.obj + ' not to be a name token');
|
||||||
|
this.assert(this.obj.value === value,
|
||||||
|
'expected ' + this.obj + ' to have tokenized ' + value,
|
||||||
|
'expected ' + this.obj + ' not to have tokenized ' + value);
|
||||||
|
};
|
||||||
expect.Assertion.prototype.constant = function (value) {
|
expect.Assertion.prototype.constant = function (value) {
|
||||||
this.assert(this.obj.id === '(constant)',
|
this.assert(this.obj.id === '(constant)',
|
||||||
'expected ' + this.obj + ' to be a constant token',
|
'expected ' + this.obj + ' to be a constant token',
|
||||||
|
@ -98,4 +106,27 @@ describe('Tokenizer', function () {
|
||||||
expect(toks[1].id).to.be(')');
|
expect(toks[1].id).to.be(')');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('functions', function () {
|
||||||
|
it('tokenizes kwargs', function () {
|
||||||
|
var toks = py.tokenize('foo(bar=3, qux=4)');
|
||||||
|
expect(toks).to.have.tokens(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Parser', function () {
|
||||||
|
describe('functions', function () {
|
||||||
|
var ast = py.parse(py.tokenize('foo(bar=3, qux=4)'));
|
||||||
|
expect(ast.id).to.be('(');
|
||||||
|
expect(ast.first).to.be.named('foo');
|
||||||
|
|
||||||
|
args = ast.second;
|
||||||
|
expect(args[0].id).to.be('=');
|
||||||
|
expect(args[0].first).to.be.named('bar');
|
||||||
|
expect(args[0].second).to.be.number(3);
|
||||||
|
|
||||||
|
expect(args[1].id).to.be('=');
|
||||||
|
expect(args[1].first).to.be.named('qux');
|
||||||
|
expect(args[1].second).to.be.number(4);
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -262,6 +262,15 @@ describe('Attribute access', function () {
|
||||||
});
|
});
|
||||||
expect(py.eval('foo.bar()', {foo: o})).to.be('ok');
|
expect(py.eval('foo.bar()', {foo: o})).to.be('ok');
|
||||||
});
|
});
|
||||||
|
it('should not convert function attributes into methods', function () {
|
||||||
|
var o = new py.object();
|
||||||
|
o.bar = new py.type(function bar() {});
|
||||||
|
o.bar.__getattribute__ = function () {
|
||||||
|
return o.bar.baz;
|
||||||
|
}
|
||||||
|
o.bar.baz = py.True;
|
||||||
|
expect(py.eval('foo.bar.baz', {foo: o})).to.be(true);
|
||||||
|
});
|
||||||
it('should work on instance attributes', function () {
|
it('should work on instance attributes', function () {
|
||||||
var typ = py.type(function MyType() {
|
var typ = py.type(function MyType() {
|
||||||
this.attr = new py.float(3);
|
this.attr = new py.float(3);
|
||||||
|
@ -296,16 +305,37 @@ describe('Callables', function () {
|
||||||
});
|
});
|
||||||
expect(py.eval('MyType()', {MyType: typ})).to.be(true);
|
expect(py.eval('MyType()', {MyType: typ})).to.be(true);
|
||||||
});
|
});
|
||||||
|
it('should accept kwargs', function () {
|
||||||
|
expect(py.eval('foo(ok=True)', {
|
||||||
|
foo: function foo() { return py.True; }
|
||||||
|
})).to.be(true);
|
||||||
|
});
|
||||||
|
it('should be able to get its kwargs', function () {
|
||||||
|
expect(py.eval('foo(ok=True)', {
|
||||||
|
foo: function foo(args, kwargs) { return kwargs.ok; }
|
||||||
|
})).to.be(true);
|
||||||
|
});
|
||||||
|
it('should be able to have both args and kwargs', function () {
|
||||||
|
expect(py.eval('foo(1, 2, 3, ok=True, nok=False)', {
|
||||||
|
foo: function (args, kwargs) {
|
||||||
|
expect(args).to.have.length(3);
|
||||||
|
expect(args[0].toJSON()).to.be(1);
|
||||||
|
expect(kwargs).to.only.have.keys('ok', 'nok')
|
||||||
|
expect(kwargs.nok.toJSON()).to.be(false);
|
||||||
|
return kwargs.ok;
|
||||||
|
}
|
||||||
|
})).to.be(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('issubclass', function () {
|
describe('issubclass', function () {
|
||||||
it('should say a type is its own subclass', function () {
|
it('should say a type is its own subclass', function () {
|
||||||
expect(py.issubclass.__call__(py.dict, py.dict).toJSON())
|
expect(py.issubclass.__call__([py.dict, py.dict]).toJSON())
|
||||||
.to.be(true);
|
.to.be(true);
|
||||||
expect(py.eval('issubclass(dict, dict)'))
|
expect(py.eval('issubclass(dict, dict)'))
|
||||||
.to.be(true);
|
.to.be(true);
|
||||||
});
|
});
|
||||||
it('should work with subtypes', function () {
|
it('should work with subtypes', function () {
|
||||||
expect(py.issubclass.__call__(py.bool, py.object).toJSON())
|
expect(py.issubclass.__call__([py.bool, py.object]).toJSON())
|
||||||
.to.be(true);
|
.to.be(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -313,4 +343,37 @@ describe('builtins', function () {
|
||||||
it('should aways be available', function () {
|
it('should aways be available', function () {
|
||||||
expect(py.eval('bool("foo")')).to.be(true);
|
expect(py.eval('bool("foo")')).to.be(true);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('numerical protocols', function () {
|
||||||
|
describe('True numbers (float)', function () {
|
||||||
|
describe('Basic arithmetic', function () {
|
||||||
|
it('can be added', function () {
|
||||||
|
expect(py.eval('1 + 1')).to.be(2);
|
||||||
|
expect(py.eval('1.5 + 2')).to.be(3.5);
|
||||||
|
expect(py.eval('1 + -1')).to.be(0);
|
||||||
|
});
|
||||||
|
it('can be subtracted', function () {
|
||||||
|
expect(py.eval('1 - 1')).to.be(0);
|
||||||
|
expect(py.eval('1.5 - 2')).to.be(-0.5);
|
||||||
|
expect(py.eval('2 - 1.5')).to.be(0.5);
|
||||||
|
});
|
||||||
|
it('can be multiplied', function () {
|
||||||
|
expect(py.eval('1 * 3')).to.be(3);
|
||||||
|
expect(py.eval('0 * 5')).to.be(0);
|
||||||
|
expect(py.eval('42 * -2')).to.be(-84);
|
||||||
|
});
|
||||||
|
it('can be divided', function () {
|
||||||
|
expect(py.eval('1 / 2')).to.be(0.5);
|
||||||
|
expect(py.eval('2 / 1')).to.be(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Strings', function () {
|
||||||
|
describe('Basic arithmetics operators', function () {
|
||||||
|
it('can be added (concatenation)', function () {
|
||||||
|
expect(py.eval('"foo" + "bar"')).to.be('foobar');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -322,19 +322,34 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
|
||||||
return this.session_init();
|
return this.session_init();
|
||||||
},
|
},
|
||||||
test_eval_get_context: function () {
|
test_eval_get_context: function () {
|
||||||
|
var asJS = function (arg) {
|
||||||
|
if (arg instanceof py.object) {
|
||||||
|
return arg.toJSON();
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
};
|
||||||
|
|
||||||
var datetime = new py.object();
|
var datetime = new py.object();
|
||||||
|
datetime.datetime = new py.type(function datetime() {
|
||||||
|
throw new Error('datetime.datetime not implemented');
|
||||||
|
});
|
||||||
var date = datetime.date = new py.type(function date(y, m, d) {
|
var date = datetime.date = new py.type(function date(y, m, d) {
|
||||||
this._year = y;
|
if (y instanceof Array) {
|
||||||
this._month = m;
|
d = y[2];
|
||||||
this._day = d;
|
m = y[1];
|
||||||
|
y = y[0];
|
||||||
|
}
|
||||||
|
this.year = asJS(y);
|
||||||
|
this.month = asJS(m);
|
||||||
|
this.day = asJS(d);
|
||||||
}, py.object, {
|
}, py.object, {
|
||||||
strftime: function (format) {
|
strftime: function (args) {
|
||||||
var f = format.toJSON(), self = this;
|
var f = asJS(args[0]), self = this;
|
||||||
return new py.str(f.replace(/%([A-Za-z])/g, function (m, c) {
|
return new py.str(f.replace(/%([A-Za-z])/g, function (m, c) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'Y': return self._year;
|
case 'Y': return self.year;
|
||||||
case 'm': return _.str.sprintf('%02d', self._month);
|
case 'm': return _.str.sprintf('%02d', self.month);
|
||||||
case 'd': return _.str.sprintf('%02d', self._day);
|
case 'd': return _.str.sprintf('%02d', self.day);
|
||||||
}
|
}
|
||||||
throw new Error('ValueError: No known conversion for ' + m);
|
throw new Error('ValueError: No known conversion for ' + m);
|
||||||
}));
|
}));
|
||||||
|
@ -350,16 +365,107 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
return new date(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
|
return new date(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
|
||||||
});
|
});
|
||||||
|
datetime.time = new py.type(function time() {
|
||||||
|
throw new Error('datetime.time not implemented');
|
||||||
|
});
|
||||||
|
|
||||||
var time = new py.object();
|
var time = new py.object();
|
||||||
time.strftime = new py.def(function (format) {
|
time.strftime = new py.def(function (args) {
|
||||||
return date.today.__call__().strftime(format);
|
return date.today.__call__().strftime(args);
|
||||||
|
});
|
||||||
|
|
||||||
|
var relativedelta = new py.type(function relativedelta(args, kwargs) {
|
||||||
|
if (!_.isEmpty(args)) {
|
||||||
|
throw new Error('Extraction of relative deltas from existing datetimes not supported');
|
||||||
|
}
|
||||||
|
this.ops = kwargs;
|
||||||
|
}, py.object, {
|
||||||
|
__add__: function (other) {
|
||||||
|
if (!(other instanceof datetime.date)) {
|
||||||
|
return py.NotImplemented;
|
||||||
|
}
|
||||||
|
// TODO: test this whole mess
|
||||||
|
var year = asJS(this.ops.year) || asJS(other.year);
|
||||||
|
if (asJS(this.ops.years)) {
|
||||||
|
year += asJS(this.ops.years);
|
||||||
|
}
|
||||||
|
|
||||||
|
var month = asJS(this.ops.month) || asJS(other.month);
|
||||||
|
if (asJS(this.ops.months)) {
|
||||||
|
month += asJS(this.ops.months);
|
||||||
|
// FIXME: no divmod in JS?
|
||||||
|
while (month < 1) {
|
||||||
|
year -= 1;
|
||||||
|
month += 12;
|
||||||
|
}
|
||||||
|
while (month > 12) {
|
||||||
|
year += 1;
|
||||||
|
month -= 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastMonthDay = new Date(year, month, 0).getDate();
|
||||||
|
var day = asJS(this.ops.day) || asJS(other.day);
|
||||||
|
if (day > lastMonthDay) { day = lastMonthDay; }
|
||||||
|
var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
|
||||||
|
if (days_offset) {
|
||||||
|
day = new Date(year, month-1, day + days_offset).getDate();
|
||||||
|
}
|
||||||
|
// TODO: leapdays?
|
||||||
|
// TODO: hours, minutes, seconds? Not used in XML domains
|
||||||
|
// TODO: weekday?
|
||||||
|
return new datetime.date(year, month, day);
|
||||||
|
},
|
||||||
|
__radd__: function (other) {
|
||||||
|
return this.__add__(other);
|
||||||
|
},
|
||||||
|
|
||||||
|
__sub__: function (other) {
|
||||||
|
if (!(other instanceof datetime.date)) {
|
||||||
|
return py.NotImplemented;
|
||||||
|
}
|
||||||
|
// TODO: test this whole mess
|
||||||
|
var year = asJS(this.ops.year) || asJS(other.year);
|
||||||
|
if (asJS(this.ops.years)) {
|
||||||
|
year -= asJS(this.ops.years);
|
||||||
|
}
|
||||||
|
|
||||||
|
var month = asJS(this.ops.month) || asJS(other.month);
|
||||||
|
if (asJS(this.ops.months)) {
|
||||||
|
month -= asJS(this.ops.months);
|
||||||
|
// FIXME: no divmod in JS?
|
||||||
|
while (month < 1) {
|
||||||
|
year -= 1;
|
||||||
|
month += 12;
|
||||||
|
}
|
||||||
|
while (month > 12) {
|
||||||
|
year += 1;
|
||||||
|
month -= 12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastMonthDay = new Date(year, month, 0).getDate();
|
||||||
|
var day = asJS(this.ops.day) || asJS(other.day);
|
||||||
|
if (day > lastMonthDay) { day = lastMonthDay; }
|
||||||
|
var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
|
||||||
|
if (days_offset) {
|
||||||
|
day = new Date(year, month-1, day - days_offset).getDate();
|
||||||
|
}
|
||||||
|
// TODO: leapdays?
|
||||||
|
// TODO: hours, minutes, seconds? Not used in XML domains
|
||||||
|
// TODO: weekday?
|
||||||
|
return new datetime.date(year, month, day);
|
||||||
|
},
|
||||||
|
__rsub__: function (other) {
|
||||||
|
return this.__sub__(other);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: new py.float(this.uid),
|
uid: new py.float(this.uid),
|
||||||
datetime: datetime,
|
datetime: datetime,
|
||||||
time: time
|
time: time,
|
||||||
|
relativedelta: relativedelta
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
test_eval: function (source, expected) {
|
test_eval: function (source, expected) {
|
||||||
|
|
Loading…
Reference in New Issue