У меня есть 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) + ")"
};
...
Как можно решить эту двусмысленность?
()
в сериализации, чтобы указать приоритет; источник, из которого вы получили свой AST, тоже должен был бы использовать их (кроме тех случаев, когда он определил + как более высокий приоритет, чем *). Вы можете проверить, возвращает ли parseExpr «простое» или «составное» выражение для условной вставки (). - person Kenney   schedule 29.07.2015