1 | // Copyright 2013 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package loader |
6 | |
7 | import ( |
8 | "go/ast" |
9 | "go/build" |
10 | "go/parser" |
11 | "go/token" |
12 | "io" |
13 | "os" |
14 | "strconv" |
15 | "sync" |
16 | |
17 | "golang.org/x/tools/go/buildutil" |
18 | ) |
19 | |
20 | // We use a counting semaphore to limit |
21 | // the number of parallel I/O calls per process. |
22 | var ioLimit = make(chan bool, 10) |
23 | |
24 | // parseFiles parses the Go source files within directory dir and |
25 | // returns the ASTs of the ones that could be at least partially parsed, |
26 | // along with a list of I/O and parse errors encountered. |
27 | // |
28 | // I/O is done via ctxt, which may specify a virtual file system. |
29 | // displayPath is used to transform the filenames attached to the ASTs. |
30 | func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { |
31 | if displayPath == nil { |
32 | displayPath = func(path string) string { return path } |
33 | } |
34 | var wg sync.WaitGroup |
35 | n := len(files) |
36 | parsed := make([]*ast.File, n) |
37 | errors := make([]error, n) |
38 | for i, file := range files { |
39 | if !buildutil.IsAbsPath(ctxt, file) { |
40 | file = buildutil.JoinPath(ctxt, dir, file) |
41 | } |
42 | wg.Add(1) |
43 | go func(i int, file string) { |
44 | ioLimit <- true // wait |
45 | defer func() { |
46 | wg.Done() |
47 | <-ioLimit // signal |
48 | }() |
49 | var rd io.ReadCloser |
50 | var err error |
51 | if ctxt.OpenFile != nil { |
52 | rd, err = ctxt.OpenFile(file) |
53 | } else { |
54 | rd, err = os.Open(file) |
55 | } |
56 | if err != nil { |
57 | errors[i] = err // open failed |
58 | return |
59 | } |
60 | |
61 | // ParseFile may return both an AST and an error. |
62 | parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) |
63 | rd.Close() |
64 | }(i, file) |
65 | } |
66 | wg.Wait() |
67 | |
68 | // Eliminate nils, preserving order. |
69 | var o int |
70 | for _, f := range parsed { |
71 | if f != nil { |
72 | parsed[o] = f |
73 | o++ |
74 | } |
75 | } |
76 | parsed = parsed[:o] |
77 | |
78 | o = 0 |
79 | for _, err := range errors { |
80 | if err != nil { |
81 | errors[o] = err |
82 | o++ |
83 | } |
84 | } |
85 | errors = errors[:o] |
86 | |
87 | return parsed, errors |
88 | } |
89 | |
90 | // scanImports returns the set of all import paths from all |
91 | // import specs in the specified files. |
92 | func scanImports(files []*ast.File) map[string]bool { |
93 | imports := make(map[string]bool) |
94 | for _, f := range files { |
95 | for _, decl := range f.Decls { |
96 | if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { |
97 | for _, spec := range decl.Specs { |
98 | spec := spec.(*ast.ImportSpec) |
99 | |
100 | // NB: do not assume the program is well-formed! |
101 | path, err := strconv.Unquote(spec.Path.Value) |
102 | if err != nil { |
103 | continue // quietly ignore the error |
104 | } |
105 | if path == "C" { |
106 | continue // skip pseudopackage |
107 | } |
108 | imports[path] = true |
109 | } |
110 | } |
111 | } |
112 | } |
113 | return imports |
114 | } |
115 | |
116 | // ---------- Internal helpers ---------- |
117 | |
118 | // TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) |
119 | func tokenFileContainsPos(f *token.File, pos token.Pos) bool { |
120 | p := int(pos) |
121 | base := f.Base() |
122 | return base <= p && p < base+f.Size() |
123 | } |
124 |
Members