~ubuntu-branches/ubuntu/saucy/golang/saucy

« back to all changes in this revision

Viewing changes to src/pkg/go/doc/example.go

  • Committer: Package Import Robot
  • Author(s): Adam Conrad
  • Date: 2013-07-08 05:52:37 UTC
  • mfrom: (29.1.1 sid)
  • Revision ID: package-import@ubuntu.com-20130708055237-at01839e0hp8z3ni
Tags: 2:1.1-1ubuntu1
016-armhf-elf-header.patch: Use correct ELF header for armhf binaries.

Show diffs side-by-side

added added

removed removed

Lines of Context:
9
9
import (
10
10
        "go/ast"
11
11
        "go/token"
 
12
        "path"
12
13
        "regexp"
13
14
        "sort"
 
15
        "strconv"
14
16
        "strings"
15
17
        "unicode"
16
18
        "unicode/utf8"
17
19
)
18
20
 
 
21
// An Example represents an example function found in a source files.
19
22
type Example struct {
20
 
        Name     string // name of the item being exemplified
21
 
        Doc      string // example function doc string
22
 
        Code     ast.Node
23
 
        Comments []*ast.CommentGroup
24
 
        Output   string // expected output
 
23
        Name        string // name of the item being exemplified
 
24
        Doc         string // example function doc string
 
25
        Code        ast.Node
 
26
        Play        *ast.File // a whole program version of the example
 
27
        Comments    []*ast.CommentGroup
 
28
        Output      string // expected output
 
29
        EmptyOutput bool   // expect empty output
 
30
        Order       int    // original source code order
25
31
}
26
32
 
 
33
// Examples returns the examples found in the files, sorted by Name field.
 
34
// The Order fields record the order in which the examples were encountered.
27
35
func Examples(files ...*ast.File) []*Example {
28
36
        var list []*Example
29
37
        for _, file := range files {
52
60
                        if f.Doc != nil {
53
61
                                doc = f.Doc.Text()
54
62
                        }
 
63
                        output, hasOutput := exampleOutput(f.Body, file.Comments)
55
64
                        flist = append(flist, &Example{
56
 
                                Name:     name[len("Example"):],
57
 
                                Doc:      doc,
58
 
                                Code:     f.Body,
59
 
                                Comments: file.Comments,
60
 
                                Output:   exampleOutput(f, file.Comments),
 
65
                                Name:        name[len("Example"):],
 
66
                                Doc:         doc,
 
67
                                Code:        f.Body,
 
68
                                Play:        playExample(file, f.Body),
 
69
                                Comments:    file.Comments,
 
70
                                Output:      output,
 
71
                                EmptyOutput: output == "" && hasOutput,
 
72
                                Order:       len(flist),
61
73
                        })
62
74
                }
63
75
                if !hasTests && numDecl > 1 && len(flist) == 1 {
65
77
                        // other top-level declarations, and no tests or
66
78
                        // benchmarks, use the whole file as the example.
67
79
                        flist[0].Code = file
 
80
                        flist[0].Play = playExampleFile(file)
68
81
                }
69
82
                list = append(list, flist...)
70
83
        }
74
87
 
75
88
var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`)
76
89
 
77
 
func exampleOutput(fun *ast.FuncDecl, comments []*ast.CommentGroup) string {
78
 
        // find the last comment in the function
79
 
        var last *ast.CommentGroup
80
 
        for _, cg := range comments {
81
 
                if cg.Pos() < fun.Pos() {
82
 
                        continue
83
 
                }
84
 
                if cg.End() > fun.End() {
85
 
                        break
86
 
                }
87
 
                last = cg
88
 
        }
89
 
        if last != nil {
 
90
// Extracts the expected output and whether there was a valid output comment
 
91
func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, ok bool) {
 
92
        if _, last := lastComment(b, comments); last != nil {
90
93
                // test that it begins with the correct prefix
91
94
                text := last.Text()
92
95
                if loc := outputPrefix.FindStringIndex(text); loc != nil {
93
 
                        return strings.TrimSpace(text[loc[1]:])
 
96
                        text = text[loc[1]:]
 
97
                        // Strip zero or more spaces followed by \n or a single space.
 
98
                        text = strings.TrimLeft(text, " ")
 
99
                        if len(text) > 0 && text[0] == '\n' {
 
100
                                text = text[1:]
 
101
                        }
 
102
                        return text, true
94
103
                }
95
104
        }
96
 
        return "" // no suitable comment found
 
105
        return "", false // no suitable comment found
97
106
}
98
107
 
99
108
// isTest tells whether name looks like a test, example, or benchmark.
115
124
func (s exampleByName) Len() int           { return len(s) }
116
125
func (s exampleByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
117
126
func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
 
127
 
 
128
// playExample synthesizes a new *ast.File based on the provided
 
129
// file with the provided function body as the body of main.
 
130
func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
 
131
        if !strings.HasSuffix(file.Name.Name, "_test") {
 
132
                // We don't support examples that are part of the
 
133
                // greater package (yet).
 
134
                return nil
 
135
        }
 
136
 
 
137
        // Find top-level declarations in the file.
 
138
        topDecls := make(map[*ast.Object]bool)
 
139
        for _, decl := range file.Decls {
 
140
                switch d := decl.(type) {
 
141
                case *ast.FuncDecl:
 
142
                        topDecls[d.Name.Obj] = true
 
143
                case *ast.GenDecl:
 
144
                        for _, spec := range d.Specs {
 
145
                                switch s := spec.(type) {
 
146
                                case *ast.TypeSpec:
 
147
                                        topDecls[s.Name.Obj] = true
 
148
                                case *ast.ValueSpec:
 
149
                                        for _, id := range s.Names {
 
150
                                                topDecls[id.Obj] = true
 
151
                                        }
 
152
                                }
 
153
                        }
 
154
                }
 
155
        }
 
156
 
 
157
        // Find unresolved identifiers and uses of top-level declarations.
 
158
        unresolved := make(map[string]bool)
 
159
        usesTopDecl := false
 
160
        var inspectFunc func(ast.Node) bool
 
161
        inspectFunc = func(n ast.Node) bool {
 
162
                // For selector expressions, only inspect the left hand side.
 
163
                // (For an expression like fmt.Println, only add "fmt" to the
 
164
                // set of unresolved names, not "Println".)
 
165
                if e, ok := n.(*ast.SelectorExpr); ok {
 
166
                        ast.Inspect(e.X, inspectFunc)
 
167
                        return false
 
168
                }
 
169
                // For key value expressions, only inspect the value
 
170
                // as the key should be resolved by the type of the
 
171
                // composite literal.
 
172
                if e, ok := n.(*ast.KeyValueExpr); ok {
 
173
                        ast.Inspect(e.Value, inspectFunc)
 
174
                        return false
 
175
                }
 
176
                if id, ok := n.(*ast.Ident); ok {
 
177
                        if id.Obj == nil {
 
178
                                unresolved[id.Name] = true
 
179
                        } else if topDecls[id.Obj] {
 
180
                                usesTopDecl = true
 
181
                        }
 
182
                }
 
183
                return true
 
184
        }
 
185
        ast.Inspect(body, inspectFunc)
 
186
        if usesTopDecl {
 
187
                // We don't support examples that are not self-contained (yet).
 
188
                return nil
 
189
        }
 
190
 
 
191
        // Remove predeclared identifiers from unresolved list.
 
192
        for n := range unresolved {
 
193
                if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
 
194
                        delete(unresolved, n)
 
195
                }
 
196
        }
 
197
 
 
198
        // Use unresolved identifiers to determine the imports used by this
 
199
        // example. The heuristic assumes package names match base import
 
200
        // paths for imports w/o renames (should be good enough most of the time).
 
201
        namedImports := make(map[string]string) // [name]path
 
202
        var blankImports []ast.Spec             // _ imports
 
203
        for _, s := range file.Imports {
 
204
                p, err := strconv.Unquote(s.Path.Value)
 
205
                if err != nil {
 
206
                        continue
 
207
                }
 
208
                n := path.Base(p)
 
209
                if s.Name != nil {
 
210
                        n = s.Name.Name
 
211
                        switch n {
 
212
                        case "_":
 
213
                                blankImports = append(blankImports, s)
 
214
                                continue
 
215
                        case ".":
 
216
                                // We can't resolve dot imports (yet).
 
217
                                return nil
 
218
                        }
 
219
                }
 
220
                if unresolved[n] {
 
221
                        namedImports[n] = p
 
222
                        delete(unresolved, n)
 
223
                }
 
224
        }
 
225
 
 
226
        // If there are other unresolved identifiers, give up because this
 
227
        // synthesized file is not going to build.
 
228
        if len(unresolved) > 0 {
 
229
                return nil
 
230
        }
 
231
 
 
232
        // Include documentation belonging to blank imports.
 
233
        var comments []*ast.CommentGroup
 
234
        for _, s := range blankImports {
 
235
                if c := s.(*ast.ImportSpec).Doc; c != nil {
 
236
                        comments = append(comments, c)
 
237
                }
 
238
        }
 
239
 
 
240
        // Include comments that are inside the function body.
 
241
        for _, c := range file.Comments {
 
242
                if body.Pos() <= c.Pos() && c.End() <= body.End() {
 
243
                        comments = append(comments, c)
 
244
                }
 
245
        }
 
246
 
 
247
        // Strip "Output:" commment and adjust body end position.
 
248
        body, comments = stripOutputComment(body, comments)
 
249
 
 
250
        // Synthesize import declaration.
 
251
        importDecl := &ast.GenDecl{
 
252
                Tok:    token.IMPORT,
 
253
                Lparen: 1, // Need non-zero Lparen and Rparen so that printer
 
254
                Rparen: 1, // treats this as a factored import.
 
255
        }
 
256
        for n, p := range namedImports {
 
257
                s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}}
 
258
                if path.Base(p) != n {
 
259
                        s.Name = ast.NewIdent(n)
 
260
                }
 
261
                importDecl.Specs = append(importDecl.Specs, s)
 
262
        }
 
263
        importDecl.Specs = append(importDecl.Specs, blankImports...)
 
264
 
 
265
        // Synthesize main function.
 
266
        funcDecl := &ast.FuncDecl{
 
267
                Name: ast.NewIdent("main"),
 
268
                Type: &ast.FuncType{},
 
269
                Body: body,
 
270
        }
 
271
 
 
272
        // Synthesize file.
 
273
        return &ast.File{
 
274
                Name:     ast.NewIdent("main"),
 
275
                Decls:    []ast.Decl{importDecl, funcDecl},
 
276
                Comments: comments,
 
277
        }
 
278
}
 
279
 
 
280
// playExampleFile takes a whole file example and synthesizes a new *ast.File
 
281
// such that the example is function main in package main.
 
282
func playExampleFile(file *ast.File) *ast.File {
 
283
        // Strip copyright comment if present.
 
284
        comments := file.Comments
 
285
        if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
 
286
                comments = comments[1:]
 
287
        }
 
288
 
 
289
        // Copy declaration slice, rewriting the ExampleX function to main.
 
290
        var decls []ast.Decl
 
291
        for _, d := range file.Decls {
 
292
                if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
 
293
                        // Copy the FuncDecl, as it may be used elsewhere.
 
294
                        newF := *f
 
295
                        newF.Name = ast.NewIdent("main")
 
296
                        newF.Body, comments = stripOutputComment(f.Body, comments)
 
297
                        d = &newF
 
298
                }
 
299
                decls = append(decls, d)
 
300
        }
 
301
 
 
302
        // Copy the File, as it may be used elsewhere.
 
303
        f := *file
 
304
        f.Name = ast.NewIdent("main")
 
305
        f.Decls = decls
 
306
        f.Comments = comments
 
307
        return &f
 
308
}
 
309
 
 
310
// stripOutputComment finds and removes an "Output:" commment from body
 
311
// and comments, and adjusts the body block's end position.
 
312
func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
 
313
        // Do nothing if no "Output:" comment found.
 
314
        i, last := lastComment(body, comments)
 
315
        if last == nil || !outputPrefix.MatchString(last.Text()) {
 
316
                return body, comments
 
317
        }
 
318
 
 
319
        // Copy body and comments, as the originals may be used elsewhere.
 
320
        newBody := &ast.BlockStmt{
 
321
                Lbrace: body.Lbrace,
 
322
                List:   body.List,
 
323
                Rbrace: last.Pos(),
 
324
        }
 
325
        newComments := make([]*ast.CommentGroup, len(comments)-1)
 
326
        copy(newComments, comments[:i])
 
327
        copy(newComments[i:], comments[i+1:])
 
328
        return newBody, newComments
 
329
}
 
330
 
 
331
// lastComment returns the last comment inside the provided block.
 
332
func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
 
333
        pos, end := b.Pos(), b.End()
 
334
        for j, cg := range c {
 
335
                if cg.Pos() < pos {
 
336
                        continue
 
337
                }
 
338
                if cg.End() > end {
 
339
                        break
 
340
                }
 
341
                i, last = j, cg
 
342
        }
 
343
        return
 
344
}