У меня есть AST, генерируемый грамматикой выражения разбора с целевого языка, который будет скомпилировать исходный язык путем перемещения его узлов. Простой источник , как (10 + 20) * 2
должен генерировать следующее представление, как родной объект ECMAScript:Различают приоритет оператора при обходе AST
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) + ")"
};
...
Как можно решить эту двусмысленность в хорошем смысле?
Вам будет нужно использовать '()' в вашей сериализации, чтобы указать приоритет; источник, которому вы получили свой АСТ, должен был бы использовать их также (кроме случаев, когда он был установлен как более высокий приоритет, чем *). Вы можете проверить, возвращает ли parseExpr выражение «простое» или «составное», чтобы условно вставить «()». – Kenney