基于Golang AST的代码转换技术探讨
基于Golang AST的代码转换技术探讨 亲爱的Gopher们,
我一直面临着一个问题:如何一次性将类型为 ast.IndexExpr 的元素添加到一个空列表中。
例如,在下面的代码中。当我有 Put(CreateTuple(name, 10), A) 时,我希望获取 A 的内容,以便在转换后的输出程序中得到类似这样的结果:A[i] = uri[s1], uri[s2], uri[s3],而不是只有最后一个元素:A[0] = uri[s3]。变量 uri[s1], uri[s2], uri[s3] 的名称是 indexpr02。然后,当我把它放到 assignment06 的右侧时,我只得到了最后一个元素 A[i] = uri[s3]。任何帮助都将不胜感激。
// 这是一个转换给定输入程序中 Put 操作的程序,
// 通过将转换后的 Put 操作插入到输出程序中:例如…
// spaceid.Put(tuple)
// 转换为:
// Put(CreateTuple(tuple), a)
// 其中:a 代表空间
// 转换 GetP 和 QueryP 操作
// s1.GetP(&description, &key) 转换为:GetP("A", &key, uri[s1], uri[s2])
////…
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
package main
// 导入有用的包
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"log"
"os"
"reflect"
)
func translation3(filename string) {
//////////
////////// 第 1 部分:(解析)从输入程序创建 AST
//////////
// 创建一个新的 FileSet,代表解析器的一组源文件。
fset := token.NewFileSet()
// 解析源文件名和注释,并将它们添加到 AST。
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
fmt.Printf(" ====> 输入程序: ===== \n")
// 打印输入程序文件。
printer.Fprint(os.Stdout, fset, node)
//////////
////////// 第 2 部分:/ AST 的转换
//////////
// 生成有用的标识符
identurl := ast.NewIdent("uri")
identm := ast.NewIdent("m")
var basiclit1 *ast.BasicLit
// 生成:a
identa := ast.NewIdent("a")
// 有用的语句和表达式的声明。
//var indexpr02 *ast.IndexExpr
var indexpr02 *ast.IndexExpr
var callexpr01 *ast.CallExpr
var assignmentO2 *ast.AssignStmt
var assignmentO1 *ast.AssignStmt
var assignment04 *ast.AssignStmt
// 声明为全局使用而准备的子 AST 的有用语句
var assignment05 *ast.AssignStmt
var assignment06 *ast.AssignStmt
// Put 操作的目标位置 // 这是一个空数组
targets := []ast.Expr{}
// 从所有声明开始遍历 AST
for _, f := range node.Decls {
var list []ast.Stmt
// 查找函数
fn, ok := f.(*ast.FuncDecl)
if !ok {
continue
}
// 在主体列表中查找赋值语句
for _, k := range fn.Body.List {
switch reflect.TypeOf(k).String() {
case "*ast.AssignStmt":
astmt, ok := k.(*ast.AssignStmt)
if ok {
switch reflect.TypeOf(astmt.Rhs[0]).String() {
case "*ast.CallExpr":
// 调用函数 NewSpace 来提取所有创建的空间
if ok && astmt.Rhs[0].(*ast.CallExpr).Fun.(*ast.Ident).Name == "NewSpace" && reflect.TypeOf(astmt.Lhs[0]).String() == "*ast.Ident" {
// 提取赋值语句的左侧
//(即 Space 对象的标识符)。
// 提取赋值语句右侧函数调用中的第一个参数
//(即空间的 URI)。
spaceid := astmt.Lhs[0].(*ast.Ident)
spaceuri := astmt.Rhs[0].(*ast.CallExpr).Args[0].(*ast.BasicLit)
// 准备要注入目标程序的子 AST
// 即,&spaceid, uri[spaceid], 和 m[uri]
space := &ast.UnaryExpr{Op: token.AND, X: spaceid} // &spaceid
indexpr01 := &ast.IndexExpr{X: identm, Index: spaceuri} // m[uri]
indexpr02 = &ast.IndexExpr{X: identurl, Index: spaceid} // uri[spaceid]
// 为 m[] 和 uri[] 添加赋值语句
// 转换 spaceuri -> m[spaceuri]
assignmentO1 = &ast.AssignStmt{Lhs: []ast.Expr{indexpr01}, Tok: token.ASSIGN, Rhs: []ast.Expr{space}}
assignmentO2 = &ast.AssignStmt{Lhs: []ast.Expr{indexpr02}, Tok: token.ASSIGN, Rhs: []ast.Expr{astmt.Rhs[0].(*ast.CallExpr).Args[0].(*ast.BasicLit)}}
// 将上面的两个部分(assignement01, assignement02)连接起来:
// m[spaceuri] -> &spaceid
// uri[spaceid] -> spaceuri
list = append(list, assignmentO1, assignmentO2)
targets = append(targets, indexpr02)
//targets = append(targets, Name)
}
default:
list = append(list, k)
}
}
default:
list = append(list, k)
}
fn.Body.List = list
}
}
// ...遍历语句块
ast.Inspect(node, func(n ast.Node) bool {
fn, ok := n.(*ast.BlockStmt)
if ok {
var expr ast.Expr
// 在列表中查找表达式语句
for _, k := range fn.List {
if reflect.TypeOf(k).String() == "*ast.ExprStmt" {
fn1, ok := k.(*ast.ExprStmt)
// 调用函数 Put 来提取与其关联的所有参数
//...
if ok && reflect.TypeOf(fn1.X.(*ast.CallExpr).Fun).String() == "*ast.SelectorExpr" {
if fn1.X.(*ast.CallExpr).Fun.(*ast.SelectorExpr).Sel.Name == "Put" {
// 提取与函数调用 "Put" 关联的所有参数并创建新的参数列表
newArgs := make([]ast.Expr, len(fn1.X.(*ast.CallExpr).Args))
copy(newArgs, fn1.X.(*ast.CallExpr).Args)
// 准备子 AST:a := make([]string, 2)
//....
// 生成:2
basiclit := &ast.BasicLit{Kind: token.STRING, Value: "10"}
// 生成:string
identstring := ast.NewIdent("string")
// 生成:make
identmake := ast.NewIdent("make")
// 生成:[]string
arraytype := &ast.ArrayType{Elt: identstring}
// 生成:make([]string, 2)
callexpr02 := &ast.CallExpr{Fun: identmake, Args: []ast.Expr{arraytype, basiclit}}
//生成:a := make([]string, 2)
assignment05 = &ast.AssignStmt{Lhs: []ast.Expr{identa}, Tok: token.DEFINE, Rhs: []ast.Expr{callexpr02}}
basiclit1 = &ast.BasicLit{Kind: token.STRING, Value: "\"uri[s1]\""}
//basiclit1 = &ast.BasicLit{Kind: token.STRING, Value: "" + Name + ""}
// 生成:0
basiclit2 := &ast.BasicLit{Kind: token.STRING, Value: "0"}
// 生成:a[0]
indexexpr03 := &ast.IndexExpr{X: identa, Index: basiclit2}
// 生成:a[0] := "uri[s1]"
assignment06 = &ast.AssignStmt{Lhs: []ast.Expr{indexexpr03}, Tok: token.ASSIGN, Rhs: []ast.Expr{indexpr02}}
// 准备子 AST:t := CreateTuple(args)
//....
// 生成:CreateTuple
identcreatetuple := ast.NewIdent("CreateTuple")
// 生成:t
identt := ast.NewIdent("t")
// 生成:CreateTuple(newargs)
callexpr01 = &ast.CallExpr{Fun: identcreatetuple, Args: newArgs}
// 生成:t := CreateTuple(newargs)
assignment04 = &ast.AssignStmt{Lhs: []ast.Expr{identt}, Tok: token.DEFINE, Rhs: []ast.Expr{callexpr01, indexpr02}}
// 对于 targets 中每个非 nil 的元素:获取新的参数列表
for i := range targets {
if targets[i] != nil {
newArgs = append(newArgs, targets[i])
}
}
//生成:Put(CreateTuple(args), a)
expr = &ast.CallExpr{Fun: fn1.X.(*ast.CallExpr).Fun.(*ast.SelectorExpr).Sel, Args: []ast.Expr{callexpr01, identa}}
}
if expr != nil {
fn1.X = expr
}
}
}
}
}
return true
})
// 遍历声明块
for _, f := range node.Decls {
// 声明一个语句数组
var list2 []ast.Stmt
// 组合以下准备好的子 AST:
//a := make([]string, 2)
//a[0] := uri[s1]
//a[1] := uri[s2]
//...
list2 = append(list2, assignment05, assignment06)
// 查找函数
fn, ok := f.(*ast.FuncDecl)
if !ok {
continue
}
//Put(CreateTuple(tuple), a)
//....
if fn.Name.Name != "main" {
fn.Body.List = append(list2, fn.Body.List...)
}
}
//////////
////////// 第 3 部分:美化打印 AST,
////////// 这样我们就得到了修改后的程序。
//////////
fmt.Println(" ~~~~~~~转换后的输出程序~~~~~~~~~ ")
// 打印转换后的输出程序
printer.Fprint(os.Stdout, fset, node)
}
}
////////// ////////// 第 4 部分:确保使用命令行执行输入文件名。 //////////
// 在命令行中执行输入文件名 var inputfilename *string
func usage() { fmt.Fprintf(os.Stderr, “用法: myprog -i [输入文件]\n”) flag.PrintDefaults() os.Exit(2) }
func initparams() { inputfilename = flag.String(“i”, “”, “输入文件名”) flag.Parse()
if *inputfilename == "" {
usage()
}
}
func main() { translation3(“testfiles/input3b.go”)
}
~~~~转换后的输出程序
package main
import (
. "github.com/pspaces/gospace"
)
func main() {
m["tcp://host:192100/s1"] = &s1
uri[s1] = "tcp://host:192100/s1"
m["tcp://host:192101/s2"] = &s2
uri[s2] = "tcp://host:192101/s2"
m["tcp://host:13456/s3"] = &s3
uri[s3] = "tcp://host:13456/s3"
go Process1(&s1)
go Process1(&s2)
go Process1(&s3)
}
func Process1(s1 *Space) {
a := make([]string, 10)
a[0] = uri[s3]
var name bool
Put(CreateTuple("A", 10), a)
Put(CreateTuple(name, 10), a)
}
更多关于基于Golang AST的代码转换技术探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于基于Golang AST的代码转换技术探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在基于Golang AST的代码转换中处理ast.IndexExpr元素时,关键是要正确管理切片和表达式。从你的代码看,问题在于targets切片只存储了最后一个indexpr02的引用。以下是解决方案:
// 修改targets的存储方式,存储每个spaceid对应的indexpr02
targets := []ast.Expr{}
// 在遍历NewSpace调用时,为每个space创建独立的indexpr02
for _, f := range node.Decls {
var list []ast.Stmt
fn, ok := f.(*ast.FuncDecl)
if !ok {
continue
}
for _, k := range fn.Body.List {
switch reflect.TypeOf(k).String() {
case "*ast.AssignStmt":
astmt, ok := k.(*ast.AssignStmt)
if ok {
switch reflect.TypeOf(astmt.Rhs[0]).String() {
case "*ast.CallExpr":
if ok && astmt.Rhs[0].(*ast.CallExpr).Fun.(*ast.Ident).Name == "NewSpace" {
spaceid := astmt.Lhs[0].(*ast.Ident)
spaceuri := astmt.Rhs[0].(*ast.CallExpr).Args[0].(*ast.BasicLit)
// 为每个space创建独立的indexpr02
indexpr02 := &ast.IndexExpr{
X: identurl,
Index: ast.NewIdent(spaceid.Name),
}
assignmentO2 = &ast.AssignStmt{
Lhs: []ast.Expr{indexpr02},
Tok: token.ASSIGN,
Rhs: []ast.Expr{spaceuri},
}
// 将每个indexpr02添加到targets
targets = append(targets, indexpr02)
list = append(list, assignmentO2)
}
default:
list = append(list, k)
}
}
default:
list = append(list, k)
}
}
fn.Body.List = list
}
对于Put操作的转换,需要正确处理多个targets元素:
// 在转换Put操作时,创建包含所有targets的切片赋值
ast.Inspect(node, func(n ast.Node) bool {
fn, ok := n.(*ast.BlockStmt)
if ok {
for _, k := range fn.List {
if reflect.TypeOf(k).String() == "*ast.ExprStmt" {
fn1, ok := k.(*ast.ExprStmt)
if ok && reflect.TypeOf(fn1.X.(*ast.CallExpr).Fun).String() == "*ast.SelectorExpr" {
if fn1.X.(*ast.CallExpr).Fun.(*ast.SelectorExpr).Sel.Name == "Put" {
// 创建包含所有targets的切片赋值
var assignments []ast.Stmt
// 首先创建切片
assignment05 = &ast.AssignStmt{
Lhs: []ast.Expr{identa},
Tok: token.DEFINE,
Rhs: []ast.Expr{
&ast.CallExpr{
Fun: ast.NewIdent("make"),
Args: []ast.Expr{
&ast.ArrayType{Elt: ast.NewIdent("string")},
&ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", len(targets))},
},
},
},
}
assignments = append(assignments, assignment05)
// 为每个target创建切片元素赋值
for i, target := range targets {
if target != nil {
assignment := &ast.AssignStmt{
Lhs: []ast.Expr{
&ast.IndexExpr{
X: identa,
Index: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%d", i)},
},
},
Tok: token.ASSIGN,
Rhs: []ast.Expr{target},
}
assignments = append(assignments, assignment)
}
}
// 创建Put调用表达式
newArgs := make([]ast.Expr, len(fn1.X.(*ast.CallExpr).Args))
copy(newArgs, fn1.X.(*ast.CallExpr).Args)
callexpr01 = &ast.CallExpr{
Fun: ast.NewIdent("CreateTuple"),
Args: newArgs,
}
expr = &ast.CallExpr{
Fun: fn1.X.(*ast.CallExpr).Fun.(*ast.SelectorExpr).Sel,
Args: []ast.Expr{callexpr01, identa},
}
// 替换原始表达式
fn1.X = expr
// 将赋值语句插入到当前块中
currentIndex := -1
for idx, stmt := range fn.List {
if stmt == k {
currentIndex = idx
break
}
}
if currentIndex >= 0 {
// 在Put语句前插入所有赋值语句
newList := make([]ast.Stmt, 0, len(fn.List)+len(assignments))
newList = append(newList, fn.List[:currentIndex]...)
newList = append(newList, assignments...)
newList = append(newList, fn.List[currentIndex:]...)
fn.List = newList
}
}
}
}
}
}
return true
})
这样修改后,对于s1、s2、s3三个space,会生成:
a := make([]string, 3)
a[0] = uri[s1]
a[1] = uri[s2]
a[2] = uri[s3]
Put(CreateTuple(name, 10), a)
关键点:
- 为每个space创建独立的
ast.IndexExpr实例 - 在转换时根据
targets切片的长度动态创建切片 - 使用循环为每个索引位置创建赋值语句
- 正确管理AST节点的插入位置

