// 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.
// In cgo mode, we ignore ast2 and just apply edits directly
// the text behind ast1. In godefs mode we modify and print ast2.
ast1 := parse(name, src, parser.ParseComments)
ast2 := parse(name, src, 0)
f.Package = ast1.Name.Name
f.Name = make(map[string]*Name)
f.NamePos = make(map[*Name]token.Pos)
// 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.
if *godefs {
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]
} else {
for _, decl := range ast2.Decls {
d, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
for _, spec := range d.Specs {
if s, ok := spec.(*ast.ImportSpec); ok && s.Path.Value == `"C"` {
// Replace "C" with _ "unsafe", to keep program valid.
// (Deleting import statement or clause is not safe if it is followed
// in the source by an explicit semicolon.)
f.Edit.Replace(f.offset(s.Path.Pos()), f.offset(s.Path.End()), `_ "unsafe"`)
}
}
}
}
// Accumulate pointers to uses of C.x.
if f.Ref == nil {
f.Ref = make([]*Ref, 0, 8)
}
f.walk(ast2, ctxProg, (*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, ctxProg, (*File).saveExport)
f.walk(ast2, ctxProg, (*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 {
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 astContext) {
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 astContext) {
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 == ctxAssign2 {
context = ctxExpr
}
if context == ctxEmbedType {
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.NamePos[name] = sel.Pos()
}
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 astContext) {
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 == ctxDefer}
f.Calls = append(f.Calls, c)
}
// If a function should be exported add it to ExpFunc.
func (f *File) saveExport(x interface{}, context astContext) {
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 astContext) {
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
}
}
}
type astContext int
const (
ctxProg astContext = iota
ctxEmbedType
ctxType
ctxStmt
ctxExpr
ctxField
ctxParam
ctxAssign2 // assignment of a single expression to two variables
ctxSwitch
ctxTypeSwitch
ctxFile
ctxDecl
ctxSpec
ctxDefer
ctxCall // any function call other than ctxCall2
ctxCall2 // function call whose result is assigned to two variables
ctxSelector
)
// walk walks the AST x, calling visit(f, x, context) for each node.
func (f *File) walk(x interface{}, context astContext, visit func(*File, interface{}, astContext)) {
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 == ctxField {
f.walk(&n.Type, ctxEmbedType, visit)
} else {
f.walk(&n.Type, ctxType, 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, ctxType, visit)
f.walk(n.Body, ctxStmt, visit)
case *ast.CompositeLit:
f.walk(&n.Type, ctxType, visit)
f.walk(n.Elts, ctxExpr, visit)
case *ast.ParenExpr:
f.walk(&n.X, context, visit)
case *ast.SelectorExpr:
f.walk(&n.X, ctxSelector, visit)
case *ast.IndexExpr:
f.walk(&n.X, ctxExpr, visit)
f.walk(&n.Index, ctxExpr, visit)
case *ast.SliceExpr:
f.walk(&n.X, ctxExpr, visit)
if n.Low != nil {
f.walk(&n.Low, ctxExpr, visit)
}
if n.High != nil {
f.walk(&n.High, ctxExpr, visit)
}
if n.Max != nil {
f.walk(&n.Max, ctxExpr, visit)
}
case *ast.TypeAssertExpr:
f.walk(&n.X, ctxExpr, visit)
f.walk(&n.Type, ctxType, visit)
case *ast.CallExpr:
if context == ctxAssign2 {
f.walk(&n.Fun, ctxCall2, visit)
} else {
f.walk(&n.Fun, ctxCall, visit)
}
f.walk(n.Args, ctxExpr, visit)
case *ast.StarExpr:
f.walk(&n.X, context, visit)
case *ast.UnaryExpr:
f.walk(&n.X, ctxExpr, visit)
case *ast.BinaryExpr:
f.walk(&n.X, ctxExpr, visit)
f.walk(&n.Y, ctxExpr, visit)
case *ast.KeyValueExpr:
f.walk(&n.Key, ctxExpr, visit)
f.walk(&n.Value, ctxExpr, visit)
case *ast.ArrayType:
f.walk(&n.Len, ctxExpr, visit)
f.walk(&n.Elt, ctxType, visit)
case *ast.StructType:
f.walk(n.Fields, ctxField, visit)
case *ast.FuncType:
f.walk(n.Params, ctxParam, visit)
if n.Results != nil {
f.walk(n.Results, ctxParam, visit)
}
case *ast.InterfaceType:
f.walk(n.Methods, ctxField, visit)
case *ast.MapType:
f.walk(&n.Key, ctxType, visit)
f.walk(&n.Value, ctxType, visit)
case *ast.ChanType:
f.walk(&n.Value, ctxType, visit)
case *ast.BadStmt:
case *ast.DeclStmt:
f.walk(n.Decl, ctxDecl, visit)
case *ast.EmptyStmt:
case *ast.LabeledStmt:
f.walk(n.Stmt, ctxStmt, visit)
case *ast.ExprStmt:
f.walk(&n.X, ctxExpr, visit)
case *ast.SendStmt:
f.walk(&n.Chan, ctxExpr, visit)
f.walk(&n.Value, ctxExpr, visit)
case *ast.IncDecStmt:
f.walk(&n.X, ctxExpr, visit)
case *ast.AssignStmt:
f.walk(n.Lhs, ctxExpr, visit)
if len(n.Lhs) == 2 && len(n.Rhs) == 1 {
f.walk(n.Rhs, ctxAssign2, visit)
} else {
f.walk(n.Rhs, ctxExpr, visit)
}
case *ast.GoStmt:
f.walk(n.Call, ctxExpr, visit)
case *ast.DeferStmt:
f.walk(n.Call, ctxDefer, visit)
case *ast.ReturnStmt:
f.walk(n.Results, ctxExpr, visit)
case *ast.BranchStmt:
case *ast.BlockStmt:
f.walk(n.List, context, visit)
case *ast.IfStmt:
f.walk(n.Init, ctxStmt, visit)
f.walk(&n.Cond, ctxExpr, visit)
f.walk(n.Body, ctxStmt, visit)
f.walk(n.Else, ctxStmt, visit)
case *ast.CaseClause:
if context == ctxTypeSwitch {
context = ctxType
} else {
context = ctxExpr
}
f.walk(n.List, context, visit)
f.walk(n.Body, ctxStmt, visit)
case *ast.SwitchStmt:
f.walk(n.Init, ctxStmt, visit)
f.walk(&n.Tag, ctxExpr, visit)
f.walk(n.Body, ctxSwitch, visit)
case *ast.TypeSwitchStmt:
f.walk(n.Init, ctxStmt, visit)
f.walk(n.Assign, ctxStmt, visit)
f.walk(n.Body, ctxTypeSwitch, visit)
case *ast.CommClause:
f.walk(n.Comm, ctxStmt, visit)
f.walk(n.Body, ctxStmt, visit)
case *ast.SelectStmt:
f.walk(n.Body, ctxStmt, visit)
case *ast.ForStmt:
f.walk(n.Init, ctxStmt, visit)
f.walk(&n.Cond, ctxExpr, visit)
f.walk(n.Post, ctxStmt, visit)
f.walk(n.Body, ctxStmt, visit)
case *ast.RangeStmt:
f.walk(&n.Key, ctxExpr, visit)
f.walk(&n.Value, ctxExpr, visit)
f.walk(&n.X, ctxExpr, visit)
f.walk(n.Body, ctxStmt, visit)
case *ast.ImportSpec:
case *ast.ValueSpec:
f.walk(&n.Type, ctxType, visit)
if len(n.Names) == 2 && len(n.Values) == 1 {
f.walk(&n.Values[0], ctxAssign2, visit)
} else {
f.walk(n.Values, ctxExpr, visit)
}
case *ast.TypeSpec:
f.walk(&n.Type, ctxType, visit)
case *ast.BadDecl:
case *ast.GenDecl:
f.walk(n.Specs, ctxSpec, visit)
case *ast.FuncDecl:
if n.Recv != nil {
f.walk(n.Recv, ctxParam, visit)
}
f.walk(n.Type, ctxType, visit)
if n.Body != nil {
f.walk(n.Body, ctxStmt, visit)
}
case *ast.File:
f.walk(n.Decls, ctxDecl, visit)
case *ast.Package:
for _, file := range n.Files {
f.walk(file, ctxFile, 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)
}
}
}