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