javascript - 遍历 AST 时区分运算符优先级

标签 javascript parsing compiler-construction abstract-syntax-tree peg

我有一个由目标语言的解析表达式语法生成的 AST,该语法将通过遍历其节点来编译为源语言。一个简单的来源 像 (10 + 20) * 2 应该生成以下表示形式,作为 native 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) + ")"
  };
...

如何以好的方式解决这种歧义?

最佳答案

一个简单的优先级表并查找父表达式难道不能解决这个问题吗?

此外,开关中存在一个小错误。

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 + ")";
  }

只需检查父级,因为只有当我们已经在二进制表达式中时,我们才会传递父级。

关于javascript - 遍历 AST 时区分运算符优先级,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31704144/

相关文章:

javascript - 如何在使用自定义归约创建的组中使用 top 函数?

python - 将带有嵌套括号的字符串转换为嵌套列表,python

javascript - 声明 json 的性能 - JSON.parse 与对象文字

c++ - 尽管违反了一个定义规则,但是编译器/链接器如何选择替代的内联构造函数?

javascript - AngularJS 与 Highcharts 不带 JQuery

javascript - 使用javascript调用bean方法

javascript - 如何重置 A 形光标的不透明度动画

python - 使用 lxml xpath 解析

compiler-construction - FPGA 设计是否应该整合到计算机科学类(class)中?

compiler-construction - 缺少 Visual Studio 2010 : COBOL in VS 2010,?