Commit 81ae666f authored by Andrew Gerrand's avatar Andrew Gerrand

go/doc: rewrite whole file examples for playground

R=gri
CC=gobot, golang-dev
https://golang.org/cl/6592061
parent 3188ffc9
...@@ -69,6 +69,7 @@ func Examples(files ...*ast.File) []*Example { ...@@ -69,6 +69,7 @@ func Examples(files ...*ast.File) []*Example {
// other top-level declarations, and no tests or // other top-level declarations, and no tests or
// benchmarks, use the whole file as the example. // benchmarks, use the whole file as the example.
flist[0].Code = file flist[0].Code = file
flist[0].Play = playExampleFile(file)
} }
list = append(list, flist...) list = append(list, flist...)
} }
...@@ -79,18 +80,7 @@ func Examples(files ...*ast.File) []*Example { ...@@ -79,18 +80,7 @@ func Examples(files ...*ast.File) []*Example {
var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`) var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`)
func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) string { func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) string {
// find the last comment in the function if _, last := lastComment(b, comments); last != nil {
var last *ast.CommentGroup
for _, cg := range comments {
if cg.Pos() < b.Pos() {
continue
}
if cg.End() > b.End() {
break
}
last = cg
}
if last != nil {
// test that it begins with the correct prefix // test that it begins with the correct prefix
text := last.Text() text := last.Text()
if loc := outputPrefix.FindStringIndex(text); loc != nil { if loc := outputPrefix.FindStringIndex(text); loc != nil {
...@@ -129,18 +119,33 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { ...@@ -129,18 +119,33 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
return nil return nil
} }
// Determine the imports we need based on unresolved identifiers. // Find unresolved identifiers
// This is a heuristic that presumes package names match base import paths. unresolved := make(map[string]bool)
// (Should be good enough most of the time.)
var unresolved []*ast.Ident
ast.Inspect(body, func(n ast.Node) bool { ast.Inspect(body, func(n ast.Node) bool {
// For an expression like fmt.Println, only add "fmt" to the
// set of unresolved names.
if e, ok := n.(*ast.SelectorExpr); ok { if e, ok := n.(*ast.SelectorExpr); ok {
if id, ok := e.X.(*ast.Ident); ok && id.Obj == nil { if id, ok := e.X.(*ast.Ident); ok && id.Obj == nil {
unresolved = append(unresolved, id) unresolved[id.Name] = true
}
return false
} }
if id, ok := n.(*ast.Ident); ok && id.Obj == nil {
unresolved[id.Name] = true
} }
return true return true
}) })
// Remove predeclared identifiers from unresolved list.
for n := range unresolved {
if n == "nil" || predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
delete(unresolved, n)
}
}
// Use unresolved identifiers to determine the imports used by this
// example. The heuristic assumes package names match base import
// paths. (Should be good enough most of the time.)
imports := make(map[string]string) // [name]path imports := make(map[string]string) // [name]path
for _, s := range file.Imports { for _, s := range file.Imports {
p, err := strconv.Unquote(s.Path.Value) p, err := strconv.Unquote(s.Path.Value)
...@@ -155,30 +160,18 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { ...@@ -155,30 +160,18 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
} }
n = s.Name.Name n = s.Name.Name
} }
for _, id := range unresolved { if unresolved[n] {
if n == id.Name {
imports[n] = p imports[n] = p
break delete(unresolved, n)
}
} }
} }
// Synthesize new imports. // If there are other unresolved identifiers, give up because this
importDecl := &ast.GenDecl{ // synthesized file is not going to build.
Tok: token.IMPORT, if len(unresolved) > 0 {
Lparen: 1, // Need non-zero Lparen and Rparen so that printer return nil
Rparen: 1, // treats this as a factored import.
}
for n, p := range imports {
s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}}
if path.Base(p) != n {
s.Name = ast.NewIdent(n)
}
importDecl.Specs = append(importDecl.Specs, s)
} }
// TODO(adg): look for other unresolved identifiers and, if found, give up.
// Filter out comments that are outside the function body. // Filter out comments that are outside the function body.
var comments []*ast.CommentGroup var comments []*ast.CommentGroup
for _, c := range file.Comments { for _, c := range file.Comments {
...@@ -189,17 +182,20 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { ...@@ -189,17 +182,20 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
} }
// Strip "Output:" commment and adjust body end position. // Strip "Output:" commment and adjust body end position.
if len(comments) > 0 { body, comments = stripOutputComment(body, comments)
last := comments[len(comments)-1]
if outputPrefix.MatchString(last.Text()) { // Synthesize import declaration.
comments = comments[:len(comments)-1] importDecl := &ast.GenDecl{
// Copy body, as the original may be used elsewhere. Tok: token.IMPORT,
body = &ast.BlockStmt{ Lparen: 1, // Need non-zero Lparen and Rparen so that printer
Lbrace: body.Pos(), Rparen: 1, // treats this as a factored import.
List: body.List,
Rbrace: last.Pos(),
} }
for n, p := range imports {
s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}}
if path.Base(p) != n {
s.Name = ast.NewIdent(n)
} }
importDecl.Specs = append(importDecl.Specs, s)
} }
// Synthesize main function. // Synthesize main function.
...@@ -210,14 +206,75 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { ...@@ -210,14 +206,75 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File {
} }
// Synthesize file. // Synthesize file.
f := &ast.File{ return &ast.File{
Name: ast.NewIdent("main"), Name: ast.NewIdent("main"),
Decls: []ast.Decl{importDecl, funcDecl}, Decls: []ast.Decl{importDecl, funcDecl},
Comments: comments, Comments: comments,
} }
}
// playExample takes a whole file example and synthesizes a new *ast.File
// such that the example is function main in package main.
func playExampleFile(file *ast.File) *ast.File {
// Strip copyright comment if present.
comments := file.Comments
if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
comments = comments[1:]
}
// Copy declaration slice, rewriting the ExampleX function to main.
var decls []ast.Decl
for _, d := range file.Decls {
if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
// Copy the FuncDecl, as it may be used elsewhere.
newF := *f
newF.Name = ast.NewIdent("main")
newF.Body, comments = stripOutputComment(f.Body, comments)
d = &newF
}
decls = append(decls, d)
}
// Copy the File, as it may be used elsewhere.
f := *file
f.Name = ast.NewIdent("main")
f.Decls = decls
f.Comments = comments
return &f
}
// TODO(adg): look for resolved identifiers declared outside function scope // stripOutputComment finds and removes an "Output:" commment from body
// and include their declarations in the new file. // and comments, and adjusts the body block's end position.
func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
// Do nothing if no "Output:" comment found.
i, last := lastComment(body, comments)
if last == nil || !outputPrefix.MatchString(last.Text()) {
return body, comments
}
// Copy body and comments, as the originals may be used elsewhere.
newBody := &ast.BlockStmt{
Lbrace: body.Lbrace,
List: body.List,
Rbrace: last.Pos(),
}
newComments := make([]*ast.CommentGroup, len(comments)-1)
copy(newComments, comments[:i])
copy(newComments[i:], comments[i+1:])
return newBody, newComments
}
return f // lastComment returns the last comment inside the provided block.
func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
pos, end := b.Pos(), b.End()
for j, cg := range c {
if cg.Pos() < pos {
continue
}
if cg.End() > end {
break
}
i, last = j, cg
}
return
} }
...@@ -515,29 +515,6 @@ func (r *reader) readPackage(pkg *ast.Package, mode Mode) { ...@@ -515,29 +515,6 @@ func (r *reader) readPackage(pkg *ast.Package, mode Mode) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Types // Types
var predeclaredTypes = map[string]bool{
"bool": true,
"byte": true,
"complex64": true,
"complex128": true,
"error": true,
"float32": true,
"float64": true,
"int": true,
"int8": true,
"int16": true,
"int32": true,
"int64": true,
"rune": true,
"string": true,
"uint": true,
"uint8": true,
"uint16": true,
"uint32": true,
"uint64": true,
"uintptr": true,
}
func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func { func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func {
if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 { if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 {
return f // shouldn't happen, but be safe return f // shouldn't happen, but be safe
...@@ -772,3 +749,53 @@ func sortedFuncs(m methodSet, allMethods bool) []*Func { ...@@ -772,3 +749,53 @@ func sortedFuncs(m methodSet, allMethods bool) []*Func {
) )
return list return list
} }
// ----------------------------------------------------------------------------
// Predeclared identifiers (minus "nil")
var predeclaredTypes = map[string]bool{
"bool": true,
"byte": true,
"complex64": true,
"complex128": true,
"error": true,
"float32": true,
"float64": true,
"int": true,
"int8": true,
"int16": true,
"int32": true,
"int64": true,
"rune": true,
"string": true,
"uint": true,
"uint8": true,
"uint16": true,
"uint32": true,
"uint64": true,
"uintptr": true,
}
var predeclaredFuncs = map[string]bool{
"append": true,
"cap": true,
"close": true,
"complex": true,
"copy": true,
"delete": true,
"imag": true,
"len": true,
"make": true,
"new": true,
"panic": true,
"print": true,
"println": true,
"real": true,
"recover": true,
}
var predeclaredConstants = map[string]bool{
"iota": true,
"true": true,
"false": true,
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment