由于某些原因需要将我们的代码从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
小结
整体的解决思路就是这个样子,因为工具还在开发中,等着开发结束可以跟大家分享。