diff --git a/go/internal/gccgoimporter/gccgoinstallation.go b/go/internal/gccgoimporter/gccgoinstallation.go new file mode 100644 index 00000000..2bf1f6ff --- /dev/null +++ b/go/internal/gccgoimporter/gccgoinstallation.go @@ -0,0 +1,96 @@ +// Copyright 2013 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 gccgoimporter + +// This is a verbatim copy of $GOROOT/src/go/internal/gccgoimporter/gccgoinstallation.go. + +import ( + "bufio" + "go/types" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// Information about a specific installation of gccgo. +type GccgoInstallation struct { + // Version of gcc (e.g. 4.8.0). + GccVersion string + + // Target triple (e.g. x86_64-unknown-linux-gnu). + TargetTriple string + + // Built-in library paths used by this installation. + LibPaths []string +} + +// Ask the driver at the given path for information for this GccgoInstallation. +func (inst *GccgoInstallation) InitFromDriver(gccgoPath string) (err error) { + cmd := exec.Command(gccgoPath, "-###", "-S", "-x", "go", "-") + stderr, err := cmd.StderrPipe() + if err != nil { + return + } + + err = cmd.Start() + if err != nil { + return + } + + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(line, "Target: "): + inst.TargetTriple = line[8:] + + case line[0] == ' ': + args := strings.Fields(line) + for _, arg := range args[1:] { + if strings.HasPrefix(arg, "-L") { + inst.LibPaths = append(inst.LibPaths, arg[2:]) + } + } + } + } + + stdout, err := exec.Command(gccgoPath, "-dumpversion").Output() + if err != nil { + return + } + inst.GccVersion = strings.TrimSpace(string(stdout)) + + return +} + +// Return the list of export search paths for this GccgoInstallation. +func (inst *GccgoInstallation) SearchPaths() (paths []string) { + for _, lpath := range inst.LibPaths { + spath := filepath.Join(lpath, "go", inst.GccVersion) + fi, err := os.Stat(spath) + if err != nil || !fi.IsDir() { + continue + } + paths = append(paths, spath) + + spath = filepath.Join(spath, inst.TargetTriple) + fi, err = os.Stat(spath) + if err != nil || !fi.IsDir() { + continue + } + paths = append(paths, spath) + } + + paths = append(paths, inst.LibPaths...) + + return +} + +// Return an importer that searches incpaths followed by the gcc installation's +// built-in search paths and the current directory. +func (inst *GccgoInstallation) GetImporter(incpaths []string, initmap map[*types.Package]InitData) Importer { + return GetImporter(append(append(incpaths, inst.SearchPaths()...), "."), initmap) +} diff --git a/go/internal/gccgoimporter/gccgoinstallation_test.go b/go/internal/gccgoimporter/gccgoinstallation_test.go new file mode 100644 index 00000000..fb1fa6d4 --- /dev/null +++ b/go/internal/gccgoimporter/gccgoinstallation_test.go @@ -0,0 +1,195 @@ +// Copyright 2013 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 gccgoimporter + +// This is a verbatim copy of $GOROOT/src/go/internal/gccgoimporter/gccgoinstallation_test.go. + +import ( + "go/types" + "runtime" + "testing" +) + +var importablePackages = [...]string{ + "archive/tar", + "archive/zip", + "bufio", + "bytes", + "compress/bzip2", + "compress/flate", + "compress/gzip", + "compress/lzw", + "compress/zlib", + "container/heap", + "container/list", + "container/ring", + "crypto/aes", + "crypto/cipher", + "crypto/des", + "crypto/dsa", + "crypto/ecdsa", + "crypto/elliptic", + "crypto", + "crypto/hmac", + "crypto/md5", + "crypto/rand", + "crypto/rc4", + "crypto/rsa", + "crypto/sha1", + "crypto/sha256", + "crypto/sha512", + "crypto/subtle", + "crypto/tls", + "crypto/x509", + "crypto/x509/pkix", + "database/sql/driver", + "database/sql", + "debug/dwarf", + "debug/elf", + "debug/gosym", + "debug/macho", + "debug/pe", + "encoding/ascii85", + "encoding/asn1", + "encoding/base32", + "encoding/base64", + "encoding/binary", + "encoding/csv", + "encoding/gob", + "encoding", + "encoding/hex", + "encoding/json", + "encoding/pem", + "encoding/xml", + "errors", + "exp/proxy", + "exp/terminal", + "expvar", + "flag", + "fmt", + "go/ast", + "go/build", + "go/doc", + "go/format", + "go/parser", + "go/printer", + "go/scanner", + "go/token", + "hash/adler32", + "hash/crc32", + "hash/crc64", + "hash/fnv", + "hash", + "html", + "html/template", + "image/color", + "image/color/palette", + "image/draw", + "image/gif", + "image", + "image/jpeg", + "image/png", + "index/suffixarray", + "io", + "io/ioutil", + "log", + "log/syslog", + "math/big", + "math/cmplx", + "math", + "math/rand", + "mime", + "mime/multipart", + "net", + "net/http/cgi", + "net/http/cookiejar", + "net/http/fcgi", + "net/http", + "net/http/httptest", + "net/http/httputil", + "net/http/pprof", + "net/mail", + "net/rpc", + "net/rpc/jsonrpc", + "net/smtp", + "net/textproto", + "net/url", + "old/regexp", + "old/template", + "os/exec", + "os", + "os/signal", + "os/user", + "path/filepath", + "path", + "reflect", + "regexp", + "regexp/syntax", + "runtime/debug", + "runtime", + "runtime/pprof", + "sort", + "strconv", + "strings", + "sync/atomic", + "sync", + "syscall", + "testing", + "testing/iotest", + "testing/quick", + "text/scanner", + "text/tabwriter", + "text/template", + "text/template/parse", + "time", + "unicode", + "unicode/utf16", + "unicode/utf8", +} + +func TestInstallationImporter(t *testing.T) { + // This test relies on gccgo being around, which it most likely will be if we + // were compiled with gccgo. + if runtime.Compiler != "gccgo" { + t.Skip("This test needs gccgo") + return + } + + var inst GccgoInstallation + err := inst.InitFromDriver("gccgo") + if err != nil { + t.Fatal(err) + } + imp := inst.GetImporter(nil, nil) + + // Ensure we don't regress the number of packages we can parse. First import + // all packages into the same map and then each individually. + pkgMap := make(map[string]*types.Package) + for _, pkg := range importablePackages { + _, err = imp(pkgMap, pkg) + if err != nil { + t.Error(err) + } + } + + for _, pkg := range importablePackages { + _, err = imp(make(map[string]*types.Package), pkg) + if err != nil { + t.Error(err) + } + } + + // Test for certain specific entities in the imported data. + for _, test := range [...]importerTest{ + {pkgpath: "io", name: "Reader", want: "type Reader interface{Read(p []uint8) (n int, err error)}"}, + {pkgpath: "io", name: "ReadWriter", want: "type ReadWriter interface{Reader; Writer}"}, + {pkgpath: "math", name: "Pi", want: "const Pi untyped float"}, + {pkgpath: "math", name: "Sin", want: "func Sin(x float64) float64"}, + {pkgpath: "sort", name: "Ints", want: "func Ints(a []int)"}, + {pkgpath: "unsafe", name: "Pointer", want: "type Pointer unsafe.Pointer"}, + } { + runImporterTest(t, imp, nil, &test) + } +} diff --git a/go/internal/gccgoimporter/importer.go b/go/internal/gccgoimporter/importer.go new file mode 100644 index 00000000..98454ba4 --- /dev/null +++ b/go/internal/gccgoimporter/importer.go @@ -0,0 +1,208 @@ +// Copyright 2013 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 gccgoimporter implements Import for gccgo-generated object files. +package gccgoimporter // import "golang.org/x/tools/go/internal/gccgoimporter" + +// This is a verbatim copy of $GOROOT/src/go/internal/gccgoimporter/importer.go. + +import ( + "bytes" + "debug/elf" + "fmt" + "go/types" + "io" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// A PackageInit describes an imported package that needs initialization. +type PackageInit struct { + Name string // short package name + InitFunc string // name of init function + Priority int // priority of init function, see InitData.Priority +} + +// The gccgo-specific init data for a package. +type InitData struct { + // Initialization priority of this package relative to other packages. + // This is based on the maximum depth of the package's dependency graph; + // it is guaranteed to be greater than that of its dependencies. + Priority int + + // The list of packages which this package depends on to be initialized, + // including itself if needed. This is the subset of the transitive closure of + // the package's dependencies that need initialization. + Inits []PackageInit +} + +// Locate the file from which to read export data. +// This is intended to replicate the logic in gofrontend. +func findExportFile(searchpaths []string, pkgpath string) (string, error) { + for _, spath := range searchpaths { + pkgfullpath := filepath.Join(spath, pkgpath) + pkgdir, name := filepath.Split(pkgfullpath) + + for _, filepath := range [...]string{ + pkgfullpath, + pkgfullpath + ".gox", + pkgdir + "lib" + name + ".so", + pkgdir + "lib" + name + ".a", + pkgfullpath + ".o", + } { + fi, err := os.Stat(filepath) + if err == nil && !fi.IsDir() { + return filepath, nil + } + } + } + + return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":")) +} + +const ( + gccgov1Magic = "v1;\n" + goimporterMagic = "\n$$ " + archiveMagic = "! 0 { + initdata := initmap[pkg] + found := false + // Check that the package's own init function has the package's priority + for _, pkginit := range initdata.Inits { + if pkginit.InitFunc == test.wantinits[0] { + if initdata.Priority != pkginit.Priority { + t.Errorf("%s: got self priority %d; want %d", test.pkgpath, pkginit.Priority, initdata.Priority) + } + found = true + break + } + } + + if !found { + t.Errorf("%s: could not find expected function %q", test.pkgpath, test.wantinits[0]) + } + + // Each init function in the list other than the first one is a + // dependency of the function immediately before it. Check that + // the init functions appear in descending priority order. + priority := initdata.Priority + for _, wantdepinit := range test.wantinits[1:] { + found = false + for _, pkginit := range initdata.Inits { + if pkginit.InitFunc == wantdepinit { + if priority <= pkginit.Priority { + t.Errorf("%s: got dep priority %d; want less than %d", test.pkgpath, pkginit.Priority, priority) + } + found = true + priority = pkginit.Priority + break + } + } + + if !found { + t.Errorf("%s: could not find expected function %q", test.pkgpath, wantdepinit) + } + } + } +} + +var importerTests = [...]importerTest{ + {pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"}, + {pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1 + -1i)"}, + {pkgpath: "complexnums", name: "NP", want: "const NP untyped complex", wantval: "(-1 + 1i)"}, + {pkgpath: "complexnums", name: "PN", want: "const PN untyped complex", wantval: "(1 + -1i)"}, + {pkgpath: "complexnums", name: "PP", want: "const PP untyped complex", wantval: "(1 + 1i)"}, + // TODO: enable this entry once bug has been tracked down + //{pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import", "math..import"}}, +} + +func TestGoxImporter(t *testing.T) { + testenv.MustHaveGoBuild(t) + + initmap := make(map[*types.Package]InitData) + imp := GetImporter([]string{"testdata"}, initmap) + + for _, test := range importerTests { + runImporterTest(t, imp, initmap, &test) + } +} + +func TestObjImporter(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // This test relies on gccgo being around, which it most likely will be if we + // were compiled with gccgo. + if runtime.Compiler != "gccgo" { + t.Skip("This test needs gccgo") + return + } + + tmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + initmap := make(map[*types.Package]InitData) + imp := GetImporter([]string{tmpdir}, initmap) + + artmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + arinitmap := make(map[*types.Package]InitData) + arimp := GetImporter([]string{artmpdir}, arinitmap) + + for _, test := range importerTests { + gofile := filepath.Join("testdata", test.pkgpath+".go") + ofile := filepath.Join(tmpdir, test.pkgpath+".o") + afile := filepath.Join(artmpdir, "lib"+test.pkgpath+".a") + + cmd := exec.Command("gccgo", "-fgo-pkgpath="+test.pkgpath, "-c", "-o", ofile, gofile) + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("gccgo %s failed: %s", gofile, err) + } + + runImporterTest(t, imp, initmap, &test) + + cmd = exec.Command("ar", "cr", afile, ofile) + out, err = cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("ar cr %s %s failed: %s", afile, ofile, err) + } + + runImporterTest(t, arimp, arinitmap, &test) + + if err = os.Remove(ofile); err != nil { + t.Fatal(err) + } + if err = os.Remove(afile); err != nil { + t.Fatal(err) + } + } + + if err = os.Remove(tmpdir); err != nil { + t.Fatal(err) + } +} diff --git a/go/internal/gccgoimporter/parser.go b/go/internal/gccgoimporter/parser.go new file mode 100644 index 00000000..68b196e7 --- /dev/null +++ b/go/internal/gccgoimporter/parser.go @@ -0,0 +1,857 @@ +// Copyright 2013 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 gccgoimporter + +// This is a verbatim copy of $GOROOT/src/go/internal/gccgoimporter/parser.go. + +import ( + "bytes" + "errors" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "strconv" + "strings" + "text/scanner" +) + +type parser struct { + scanner scanner.Scanner + tok rune // current token + lit string // literal string; only valid for Ident, Int, String tokens + pkgpath string // package path of imported package + pkgname string // name of imported package + pkg *types.Package // reference to imported package + imports map[string]*types.Package // package path -> package object + typeMap map[int]types.Type // type number -> type + initdata InitData // package init priority data +} + +func (p *parser) init(filename string, src io.Reader, imports map[string]*types.Package) { + p.scanner.Init(src) + p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } + p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments + p.scanner.Whitespace = 1<<'\t' | 1<<'\n' | 1<<' ' + p.scanner.Filename = filename // for good error messages + p.next() + p.imports = imports + p.typeMap = make(map[int]types.Type) +} + +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 *parser) 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 *parser) errorf(format string, args ...interface{}) { + p.error(fmt.Errorf(format, args...)) +} + +func (p *parser) 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 *parser) expectKeyword(keyword string) { + lit := p.expect(scanner.Ident) + if lit != keyword { + p.errorf("expected keyword %s, got %q", keyword, lit) + } +} + +func (p *parser) parseString() string { + str, err := strconv.Unquote(p.expect(scanner.String)) + if err != nil { + p.error(err) + } + return str +} + +// unquotedString = { unquotedStringChar } . +// unquotedStringChar = . +func (p *parser) parseUnquotedString() string { + if p.tok == scanner.EOF { + p.error("unexpected EOF") + } + var buf bytes.Buffer + buf.WriteString(p.scanner.TokenText()) + // This loop needs to examine each character before deciding whether to consume it. If we see a semicolon, + // we need to let it be consumed by p.next(). + for ch := p.scanner.Peek(); ch != ';' && ch != scanner.EOF && p.scanner.Whitespace&(1< 0 { + p.expect(',') + } + par, variadic := p.parseParam(pkg) + list = append(list, par) + if variadic { + if isVariadic { + p.error("... not on final argument") + } + isVariadic = true + } + } + p.expect(')') + + return types.NewTuple(list...), isVariadic +} + +// ResultList = Type | ParamList . +func (p *parser) parseResultList(pkg *types.Package) *types.Tuple { + switch p.tok { + case '<': + return types.NewTuple(types.NewParam(token.NoPos, pkg, "", p.parseType(pkg))) + + case '(': + params, _ := p.parseParamList(pkg) + return params + + default: + return nil + } +} + +// FunctionType = ParamList ResultList . +func (p *parser) parseFunctionType(pkg *types.Package) *types.Signature { + params, isVariadic := p.parseParamList(pkg) + results := p.parseResultList(pkg) + return types.NewSignature(nil, params, results, isVariadic) +} + +// Func = Name FunctionType . +func (p *parser) parseFunc(pkg *types.Package) *types.Func { + name := p.parseName() + if strings.ContainsRune(name, '$') { + // This is a Type$equal or Type$hash function, which we don't want to parse, + // except for the types. + p.discardDirectiveWhileParsingTypes(pkg) + return nil + } + return types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg)) +} + +// InterfaceType = "interface" "{" { ("?" Type | Func) ";" } "}" . +func (p *parser) parseInterfaceType(pkg *types.Package) types.Type { + p.expectKeyword("interface") + + var methods []*types.Func + var typs []*types.Named + + p.expect('{') + for p.tok != '}' && p.tok != scanner.EOF { + if p.tok == '?' { + p.next() + typs = append(typs, p.parseType(pkg).(*types.Named)) + } else { + method := p.parseFunc(pkg) + methods = append(methods, method) + } + p.expect(';') + } + p.expect('}') + + return types.NewInterface(methods, typs) +} + +// PointerType = "*" ("any" | Type) . +func (p *parser) parsePointerType(pkg *types.Package) types.Type { + p.expect('*') + if p.tok == scanner.Ident { + p.expectKeyword("any") + return types.Typ[types.UnsafePointer] + } + return types.NewPointer(p.parseType(pkg)) +} + +// TypeDefinition = NamedType | MapType | ChanType | StructType | InterfaceType | PointerType | ArrayOrSliceType | FunctionType . +func (p *parser) parseTypeDefinition(pkg *types.Package, n int) types.Type { + var t types.Type + switch p.tok { + case scanner.String: + t = p.parseNamedType(n) + + case scanner.Ident: + switch p.lit { + case "map": + t = p.parseMapType(pkg) + + case "chan": + t = p.parseChanType(pkg) + + case "struct": + t = p.parseStructType(pkg) + + case "interface": + t = p.parseInterfaceType(pkg) + } + + case '*': + t = p.parsePointerType(pkg) + + case '[': + t = p.parseArrayOrSliceType(pkg) + + case '(': + t = p.parseFunctionType(pkg) + } + + p.typeMap[n] = t + return t +} + +const ( + // From gofrontend/go/export.h + // Note that these values are negative in the gofrontend and have been made positive + // in the gccgoimporter. + gccgoBuiltinINT8 = 1 + gccgoBuiltinINT16 = 2 + gccgoBuiltinINT32 = 3 + gccgoBuiltinINT64 = 4 + gccgoBuiltinUINT8 = 5 + gccgoBuiltinUINT16 = 6 + gccgoBuiltinUINT32 = 7 + gccgoBuiltinUINT64 = 8 + gccgoBuiltinFLOAT32 = 9 + gccgoBuiltinFLOAT64 = 10 + gccgoBuiltinINT = 11 + gccgoBuiltinUINT = 12 + gccgoBuiltinUINTPTR = 13 + gccgoBuiltinBOOL = 15 + gccgoBuiltinSTRING = 16 + gccgoBuiltinCOMPLEX64 = 17 + gccgoBuiltinCOMPLEX128 = 18 + gccgoBuiltinERROR = 19 + gccgoBuiltinBYTE = 20 + gccgoBuiltinRUNE = 21 +) + +func lookupBuiltinType(typ int) types.Type { + return [...]types.Type{ + gccgoBuiltinINT8: types.Typ[types.Int8], + gccgoBuiltinINT16: types.Typ[types.Int16], + gccgoBuiltinINT32: types.Typ[types.Int32], + gccgoBuiltinINT64: types.Typ[types.Int64], + gccgoBuiltinUINT8: types.Typ[types.Uint8], + gccgoBuiltinUINT16: types.Typ[types.Uint16], + gccgoBuiltinUINT32: types.Typ[types.Uint32], + gccgoBuiltinUINT64: types.Typ[types.Uint64], + gccgoBuiltinFLOAT32: types.Typ[types.Float32], + gccgoBuiltinFLOAT64: types.Typ[types.Float64], + gccgoBuiltinINT: types.Typ[types.Int], + gccgoBuiltinUINT: types.Typ[types.Uint], + gccgoBuiltinUINTPTR: types.Typ[types.Uintptr], + gccgoBuiltinBOOL: types.Typ[types.Bool], + gccgoBuiltinSTRING: types.Typ[types.String], + gccgoBuiltinCOMPLEX64: types.Typ[types.Complex64], + gccgoBuiltinCOMPLEX128: types.Typ[types.Complex128], + gccgoBuiltinERROR: types.Universe.Lookup("error").Type(), + gccgoBuiltinBYTE: types.Universe.Lookup("byte").Type(), + gccgoBuiltinRUNE: types.Universe.Lookup("rune").Type(), + }[typ] +} + +// Type = "<" "type" ( "-" int | int [ TypeDefinition ] ) ">" . +func (p *parser) parseType(pkg *types.Package) (t types.Type) { + p.expect('<') + p.expectKeyword("type") + + switch p.tok { + case scanner.Int: + n := p.parseInt() + + if p.tok == '>' { + t = p.typeMap[int(n)] + } else { + t = p.parseTypeDefinition(pkg, int(n)) + } + + case '-': + p.next() + n := p.parseInt() + t = lookupBuiltinType(int(n)) + + default: + p.errorf("expected type number, got %s (%q)", scanner.TokenString(p.tok), p.lit) + return nil + } + + p.expect('>') + return +} + +// PackageInit = unquotedString unquotedString int . +func (p *parser) parsePackageInit() PackageInit { + name := p.parseUnquotedString() + initfunc := p.parseUnquotedString() + priority := int(p.parseInt()) + return PackageInit{Name: name, InitFunc: initfunc, Priority: priority} +} + +// Throw away tokens until we see a ';'. If we see a '<', attempt to parse as a type. +func (p *parser) discardDirectiveWhileParsingTypes(pkg *types.Package) { + for { + switch p.tok { + case ';': + return + case '<': + p.parseType(p.pkg) + case scanner.EOF: + p.error("unexpected EOF") + default: + p.next() + } + } +} + +// Create the package if we have parsed both the package path and package name. +func (p *parser) maybeCreatePackage() { + if p.pkgname != "" && p.pkgpath != "" { + p.pkg = p.getPkg(p.pkgpath, p.pkgname) + } +} + +// InitDataDirective = "v1" ";" | +// "priority" int ";" | +// "init" { PackageInit } ";" | +// "checksum" unquotedString ";" . +func (p *parser) parseInitDataDirective() { + if p.tok != scanner.Ident { + // unexpected token kind; panic + p.expect(scanner.Ident) + } + + switch p.lit { + case "v1": + p.next() + p.expect(';') + + case "priority": + p.next() + p.initdata.Priority = int(p.parseInt()) + p.expect(';') + + case "init": + p.next() + for p.tok != ';' && p.tok != scanner.EOF { + p.initdata.Inits = append(p.initdata.Inits, p.parsePackageInit()) + } + p.expect(';') + + case "checksum": + // Don't let the scanner try to parse the checksum as a number. + defer func(mode uint) { + p.scanner.Mode = mode + }(p.scanner.Mode) + p.scanner.Mode &^= scanner.ScanInts | scanner.ScanFloats + p.next() + p.parseUnquotedString() + p.expect(';') + + default: + p.errorf("unexpected identifier: %q", p.lit) + } +} + +// Directive = InitDataDirective | +// "package" unquotedString ";" | +// "pkgpath" unquotedString ";" | +// "import" unquotedString unquotedString string ";" | +// "func" Func ";" | +// "type" Type ";" | +// "var" Var ";" | +// "const" Const ";" . +func (p *parser) parseDirective() { + if p.tok != scanner.Ident { + // unexpected token kind; panic + p.expect(scanner.Ident) + } + + switch p.lit { + case "v1", "priority", "init", "checksum": + p.parseInitDataDirective() + + case "package": + p.next() + p.pkgname = p.parseUnquotedString() + p.maybeCreatePackage() + p.expect(';') + + case "pkgpath": + p.next() + p.pkgpath = p.parseUnquotedString() + p.maybeCreatePackage() + p.expect(';') + + case "import": + p.next() + pkgname := p.parseUnquotedString() + pkgpath := p.parseUnquotedString() + p.getPkg(pkgpath, pkgname) + p.parseString() + p.expect(';') + + case "func": + p.next() + fun := p.parseFunc(p.pkg) + if fun != nil { + p.pkg.Scope().Insert(fun) + } + p.expect(';') + + case "type": + p.next() + p.parseType(p.pkg) + p.expect(';') + + case "var": + p.next() + v := p.parseVar(p.pkg) + p.pkg.Scope().Insert(v) + p.expect(';') + + case "const": + p.next() + c := p.parseConst(p.pkg) + p.pkg.Scope().Insert(c) + p.expect(';') + + default: + p.errorf("unexpected identifier: %q", p.lit) + } +} + +// Package = { Directive } . +func (p *parser) parsePackage() *types.Package { + for p.tok != scanner.EOF { + p.parseDirective() + } + for _, typ := range p.typeMap { + if it, ok := typ.(*types.Interface); ok { + it.Complete() + } + } + p.pkg.MarkComplete() + return p.pkg +} + +// InitData = { InitDataDirective } . +func (p *parser) parseInitData() { + for p.tok != scanner.EOF { + p.parseInitDataDirective() + } +} diff --git a/go/internal/gccgoimporter/parser_test.go b/go/internal/gccgoimporter/parser_test.go new file mode 100644 index 00000000..427d943c --- /dev/null +++ b/go/internal/gccgoimporter/parser_test.go @@ -0,0 +1,74 @@ +// Copyright 2013 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 gccgoimporter + +// This is a verbatim copy of $GOROOT/src/go/internal/gccgoimporter/parser_test.go. + +import ( + "bytes" + "go/types" + "strings" + "testing" + "text/scanner" +) + +var typeParserTests = []struct { + id, typ, want, underlying, methods string +}{ + {id: "foo", typ: "", want: "int8"}, + {id: "foo", typ: ">", want: "*error"}, + {id: "foo", typ: "", want: "unsafe.Pointer"}, + {id: "foo", typ: ">>", want: "foo.Bar", underlying: "*foo.Bar"}, + {id: "foo", typ: " func (? ) M (); >", want: "bar.Foo", underlying: "int8", methods: "func (bar.Foo).M()"}, + {id: "foo", typ: ">", want: "bar.foo", underlying: "int8"}, + {id: "foo", typ: ">", want: "[]int8"}, + {id: "foo", typ: ">", want: "[42]int8"}, + {id: "foo", typ: "] >", want: "map[int8]int16"}, + {id: "foo", typ: ">", want: "chan int8"}, + {id: "foo", typ: ">", want: "<-chan int8"}, + {id: "foo", typ: ">", want: "chan<- int8"}, + {id: "foo", typ: "; I16 \"i16\"; }>", want: "struct{I8 int8; I16 int16 \"i16\"}"}, + {id: "foo", typ: ", b ) ; Bar (? , ? ...) (? , ? ); Baz (); }>", want: "interface{Bar(int16, ...int8) (int16, int8); Baz(); Foo(a int8, b int16) int8}"}, + {id: "foo", typ: ") >", want: "func(int8) int16"}, +} + +func TestTypeParser(t *testing.T) { + for _, test := range typeParserTests { + var p parser + p.init("test.gox", strings.NewReader(test.typ), make(map[string]*types.Package)) + p.pkgname = test.id + p.pkgpath = test.id + p.maybeCreatePackage() + typ := p.parseType(p.pkg) + + if p.tok != scanner.EOF { + t.Errorf("expected full parse, stopped at %q", p.lit) + } + + got := typ.String() + if got != test.want { + t.Errorf("got type %q, expected %q", got, test.want) + } + + if test.underlying != "" { + underlying := typ.Underlying().String() + if underlying != test.underlying { + t.Errorf("got underlying type %q, expected %q", underlying, test.underlying) + } + } + + if test.methods != "" { + nt := typ.(*types.Named) + var buf bytes.Buffer + for i := 0; i != nt.NumMethods(); i++ { + buf.WriteString(nt.Method(i).String()) + } + methods := buf.String() + if methods != test.methods { + t.Errorf("got methods %q, expected %q", methods, test.methods) + } + } + } +} diff --git a/go/internal/gccgoimporter/testdata/complexnums.go b/go/internal/gccgoimporter/testdata/complexnums.go new file mode 100644 index 00000000..a51b6b01 --- /dev/null +++ b/go/internal/gccgoimporter/testdata/complexnums.go @@ -0,0 +1,6 @@ +package complexnums + +const NN = -1 - 1i +const NP = -1 + 1i +const PN = 1 - 1i +const PP = 1 + 1i diff --git a/go/internal/gccgoimporter/testdata/complexnums.gox b/go/internal/gccgoimporter/testdata/complexnums.gox new file mode 100644 index 00000000..b66524f8 --- /dev/null +++ b/go/internal/gccgoimporter/testdata/complexnums.gox @@ -0,0 +1,8 @@ +v1; +package complexnums; +pkgpath complexnums; +priority 1; +const NN = -0.1E1-0.1E1i ; +const NP = -0.1E1+0.1E1i ; +const PN = 0.1E1-0.1E1i ; +const PP = 0.1E1+0.1E1i ; diff --git a/go/internal/gccgoimporter/testdata/imports.go b/go/internal/gccgoimporter/testdata/imports.go new file mode 100644 index 00000000..7907316a --- /dev/null +++ b/go/internal/gccgoimporter/testdata/imports.go @@ -0,0 +1,5 @@ +package imports + +import "fmt" + +var Hello = fmt.Sprintf("Hello, world") diff --git a/go/internal/gccgoimporter/testdata/imports.gox b/go/internal/gccgoimporter/testdata/imports.gox new file mode 100644 index 00000000..958a4f5b --- /dev/null +++ b/go/internal/gccgoimporter/testdata/imports.gox @@ -0,0 +1,7 @@ +v1; +package imports; +pkgpath imports; +priority 7; +import fmt fmt "fmt"; +init imports imports..import 7 math math..import 1 runtime runtime..import 1 strconv strconv..import 2 io io..import 3 reflect reflect..import 3 syscall syscall..import 3 time time..import 4 os os..import 5 fmt fmt..import 6; +var Hello ; diff --git a/go/internal/gccgoimporter/testdata/pointer.go b/go/internal/gccgoimporter/testdata/pointer.go new file mode 100644 index 00000000..4ebc6713 --- /dev/null +++ b/go/internal/gccgoimporter/testdata/pointer.go @@ -0,0 +1,3 @@ +package pointer + +type Int8Ptr *int8 diff --git a/go/internal/gccgoimporter/testdata/pointer.gox b/go/internal/gccgoimporter/testdata/pointer.gox new file mode 100644 index 00000000..d96ebbdd --- /dev/null +++ b/go/internal/gccgoimporter/testdata/pointer.gox @@ -0,0 +1,4 @@ +v1; +package pointer; +pkgpath pointer; +type >>; diff --git a/go/internal/gccgoimporter/testenv_test.go b/go/internal/gccgoimporter/testenv_test.go new file mode 100644 index 00000000..10c11fd2 --- /dev/null +++ b/go/internal/gccgoimporter/testenv_test.go @@ -0,0 +1,40 @@ +package gccgoimporter + +// This file contains testing utilities copied from $GOROOT/src/internal/testenv/testenv.go. + +import ( + "runtime" + "strings" + "testing" +) + +// HasGoBuild reports whether the current system can build programs with ``go build'' +// and then run them with os.StartProcess or exec.Command. +func HasGoBuild() bool { + switch runtime.GOOS { + case "android", "nacl": + return false + case "darwin": + if strings.HasPrefix(runtime.GOARCH, "arm") { + return false + } + } + return true +} + +// MustHaveGoBuild checks that the current system can build programs with ``go build'' +// and then run them with os.StartProcess or exec.Command. +// If not, MustHaveGoBuild calls t.Skip with an explanation. +func MustHaveGoBuild(t *testing.T) { + if !HasGoBuild() { + t.Skipf("skipping test: 'go build' not available on %s/%s", runtime.GOOS, runtime.GOARCH) + } +} + +var testenv = struct { + HasGoBuild func() bool + MustHaveGoBuild func(*testing.T) +}{ + HasGoBuild: HasGoBuild, + MustHaveGoBuild: MustHaveGoBuild, +}