基于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

1 回复

更多关于基于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
})

这样修改后,对于s1s2s3三个space,会生成:

a := make([]string, 3)
a[0] = uri[s1]
a[1] = uri[s2]
a[2] = uri[s3]
Put(CreateTuple(name, 10), a)

关键点:

  1. 为每个space创建独立的ast.IndexExpr实例
  2. 在转换时根据targets切片的长度动态创建切片
  3. 使用循环为每个索引位置创建赋值语句
  4. 正确管理AST节点的插入位置
回到顶部