仓库源文

接上文重现木兰编程语言(四)——行号与多行支持,在说行列号之前,(早)该把生成的 Python 语法树的基本结构说明一下,同时为了今后的调试写点辅助代码。

比如简单代码:

print(2)

原始木兰用--dump-ast选项运行,生成的语法树 dump 出来是这样一长溜(调用了ast.dump):

Module(body=[Expr(value=Call(func=Name(id='print', ctx=Load(), lineno=1, col_offset=1), args=[Num(n=2, lineno=1, col_offset=7)], keywords=[], lineno=1, col_offset=1), lineno=1, col_offset=1)])

可以想象,如果稍微复杂一点的代码,如果要查看生成的语法树,以及肉眼比较两颗语法树的话,会有多么辣眼睛。

于是,为了生成稍微肉眼友好的格式:

Module(
  body=[Expr(
      value=Call(
        func=Name(
          id=print
          ctx=Load()
          lineno=1
          col_offset=1
        )
        args=[Num(
            n=2
            lineno=1
            col_offset=7
          )]
        keywords=[]
        lineno=1
        col_offset=1
      )
      lineno=1
      col_offset=1
    )]
)

粗写了个递归来遍历语法树并格式化输出:

    def 格式化节点(节点, 层次):
        缩进 = "  "
        输出 = ""
        if isinstance(节点, list):
            输出 += "["
            for 子节点 in 节点:
                输出 += 语法树相关.格式化节点(子节点, 层次 + 1)
            输出 += "]"
        elif isinstance(节点, int):
            输出 += str(节点)
        elif isinstance(节点, str):
            输出 += 节点
        else:
            输出 += type(节点).__name__ + "("
            属性个数 = 0
            for 属性 in ast.iter_fields(节点):
                属性个数 += 1
                输出 += "\n" + 缩进 * 层次 + 属性[0] + "="
                输出 += 语法树相关.格式化节点(属性[1], 层次 + 1)
            if isinstance(节点, ast.stmt) or isinstance(节点, ast.expr):
                输出 += "\n" + 缩进 * 层次 + "lineno=" + str(节点.lineno)
                输出 += "\n" + 缩进 * 层次 + "col_offset=" + str(节点.col_offset)
            if 属性个数 == 0:
                return 输出 + ")"
            return 输出 + "\n" + 缩进 * (层次 - 1) + ")"
        return 输出

上面代码结合输出,可以更加直观地了解 Python 语法树的结构。

如果是单个节点,比如根节点——模块(Module),就由else的部分输出它的类型,并遍历它的所有属性;节点的属性值可以是节点,或者节点列表,比如模块节点的body属性的值可以是多个表达式(Expr) 。如果是声明(stmt)或者表达式,有行列号两个属性。所有可能节点参考 Python 官方语法描述

这样格式化输出之后,将木兰生成的语法树与我的原型生成的进行肉眼对比就稍微方便点,题图是 vsc diff 的效果(右边是木兰的):

可见行列号已经填上了(且听下回分解),但细节仍有些不同。继续调试。。