-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcc.go
151 lines (138 loc) · 3.82 KB
/
cc.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Package cc implements parsing of C and C++ source files using Clang.
package cc
import (
"fmt"
"strings"
"github.com/go-clang/clang-v3.9/clang"
multierror "github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)
// File is a parsed source file.
type File struct {
// Root node of the parsed AST.
Root *Node
// Index of translation units.
idx clang.Index
// Translation unit.
tu clang.TranslationUnit
}
// Close releases the resources associated with the parsed source file. Note
// that calling methods on nodes of the AST is only valid until the file is
// closed.
func (file *File) Close() {
file.tu.Dispose()
file.idx.Dispose()
}
// ParseFile parses the given source file, returning the root node of the AST.
// Note, a (partial) AST is returned even when an error is encountered.
func ParseFile(srcPath string, clangArgs ...string) (*File, error) {
// Create index.
idx := clang.NewIndex(0, 1)
// Create translation unit.
tu := idx.ParseTranslationUnit(srcPath, clangArgs, nil, 0)
// Record errors.
diagnostics := tu.Diagnostics()
var err error
for _, d := range diagnostics {
err = multierror.Append(err, errors.New(d.Spelling()))
}
// Parse source file.
nodeFromHash := make(map[string]*Node)
cursor := tu.TranslationUnitCursor()
loc := cursor.Location()
file, line, col := loc.PresumedLocation()
root := &Node{
Body: cursor,
Loc: Location{
File: file,
Line: line,
Col: col,
},
}
nodeFromHash[hashFromCursor(root.Body)] = root
visit := func(cursor, parent clang.Cursor) clang.ChildVisitResult {
if cursor.IsNull() {
return clang.ChildVisit_Continue
}
parentNode, ok := nodeFromHash[hashFromCursor(parent)]
if !ok {
panic(fmt.Errorf("unable to locate node of parent cursor %v(%v)", parentNode.Body.Kind(), parentNode.Body.Spelling()))
}
loc := cursor.Location()
file, line, col := loc.PresumedLocation()
n := &Node{
Body: cursor,
Loc: Location{
File: file,
Line: line,
Col: col,
},
}
nodeFromHash[hashFromCursor(n.Body)] = n
parentNode.Children = append(parentNode.Children, n)
return clang.ChildVisit_Recurse
}
cursor.Visit(visit)
return &File{
Root: root,
idx: idx,
tu: tu,
}, err
}
// Node is a node of the AST.
type Node struct {
// Node contents.
Body clang.Cursor
// Source location of node.
Loc Location // cached result of Body.Location().PersumedLocation()
// Child nodes of the node.
Children []*Node
}
// Location denotes a location in a source file.
type Location struct {
// Source file.
File string
// Line number (1-indexed).
Line uint32
// Column (1-indexed).
Col uint32
}
// NewLocation returns a new location based on the given Clang source location.
func NewLocation(loc clang.SourceLocation) Location {
file, line, col := loc.PresumedLocation()
return Location{
File: file,
Line: line,
Col: col,
}
}
// String returns a string representation of the source code location.
func (loc Location) String() string {
return fmt.Sprintf("%s:%d:%d", loc.File, loc.Line, loc.Col)
}
// PrintTree pretty-prints the given AST starting at the root node.
func PrintTree(root *Node) {
printTree(root, 0)
}
// printTree pretty-prints the given AST node and its children with the
// corresponding indentation level.
func printTree(n *Node, indentLevel int) {
indent := strings.Repeat("\t", indentLevel)
fmt.Printf("%s%s\n", indent, n.Body.Kind().String())
for _, child := range n.Children {
printTree(child, indentLevel+1)
}
}
// Walk walks the given AST, invoking f for each node visited.
func Walk(root *Node, f func(n *Node)) {
f(root)
for _, child := range root.Children {
Walk(child, f)
}
}
// hashFromCursor returns a hash to uniquely identify the given cursor.
func hashFromCursor(cursor clang.Cursor) string {
kind := cursor.Kind().String()
loc := NewLocation(cursor.Location())
return fmt.Sprintf("%s_%s", kind, loc)
}