我有一个由目标语言的解析表达式语法生成的AST,它将通过遍历其节点编译为源语言。一个简单的来源
像(10 + 20) * 2
一样应该生成以下表示,作为本机ECMAScript对象:
var ast = {
"type": "Stmt",
"body": [
{
"type": "Expr",
"expression": {
"type": "BinaryExpr",
"operator": "*",
"left": {
"type": "BinaryExpr",
"operator": "+",
"left": {
"type": "Literal",
"value": 10
},
"right": {
"type": "Literal",
"value": 20
}
},
"right": {
"type": "Literal",
"value": 2
}
}
}
]
};
生成的对象清楚地定义了运算符的优先级,并且评估此源非常简单,但是,当您必须处理括号求解时,从中生成代码是一项相当复杂的任务。
通过遍历节点生成代码时,优先级完全丢失。我有一个名为visitor
的函数,它是程序的入口点:
function visitor(node) {
switch (node.type) {
case "Stmt":
return parseStmt(node.body);
}
}
这个简单的语法可以接受多个语句......
function parseStmt(body) {
var stmtList = Array(body.length);
for (var i = 0, len = body.length; i < len; i++) {
stmtList[i] = (function(stmt) {
switch (stmt.type) {
case "Expr":
return parseExpr(stmt.expression);
}
})(body[i]);
}
return stmtList.join(";\n");
}
......还有两种表达方式:
function parseExpr(expr) {
switch (expr.type) {
case "BinaryExpr":
return parseBinaryExpr(expr);
case "Literal":
return parseLiteral(expr);
}
}
Literal
只处理字符串转换......
function parseLiteral(expr) {
return expr.value.toString();
}
...和BinaryExpr
在求解优先级时不明确:
function parseBinaryExpr(expr) {
var op = {
left: parseExpr(expr.left),
right: parseExpr(expr.right)
};
switch (expr.operator) {
case "+":
return Codegen.OP_ADD(op.left, op.right);
case "*":
return Codegen.OP_MUL(op.left, op.right);
}
}
这里只支持两个数学运算,代码生成在这里:
var Codegen = {
OP_ADD: function(left, right) {
return left + " + " + right;
},
OP_MUL: function(left, right) {
return left + " * " + right;
}
};
当回调visitor(ast)
时,我得到10 + 20 * 2
,它将评估为10 + (20 * 2)
而不是(10 + 20) * 2
,并且在二进制表达式的每一侧插入括号将是荒谬的解决方法:(10 + 20) * 2
其中:
function parseBinaryExpr(expr) {
var op = {
left: "(" + parseExpr(expr.left) + ")",
right: "(" + parseExpr(expr.right) + ")"
};
...
如何以一种好的方式解决这种歧义?
答案 0 :(得分:1)
不是一个简单的优先级表,并查看父表达式来解决它吗?
此外,交换机中还有一个小错误。
var ast = {
"type": "Stmt",
"body": [
{
"type": "Expr",
"expression": {
"type": "BinaryExpr",
"operator": "*",
"left": {
"type": "BinaryExpr",
"operator": "+",
"left": {
"type": "Literal",
"value": 10
},
"right": {
"type": "Literal",
"value": 20
}
},
"right": {
"type": "Literal",
"value": 2
}
}
}
]
};
var precedence = { "*": 0, "+": 1 };
var Codegen = {
OP_ADD: function(left, right) {
return left + " + " + right;
},
OP_MUL: function(left, right) {
return left + " * " + right;
}
};
function visitor(node) {
switch (node.type) {
case "Stmt":
return parseStmt(node.body);
}
}
function parseStmt(body) {
var stmtList = Array(body.length);
for (var i = 0, len = body.length; i < len; i++) {
stmtList[i] = (function(stmt) {
switch (stmt.type) {
case "Expr":
return parseExpr(stmt.expression, null);
}
})(body[i]);
}
return stmtList.join(";\n");
}
function parseExpr(expr, parent) {
switch (expr.type) {
case "BinaryExpr":
return parseBinaryExpr(expr, parent);
case "Literal":
return parseLiteral(expr);
}
}
function parseLiteral(expr) {
return expr.value.toString();
}
function parseBinaryExpr(expr, parent) {
var op = {
left: parseExpr(expr.left, expr),
right: parseExpr(expr.right, expr)
};
var ret = "";
switch (expr.operator) {
case "+":
ret = Codegen.OP_ADD(op.left, op.right);
break;
case "*":
ret = Codegen.OP_MUL(op.left, op.right);
break;
}
if (parent && precedence[expr.operator] > precedence[parent.operator]) {
ret = "(" + ret + ")";
}
return ret;
}
visitor(ast);
或者如果在另一个内部嵌套二进制表达式,你总是可以加上一个括号,这样做也可以。
if (parent) {
ret = "(" + ret + ")";
}
只检查父项,因为如果我们已经在二进制表达式中,我们只传递父项。
答案 1 :(得分:0)
我会在CodeGen
而不是ParseBinaryExpr
中添加括号:
var Codegen = {
OP_ADD: function(left, right) {
return "(" + left + " + " + right + ")";
},
OP_MUL: function(left, right) {
return "(" + left + " * " + right + ")";
}
};
这将导致更少的冗余括号,尽管你仍然会得到很多括号。从积极的方面来看,毫无疑问,结果表达式对应于AST。 (顺便说一下,你需要在代码gen中为括号运算符添加括号。)
通过检查ParseBinaryExpr
中的运算符优先级可以避免所有冗余括号 - 也就是说,只有当它的优先级小于二进制表达式的运算符的优先级时,才用括号括起一个参数 - 但这很容易出错并导致微妙的错误。