* gcc.dg/guality/guality.exp: Skip on AIX.
[official-gcc.git] / libgo / go / go / doc / example.go
blob2761083c7eed99c6828bfc19b4ee74808183b8da
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.
5 // Extract example functions from file ASTs.
7 package doc
9 import (
10 "go/ast"
11 "go/token"
12 "path"
13 "regexp"
14 "sort"
15 "strconv"
16 "strings"
17 "unicode"
18 "unicode/utf8"
21 // An Example represents an example function found in a source files.
22 type Example struct {
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
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.
35 func Examples(files ...*ast.File) []*Example {
36 var list []*Example
37 for _, file := range files {
38 hasTests := false // file contains tests or benchmarks
39 numDecl := 0 // number of non-import declarations in the file
40 var flist []*Example
41 for _, decl := range file.Decls {
42 if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT {
43 numDecl++
44 continue
46 f, ok := decl.(*ast.FuncDecl)
47 if !ok {
48 continue
50 numDecl++
51 name := f.Name.Name
52 if isTest(name, "Test") || isTest(name, "Benchmark") {
53 hasTests = true
54 continue
56 if !isTest(name, "Example") {
57 continue
59 var doc string
60 if f.Doc != nil {
61 doc = f.Doc.Text()
63 output, hasOutput := exampleOutput(f.Body, file.Comments)
64 flist = append(flist, &Example{
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),
75 if !hasTests && numDecl > 1 && len(flist) == 1 {
76 // If this file only has one example function, some
77 // other top-level declarations, and no tests or
78 // benchmarks, use the whole file as the example.
79 flist[0].Code = file
80 flist[0].Play = playExampleFile(file)
82 list = append(list, flist...)
84 sort.Sort(exampleByName(list))
85 return list
88 var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`)
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 {
93 // test that it begins with the correct prefix
94 text := last.Text()
95 if loc := outputPrefix.FindStringIndex(text); loc != nil {
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:]
102 return text, true
105 return "", false // no suitable comment found
108 // isTest tells whether name looks like a test, example, or benchmark.
109 // It is a Test (say) if there is a character after Test that is not a
110 // lower-case letter. (We don't want Testiness.)
111 func isTest(name, prefix string) bool {
112 if !strings.HasPrefix(name, prefix) {
113 return false
115 if len(name) == len(prefix) { // "Test" is ok
116 return true
118 rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
119 return !unicode.IsLower(rune)
122 type exampleByName []*Example
124 func (s exampleByName) Len() int { return len(s) }
125 func (s exampleByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
126 func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
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
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
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
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
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
183 return true
185 ast.Inspect(body, inspectFunc)
186 if usesTopDecl {
187 // We don't support examples that are not self-contained (yet).
188 return nil
191 // Remove predeclared identifiers from unresolved list.
192 for n := range unresolved {
193 if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
194 delete(unresolved, n)
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
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
220 if unresolved[n] {
221 namedImports[n] = p
222 delete(unresolved, n)
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
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)
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)
247 // Strip "Output:" commment and adjust body end position.
248 body, comments = stripOutputComment(body, comments)
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.
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)
261 importDecl.Specs = append(importDecl.Specs, s)
263 importDecl.Specs = append(importDecl.Specs, blankImports...)
265 // Synthesize main function.
266 funcDecl := &ast.FuncDecl{
267 Name: ast.NewIdent("main"),
268 Type: &ast.FuncType{},
269 Body: body,
272 // Synthesize file.
273 return &ast.File{
274 Name: ast.NewIdent("main"),
275 Decls: []ast.Decl{importDecl, funcDecl},
276 Comments: comments,
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:]
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
299 decls = append(decls, d)
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
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
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(),
325 newComments := make([]*ast.CommentGroup, len(comments)-1)
326 copy(newComments, comments[:i])
327 copy(newComments[i:], comments[i+1:])
328 return newBody, newComments
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
338 if cg.End() > end {
339 break
341 i, last = j, cg
343 return