summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorGéry Debongnie <ged@odoo.com>2018-06-22 10:53:56 +0200
committerAdrien Dieudonne <adr@odoo.com>2018-07-06 14:09:09 +0200
commit3248498f4844bbeb06768c064b6fd3a1f400030e (patch)
tree61cf861b0bbb27ea3bfb183c1350eff8a683f5f2 /addons/web/static/src
parenteed4d586a026544facd35e3ca5184ef7f609c562 (diff)
[IMP] web: implement formatAST utility function
In this commit, we implement a function which format a py.js abstract syntax tree into a string. This is really useful whenever we need to manipulate python expressions.
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/js/core/py_utils.js93
1 files changed, 93 insertions, 0 deletions
diff --git a/addons/web/static/src/js/core/py_utils.js b/addons/web/static/src/js/core/py_utils.js
index 8590965cee2..c077cfc2e7e 100644
--- a/addons/web/static/src/js/core/py_utils.js
+++ b/addons/web/static/src/js/core/py_utils.js
@@ -314,6 +314,98 @@ function py_eval(expr, context) {
return py.eval(expr, _.extend({}, context || {}, {"true": true, "false": false, "null": null}));
}
+// Binding power for prefix operator is not accessible in the AST generated by
+// py.js, so we have to hardcode some values here
+var BINDING_POWERS = {
+ or: 30,
+ and: 40,
+ not: 50,
+};
+
+/**
+ * Convert a python AST (generated by py.js) to a string form, which should
+ * represent the same AST.
+ *
+ * @param {Object} ast a valid AST obtained by py.js, which represent a python
+ * expression
+ * @param {integer} [lbp=0] a binding power. This is necessary to be able to
+ * format sub expressions: the + sub expression in "3 * (a + 2)" should be
+ * formatted with parenthesis, because its binding power is lower than the
+ * binding power of *.
+ * @returns {string}
+ */
+function formatAST(ast, lbp) {
+ lbp = lbp || 0;
+ switch (ast.id) {
+ // basic values
+ case "(number)":
+ return String(ast.value);
+ case "(string)":
+ return "'" + ast.value + "'";
+ case "(constant)":
+ return ast.value;
+ case "(name)":
+ return ast.value;
+ case "[":
+ if (ast.second) {
+ // read a value in a dictionary: d['a']
+ return formatAST(ast.first) + '[' + formatAST(ast.second) + ']';
+ } else {
+ // list: [1, 2]
+ var values = ast.first.map(formatAST);
+ return '[' + values.join(', ') + ']';
+ }
+ case "{":
+ var keyValues = ast.first.map(function (kv) {
+ return formatAST(kv[0]) + ': ' + formatAST(kv[1]);
+ });
+ return '{' + keyValues.join(', ') + '}';
+
+ // relations
+ case "=":
+ return formatAST(ast.first) + ' ' + ast.id + ' ' + formatAST(ast.second);
+ // operators
+ case "-":
+ case "+":
+ case "~":
+ case "*":
+ case "**":
+ case "%":
+ case "//":
+ case "and":
+ case "or":
+ if (ast.second) {
+ // infix
+ var r = formatAST(ast.first, ast.lbp) + ' ' + ast.id + ' ' + formatAST(ast.second, ast.lbp);
+ if (ast.lbp < lbp) {
+ r = '(' + r + ')';
+ }
+ return r;
+ }
+ // prefix
+ // real lbp is not accessible, it is inside a closure
+ var actualBP = BINDING_POWERS[ast.id] || 130;
+ return ast.id + formatAST(ast.first, actualBP);
+ case ".":
+ return formatAST(ast.first, ast.lbp) + '.' + formatAST(ast.second);
+ case "not":
+ return "not " + formatAST(ast.first);
+ case "(comparator)":
+ var operator = ast.operators[0]
+ return formatAST(ast.expressions[0]) + ' ' + operator + ' ' + formatAST(ast.expressions[1]);
+
+ // function call
+ case "(":
+ if (ast.second) {
+ // this is a function call: f(a, b)
+ return formatAST(ast.first) + '(' + ast.second.map(formatAST).join(', ') + ')';
+ } else {
+ // this is a tuple
+ return '(' + ast.first.map(formatAST).join(', ') + ')';
+ }
+ }
+ throw new Error("Unimplemented python construct");
+}
return {
context: pycontext,
@@ -321,6 +413,7 @@ return {
eval: pyeval,
eval_domains_and_contexts: eval_domains_and_contexts,
py_eval: py_eval,
+ formatAST: formatAST,
};
});