bad98e7b2a08fb66848c5b1946a9c9190e0c08a4
[odoo/odoo.git] / addons / web / static / src / js / pyeval.js
1 /*
2  * py.js helpers and setup
3  */
4 (function() {
5
6     var instance = openerp;
7
8     instance.web.pyeval = {};
9
10     var obj = function () {};
11     obj.prototype = py.object;
12     var asJS = function (arg) {
13         if (arg instanceof obj) {
14             return arg.toJSON();
15         }
16         return arg;
17     };
18
19     var datetime = py.PY_call(py.object);
20
21     /**
22      * computes (Math.floor(a/b), a%b and passes that to the callback.
23      *
24      * returns the callback's result
25      */
26     var divmod = function (a, b, fn) {
27         var mod = a%b;
28         // in python, sign(a % b) === sign(b). Not in JS. If wrong side, add a
29         // round of b
30         if (mod > 0 && b < 0 || mod < 0 && b > 0) {
31             mod += b;
32         }
33         return fn(Math.floor(a/b), mod);
34     };
35     /**
36      * Passes the fractional and integer parts of x to the callback, returns
37      * the callback's result
38      */
39     var modf = function (x, fn) {
40         var mod = x%1;
41         if (mod < 0) {
42             mod += 1;
43         }
44         return fn(mod, Math.floor(x));
45     };
46     var zero = py.float.fromJSON(0);
47
48     // Port from pypy/lib_pypy/datetime.py
49     var DAYS_IN_MONTH = [null, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
50     var DAYS_BEFORE_MONTH = [null];
51     var dbm = 0;
52     for (var i=1; i<DAYS_IN_MONTH.length; ++i) {
53         DAYS_BEFORE_MONTH.push(dbm);
54         dbm += DAYS_IN_MONTH[i];
55     }
56     var is_leap = function (year) {
57         return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
58     };
59     var days_before_year = function (year) {
60         var y = year - 1;
61         return y*365 + Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400);
62     };
63     var days_in_month = function (year, month) {
64         if (month === 2 && is_leap(year)) {
65             return 29;
66         }
67         return DAYS_IN_MONTH[month];
68     };
69     var days_before_month = function (year, month) {
70         var post_leap_feb = month > 2 && is_leap(year);
71         return DAYS_BEFORE_MONTH[month]
72              + (post_leap_feb ? 1 : 0);
73     };
74     var ymd2ord = function (year, month, day) {
75         var dim = days_in_month(year, month);
76         if (!(1 <= day && day <= dim)) {
77             throw new Error("ValueError: day must be in 1.." + dim);
78         }
79         return days_before_year(year)
80              + days_before_month(year, month)
81              + day;
82     };
83     var DI400Y = days_before_year(401);
84     var DI100Y = days_before_year(101);
85     var DI4Y = days_before_year(5);
86     var assert = function (bool) {
87         if (!bool) {
88             throw new Error("AssertionError");
89         }
90     };
91     var ord2ymd = function (n) {
92         --n;
93         var n400, n100, n4, n1, n0;
94         divmod(n, DI400Y, function (_n400, n) {
95             n400 = _n400;
96             divmod(n, DI100Y, function (_n100, n) {
97                 n100 = _n100;
98                 divmod(n, DI4Y, function (_n4, n) {
99                     n4 = _n4;
100                     divmod(n, 365, function (_n1, n) {
101                         n1 = _n1;
102                         n0 = n;
103                     });
104                 });
105             });
106         });
107
108         n = n0;
109         var year = n400 * 400 + 1 + n100 * 100 + n4 * 4 + n1;
110         if (n1 == 4 || n100 == 100) {
111             assert(n0 === 0);
112             return {
113                 year: year - 1,
114                 month: 12,
115                 day: 31
116             };
117         }
118
119         var leapyear = n1 === 3 && (n4 !== 24 || n100 == 3);
120         assert(leapyear == is_leap(year));
121         var month = (n + 50) >> 5;
122         var preceding = DAYS_BEFORE_MONTH[month] + ((month > 2 && leapyear) ? 1 : 0);
123         if (preceding > n) {
124             --month;
125             preceding -= DAYS_IN_MONTH[month] + ((month === 2 && leapyear) ? 1 : 0);
126         }
127         n -= preceding;
128         return {
129             year: year,
130             month: month,
131             day: n+1
132         };
133     };
134
135     /**
136      * Converts the stuff passed in into a valid date, applying overflows as needed
137      */
138     var tmxxx = function (year, month, day, hour, minute, second, microsecond) {
139         hour = hour || 0; minute = minute || 0; second = second || 0;
140         microsecond = microsecond || 0;
141
142         if (microsecond < 0 || microsecond > 999999) {
143             divmod(microsecond, 1000000, function (carry, ms) {
144                 microsecond = ms;
145                 second += carry;
146             });
147         }
148         if (second < 0 || second > 59) {
149             divmod(second, 60, function (carry, s) {
150                 second = s;
151                 minute += carry;
152             });
153         }
154         if (minute < 0 || minute > 59) {
155             divmod(minute, 60, function (carry, m) {
156                 minute = m;
157                 hour += carry;
158             });
159         }
160         if (hour < 0 || hour > 23) {
161             divmod(hour, 24, function (carry, h) {
162                 hour = h;
163                 day += carry;
164             });
165         }
166         // That was easy.  Now it gets muddy:  the proper range for day
167         // can't be determined without knowing the correct month and year,
168         // but if day is, e.g., plus or minus a million, the current month
169         // and year values make no sense (and may also be out of bounds
170         // themselves).
171         // Saying 12 months == 1 year should be non-controversial.
172         if (month < 1 || month > 12) {
173             divmod(month-1, 12, function (carry, m) {
174                 month = m + 1;
175                 year += carry;
176             });
177         }
178         // Now only day can be out of bounds (year may also be out of bounds
179         // for a datetime object, but we don't care about that here).
180         // If day is out of bounds, what to do is arguable, but at least the
181         // method here is principled and explainable.
182         var dim = days_in_month(year, month);
183         if (day < 1 || day > dim) {
184             // Move day-1 days from the first of the month.  First try to
185             // get off cheap if we're only one day out of range (adjustments
186             // for timezone alone can't be worse than that).
187             if (day === 0) {
188                 --month;
189                 if (month > 0) {
190                     day = days_in_month(year, month);
191                 } else {
192                     --year; month=12; day=31;
193                 }
194             } else if (day == dim + 1) {
195                 ++month;
196                 day = 1;
197                 if (month > 12) {
198                     month = 1;
199                     ++year;
200                 }
201             } else {
202                 var r = ord2ymd(ymd2ord(year, month, 1) + (day - 1));
203                 year = r.year;
204                 month = r.month;
205                 day = r.day;
206             }
207         }
208         return {
209             year: year,
210             month: month,
211             day: day,
212             hour: hour,
213             minute: minute,
214             second: second,
215             microsecond: microsecond
216         };
217     };
218     datetime.timedelta = py.type('timedelta', null, {
219         __init__: function () {
220             var args = py.PY_parseArgs(arguments, [
221                 ['days', zero], ['seconds', zero], ['microseconds', zero],
222                 ['milliseconds', zero], ['minutes', zero], ['hours', zero],
223                 ['weeks', zero]
224             ]);
225
226             var d = 0, s = 0, m = 0;
227             var days = args.days.toJSON() + args.weeks.toJSON() * 7;
228             var seconds = args.seconds.toJSON()
229                         + args.minutes.toJSON() * 60
230                         + args.hours.toJSON() * 3600;
231             var microseconds = args.microseconds.toJSON()
232                              + args.milliseconds.toJSON() * 1000;
233
234             // Get rid of all fractions, and normalize s and us.
235             // Take a deep breath <wink>.
236             var daysecondsfrac = modf(days, function (dayfrac, days) {
237                 d = days;
238                 if (dayfrac) {
239                     return modf(dayfrac * 24 * 3600, function (dsf, dsw) {
240                         s = dsw;
241                         return dsf;
242                     });
243                 }
244                 return 0;
245             });
246
247             var secondsfrac = modf(seconds, function (sf, s) {
248                 seconds = s;
249                 return sf + daysecondsfrac;
250             });
251             divmod(seconds, 24*3600, function (days, seconds) {
252                 d += days;
253                 s += seconds;
254             });
255             // seconds isn't referenced again before redefinition
256
257             microseconds += secondsfrac * 1e6;
258             divmod(microseconds, 1000000, function (seconds, microseconds) {
259                 divmod(seconds, 24*3600, function (days, seconds) {
260                     d += days;
261                     s += seconds;
262                     m += Math.round(microseconds);
263                 });
264             });
265
266             // Carrying still possible here?
267
268             this.days = d;
269             this.seconds = s;
270             this.microseconds = m;
271         },
272         __str__: function () {
273             var hh, mm, ss;
274             divmod(this.seconds, 60, function (m, s) {
275                 divmod(m, 60, function (h, m) {
276                     hh = h;
277                     mm = m;
278                     ss = s;
279                 });
280             });
281             var s = _.str.sprintf("%d:%02d:%02d", hh, mm, ss);
282             if (this.days) {
283                 s = _.str.sprintf("%d day%s, %s",
284                     this.days,
285                     (this.days != 1 && this.days != -1) ? 's' : '',
286                     s);
287             }
288             if (this.microseconds) {
289                 s = _.str.sprintf("%s.%06d", s, this.microseconds);
290             }
291             return py.str.fromJSON(s);
292         },
293         __eq__: function (other) {
294             if (!py.PY_isInstance(other, datetime.timedelta)) {
295                 return py.False;
296             }
297             return (this.days === other.days
298                 && this.seconds === other.seconds
299                 && this.microseconds === other.microseconds)
300                     ? py.True : py.False;
301         },
302         __add__: function (other) {
303             if (!py.PY_isInstance(other, datetime.timedelta)) {
304                 return py.NotImplemented;
305             }
306             return py.PY_call(datetime.timedelta, [
307                 py.float.fromJSON(this.days + other.days),
308                 py.float.fromJSON(this.seconds + other.seconds),
309                 py.float.fromJSON(this.microseconds + other.microseconds)
310             ]);
311         },
312         __radd__: function (other) { return this.__add__(other); },
313         __sub__: function (other) {
314             if (!py.PY_isInstance(other, datetime.timedelta)) {
315                 return py.NotImplemented;
316             }
317             return py.PY_call(datetime.timedelta, [
318                 py.float.fromJSON(this.days - other.days),
319                 py.float.fromJSON(this.seconds - other.seconds),
320                 py.float.fromJSON(this.microseconds - other.microseconds)
321             ]);
322         },
323         __rsub__: function (other) {
324             if (!py.PY_isInstance(other, datetime.timedelta)) {
325                 return py.NotImplemented;
326             }
327             return this.__neg__().__add__(other);
328         },
329         __neg__: function () {
330             return py.PY_call(datetime.timedelta, [
331                 py.float.fromJSON(-this.days),
332                 py.float.fromJSON(-this.seconds),
333                 py.float.fromJSON(-this.microseconds)
334             ]);
335         },
336         __pos__: function () { return this; },
337         __mul__: function (other) {
338             if (!py.PY_isInstance(other, py.float)) {
339                 return py.NotImplemented;
340             }
341             var n = other.toJSON();
342             return py.PY_call(datetime.timedelta, [
343                 py.float.fromJSON(this.days * n),
344                 py.float.fromJSON(this.seconds * n),
345                 py.float.fromJSON(this.microseconds * n)
346             ]);
347         },
348         __rmul__: function (other) { return this.__mul__(other); },
349         __div__: function (other) {
350             if (!py.PY_isInstance(other, py.float)) {
351                 return py.NotImplemented;
352             }
353             var usec = ((this.days * 24 * 3600) + this.seconds) * 1000000
354                         + this.microseconds;
355             return py.PY_call(
356                 datetime.timedelta, [
357                     zero, zero, py.float.fromJSON(usec / other.toJSON())]);
358         },
359         __floordiv__: function (other) { return this.__div__(other); },
360         total_seconds: function () {
361             return py.float.fromJSON(
362                 this.days * 86400
363               + this.seconds
364               + this.microseconds / 1000000);
365         },
366         __nonzero__: function () {
367             return (!!this.days || !!this.seconds || !!this.microseconds)
368                 ? py.True
369                 : py.False;
370         }
371     });
372     datetime.datetime = py.type('datetime', null, {
373         __init__: function () {
374             var zero = py.float.fromJSON(0);
375             var args = py.PY_parseArgs(arguments, [
376                 'year', 'month', 'day',
377                 ['hour', zero], ['minute', zero], ['second', zero],
378                 ['microsecond', zero], ['tzinfo', py.None]
379             ]);
380             for(var key in args) {
381                 if (!args.hasOwnProperty(key)) { continue; }
382                 this[key] = asJS(args[key]);
383             }
384         },
385         replace: function () {
386             var args = py.PY_parseArgs(arguments, [
387                 ['year', py.None], ['month', py.None], ['day', py.None],
388                 ['hour', py.None], ['minute', py.None], ['second', py.None],
389                 ['microsecond', py.None] // FIXME: tzinfo, can't use None as valid input
390             ]);
391             var params = {};
392             for(var key in args) {
393                 if (!args.hasOwnProperty(key)) { continue; }
394
395                 var arg = args[key];
396                 params[key] = (arg === py.None ? this[key] : asJS(arg));
397             }
398             return py.PY_call(datetime.datetime, params);
399         },
400         strftime: function () {
401             var self = this;
402             var args = py.PY_parseArgs(arguments, 'format');
403             return py.str.fromJSON(args.format.toJSON()
404                 .replace(/%([A-Za-z])/g, function (m, c) {
405                     switch (c) {
406                     case 'Y': return _.str.sprintf('%04d', self.year);
407                     case 'm': return _.str.sprintf('%02d', self.month);
408                     case 'd': return _.str.sprintf('%02d', self.day);
409                     case 'H': return _.str.sprintf('%02d', self.hour);
410                     case 'M': return _.str.sprintf('%02d', self.minute);
411                     case 'S': return _.str.sprintf('%02d', self.second);
412                     }
413                     throw new Error('ValueError: No known conversion for ' + m);
414                 }));
415         },
416         now: py.classmethod.fromJSON(function () {
417             var d = new Date;
418             return py.PY_call(datetime.datetime, [
419                 d.getFullYear(), d.getMonth() + 1, d.getDate(),
420                 d.getHours(), d.getMinutes(), d.getSeconds(),
421                 d.getMilliseconds() * 1000]);
422         }),
423         today: py.classmethod.fromJSON(function () {
424             var dt_class = py.PY_getAttr(datetime, 'datetime');
425             return py.PY_call(py.PY_getAttr(dt_class, 'now'));
426         }),
427         utcnow: py.classmethod.fromJSON(function () {
428             var d = new Date();
429             return py.PY_call(datetime.datetime,
430                 [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),
431                  d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(),
432                  d.getUTCMilliseconds() * 1000]);
433         }),
434         combine: py.classmethod.fromJSON(function () {
435             var args = py.PY_parseArgs(arguments, 'date time');
436             return py.PY_call(datetime.datetime, [
437                 py.PY_getAttr(args.date, 'year'),
438                 py.PY_getAttr(args.date, 'month'),
439                 py.PY_getAttr(args.date, 'day'),
440                 py.PY_getAttr(args.time, 'hour'),
441                 py.PY_getAttr(args.time, 'minute'),
442                 py.PY_getAttr(args.time, 'second')
443             ]);
444         }),
445         toJSON: function () {
446             return new Date(
447                 this.year,
448                 this.month - 1,
449                 this.day,
450                 this.hour,
451                 this.minute,
452                 this.second,
453                 this.microsecond / 1000);
454         },
455     });
456     datetime.date = py.type('date', null, {
457         __init__: function () {
458             var args = py.PY_parseArgs(arguments, 'year month day');
459             this.year = asJS(args.year);
460             this.month = asJS(args.month);
461             this.day = asJS(args.day);
462         },
463         strftime: function () {
464             var self = this;
465             var args = py.PY_parseArgs(arguments, 'format');
466             return py.str.fromJSON(args.format.toJSON()
467                 .replace(/%([A-Za-z])/g, function (m, c) {
468                     switch (c) {
469                     case 'Y': return self.year;
470                     case 'm': return _.str.sprintf('%02d', self.month);
471                     case 'd': return _.str.sprintf('%02d', self.day);
472                     }
473                     throw new Error('ValueError: No known conversion for ' + m);
474                 }));
475         },
476         __eq__: function (other) {
477             return (this.year === other.year
478                  && this.month === other.month
479                  && this.day === other.day)
480                 ? py.True : py.False;
481         },
482         __add__: function (other) {
483             if (!py.PY_isInstance(other, datetime.timedelta)) {
484                 return py.NotImplemented;
485             }
486             var s = tmxxx(this.year, this.month, this.day + other.days);
487             return datetime.date.fromJSON(s.year, s.month, s.day);
488         },
489         __radd__: function (other) { return this.__add__(other); },
490         __sub__: function (other) {
491             if (py.PY_isInstance(other, datetime.timedelta)) {
492                 return this.__add__(other.__neg__());
493             }
494             if (py.PY_isInstance(other, datetime.date)) {
495                 // FIXME: getattr and sub API methods
496                 return py.PY_call(datetime.timedelta, [
497                     py.PY_subtract(
498                         py.PY_call(py.PY_getAttr(this, 'toordinal')),
499                         py.PY_call(py.PY_getAttr(other, 'toordinal')))
500                 ]);
501             }
502             return py.NotImplemented;
503         },
504         toordinal: function () {
505             return py.float.fromJSON(ymd2ord(this.year, this.month, this.day));
506         },
507         fromJSON: function (year, month, day) {
508             return py.PY_call(datetime.date, [year, month, day]);
509         },
510         today: py.classmethod.fromJSON(function () {
511             var d = new Date;
512             return py.PY_call(datetime.date, [
513                 d.getFullYear(), d.getMonth() + 1, d.getDate()]);
514         }),
515     });
516     /**
517         Returns the current local date, which means the date on the client (which can be different
518         compared to the date of the server).
519
520         @return {datetime.date}
521     */
522     var context_today = function() {
523         var d = new Date();
524         return py.PY_call(
525             datetime.date, [d.getFullYear(), d.getMonth() + 1, d.getDate()]);
526     };
527     datetime.time = py.type('time', null, {
528         __init__: function () {
529             var zero = py.float.fromJSON(0);
530             var args = py.PY_parseArgs(arguments, [
531                 ['hour', zero], ['minute', zero], ['second', zero], ['microsecond', zero],
532                 ['tzinfo', py.None]
533             ]);
534
535             for(var k in args) {
536                 if (!args.hasOwnProperty(k)) { continue; }
537                 this[k] = asJS(args[k]);
538             }
539         }
540     });
541     var time = py.PY_call(py.object);
542     time.strftime = py.PY_def.fromJSON(function () {
543         var args  = py.PY_parseArgs(arguments, 'format');
544         var dt_class = py.PY_getAttr(datetime, 'datetime');
545         var d = py.PY_call(py.PY_getAttr(dt_class, 'utcnow'));
546         return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
547     });
548
549     var args = _.map(('year month day hour minute second microsecond '
550                     + 'years months weeks days hours minutes secondes microseconds '
551                     + 'weekday leakdays yearday nlyearday').split(' '), function (arg) {
552         return [arg, null];
553     });
554     args.unshift('*');
555     var relativedelta = py.type('relativedelta', null, {
556         __init__: function () {
557             this.ops = py.PY_parseArgs(arguments, args);
558         },
559         __add__: function (other) {
560             if (!py.PY_isInstance(other, datetime.date)) {
561                 return py.NotImplemented;
562             }
563             // TODO: test this whole mess
564             var year = asJS(this.ops.year) || asJS(other.year);
565             if (asJS(this.ops.years)) {
566                 year += asJS(this.ops.years);
567             }
568
569             var month = asJS(this.ops.month) || asJS(other.month);
570             if (asJS(this.ops.months)) {
571                 month += asJS(this.ops.months);
572                 // FIXME: no divmod in JS?
573                 while (month < 1) {
574                     year -= 1;
575                     month += 12;
576                 }
577                 while (month > 12) {
578                     year += 1;
579                     month -= 12;
580                 }
581             }
582
583             var lastMonthDay = new Date(year, month, 0).getDate();
584             var day = asJS(this.ops.day) || asJS(other.day);
585             if (day > lastMonthDay) { day = lastMonthDay; }
586             var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
587             if (days_offset) {
588                 day = new Date(year, month-1, day + days_offset).getDate();
589             }
590             // TODO: leapdays?
591             // TODO: hours, minutes, seconds? Not used in XML domains
592             // TODO: weekday?
593             // FIXME: use date.replace
594             return py.PY_call(datetime.date, [
595                 py.float.fromJSON(year),
596                 py.float.fromJSON(month),
597                 py.float.fromJSON(day)
598             ]);
599         },
600         __radd__: function (other) {
601             return this.__add__(other);
602         },
603
604         __sub__: function (other) {
605             if (!py.PY_isInstance(other, datetime.date)) {
606                 return py.NotImplemented;
607             }
608             // TODO: test this whole mess
609             var year = asJS(this.ops.year) || asJS(other.year);
610             if (asJS(this.ops.years)) {
611                 year -= asJS(this.ops.years);
612             }
613
614             var month = asJS(this.ops.month) || asJS(other.month);
615             if (asJS(this.ops.months)) {
616                 month -= asJS(this.ops.months);
617                 // FIXME: no divmod in JS?
618                 while (month < 1) {
619                     year -= 1;
620                     month += 12;
621                 }
622                 while (month > 12) {
623                     year += 1;
624                     month -= 12;
625                 }
626             }
627
628             var lastMonthDay = new Date(year, month, 0).getDate();
629             var day = asJS(this.ops.day) || asJS(other.day);
630             if (day > lastMonthDay) { day = lastMonthDay; }
631             var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
632             if (days_offset) {
633                 day = new Date(year, month-1, day - days_offset).getDate();
634             }
635             // TODO: leapdays?
636             // TODO: hours, minutes, seconds? Not used in XML domains
637             // TODO: weekday?
638             return py.PY_call(datetime.date, [
639                 py.float.fromJSON(year),
640                 py.float.fromJSON(month),
641                 py.float.fromJSON(day)
642             ]);
643         },
644         __rsub__: function (other) {
645             return this.__sub__(other);
646         }
647     });
648
649     // recursively wraps JS objects passed into the context to attributedicts
650     // which jsonify back to JS objects
651     var wrap = function (value) {
652         if (value === null) { return py.None; }
653
654         switch (typeof value) {
655         case 'undefined': throw new Error("No conversion for undefined");
656         case 'boolean': return py.bool.fromJSON(value);
657         case 'number': return py.float.fromJSON(value);
658         case 'string': return py.str.fromJSON(value);
659         }
660
661         switch(value.constructor) {
662         case Object: return wrapping_dict.fromJSON(value);
663         case Array: return wrapping_list.fromJSON(value);
664         }
665
666         throw new Error("ValueError: unable to wrap " + value);
667     };
668     var wrapping_dict = py.type('wrapping_dict', null, {
669         __init__: function () {
670             this._store = {};
671         },
672         __getitem__: function (key) {
673             var k = key.toJSON();
674             if (!(k in this._store)) {
675                 throw new Error("KeyError: '" + k + "'");
676             }
677             return wrap(this._store[k]);
678         },
679         __getattr__: function (key) {
680             return this.__getitem__(py.str.fromJSON(key));
681         },
682         get: function () {
683             var args = py.PY_parseArgs(arguments, ['k', ['d', py.None]]);
684
685             if (!(args.k.toJSON() in this._store)) { return args.d; }
686             return this.__getitem__(args.k);
687         },
688         fromJSON: function (d) {
689             var instance = py.PY_call(wrapping_dict);
690             instance._store = d;
691             return instance;
692         },
693         toJSON: function () {
694             return this._store;
695         },
696     });
697     var wrapping_list = py.type('wrapping_list', null, {
698         __init__: function () {
699             this._store = [];
700         },
701         __getitem__: function (index) {
702             return wrap(this._store[index.toJSON()]);
703         },
704         fromJSON: function (ar) {
705             var instance = py.PY_call(wrapping_list);
706             instance._store = ar;
707             return instance;
708         },
709         toJSON: function () {
710             return this._store;
711         },
712     });
713     var wrap_context = function (context) {
714         for (var k in context) {
715             if (!context.hasOwnProperty(k)) { continue; }
716             var val = context[k];
717
718             if (val === null) { continue; }
719             if (val.constructor === Array) {
720                 context[k] = wrapping_list.fromJSON(val);
721             } else if (val.constructor === Object
722                        && !py.PY_isInstance(val, py.object)) {
723                 context[k] = wrapping_dict.fromJSON(val);
724             }
725         }
726         return context;
727     };
728
729     var eval_contexts = function (contexts, evaluation_context) {
730         evaluation_context = _.extend(instance.web.pyeval.context(), evaluation_context || {});
731         return _(contexts).reduce(function (result_context, ctx) {
732             // __eval_context evaluations can lead to some of `contexts`'s
733             // values being null, skip them as well as empty contexts
734             if (_.isEmpty(ctx)) { return result_context; }
735             if (_.isString(ctx)) {
736                 // wrap raw strings in context
737                 ctx = { __ref: 'context', __debug: ctx };
738             }
739             var evaluated = ctx;
740             switch(ctx.__ref) {
741             case 'context':
742                 evaluation_context.context = evaluation_context;
743                 evaluated = py.eval(ctx.__debug, wrap_context(evaluation_context));
744                 break;
745             case 'compound_context':
746                 var eval_context = eval_contexts([ctx.__eval_context]);
747                 evaluated = eval_contexts(
748                     ctx.__contexts, _.extend({}, evaluation_context, eval_context));
749                 break;
750             }
751             // add newly evaluated context to evaluation context for following
752             // siblings
753             _.extend(evaluation_context, evaluated);
754             return _.extend(result_context, evaluated);
755         }, {});
756     };
757     var eval_domains = function (domains, evaluation_context) {
758         evaluation_context = _.extend(instance.web.pyeval.context(), evaluation_context || {});
759         var result_domain = [];
760         _(domains).each(function (domain) {
761             if (_.isString(domain)) {
762                 // wrap raw strings in domain
763                 domain = { __ref: 'domain', __debug: domain };
764             }
765             switch(domain.__ref) {
766             case 'domain':
767                 evaluation_context.context = evaluation_context;
768                 result_domain.push.apply(
769                     result_domain, py.eval(domain.__debug, wrap_context(evaluation_context)));
770                 break;
771             case 'compound_domain':
772                 var eval_context = eval_contexts([domain.__eval_context]);
773                 result_domain.push.apply(
774                     result_domain, eval_domains(
775                         domain.__domains, _.extend(
776                             {}, evaluation_context, eval_context)));
777                 break;
778             default:
779                 result_domain.push.apply(result_domain, domain);
780             }
781         });
782         return result_domain;
783     };
784     var eval_groupbys = function (contexts, evaluation_context) {
785         evaluation_context = _.extend(instance.web.pyeval.context(), evaluation_context || {});
786         var result_group = [];
787         _(contexts).each(function (ctx) {
788             if (_.isString(ctx)) {
789                 // wrap raw strings in context
790                 ctx = { __ref: 'context', __debug: ctx };
791             }
792             var group;
793             var evaluated = ctx;
794             switch(ctx.__ref) {
795             case 'context':
796                 evaluation_context.context = evaluation_context;
797                 evaluated = py.eval(ctx.__debug, wrap_context(evaluation_context));
798                 break;
799             case 'compound_context':
800                 var eval_context = eval_contexts([ctx.__eval_context]);
801                 evaluated = eval_contexts(
802                     ctx.__contexts, _.extend({}, evaluation_context, eval_context));
803                 break;
804             }
805             group = evaluated.group_by;
806             if (!group) { return; }
807             if (typeof group === 'string') {
808                 result_group.push(group);
809             } else if (group instanceof Array) {
810                 result_group.push.apply(result_group, group);
811             } else {
812                 throw new Error('Got invalid groupby {{'
813                         + JSON.stringify(group) + '}}');
814             }
815             _.extend(evaluation_context, evaluated);
816         });
817         return result_group;
818     };
819
820     instance.web.pyeval.context = function () {
821         return _.extend({
822             datetime: datetime,
823             context_today: context_today,
824             time: time,
825             relativedelta: relativedelta,
826             current_date: py.PY_call(
827                 time.strftime, [py.str.fromJSON('%Y-%m-%d')]),
828         }, instance.session.user_context);
829     };
830
831     /**
832      * @param {String} type "domains", "contexts" or "groupbys"
833      * @param {Array} object domains or contexts to evaluate
834      * @param {Object} [context] evaluation context
835      */
836     instance.web.pyeval.eval = function (type, object, context, options) {
837         options = options || {};
838         context = _.extend(instance.web.pyeval.context(), context || {});
839
840         //noinspection FallthroughInSwitchStatementJS
841         switch(type) {
842         case 'context':
843         case 'contexts':
844             if (type === 'context')
845                 object = [object];
846             return eval_contexts((options.no_user_context ? [] : [instance.session.user_context]).concat(object), context);
847         case 'domain':
848         case 'domains':
849             if (type === 'domain')
850                 object = [object];
851             return eval_domains(object, context);
852         case 'groupbys':
853             return eval_groupbys(object, context);
854         }
855         throw new Error("Unknow evaluation type " + type);
856     };
857
858     var eval_arg = function (arg) {
859         if (typeof arg !== 'object' || !arg.__ref) { return arg; }
860         switch(arg.__ref) {
861         case 'domain': case 'compound_domain':
862             return instance.web.pyeval.eval('domains', [arg]);
863         case 'context': case 'compound_context':
864             return instance.web.pyeval.eval('contexts', [arg]);
865         default:
866             throw new Error(instance.web._t("Unknown nonliteral type " + arg.__ref));
867         }
868     };
869     /**
870      * If args or kwargs are unevaluated contexts or domains (compound or not),
871      * evaluated them in-place.
872      *
873      * Potentially mutates both parameters.
874      *
875      * @param args
876      * @param kwargs
877      */
878     instance.web.pyeval.ensure_evaluated = function (args, kwargs) {
879         for (var i=0; i<args.length; ++i) {
880             args[i] = eval_arg(args[i]);
881         }
882         for (var k in kwargs) {
883             if (!kwargs.hasOwnProperty(k)) { continue; }
884             kwargs[k] = eval_arg(kwargs[k]);
885         }
886     };
887     instance.web.pyeval.eval_domains_and_contexts = function (source) {
888         return new $.Deferred(function (d) {setTimeout(function () {
889             var result;
890             try {
891                 result = instance.web.pyeval.sync_eval_domains_and_contexts(source);
892             }
893             catch (e) {
894                 result = { error: {
895                     code: 400,
896                     message: instance.web._t("Evaluation Error"),
897                     data: {
898                         type: 'local_exception',
899                         debug: _.str.sprintf(
900                                 instance.web._t("Local evaluation failure\n%s\n\n%s"),
901                                 e.message, JSON.stringify(source))
902                     }
903                 }};                
904             }
905             d.resolve(result);
906         }, 0); });
907     };
908     instance.web.pyeval.sync_eval_domains_and_contexts = function (source) {
909         var contexts = ([instance.session.user_context] || []).concat(source.contexts);
910         // see Session.eval_context in Python
911         return {
912             context: instance.web.pyeval.eval('contexts', contexts),
913             domain: instance.web.pyeval.eval('domains', source.domains),
914             group_by: instance.web.pyeval.eval('groupbys', source.group_by_seq || [])
915         };
916     };
917 })();