func stripParens(x ast.Expr) ast.Expr { if px, strip := x.(*ast.ParenExpr); strip { // parentheses must not be stripped if there are any // unparenthesized composite literals starting with // a type name ast.Inspect(px.X, func(node ast.Node) bool { switch x := node.(type) { case *ast.ParenExpr: // parentheses protect enclosed composite literals return false case *ast.CompositeLit: if isTypeName(x.Type) { strip = false // do not strip parentheses } return false } // in all other cases, keep inspecting return true }) if strip { return stripParens(px.X) } } return x }
// index all the spec definitions for inputs and outputs to element func (p *compiler) indexParams(d []ast.Decl) { for i := range d { switch decl := d[i].(type) { case *ast.GenDecl: switch decl.Tok { case token.PARAMETERS, token.INPUTS: for j := range decl.Specs { tmp := decl.Specs[j].(*ast.ValueSpec) p.params = append(p.params, tmp) for k := range tmp.Names { //param := strings.Title(tmp.Names[k].String()) //TODO check input data cannot be lowercase param := tmp.Names[k].String() p.paramTypes[param] = p.getTypeString(tmp.Type) p.paramMap[param] = "p." + param } } case token.OUTPUTS, token.DATA: for j := range decl.Specs { tmp := decl.Specs[j].(*ast.ValueSpec) p.results = append(p.results, tmp) for k := range tmp.Names { if string(tmp.Names[k].String()[0]) == strings.ToUpper(string(tmp.Names[k].String()[0])) { result := tmp.Names[k].String() p.paramTypes[result] = p.getTypeString(tmp.Type) p.resultMap[result] = "r." + result //assign to resultblock, later staged } else { //lowercase, assign to resultblock result := tmp.Names[k].String() p.paramTypes[result] = p.getTypeString(tmp.Type) p.resultMap[result] = "r." + result } } } } case *ast.AnthaDecl: var ref ast.AnthaDecl ref = *decl tok := ref.Tok ast.Inspect(ref.Body, func(n ast.Node) bool { switch n.(type) { case *ast.Ident: if _, is := p.resultMap[n.(*ast.Ident).Name]; is { varName := n.(*ast.Ident).Name if _, ok := p.reuseMap[tok]; !ok { p.reuseMap[tok] = make(map[string]bool) } p.reuseMap[tok][varName] = true } } return true }) default: continue } } }
func TestObjects(t *testing.T) { const src = ` package p import fmt "fmt" const pi = 3.14 type T struct{} var x int func f() { L: } ` f, err := ParseFile(fset, "", src, 0) if err != nil { t.Fatal(err) } objects := map[string]ast.ObjKind{ "p": ast.Bad, // not in a scope "fmt": ast.Bad, // not resolved yet "pi": ast.Con, "T": ast.Typ, "x": ast.Var, "int": ast.Bad, // not resolved yet "f": ast.Fun, "L": ast.Lbl, } ast.Inspect(f, func(n ast.Node) bool { if ident, ok := n.(*ast.Ident); ok { obj := ident.Obj if obj == nil { if objects[ident.Name] != ast.Bad { t.Errorf("no object for %s", ident.Name) } return true } if obj.Name != ident.Name { t.Errorf("names don't match: obj.Name = %s, ident.Name = %s", obj.Name, ident.Name) } kind := objects[ident.Name] if obj.Kind != kind { t.Errorf("%s: obj.Kind = %s; want %s", ident.Name, obj.Kind, kind) } } return true }) }
// Replace bare antha function names with go qualified names func (p *compiler) sugarForIntrinsics(root ast.Node) { ast.Inspect(root, func(n ast.Node) bool { switch node := n.(type) { case nil: return false case *ast.Ident: // sugar the name if it is a known param if sugar, ok := p.intrinsicMap[node.Name]; ok { node.Name = sugar } // test if the left hand side of an assignment is to a results variable // if so, sugar it by replacing the = with a channel operation default: } return true }) }
// Replace bare antha identifiers with go qualified names func (p *compiler) sugarForParams(body *ast.BlockStmt) { list := body.List for i := range list { ast.Inspect(list[i], func(n ast.Node) bool { switch node := n.(type) { case nil: return false case *ast.Ident: // sugar the name if it is a known param if sugar, ok := p.paramMap[node.Name]; ok { node.Name = sugar } else if sugar, ok := p.resultMap[node.Name]; ok { node.Name = sugar } // test if the left hand side of an assignment is to a results variable // if so, sugar it by replacing the = with a channel operation case *ast.AssignStmt: // test if a result is being assigned to. fix_assign := false for j := range node.Lhs { if ident, ok := node.Lhs[j].(*ast.Ident); ok { isUpper := string(ident.Name[0]) == strings.ToUpper(string(ident.Name[0])) if sugar, fixme := p.resultMap[ident.Name]; fixme && isUpper { //fix_assign = true ident.Name = sugar } } } //TODO: generate multiple stmts if needed if fix_assign { if len(node.Rhs) > 1 { // currently undefined so panic panic("too many right hand exprs") } list[i] = &ast.SendStmt{node.Lhs[0], node.TokPos, p.wrapParamExpr(node.Rhs[0])} } default: } return true }) } }
// This example demonstrates how to inspect the AST of a Go/Antha program. func ExampleInspect() { // src is the input for which we want to inspect the AST. src := ` package p const c = 1.0 var X = f(3.14)*2 + c ` // Create the AST by parsing src. fset := token.NewFileSet() // positions are relative to fset f, err := parser.ParseFile(fset, "src.go", src, 0) if err != nil { panic(err) } // Inspect the AST and print all identifiers and literals. ast.Inspect(f, func(n ast.Node) bool { var s string switch x := n.(type) { case *ast.BasicLit: s = x.Value case *ast.Ident: s = x.Name } if s != "" { fmt.Printf("%s:\t%s\n", fset.Position(n.Pos()), s) } return true }) // output: // src.go:2:9: p // src.go:3:7: c // src.go:3:11: 1.0 // src.go:4:5: X // src.go:4:9: f // src.go:4:11: 3.14 // src.go:4:17: 2 // src.go:4:21: c }
// playExample synthesizes a new *ast.File based on the provided // file with the provided function body as the body of main. func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { if !strings.HasSuffix(file.Name.Name, "_test") { // We don't support examples that are part of the // greater package (yet). return nil } // Find top-level declarations in the file. topDecls := make(map[*ast.Object]bool) for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: topDecls[d.Name.Obj] = true case *ast.GenDecl: for _, spec := range d.Specs { switch s := spec.(type) { case *ast.TypeSpec: topDecls[s.Name.Obj] = true case *ast.ValueSpec: for _, id := range s.Names { topDecls[id.Obj] = true } } } } } // Find unresolved identifiers and uses of top-level declarations. unresolved := make(map[string]bool) usesTopDecl := false var inspectFunc func(ast.Node) bool inspectFunc = func(n ast.Node) bool { // For selector expressions, only inspect the left hand side. // (For an expression like fmt.Println, only add "fmt" to the // set of unresolved names, not "Println".) if e, ok := n.(*ast.SelectorExpr); ok { ast.Inspect(e.X, inspectFunc) return false } // For key value expressions, only inspect the value // as the key should be resolved by the type of the // composite literal. if e, ok := n.(*ast.KeyValueExpr); ok { ast.Inspect(e.Value, inspectFunc) return false } if id, ok := n.(*ast.Ident); ok { if id.Obj == nil { unresolved[id.Name] = true } else if topDecls[id.Obj] { usesTopDecl = true } } return true } ast.Inspect(body, inspectFunc) if usesTopDecl { // We don't support examples that are not self-contained (yet). return nil } // Remove predeclared identifiers from unresolved list. for n := range unresolved { if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] { delete(unresolved, n) } } // Use unresolved identifiers to determine the imports used by this // example. The heuristic assumes package names match base import // paths for imports w/o renames (should be good enough most of the time). namedImports := make(map[string]string) // [name]path var blankImports []ast.Spec // _ imports for _, s := range file.Imports { p, err := strconv.Unquote(s.Path.Value) if err != nil { continue } n := path.Base(p) if s.Name != nil { n = s.Name.Name switch n { case "_": blankImports = append(blankImports, s) continue case ".": // We can't resolve dot imports (yet). return nil } } if unresolved[n] { namedImports[n] = p delete(unresolved, n) } } // If there are other unresolved identifiers, give up because this // synthesized file is not going to build. if len(unresolved) > 0 { return nil } // Include documentation belonging to blank imports. var comments []*ast.CommentGroup for _, s := range blankImports { if c := s.(*ast.ImportSpec).Doc; c != nil { comments = append(comments, c) } } // Include comments that are inside the function body. for _, c := range file.Comments { if body.Pos() <= c.Pos() && c.End() <= body.End() { comments = append(comments, c) } } // Strip "Output:" comment and adjust body end position. body, comments = stripOutputComment(body, comments) // Synthesize import declaration. importDecl := &ast.GenDecl{ Tok: token.IMPORT, Lparen: 1, // Need non-zero Lparen and Rparen so that printer Rparen: 1, // treats this as a factored import. } for n, p := range namedImports { s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}} if path.Base(p) != n { s.Name = ast.NewIdent(n) } importDecl.Specs = append(importDecl.Specs, s) } importDecl.Specs = append(importDecl.Specs, blankImports...) // Synthesize main function. funcDecl := &ast.FuncDecl{ Name: ast.NewIdent("main"), Type: &ast.FuncType{Params: &ast.FieldList{}}, // FuncType.Params must be non-nil Body: body, } // Synthesize file. return &ast.File{ Name: ast.NewIdent("main"), Decls: []ast.Decl{importDecl, funcDecl}, Comments: comments, } }