]> Cypherpunks repositories - gostls13.git/commitdiff
exp/types/staging: updated gcimporter
authorRobert Griesemer <gri@golang.org>
Wed, 26 Sep 2012 00:39:02 +0000 (17:39 -0700)
committerRobert Griesemer <gri@golang.org>
Wed, 26 Sep 2012 00:39:02 +0000 (17:39 -0700)
Mostly minor changes to match the new
definitions in types.go and const.go.

R=rsc, r
CC=golang-dev
https://golang.org/cl/6506101

src/pkg/exp/types/staging/exportdata.go [new file with mode: 0644]
src/pkg/exp/types/staging/gcimporter.go [new file with mode: 0644]
src/pkg/exp/types/staging/gcimporter_test.go [new file with mode: 0644]
src/pkg/exp/types/staging/testdata/exports.go [new file with mode: 0644]

diff --git a/src/pkg/exp/types/staging/exportdata.go b/src/pkg/exp/types/staging/exportdata.go
new file mode 100644 (file)
index 0000000..2219015
--- /dev/null
@@ -0,0 +1,110 @@
+// Copyright 2011 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.
+
+// This file implements FindGcExportData.
+
+package types
+
+import (
+       "bufio"
+       "errors"
+       "fmt"
+       "io"
+       "strconv"
+       "strings"
+)
+
+func readGopackHeader(r *bufio.Reader) (name string, size int, err error) {
+       // See $GOROOT/include/ar.h.
+       hdr := make([]byte, 16+12+6+6+8+10+2)
+       _, err = io.ReadFull(r, hdr)
+       if err != nil {
+               return
+       }
+       if trace {
+               fmt.Printf("header: %s", hdr)
+       }
+       s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10]))
+       size, err = strconv.Atoi(s)
+       if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
+               err = errors.New("invalid archive header")
+               return
+       }
+       name = strings.TrimSpace(string(hdr[:16]))
+       return
+}
+
+// FindGcExportData positions the reader r at the beginning of the
+// export data section of an underlying GC-created object/archive
+// file by reading from it. The reader must be positioned at the
+// start of the file before calling this function.
+//
+func FindGcExportData(r *bufio.Reader) (err error) {
+       // Read first line to make sure this is an object file.
+       line, err := r.ReadSlice('\n')
+       if err != nil {
+               return
+       }
+       if string(line) == "!<arch>\n" {
+               // Archive file.  Scan to __.PKGDEF, which should
+               // be second archive entry.
+               var name string
+               var size int
+
+               // First entry should be __.GOSYMDEF.
+               // Older archives used __.SYMDEF, so allow that too.
+               // Read and discard.
+               if name, size, err = readGopackHeader(r); err != nil {
+                       return
+               }
+               if name != "__.SYMDEF" && name != "__.GOSYMDEF" {
+                       err = errors.New("go archive does not begin with __.SYMDEF or __.GOSYMDEF")
+                       return
+               }
+               const block = 4096
+               tmp := make([]byte, block)
+               for size > 0 {
+                       n := size
+                       if n > block {
+                               n = block
+                       }
+                       if _, err = io.ReadFull(r, tmp[:n]); err != nil {
+                               return
+                       }
+                       size -= n
+               }
+
+               // Second entry should be __.PKGDEF.
+               if name, size, err = readGopackHeader(r); err != nil {
+                       return
+               }
+               if name != "__.PKGDEF" {
+                       err = errors.New("go archive is missing __.PKGDEF")
+                       return
+               }
+
+               // Read first line of __.PKGDEF data, so that line
+               // is once again the first line of the input.
+               if line, err = r.ReadSlice('\n'); err != nil {
+                       return
+               }
+       }
+
+       // Now at __.PKGDEF in archive or still at beginning of file.
+       // Either way, line should begin with "go object ".
+       if !strings.HasPrefix(string(line), "go object ") {
+               err = errors.New("not a go object file")
+               return
+       }
+
+       // Skip over object header to export data.
+       // Begins after first line with $$.
+       for line[0] != '$' {
+               if line, err = r.ReadSlice('\n'); err != nil {
+                       return
+               }
+       }
+
+       return
+}
diff --git a/src/pkg/exp/types/staging/gcimporter.go b/src/pkg/exp/types/staging/gcimporter.go
new file mode 100644 (file)
index 0000000..3e51abb
--- /dev/null
@@ -0,0 +1,887 @@
+// Copyright 2011 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.
+
+// This file implements an ast.Importer for gc-generated object files.
+// TODO(gri) Eventually move this into a separate package outside types.
+
+package types
+
+import (
+       "bufio"
+       "errors"
+       "fmt"
+       "go/ast"
+       "go/build"
+       "go/token"
+       "io"
+       "math/big"
+       "os"
+       "path/filepath"
+       "strconv"
+       "strings"
+       "text/scanner"
+)
+
+var pkgExts = [...]string{".a", ".5", ".6", ".8"}
+
+// FindPkg returns the filename and unique package id for an import
+// path based on package information provided by build.Import (using
+// the build.Default build.Context).
+// If no file was found, an empty filename is returned.
+//
+func FindPkg(path, srcDir string) (filename, id string) {
+       if len(path) == 0 {
+               return
+       }
+
+       id = path
+       var noext string
+       switch {
+       default:
+               // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
+               bp, _ := build.Import(path, srcDir, build.FindOnly)
+               if bp.PkgObj == "" {
+                       return
+               }
+               noext = bp.PkgObj
+               if strings.HasSuffix(noext, ".a") {
+                       noext = noext[:len(noext)-len(".a")]
+               }
+
+       case build.IsLocalImport(path):
+               // "./x" -> "/this/directory/x.ext", "/this/directory/x"
+               noext = filepath.Join(srcDir, path)
+               id = noext
+
+       case filepath.IsAbs(path):
+               // for completeness only - go/build.Import
+               // does not support absolute imports
+               // "/x" -> "/x.ext", "/x"
+               noext = path
+       }
+
+       // try extensions
+       for _, ext := range pkgExts {
+               filename = noext + ext
+               if f, err := os.Stat(filename); err == nil && !f.IsDir() {
+                       return
+               }
+       }
+
+       filename = "" // not found
+       return
+}
+
+// GcImportData imports a package by reading the gc-generated export data,
+// adds the corresponding package object to the imports map indexed by id,
+// and returns the object.
+//
+// The imports map must contains all packages already imported, and no map
+// entry with id as the key must be present. The data reader position must
+// be the beginning of the export data section. The filename is only used
+// in error messages.
+//
+func GcImportData(imports map[string]*ast.Object, filename, id string, data *bufio.Reader) (pkg *ast.Object, err error) {
+       if trace {
+               fmt.Printf("importing %s (%s)\n", id, filename)
+       }
+
+       // support for gcParser error handling
+       defer func() {
+               if r := recover(); r != nil {
+                       err = r.(importError) // will re-panic if r is not an importError
+               }
+       }()
+
+       var p gcParser
+       p.init(filename, id, data, imports)
+       pkg = p.parseExport()
+
+       return
+}
+
+// GcImport imports a gc-generated package given its import path, adds the
+// corresponding package object to the imports map, and returns the object.
+// Local import paths are interpreted relative to the current working directory.
+// The imports map must contains all packages already imported.
+// GcImport satisfies the ast.Importer signature.
+//
+func GcImport(imports map[string]*ast.Object, path string) (pkg *ast.Object, err error) {
+       if path == "unsafe" {
+               return Unsafe, nil
+       }
+
+       srcDir, err := os.Getwd()
+       if err != nil {
+               return
+       }
+       filename, id := FindPkg(path, srcDir)
+       if filename == "" {
+               err = errors.New("can't find import: " + id)
+               return
+       }
+
+       // Note: imports[id] may already contain a partially imported package.
+       //       We must continue doing the full import here since we don't
+       //       know if something is missing.
+       // TODO: There's no need to re-import a package if we know that we
+       //       have done a full import before. At the moment we cannot
+       //       tell from the available information in this function alone.
+
+       // open file
+       f, err := os.Open(filename)
+       if err != nil {
+               return
+       }
+       defer func() {
+               f.Close()
+               if err != nil {
+                       // Add file name to error.
+                       err = fmt.Errorf("reading export data: %s: %v", filename, err)
+               }
+       }()
+
+       buf := bufio.NewReader(f)
+       if err = FindGcExportData(buf); err != nil {
+               return
+       }
+
+       pkg, err = GcImportData(imports, filename, id, buf)
+
+       return
+}
+
+// ----------------------------------------------------------------------------
+// gcParser
+
+// gcParser parses the exports inside a gc compiler-produced
+// object/archive file and populates its scope with the results.
+type gcParser struct {
+       scanner scanner.Scanner
+       tok     rune                   // current token
+       lit     string                 // literal string; only valid for Ident, Int, String tokens
+       id      string                 // package id of imported package
+       imports map[string]*ast.Object // package id -> package object
+}
+
+func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*ast.Object) {
+       p.scanner.Init(src)
+       p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
+       p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
+       p.scanner.Whitespace = 1<<'\t' | 1<<' '
+       p.scanner.Filename = filename // for good error messages
+       p.next()
+       p.id = id
+       p.imports = imports
+}
+
+func (p *gcParser) next() {
+       p.tok = p.scanner.Scan()
+       switch p.tok {
+       case scanner.Ident, scanner.Int, scanner.Char, scanner.String, '·':
+               p.lit = p.scanner.TokenText()
+       default:
+               p.lit = ""
+       }
+       if trace {
+               fmt.Printf("%s: %q -> %q\n", scanner.TokenString(p.tok), p.scanner.TokenText(), p.lit)
+       }
+}
+
+// Declare inserts a named object of the given kind in scope.
+func (p *gcParser) declare(scope *ast.Scope, kind ast.ObjKind, name string) *ast.Object {
+       // the object may have been imported before - if it exists
+       // already in the respective package scope, return that object
+       if obj := scope.Lookup(name); obj != nil {
+               assert(obj.Kind == kind)
+               return obj
+       }
+
+       // otherwise create a new object and insert it into the package scope
+       obj := ast.NewObj(kind, name)
+       if scope.Insert(obj) != nil {
+               p.errorf("already declared: %v %s", kind, obj.Name)
+       }
+
+       // if the new type object is a named type it may be referred
+       // to before the underlying type is known - set it up
+       if kind == ast.Typ {
+               obj.Type = &NamedType{Obj: obj}
+       }
+
+       return obj
+}
+
+// ----------------------------------------------------------------------------
+// Error handling
+
+// Internal errors are boxed as importErrors.
+type importError struct {
+       pos scanner.Position
+       err error
+}
+
+func (e importError) Error() string {
+       return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err)
+}
+
+func (p *gcParser) error(err interface{}) {
+       if s, ok := err.(string); ok {
+               err = errors.New(s)
+       }
+       // panic with a runtime.Error if err is not an error
+       panic(importError{p.scanner.Pos(), err.(error)})
+}
+
+func (p *gcParser) errorf(format string, args ...interface{}) {
+       p.error(fmt.Sprintf(format, args...))
+}
+
+func (p *gcParser) expect(tok rune) string {
+       lit := p.lit
+       if p.tok != tok {
+               p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit)
+       }
+       p.next()
+       return lit
+}
+
+func (p *gcParser) expectSpecial(tok string) {
+       sep := 'x' // not white space
+       i := 0
+       for i < len(tok) && p.tok == rune(tok[i]) && sep > ' ' {
+               sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token
+               p.next()
+               i++
+       }
+       if i < len(tok) {
+               p.errorf("expected %q, got %q", tok, tok[0:i])
+       }
+}
+
+func (p *gcParser) expectKeyword(keyword string) {
+       lit := p.expect(scanner.Ident)
+       if lit != keyword {
+               p.errorf("expected keyword %s, got %q", keyword, lit)
+       }
+}
+
+// ----------------------------------------------------------------------------
+// Import declarations
+
+// ImportPath = string_lit .
+//
+func (p *gcParser) parsePkgId() *ast.Object {
+       id, err := strconv.Unquote(p.expect(scanner.String))
+       if err != nil {
+               p.error(err)
+       }
+
+       switch id {
+       case "":
+               // id == "" stands for the imported package id
+               // (only known at time of package installation)
+               id = p.id
+       case "unsafe":
+               // package unsafe is not in the imports map - handle explicitly
+               return Unsafe
+       }
+
+       pkg := p.imports[id]
+       if pkg == nil {
+               pkg = ast.NewObj(ast.Pkg, "")
+               pkg.Data = ast.NewScope(nil)
+               p.imports[id] = pkg
+       }
+
+       return pkg
+}
+
+// dotIdentifier = ( ident | '·' ) { ident | int | '·' } .
+func (p *gcParser) parseDotIdent() string {
+       ident := ""
+       if p.tok != scanner.Int {
+               sep := 'x' // not white space
+               for (p.tok == scanner.Ident || p.tok == scanner.Int || p.tok == '·') && sep > ' ' {
+                       ident += p.lit
+                       sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token
+                       p.next()
+               }
+       }
+       if ident == "" {
+               p.expect(scanner.Ident) // use expect() for error handling
+       }
+       return ident
+}
+
+// ExportedName = "@" ImportPath "." dotIdentifier .
+//
+func (p *gcParser) parseExportedName() (*ast.Object, string) {
+       p.expect('@')
+       pkg := p.parsePkgId()
+       p.expect('.')
+       name := p.parseDotIdent()
+       return pkg, name
+}
+
+// ----------------------------------------------------------------------------
+// Types
+
+// BasicType = identifier .
+//
+func (p *gcParser) parseBasicType() Type {
+       id := p.expect(scanner.Ident)
+       obj := Universe.Lookup(id)
+       if obj == nil || obj.Kind != ast.Typ {
+               p.errorf("not a basic type: %s", id)
+       }
+       return obj.Type.(Type)
+}
+
+// ArrayType = "[" int_lit "]" Type .
+//
+func (p *gcParser) parseArrayType() Type {
+       // "[" already consumed and lookahead known not to be "]"
+       lit := p.expect(scanner.Int)
+       p.expect(']')
+       elt := p.parseType()
+       n, err := strconv.ParseInt(lit, 10, 64)
+       if err != nil {
+               p.error(err)
+       }
+       return &Array{Len: n, Elt: elt}
+}
+
+// MapType = "map" "[" Type "]" Type .
+//
+func (p *gcParser) parseMapType() Type {
+       p.expectKeyword("map")
+       p.expect('[')
+       key := p.parseType()
+       p.expect(']')
+       elt := p.parseType()
+       return &Map{Key: key, Elt: elt}
+}
+
+// Name = identifier | "?" | ExportedName  .
+//
+func (p *gcParser) parseName() (name string) {
+       switch p.tok {
+       case scanner.Ident:
+               name = p.lit
+               p.next()
+       case '?':
+               // anonymous
+               p.next()
+       case '@':
+               // exported name prefixed with package path
+               _, name = p.parseExportedName()
+       default:
+               p.error("name expected")
+       }
+       return
+}
+
+// Field = Name Type [ string_lit ] .
+//
+func (p *gcParser) parseField() *StructField {
+       var f StructField
+       f.Name = p.parseName()
+       f.Type = p.parseType()
+       if p.tok == scanner.String {
+               f.Tag = p.expect(scanner.String)
+       }
+       if f.Name == "" {
+               // anonymous field - typ must be T or *T and T must be a type name
+               if typ, ok := deref(f.Type).(*NamedType); ok && typ.Obj != nil {
+                       f.Name = typ.Obj.Name
+               } else {
+                       p.errorf("anonymous field expected")
+               }
+       }
+       return &f
+}
+
+// StructType = "struct" "{" [ FieldList ] "}" .
+// FieldList  = Field { ";" Field } .
+//
+func (p *gcParser) parseStructType() Type {
+       var fields []*StructField
+
+       parseField := func() {
+               fields = append(fields, p.parseField())
+       }
+
+       p.expectKeyword("struct")
+       p.expect('{')
+       if p.tok != '}' {
+               parseField()
+               for p.tok == ';' {
+                       p.next()
+                       parseField()
+               }
+       }
+       p.expect('}')
+
+       return &Struct{Fields: fields}
+}
+
+// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] .
+//
+func (p *gcParser) parseParameter() (par *ast.Object, isVariadic bool) {
+       name := p.parseName()
+       if name == "" {
+               name = "_" // cannot access unnamed identifiers
+       }
+       if p.tok == '.' {
+               p.expectSpecial("...")
+               isVariadic = true
+       }
+       ptyp := p.parseType()
+       // ignore argument tag (e.g. "noescape")
+       if p.tok == scanner.String {
+               p.expect(scanner.String)
+       }
+       par = ast.NewObj(ast.Var, name)
+       par.Type = ptyp
+       return
+}
+
+// Parameters    = "(" [ ParameterList ] ")" .
+// ParameterList = { Parameter "," } Parameter .
+//
+func (p *gcParser) parseParameters() (list []*ast.Object, isVariadic bool) {
+       parseParameter := func() {
+               par, variadic := p.parseParameter()
+               list = append(list, par)
+               if variadic {
+                       if isVariadic {
+                               p.error("... not on final argument")
+                       }
+                       isVariadic = true
+               }
+       }
+
+       p.expect('(')
+       if p.tok != ')' {
+               parseParameter()
+               for p.tok == ',' {
+                       p.next()
+                       parseParameter()
+               }
+       }
+       p.expect(')')
+
+       return
+}
+
+// Signature = Parameters [ Result ] .
+// Result    = Type | Parameters .
+//
+func (p *gcParser) parseSignature() *Signature {
+       params, isVariadic := p.parseParameters()
+
+       // optional result type
+       var results []*ast.Object
+       switch p.tok {
+       case scanner.Ident, '[', '*', '<', '@':
+               // single, unnamed result
+               result := ast.NewObj(ast.Var, "_")
+               result.Type = p.parseType()
+               results = []*ast.Object{result}
+       case '(':
+               // named or multiple result(s)
+               var variadic bool
+               results, variadic = p.parseParameters()
+               if variadic {
+                       p.error("... not permitted on result type")
+               }
+       }
+
+       return &Signature{Params: params, Results: results, IsVariadic: isVariadic}
+}
+
+// InterfaceType = "interface" "{" [ MethodList ] "}" .
+// MethodList = Method { ";" Method } .
+// Method = Name Signature .
+//
+// (The methods of embedded interfaces are always "inlined"
+// by the compiler and thus embedded interfaces are never
+// visible in the export data.)
+//
+func (p *gcParser) parseInterfaceType() Type {
+       var methods ObjList
+
+       parseMethod := func() {
+               obj := ast.NewObj(ast.Fun, p.parseName())
+               obj.Type = p.parseSignature()
+               methods = append(methods, obj)
+       }
+
+       p.expectKeyword("interface")
+       p.expect('{')
+       if p.tok != '}' {
+               parseMethod()
+               for p.tok == ';' {
+                       p.next()
+                       parseMethod()
+               }
+       }
+       p.expect('}')
+
+       methods.Sort()
+       return &Interface{Methods: methods}
+}
+
+// ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type .
+//
+func (p *gcParser) parseChanType() Type {
+       dir := ast.SEND | ast.RECV
+       if p.tok == scanner.Ident {
+               p.expectKeyword("chan")
+               if p.tok == '<' {
+                       p.expectSpecial("<-")
+                       dir = ast.SEND
+               }
+       } else {
+               p.expectSpecial("<-")
+               p.expectKeyword("chan")
+               dir = ast.RECV
+       }
+       elt := p.parseType()
+       return &Chan{Dir: dir, Elt: elt}
+}
+
+// Type =
+//     BasicType | TypeName | ArrayType | SliceType | StructType |
+//      PointerType | FuncType | InterfaceType | MapType | ChanType |
+//      "(" Type ")" .
+// BasicType = ident .
+// TypeName = ExportedName .
+// SliceType = "[" "]" Type .
+// PointerType = "*" Type .
+// FuncType = "func" Signature .
+//
+func (p *gcParser) parseType() Type {
+       switch p.tok {
+       case scanner.Ident:
+               switch p.lit {
+               default:
+                       return p.parseBasicType()
+               case "struct":
+                       return p.parseStructType()
+               case "func":
+                       // FuncType
+                       p.next()
+                       return p.parseSignature()
+               case "interface":
+                       return p.parseInterfaceType()
+               case "map":
+                       return p.parseMapType()
+               case "chan":
+                       return p.parseChanType()
+               }
+       case '@':
+               // TypeName
+               pkg, name := p.parseExportedName()
+               return p.declare(pkg.Data.(*ast.Scope), ast.Typ, name).Type.(Type)
+       case '[':
+               p.next() // look ahead
+               if p.tok == ']' {
+                       // SliceType
+                       p.next()
+                       return &Slice{Elt: p.parseType()}
+               }
+               return p.parseArrayType()
+       case '*':
+               // PointerType
+               p.next()
+               return &Pointer{Base: p.parseType()}
+       case '<':
+               return p.parseChanType()
+       case '(':
+               // "(" Type ")"
+               p.next()
+               typ := p.parseType()
+               p.expect(')')
+               return typ
+       }
+       p.errorf("expected type, got %s (%q)", scanner.TokenString(p.tok), p.lit)
+       return nil
+}
+
+// ----------------------------------------------------------------------------
+// Declarations
+
+// ImportDecl = "import" identifier string_lit .
+//
+func (p *gcParser) parseImportDecl() {
+       p.expectKeyword("import")
+       // The identifier has no semantic meaning in the import data.
+       // It exists so that error messages can print the real package
+       // name: binary.ByteOrder instead of "encoding/binary".ByteOrder.
+       name := p.expect(scanner.Ident)
+       pkg := p.parsePkgId()
+       assert(pkg.Name == "" || pkg.Name == name)
+       pkg.Name = name
+}
+
+// int_lit = [ "+" | "-" ] { "0" ... "9" } .
+//
+func (p *gcParser) parseInt() (neg bool, val string) {
+       switch p.tok {
+       case '-':
+               neg = true
+               fallthrough
+       case '+':
+               p.next()
+       }
+       val = p.expect(scanner.Int)
+       return
+}
+
+// number = int_lit [ "p" int_lit ] .
+//
+func (p *gcParser) parseNumber() (x operand) {
+       x.mode = constant
+
+       // mantissa
+       neg, val := p.parseInt()
+       mant, ok := new(big.Int).SetString(val, 0)
+       assert(ok)
+       if neg {
+               mant.Neg(mant)
+       }
+
+       if p.lit == "p" {
+               // exponent (base 2)
+               p.next()
+               neg, val = p.parseInt()
+               exp64, err := strconv.ParseUint(val, 10, 0)
+               if err != nil {
+                       p.error(err)
+               }
+               exp := uint(exp64)
+               if neg {
+                       denom := big.NewInt(1)
+                       denom.Lsh(denom, exp)
+                       x.typ = Typ[UntypedFloat]
+                       x.val = normalizeRatConst(new(big.Rat).SetFrac(mant, denom))
+                       return
+               }
+               if exp > 0 {
+                       mant.Lsh(mant, exp)
+               }
+               x.typ = Typ[UntypedFloat]
+               x.val = normalizeIntConst(mant)
+               return
+       }
+
+       x.typ = Typ[UntypedInt]
+       x.val = normalizeIntConst(mant)
+       return
+}
+
+// ConstDecl   = "const" ExportedName [ Type ] "=" Literal .
+// Literal     = bool_lit | int_lit | float_lit | complex_lit | rune_lit | string_lit .
+// bool_lit    = "true" | "false" .
+// complex_lit = "(" float_lit "+" float_lit ")" .
+// rune_lit = "(" int_lit "+" int_lit ")" .
+// string_lit  = `"` { unicode_char } `"` .
+//
+func (p *gcParser) parseConstDecl() {
+       p.expectKeyword("const")
+       pkg, name := p.parseExportedName()
+       obj := p.declare(pkg.Data.(*ast.Scope), ast.Con, name)
+       var x operand
+       if p.tok != '=' {
+               obj.Type = p.parseType()
+       }
+       p.expect('=')
+       switch p.tok {
+       case scanner.Ident:
+               // bool_lit
+               if p.lit != "true" && p.lit != "false" {
+                       p.error("expected true or false")
+               }
+               x.typ = Typ[UntypedBool]
+               x.val = p.lit == "true"
+               p.next()
+
+       case '-', scanner.Int:
+               // int_lit
+               x = p.parseNumber()
+
+       case '(':
+               // complex_lit or rune_lit
+               p.next()
+               if p.tok == scanner.Char {
+                       p.next()
+                       p.expect('+')
+                       x = p.parseNumber()
+                       x.typ = Typ[UntypedRune]
+                       p.expect(')')
+                       break
+               }
+               re := p.parseNumber()
+               p.expect('+')
+               im := p.parseNumber()
+               p.expect(')')
+               x.typ = Typ[UntypedComplex]
+               // TODO(gri) fix this
+               _, _ = re, im
+               x.val = zeroConst
+
+       case scanner.Char:
+               // rune_lit
+               x.setConst(token.CHAR, p.lit)
+               p.next()
+
+       case scanner.String:
+               // string_lit
+               x.setConst(token.STRING, p.lit)
+               p.next()
+
+       default:
+               p.errorf("expected literal got %s", scanner.TokenString(p.tok))
+       }
+       if obj.Type == nil {
+               obj.Type = x.typ
+       }
+       assert(x.val != nil)
+       obj.Data = x.val
+}
+
+// TypeDecl = "type" ExportedName Type .
+//
+func (p *gcParser) parseTypeDecl() {
+       p.expectKeyword("type")
+       pkg, name := p.parseExportedName()
+       obj := p.declare(pkg.Data.(*ast.Scope), ast.Typ, name)
+
+       // The type object may have been imported before and thus already
+       // have a type associated with it. We still need to parse the type
+       // structure, but throw it away if the object already has a type.
+       // This ensures that all imports refer to the same type object for
+       // a given type declaration.
+       typ := p.parseType()
+
+       if name := obj.Type.(*NamedType); name.Underlying == nil {
+               name.Underlying = typ
+       }
+}
+
+// VarDecl = "var" ExportedName Type .
+//
+func (p *gcParser) parseVarDecl() {
+       p.expectKeyword("var")
+       pkg, name := p.parseExportedName()
+       obj := p.declare(pkg.Data.(*ast.Scope), ast.Var, name)
+       obj.Type = p.parseType()
+}
+
+// FuncBody = "{" ... "}" .
+//
+func (p *gcParser) parseFuncBody() {
+       p.expect('{')
+       for i := 1; i > 0; p.next() {
+               switch p.tok {
+               case '{':
+                       i++
+               case '}':
+                       i--
+               }
+       }
+}
+
+// FuncDecl = "func" ExportedName Signature [ FuncBody ] .
+//
+func (p *gcParser) parseFuncDecl() {
+       // "func" already consumed
+       pkg, name := p.parseExportedName()
+       obj := p.declare(pkg.Data.(*ast.Scope), ast.Fun, name)
+       obj.Type = p.parseSignature()
+       if p.tok == '{' {
+               p.parseFuncBody()
+       }
+}
+
+// MethodDecl = "func" Receiver Name Signature .
+// Receiver   = "(" ( identifier | "?" ) [ "*" ] ExportedName ")" [ FuncBody ].
+//
+func (p *gcParser) parseMethodDecl() {
+       // "func" already consumed
+       p.expect('(')
+       p.parseParameter() // receiver
+       p.expect(')')
+       p.parseName() // unexported method names in imports are qualified with their package.
+       p.parseSignature()
+       if p.tok == '{' {
+               p.parseFuncBody()
+       }
+}
+
+// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" .
+//
+func (p *gcParser) parseDecl() {
+       switch p.lit {
+       case "import":
+               p.parseImportDecl()
+       case "const":
+               p.parseConstDecl()
+       case "type":
+               p.parseTypeDecl()
+       case "var":
+               p.parseVarDecl()
+       case "func":
+               p.next() // look ahead
+               if p.tok == '(' {
+                       p.parseMethodDecl()
+               } else {
+                       p.parseFuncDecl()
+               }
+       }
+       p.expect('\n')
+}
+
+// ----------------------------------------------------------------------------
+// Export
+
+// Export        = "PackageClause { Decl } "$$" .
+// PackageClause = "package" identifier [ "safe" ] "\n" .
+//
+func (p *gcParser) parseExport() *ast.Object {
+       p.expectKeyword("package")
+       name := p.expect(scanner.Ident)
+       if p.tok != '\n' {
+               // A package is safe if it was compiled with the -u flag,
+               // which disables the unsafe package.
+               // TODO(gri) remember "safe" package
+               p.expectKeyword("safe")
+       }
+       p.expect('\n')
+
+       pkg := p.imports[p.id]
+       if pkg == nil {
+               pkg = ast.NewObj(ast.Pkg, name)
+               pkg.Data = ast.NewScope(nil)
+               p.imports[p.id] = pkg
+       }
+
+       for p.tok != '$' && p.tok != scanner.EOF {
+               p.parseDecl()
+       }
+
+       if ch := p.scanner.Peek(); p.tok != '$' || ch != '$' {
+               // don't call next()/expect() since reading past the
+               // export data may cause scanner errors (e.g. NUL chars)
+               p.errorf("expected '$$', got %s %c", scanner.TokenString(p.tok), ch)
+       }
+
+       if n := p.scanner.ErrorCount; n != 0 {
+               p.errorf("expected no scanner errors, got %d", n)
+       }
+
+       return pkg
+}
diff --git a/src/pkg/exp/types/staging/gcimporter_test.go b/src/pkg/exp/types/staging/gcimporter_test.go
new file mode 100644 (file)
index 0000000..b85207b
--- /dev/null
@@ -0,0 +1,154 @@
+// Copyright 2011 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.
+
+package types
+
+import (
+       "go/ast"
+       "go/build"
+       "io/ioutil"
+       "os"
+       "os/exec"
+       "path/filepath"
+       "runtime"
+       "strings"
+       "testing"
+       "time"
+)
+
+var gcPath string // Go compiler path
+
+func init() {
+       // determine compiler
+       var gc string
+       switch runtime.GOARCH {
+       case "386":
+               gc = "8g"
+       case "amd64":
+               gc = "6g"
+       case "arm":
+               gc = "5g"
+       default:
+               gcPath = "unknown-GOARCH-compiler"
+               return
+       }
+       gcPath = filepath.Join(build.ToolDir, gc)
+}
+
+func compile(t *testing.T, dirname, filename string) string {
+       cmd := exec.Command(gcPath, filename)
+       cmd.Dir = dirname
+       out, err := cmd.CombinedOutput()
+       if err != nil {
+               t.Fatalf("%s %s failed: %s", gcPath, filename, err)
+               return ""
+       }
+       t.Logf("%s", string(out))
+       archCh, _ := build.ArchChar(runtime.GOARCH)
+       // filename should end with ".go"
+       return filepath.Join(dirname, filename[:len(filename)-2]+archCh)
+}
+
+// Use the same global imports map for all tests. The effect is
+// as if all tested packages were imported into a single package.
+var imports = make(map[string]*ast.Object)
+
+func testPath(t *testing.T, path string) bool {
+       _, err := GcImport(imports, path)
+       if err != nil {
+               t.Errorf("testPath(%s): %s", path, err)
+               return false
+       }
+       return true
+}
+
+const maxTime = 3 * time.Second
+
+func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
+       dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir)
+       list, err := ioutil.ReadDir(dirname)
+       if err != nil {
+               t.Errorf("testDir(%s): %s", dirname, err)
+       }
+       for _, f := range list {
+               if time.Now().After(endTime) {
+                       t.Log("testing time used up")
+                       return
+               }
+               switch {
+               case !f.IsDir():
+                       // try extensions
+                       for _, ext := range pkgExts {
+                               if strings.HasSuffix(f.Name(), ext) {
+                                       name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension
+                                       if testPath(t, filepath.Join(dir, name)) {
+                                               nimports++
+                                       }
+                               }
+                       }
+               case f.IsDir():
+                       nimports += testDir(t, filepath.Join(dir, f.Name()), endTime)
+               }
+       }
+       return
+}
+
+func TestGcImport(t *testing.T) {
+       // On cross-compile builds, the path will not exist.
+       // Need to use GOHOSTOS, which is not available.
+       if _, err := os.Stat(gcPath); err != nil {
+               t.Logf("skipping test: %v", err)
+               return
+       }
+
+       if outFn := compile(t, "testdata", "exports.go"); outFn != "" {
+               defer os.Remove(outFn)
+       }
+
+       nimports := 0
+       if testPath(t, "./testdata/exports") {
+               nimports++
+       }
+       nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages
+       t.Logf("tested %d imports", nimports)
+}
+
+var importedObjectTests = []struct {
+       name string
+       kind ast.ObjKind
+       typ  string
+}{
+       {"unsafe.Pointer", ast.Typ, "Pointer"},
+       {"math.Pi", ast.Con, "untyped float"},
+       {"io.Reader", ast.Typ, "interface{Read(p []byte) (n int, err error)}"},
+       {"io.ReadWriter", ast.Typ, "interface{Read(p []byte) (n int, err error); Write(p []byte) (n int, err error)}"},
+       {"math.Sin", ast.Fun, "func(x float64) (_ float64)"},
+       // TODO(gri) add more tests
+}
+
+func TestGcImportedTypes(t *testing.T) {
+       for _, test := range importedObjectTests {
+               s := strings.Split(test.name, ".")
+               if len(s) != 2 {
+                       t.Fatal("inconsistent test data")
+               }
+               importPath := s[0]
+               objName := s[1]
+
+               pkg, err := GcImport(imports, importPath)
+               if err != nil {
+                       t.Error(err)
+                       continue
+               }
+
+               obj := pkg.Data.(*ast.Scope).Lookup(objName)
+               if obj.Kind != test.kind {
+                       t.Errorf("%s: got kind = %q; want %q", test.name, obj.Kind, test.kind)
+               }
+               typ := typeString(underlying(obj.Type.(Type)))
+               if typ != test.typ {
+                       t.Errorf("%s: got type = %q; want %q", test.name, typ, test.typ)
+               }
+       }
+}
diff --git a/src/pkg/exp/types/staging/testdata/exports.go b/src/pkg/exp/types/staging/testdata/exports.go
new file mode 100644 (file)
index 0000000..8ee28b0
--- /dev/null
@@ -0,0 +1,89 @@
+// Copyright 2011 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.
+
+// This file is used to generate an object file which
+// serves as test file for gcimporter_test.go.
+
+package exports
+
+import (
+       "go/ast"
+)
+
+// Issue 3682: Correctly read dotted identifiers from export data.
+const init1 = 0
+
+func init() {}
+
+const (
+       C0 int = 0
+       C1     = 3.14159265
+       C2     = 2.718281828i
+       C3     = -123.456e-789
+       C4     = +123.456E+789
+       C5     = 1234i
+       C6     = "foo\n"
+       C7     = `bar\n`
+)
+
+type (
+       T1  int
+       T2  [10]int
+       T3  []int
+       T4  *int
+       T5  chan int
+       T6a chan<- int
+       T6b chan (<-chan int)
+       T6c chan<- (chan int)
+       T7  <-chan *ast.File
+       T8  struct{}
+       T9  struct {
+               a    int
+               b, c float32
+               d    []string `go:"tag"`
+       }
+       T10 struct {
+               T8
+               T9
+               _ *T10
+       }
+       T11 map[int]string
+       T12 interface{}
+       T13 interface {
+               m1()
+               m2(int) float32
+       }
+       T14 interface {
+               T12
+               T13
+               m3(x ...struct{}) []T9
+       }
+       T15 func()
+       T16 func(int)
+       T17 func(x int)
+       T18 func() float32
+       T19 func() (x float32)
+       T20 func(...interface{})
+       T21 struct{ next *T21 }
+       T22 struct{ link *T23 }
+       T23 struct{ link *T22 }
+       T24 *T24
+       T25 *T26
+       T26 *T27
+       T27 *T25
+       T28 func(T28) T28
+)
+
+var (
+       V0 int
+       V1 = -991.0
+)
+
+func F1()         {}
+func F2(x int)    {}
+func F3() int     { return 0 }
+func F4() float32 { return 0 }
+func F5(a, b, c int, u, v, w struct{ x, y T1 }, more ...interface{}) (p, q, r chan<- T10)
+
+func (p *T1) M1()