/
parse.go
127 lines (111 loc) · 3.34 KB
/
parse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/scanner"
"go/token"
"text/template"
)
var tmplDecl = template.Must(template.New("").Parse(`` +
`package p; {{ . }}`))
var tmplExprs = template.Must(template.New("").Parse(`` +
`package p; var _ = []interface{}{ {{ . }}, }`))
var tmplStmts = template.Must(template.New("").Parse(`` +
`package p; func _() { {{ . }} }`))
var tmplType = template.Must(template.New("").Parse(`` +
`package p; var _ {{ . }}`))
var tmplValSpec = template.Must(template.New("").Parse(`` +
`package p; var {{ . }}`))
func execTmpl(tmpl *template.Template, src string) string {
var buf bytes.Buffer
if err := tmpl.Execute(&buf, src); err != nil {
panic(err)
}
return buf.String()
}
func noBadNodes(node ast.Node) bool {
any := false
ast.Inspect(node, func(n ast.Node) bool {
if any {
return false
}
switch n.(type) {
case *ast.BadExpr, *ast.BadDecl:
any = true
}
return true
})
return !any
}
func parseType(fset *token.FileSet, src string) (ast.Expr, *ast.File, error) {
asType := execTmpl(tmplType, src)
f, err := parser.ParseFile(fset, "", asType, 0)
if err != nil {
return nil, nil, err
}
vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
return vs.Type, f, nil
}
// parseDetectingNode tries its best to parse the ast.Node contained in src, as
// one of: *ast.File, ast.Decl, ast.Expr, ast.Stmt, *ast.ValueSpec.
// It also returns the *ast.File used for the parsing, so that the returned node
// can be easily type-checked.
func parseDetectingNode(fset *token.FileSet, src string) (interface{}, error) {
file := fset.AddFile("", fset.Base(), len(src))
scan := scanner.Scanner{}
scan.Init(file, []byte(src), nil, 0)
if _, tok, _ := scan.Scan(); tok == token.EOF {
return nil, fmt.Errorf("empty source code")
}
var mainErr error
// first try as a whole file
if f, err := parser.ParseFile(fset, "", src, 0); err == nil && noBadNodes(f) {
return f, nil
}
// then as a single declaration, or many
asDecl := execTmpl(tmplDecl, src)
if f, err := parser.ParseFile(fset, "", asDecl, 0); err == nil && noBadNodes(f) {
if len(f.Decls) == 1 {
return f.Decls[0], nil
}
return f, nil
}
// then as value expressions
asExprs := execTmpl(tmplExprs, src)
if f, err := parser.ParseFile(fset, "", asExprs, 0); err == nil && noBadNodes(f) {
vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
cl := vs.Values[0].(*ast.CompositeLit)
if len(cl.Elts) == 1 {
return cl.Elts[0], nil
}
return cl.Elts, nil
}
// then try as statements
asStmts := execTmpl(tmplStmts, src)
if f, err := parser.ParseFile(fset, "", asStmts, 0); err == nil && noBadNodes(f) {
bl := f.Decls[0].(*ast.FuncDecl).Body
if len(bl.List) == 1 {
return bl.List[0], nil
}
return bl.List, nil
} else {
// Statements is what covers most cases, so it will give
// the best overall error message. Show positions
// relative to where the user's code is put in the
// template.
mainErr = err
}
// type expressions not yet picked up, for e.g. chans and interfaces
if typ, f, err := parseType(fset, src); err == nil && noBadNodes(f) {
return typ, nil
}
// value specs
asValSpec := execTmpl(tmplValSpec, src)
if f, err := parser.ParseFile(fset, "", asValSpec, 0); err == nil && noBadNodes(f) {
vs := f.Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec)
return vs, nil
}
return nil, mainErr
}