由于某些原因需要将我们的代码从Python转换为Golang,因此打算写一个翻译器自动化地去批量翻译。我们最终决定是使用Anltr4对Python进行语法解析。本文主要介绍一下整体的实现思路。

为何选择Antlr

ast moduleAST

Antlr4官网([https://github.com/antlr/grammars-v4/)上有主流的编程语言的语法库,因此不用为某个语言自己手写语法库了。

但是这个语法库还是有一些问题,比如下面的语法:

// Python 2 : EXCEPT test COMMA name
// Python 3 : EXCEPT test AS name
except_clause
    : EXCEPT (test ({p.CheckVersion(2)}? COMMA name {p.SetVersion(2);} | {p.CheckVersion(3)}? AS name {p.SetVersion(3);})?)? COLON suite
    ;
except,as
except_clause: 'except' [test [('as' | ',') test]]

因此,要改成:

EXCEPT (test ({p.CheckVersion(2)}? (AS | COMMA) name {p.SetVersion(2);} | {p.CheckVersion(3)}? AS name {p.SetVersion(3);})?)?
antlrgo
> antlr4 -Dlanguage=Go PythonLexer.g4
> antlr4 -Dlanguage=Go PythonParser.g4
antlrgoPython TokenC#JavaGolang
Python
// 分析某个Python文件
//    content: 文件内容
//    file: 文件名
func AnalysisOneFile(content string, file string) {
    is := antlr.NewInputStream(content)
    // 1. 构造词法解析器
    lexer := parser.NewPythonLexer(is)
    // 2. 输出各种token
    tokens := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
    // 3. 拿着token就可以做语法解析了
    par := parser.NewPythonParser(tokens)
    // 4. 设置Python版本是2.x
    par.SetVersion(parser.Python2)
    // 5. 拿到ParseTree的根节点
    tree := par.Root()
}

构造Python AST

par.Rootantlr
import math
def func(a, b, c = 1):
    pass

比如上面这么一个简单的Python代码竟然会生成下面如此复杂的树结构:

root
- file_input  
  - stmt 
    - simple_stmt 
      - small_stmt import 
        - dotted_as_names 
          - dotted_as_name 
            - dotted_name 
              - name math
  - stmt 
    - compound_stmt 
      - funcdef def 
        - name func 
        - typedargslist 
          - def_parameters 
            - def_parameter 
              - named_parameter 
                - name a
            - def_parameter  
              - named_parameter 
                - name b
            - def_parameter 
              - named_parameter 
                - name c
                - = 
                - test 
                  - logical_test 
                    - comparison 
                      - expr 
                        - atom 
                          - number 
                            - integer 1
         - suite   
           - stmt 
             - simple_stmt 
               - small_stmt pass
AST
Module
- ImportStmt
  - Alias
    - Name(math)
- FuncStmt
  - Name(func)
  - ArgumentsExpr
    - DefParaExpr
      - Name(a)
    - DefParaExpr
      - Name(b)
    - DefParaExpr
      - Name(c)
      - Default(Int(1))
  - Body
    - PassStmt
ParseTreeASTAntlrlistenerAntlr

关于Golang AST

GolangGolangAST
package main

import (
    "go/ast"
    "go/parser"
    "go/token"
)
var src = `
 package main
 import "fmt"
 func main() {
     fmt.Println("Hello, World!")
 }
`

func main() {
    fset := token.NewFileSet() // positions are relative to fset
    if f, err := parser.ParseFile(fset, "", src, 0); err == nil {
        ast.Print(fset, f)
    }
}
GolangAST
ASTGolang
goast := &ast.File{
    Name: &ast.Ident{
        Name: "main",
    },
}
fset := token.NewFileSet()
printer.Fprint(os.Stdout, fset, goast)

PythonAST->GolangAST

Python ASTGolang ASTPythonAstFuncStmtGolangFuncDecl

小结

整体的解决思路就是这个样子,因为工具还在开发中,等着开发结束可以跟大家分享。