diff options
| author | Géry Debongnie <ged@odoo.com> | 2018-06-22 10:53:56 +0200 |
|---|---|---|
| committer | Adrien Dieudonne <adr@odoo.com> | 2018-07-06 14:09:09 +0200 |
| commit | 3248498f4844bbeb06768c064b6fd3a1f400030e (patch) | |
| tree | 61cf861b0bbb27ea3bfb183c1350eff8a683f5f2 /addons/web/static/src | |
| parent | eed4d586a026544facd35e3ca5184ef7f609c562 (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.js | 93 |
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, }; }); |
