...
Run Format

Source file src/cmd/cgo/ast.go

Documentation: cmd/cgo

  // Copyright 2009 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  // Parse input AST and prepare Prog structure.
  
  package main
  
  import (
  	"fmt"
  	"go/ast"
  	"go/parser"
  	"go/scanner"
  	"go/token"
  	"os"
  	"path/filepath"
  	"strings"
  )
  
  func parse(name string, src []byte, flags parser.Mode) *ast.File {
  	ast1, err := parser.ParseFile(fset, name, src, flags)
  	if err != nil {
  		if list, ok := err.(scanner.ErrorList); ok {
  			// If err is a scanner.ErrorList, its String will print just
  			// the first error and then (+n more errors).
  			// Instead, turn it into a new Error that will return
  			// details for all the errors.
  			for _, e := range list {
  				fmt.Fprintln(os.Stderr, e)
  			}
  			os.Exit(2)
  		}
  		fatalf("parsing %s: %s", name, err)
  	}
  	return ast1
  }
  
  func sourceLine(n ast.Node) int {
  	return fset.Position(n.Pos()).Line
  }
  
  // ParseGo populates f with information learned from the Go source code
  // which was read from the named file. It gathers the C preamble
  // attached to the import "C" comment, a list of references to C.xxx,
  // a list of exported functions, and the actual AST, to be rewritten and
  // printed.
  func (f *File) ParseGo(name string, src []byte) {
  	// Create absolute path for file, so that it will be used in error
  	// messages and recorded in debug line number information.
  	// This matches the rest of the toolchain. See golang.org/issue/5122.
  	if aname, err := filepath.Abs(name); err == nil {
  		name = aname
  	}
  
  	// Two different parses: once with comments, once without.
  	// The printer is not good enough at printing comments in the
  	// right place when we start editing the AST behind its back,
  	// so we use ast1 to look for the doc comments on import "C"
  	// and on exported functions, and we use ast2 for translating
  	// and reprinting.
  	ast1 := parse(name, src, parser.ParseComments)
  	ast2 := parse(name, src, 0)
  
  	f.Package = ast1.Name.Name
  	f.Name = make(map[string]*Name)
  
  	// In ast1, find the import "C" line and get any extra C preamble.
  	sawC := false
  	for _, decl := range ast1.Decls {
  		d, ok := decl.(*ast.GenDecl)
  		if !ok {
  			continue
  		}
  		for _, spec := range d.Specs {
  			s, ok := spec.(*ast.ImportSpec)
  			if !ok || s.Path.Value != `"C"` {
  				continue
  			}
  			sawC = true
  			if s.Name != nil {
  				error_(s.Path.Pos(), `cannot rename import "C"`)
  			}
  			cg := s.Doc
  			if cg == nil && len(d.Specs) == 1 {
  				cg = d.Doc
  			}
  			if cg != nil {
  				f.Preamble += fmt.Sprintf("#line %d %q\n", sourceLine(cg), name)
  				f.Preamble += commentText(cg) + "\n"
  				f.Preamble += "#line 1 \"cgo-generated-wrapper\"\n"
  			}
  		}
  	}
  	if !sawC {
  		error_(token.NoPos, `cannot find import "C"`)
  	}
  
  	// In ast2, strip the import "C" line.
  	w := 0
  	for _, decl := range ast2.Decls {
  		d, ok := decl.(*ast.GenDecl)
  		if !ok {
  			ast2.Decls[w] = decl
  			w++
  			continue
  		}
  		ws := 0
  		for _, spec := range d.Specs {
  			s, ok := spec.(*ast.ImportSpec)
  			if !ok || s.Path.Value != `"C"` {
  				d.Specs[ws] = spec
  				ws++
  			}
  		}
  		if ws == 0 {
  			continue
  		}
  		d.Specs = d.Specs[0:ws]
  		ast2.Decls[w] = d
  		w++
  	}
  	ast2.Decls = ast2.Decls[0:w]
  
  	// Accumulate pointers to uses of C.x.
  	if f.Ref == nil {
  		f.Ref = make([]*Ref, 0, 8)
  	}
  	f.walk(ast2, "prog", (*File).saveExprs)
  
  	// Accumulate exported functions.
  	// The comments are only on ast1 but we need to
  	// save the function bodies from ast2.
  	// The first walk fills in ExpFunc, and the
  	// second walk changes the entries to
  	// refer to ast2 instead.
  	f.walk(ast1, "prog", (*File).saveExport)
  	f.walk(ast2, "prog", (*File).saveExport2)
  
  	f.Comments = ast1.Comments
  	f.AST = ast2
  }
  
  // Like ast.CommentGroup's Text method but preserves
  // leading blank lines, so that line numbers line up.
  func commentText(g *ast.CommentGroup) string {
  	if g == nil {
  		return ""
  	}
  	var pieces []string
  	for _, com := range g.List {
  		c := com.Text
  		// Remove comment markers.
  		// The parser has given us exactly the comment text.
  		switch c[1] {
  		case '/':
  			//-style comment (no newline at the end)
  			c = c[2:] + "\n"
  		case '*':
  			/*-style comment */
  			c = c[2 : len(c)-2]
  		}
  		pieces = append(pieces, c)
  	}
  	return strings.Join(pieces, "")
  }
  
  // Save various references we are going to need later.
  func (f *File) saveExprs(x interface{}, context string) {
  	switch x := x.(type) {
  	case *ast.Expr:
  		switch (*x).(type) {
  		case *ast.SelectorExpr:
  			f.saveRef(x, context)
  		}
  	case *ast.CallExpr:
  		f.saveCall(x, context)
  	}
  }
  
  // Save references to C.xxx for later processing.
  func (f *File) saveRef(n *ast.Expr, context string) {
  	sel := (*n).(*ast.SelectorExpr)
  	// For now, assume that the only instance of capital C is when
  	// used as the imported package identifier.
  	// The parser should take care of scoping in the future, so
  	// that we will be able to distinguish a "top-level C" from a
  	// local C.
  	if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" {
  		return
  	}
  	if context == "as2" {
  		context = "expr"
  	}
  	if context == "embed-type" {
  		error_(sel.Pos(), "cannot embed C type")
  	}
  	goname := sel.Sel.Name
  	if goname == "errno" {
  		error_(sel.Pos(), "cannot refer to errno directly; see documentation")
  		return
  	}
  	if goname == "_CMalloc" {
  		error_(sel.Pos(), "cannot refer to C._CMalloc; use C.malloc")
  		return
  	}
  	if goname == "malloc" {
  		goname = "_CMalloc"
  	}
  	name := f.Name[goname]
  	if name == nil {
  		name = &Name{
  			Go: goname,
  		}
  		f.Name[goname] = name
  	}
  	f.Ref = append(f.Ref, &Ref{
  		Name:    name,
  		Expr:    n,
  		Context: context,
  	})
  }
  
  // Save calls to C.xxx for later processing.
  func (f *File) saveCall(call *ast.CallExpr, context string) {
  	sel, ok := call.Fun.(*ast.SelectorExpr)
  	if !ok {
  		return
  	}
  	if l, ok := sel.X.(*ast.Ident); !ok || l.Name != "C" {
  		return
  	}
  	c := &Call{Call: call, Deferred: context == "defer"}
  	f.Calls = append(f.Calls, c)
  }
  
  // If a function should be exported add it to ExpFunc.
  func (f *File) saveExport(x interface{}, context string) {
  	n, ok := x.(*ast.FuncDecl)
  	if !ok {
  		return
  	}
  
  	if n.Doc == nil {
  		return
  	}
  	for _, c := range n.Doc.List {
  		if !strings.HasPrefix(c.Text, "//export ") {
  			continue
  		}
  
  		name := strings.TrimSpace(c.Text[9:])
  		if name == "" {
  			error_(c.Pos(), "export missing name")
  		}
  
  		if name != n.Name.Name {
  			error_(c.Pos(), "export comment has wrong name %q, want %q", name, n.Name.Name)
  		}
  
  		doc := ""
  		for _, c1 := range n.Doc.List {
  			if c1 != c {
  				doc += c1.Text + "\n"
  			}
  		}
  
  		f.ExpFunc = append(f.ExpFunc, &ExpFunc{
  			Func:    n,
  			ExpName: name,
  			Doc:     doc,
  		})
  		break
  	}
  }
  
  // Make f.ExpFunc[i] point at the Func from this AST instead of the other one.
  func (f *File) saveExport2(x interface{}, context string) {
  	n, ok := x.(*ast.FuncDecl)
  	if !ok {
  		return
  	}
  
  	for _, exp := range f.ExpFunc {
  		if exp.Func.Name.Name == n.Name.Name {
  			exp.Func = n
  			break
  		}
  	}
  }
  
  // walk walks the AST x, calling visit(f, x, context) for each node.
  func (f *File) walk(x interface{}, context string, visit func(*File, interface{}, string)) {
  	visit(f, x, context)
  	switch n := x.(type) {
  	case *ast.Expr:
  		f.walk(*n, context, visit)
  
  	// everything else just recurs
  	default:
  		error_(token.NoPos, "unexpected type %T in walk", x)
  		panic("unexpected type")
  
  	case nil:
  
  	// These are ordered and grouped to match ../../go/ast/ast.go
  	case *ast.Field:
  		if len(n.Names) == 0 && context == "field" {
  			f.walk(&n.Type, "embed-type", visit)
  		} else {
  			f.walk(&n.Type, "type", visit)
  		}
  	case *ast.FieldList:
  		for _, field := range n.List {
  			f.walk(field, context, visit)
  		}
  	case *ast.BadExpr:
  	case *ast.Ident:
  	case *ast.Ellipsis:
  	case *ast.BasicLit:
  	case *ast.FuncLit:
  		f.walk(n.Type, "type", visit)
  		f.walk(n.Body, "stmt", visit)
  	case *ast.CompositeLit:
  		f.walk(&n.Type, "type", visit)
  		f.walk(n.Elts, "expr", visit)
  	case *ast.ParenExpr:
  		f.walk(&n.X, context, visit)
  	case *ast.SelectorExpr:
  		f.walk(&n.X, "selector", visit)
  	case *ast.IndexExpr:
  		f.walk(&n.X, "expr", visit)
  		f.walk(&n.Index, "expr", visit)
  	case *ast.SliceExpr:
  		f.walk(&n.X, "expr", visit)
  		if n.Low != nil {
  			f.walk(&n.Low, "expr", visit)
  		}
  		if n.High != nil {
  			f.walk(&n.High, "expr", visit)
  		}
  		if n.Max != nil {
  			f.walk(&n.Max, "expr", visit)
  		}
  	case *ast.TypeAssertExpr:
  		f.walk(&n.X, "expr", visit)
  		f.walk(&n.Type, "type", visit)
  	case *ast.CallExpr:
  		if context == "as2" {
  			f.walk(&n.Fun, "call2", visit)
  		} else {
  			f.walk(&n.Fun, "call", visit)
  		}
  		f.walk(n.Args, "expr", visit)
  	case *ast.StarExpr:
  		f.walk(&n.X, context, visit)
  	case *ast.UnaryExpr:
  		f.walk(&n.X, "expr", visit)
  	case *ast.BinaryExpr:
  		f.walk(&n.X, "expr", visit)
  		f.walk(&n.Y, "expr", visit)
  	case *ast.KeyValueExpr:
  		f.walk(&n.Key, "expr", visit)
  		f.walk(&n.Value, "expr", visit)
  
  	case *ast.ArrayType:
  		f.walk(&n.Len, "expr", visit)
  		f.walk(&n.Elt, "type", visit)
  	case *ast.StructType:
  		f.walk(n.Fields, "field", visit)
  	case *ast.FuncType:
  		f.walk(n.Params, "param", visit)
  		if n.Results != nil {
  			f.walk(n.Results, "param", visit)
  		}
  	case *ast.InterfaceType:
  		f.walk(n.Methods, "field", visit)
  	case *ast.MapType:
  		f.walk(&n.Key, "type", visit)
  		f.walk(&n.Value, "type", visit)
  	case *ast.ChanType:
  		f.walk(&n.Value, "type", visit)
  
  	case *ast.BadStmt:
  	case *ast.DeclStmt:
  		f.walk(n.Decl, "decl", visit)
  	case *ast.EmptyStmt:
  	case *ast.LabeledStmt:
  		f.walk(n.Stmt, "stmt", visit)
  	case *ast.ExprStmt:
  		f.walk(&n.X, "expr", visit)
  	case *ast.SendStmt:
  		f.walk(&n.Chan, "expr", visit)
  		f.walk(&n.Value, "expr", visit)
  	case *ast.IncDecStmt:
  		f.walk(&n.X, "expr", visit)
  	case *ast.AssignStmt:
  		f.walk(n.Lhs, "expr", visit)
  		if len(n.Lhs) == 2 && len(n.Rhs) == 1 {
  			f.walk(n.Rhs, "as2", visit)
  		} else {
  			f.walk(n.Rhs, "expr", visit)
  		}
  	case *ast.GoStmt:
  		f.walk(n.Call, "expr", visit)
  	case *ast.DeferStmt:
  		f.walk(n.Call, "defer", visit)
  	case *ast.ReturnStmt:
  		f.walk(n.Results, "expr", visit)
  	case *ast.BranchStmt:
  	case *ast.BlockStmt:
  		f.walk(n.List, context, visit)
  	case *ast.IfStmt:
  		f.walk(n.Init, "stmt", visit)
  		f.walk(&n.Cond, "expr", visit)
  		f.walk(n.Body, "stmt", visit)
  		f.walk(n.Else, "stmt", visit)
  	case *ast.CaseClause:
  		if context == "typeswitch" {
  			context = "type"
  		} else {
  			context = "expr"
  		}
  		f.walk(n.List, context, visit)
  		f.walk(n.Body, "stmt", visit)
  	case *ast.SwitchStmt:
  		f.walk(n.Init, "stmt", visit)
  		f.walk(&n.Tag, "expr", visit)
  		f.walk(n.Body, "switch", visit)
  	case *ast.TypeSwitchStmt:
  		f.walk(n.Init, "stmt", visit)
  		f.walk(n.Assign, "stmt", visit)
  		f.walk(n.Body, "typeswitch", visit)
  	case *ast.CommClause:
  		f.walk(n.Comm, "stmt", visit)
  		f.walk(n.Body, "stmt", visit)
  	case *ast.SelectStmt:
  		f.walk(n.Body, "stmt", visit)
  	case *ast.ForStmt:
  		f.walk(n.Init, "stmt", visit)
  		f.walk(&n.Cond, "expr", visit)
  		f.walk(n.Post, "stmt", visit)
  		f.walk(n.Body, "stmt", visit)
  	case *ast.RangeStmt:
  		f.walk(&n.Key, "expr", visit)
  		f.walk(&n.Value, "expr", visit)
  		f.walk(&n.X, "expr", visit)
  		f.walk(n.Body, "stmt", visit)
  
  	case *ast.ImportSpec:
  	case *ast.ValueSpec:
  		f.walk(&n.Type, "type", visit)
  		if len(n.Names) == 2 && len(n.Values) == 1 {
  			f.walk(&n.Values[0], "as2", visit)
  		} else {
  			f.walk(n.Values, "expr", visit)
  		}
  	case *ast.TypeSpec:
  		f.walk(&n.Type, "type", visit)
  
  	case *ast.BadDecl:
  	case *ast.GenDecl:
  		f.walk(n.Specs, "spec", visit)
  	case *ast.FuncDecl:
  		if n.Recv != nil {
  			f.walk(n.Recv, "param", visit)
  		}
  		f.walk(n.Type, "type", visit)
  		if n.Body != nil {
  			f.walk(n.Body, "stmt", visit)
  		}
  
  	case *ast.File:
  		f.walk(n.Decls, "decl", visit)
  
  	case *ast.Package:
  		for _, file := range n.Files {
  			f.walk(file, "file", visit)
  		}
  
  	case []ast.Decl:
  		for _, d := range n {
  			f.walk(d, context, visit)
  		}
  	case []ast.Expr:
  		for i := range n {
  			f.walk(&n[i], context, visit)
  		}
  	case []ast.Stmt:
  		for _, s := range n {
  			f.walk(s, context, visit)
  		}
  	case []ast.Spec:
  		for _, s := range n {
  			f.walk(s, context, visit)
  		}
  	}
  }
  

View as plain text