|  | // 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 loader | 
|  |  | 
|  | import ( | 
|  | "go/ast" | 
|  | "go/build" | 
|  | "go/parser" | 
|  | "go/token" | 
|  | "io" | 
|  | "os" | 
|  | "strconv" | 
|  | "sync" | 
|  |  | 
|  | "golang.org/x/tools/go/buildutil" | 
|  | ) | 
|  |  | 
|  | // We use a counting semaphore to limit | 
|  | // the number of parallel I/O calls per process. | 
|  | var ioLimit = make(chan bool, 10) | 
|  |  | 
|  | // parseFiles parses the Go source files within directory dir and | 
|  | // returns the ASTs of the ones that could be at least partially parsed, | 
|  | // along with a list of I/O and parse errors encountered. | 
|  | // | 
|  | // I/O is done via ctxt, which may specify a virtual file system. | 
|  | // displayPath is used to transform the filenames attached to the ASTs. | 
|  | func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { | 
|  | if displayPath == nil { | 
|  | displayPath = func(path string) string { return path } | 
|  | } | 
|  | var wg sync.WaitGroup | 
|  | n := len(files) | 
|  | parsed := make([]*ast.File, n) | 
|  | errors := make([]error, n) | 
|  | for i, file := range files { | 
|  | if !buildutil.IsAbsPath(ctxt, file) { | 
|  | file = buildutil.JoinPath(ctxt, dir, file) | 
|  | } | 
|  | wg.Add(1) | 
|  | go func(i int, file string) { | 
|  | ioLimit <- true // wait | 
|  | defer func() { | 
|  | wg.Done() | 
|  | <-ioLimit // signal | 
|  | }() | 
|  | var rd io.ReadCloser | 
|  | var err error | 
|  | if ctxt.OpenFile != nil { | 
|  | rd, err = ctxt.OpenFile(file) | 
|  | } else { | 
|  | rd, err = os.Open(file) | 
|  | } | 
|  | if err != nil { | 
|  | errors[i] = err // open failed | 
|  | return | 
|  | } | 
|  |  | 
|  | // ParseFile may return both an AST and an error. | 
|  | parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) | 
|  | rd.Close() | 
|  | }(i, file) | 
|  | } | 
|  | wg.Wait() | 
|  |  | 
|  | // Eliminate nils, preserving order. | 
|  | var o int | 
|  | for _, f := range parsed { | 
|  | if f != nil { | 
|  | parsed[o] = f | 
|  | o++ | 
|  | } | 
|  | } | 
|  | parsed = parsed[:o] | 
|  |  | 
|  | o = 0 | 
|  | for _, err := range errors { | 
|  | if err != nil { | 
|  | errors[o] = err | 
|  | o++ | 
|  | } | 
|  | } | 
|  | errors = errors[:o] | 
|  |  | 
|  | return parsed, errors | 
|  | } | 
|  |  | 
|  | // scanImports returns the set of all import paths from all | 
|  | // import specs in the specified files. | 
|  | func scanImports(files []*ast.File) map[string]bool { | 
|  | imports := make(map[string]bool) | 
|  | for _, f := range files { | 
|  | for _, decl := range f.Decls { | 
|  | if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { | 
|  | for _, spec := range decl.Specs { | 
|  | spec := spec.(*ast.ImportSpec) | 
|  |  | 
|  | // NB: do not assume the program is well-formed! | 
|  | path, err := strconv.Unquote(spec.Path.Value) | 
|  | if err != nil { | 
|  | continue // quietly ignore the error | 
|  | } | 
|  | if path == "C" { | 
|  | continue // skip pseudopackage | 
|  | } | 
|  | imports[path] = true | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return imports | 
|  | } | 
|  |  | 
|  | // ---------- Internal helpers ---------- | 
|  |  | 
|  | // TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) | 
|  | func tokenFileContainsPos(f *token.File, pos token.Pos) bool { | 
|  | p := int(pos) | 
|  | base := f.Base() | 
|  | return base <= p && p < base+f.Size() | 
|  | } |