-
Notifications
You must be signed in to change notification settings - Fork 2
/
walk.go
184 lines (160 loc) · 5.14 KB
/
walk.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package codf
import (
"fmt"
)
// Walker is used by Walk to consume statements and sections, recursively, in ParentNodes (sections
// and documents).
//
// Optionally, Walkers may also implement WalkExiter to see receive an ExitSection call when exiting
// a section.
type Walker interface {
Statement(*Statement) error
EnterSection(*Section) (Walker, error)
}
// WalkMapper is an optional interface implemented for a Walker to have Walk replace the node passed
// to Map with the returned Node. If Map returns a nil node without an error, the mapped node is
// removed from its parent.
type WalkMapper interface {
Walker
Map(node Node) (Node, error)
}
// WalkExiter is an optional interface implemented for a Walker to have Walk call ExitSection when
// it has finished consuming all children in a section.
type WalkExiter interface {
Walker
// ExitSection is called with the parent Walker, the exited node, and its parent node
// (either the root document given to Walk or a section).
ExitSection(Walker, *Section, ParentNode) error
}
// Walk walks a codf AST starting with but not including parent.
// It is assumed that by having the parent, it has already been walked.
//
// Walk will call walker.Statement for each statement encountered in parent and walker.EnterSection
// for each section (and walker.ExitSection if implemented).
//
// If walker.EnterSection returns a non-nil Walker, Walk will recursively call Walk with the section
// and the returned Walker.
//
// Walk will return a *WalkError if any error occurs during a walk. The WalkError will contain both
// the parent and child node that the error occurred for.
//
// If the walker is a WalkMapper, any error in attempting to map a node will return a WalkError with
// the original node, not any resulting node. If the mapping is to a nil node without error, the
// node is deleted from the parent.
//
// Nil child nodes are skipped.
func Walk(parent ParentNode, walker Walker) (err error) {
return walkInContext(parent, parent, walker)
}
func walkInContext(context, parent ParentNode, walker Walker) (err error) {
children := parent.Nodes()
mapper, _ := walker.(WalkMapper)
for i := 0; i < len(children); i++ {
child := children[i]
if child == nil {
// Skip over nil nodes because they're mostly harmless -- you just can't act
// on them at all.
continue
}
// Remap the child node if the walker implemented WalkMapper
if mapper != nil {
var newChild Node
newChild, err = mapper.Map(child)
if err != nil {
return walkErr(parent, context, child, err)
}
// If the new child is nil, remove the original child from the slice of children
if newChild == nil {
copy(children[i:], children[i+1:])
children[len(children)-1] = nil
children = children[:len(children)-1]
// Now that the next child is in this child's place, walk i back one cell
i--
continue
}
child = newChild
children[i] = child
}
switch child := child.(type) {
case *Statement:
// Statements are passed verbatim as directives
err = walker.Statement(child)
case *Section:
// Sections are entered, walked, and exited -- the sub-Walker is given
// a chance to interact with its parent when exiting the section, if it
// implemented ConfigExiter.
var sub Walker
if sub, err = walker.EnterSection(child); err != nil || sub == nil {
break
}
if err = walkInContext(child, child, sub); err != nil {
break
}
if ex, ok := sub.(WalkExiter); ok {
err = ex.ExitSection(walker, child, parent)
}
case *Document:
err = walkInContext(context, child, walker)
default:
err = fmt.Errorf("unrecognized node type during walk: %T", child)
}
if err != nil {
return walkErr(parent, context, child, err)
}
}
switch parent := parent.(type) {
case *Document:
parent.Children = children
case *Section:
parent.Children = children
}
return nil
}
// WalkError is an error returned by Walk if an error occurs during a Walk call.
type WalkError struct {
// Document is the document the context and node were found in, if the Walk root was
// a document.
Document *Document
// Context is the ParentNode that Node is a child of.
Context ParentNode
// Node is the node that was encountered when the error occurred.
Node Node
// Err is the error that a Walker returned.
Err error
}
func walkErr(owner ParentNode, ctx ParentNode, node Node, err error) *WalkError {
if we, ok := err.(*WalkError); ok {
if we.Document != nil {
} else if doc, ok := owner.(*Document); ok {
we.Document = doc
}
return we
}
doc, _ := owner.(*Document)
return &WalkError{
Document: doc,
Context: ctx,
Node: node,
Err: err,
}
}
func (e *WalkError) Error() string {
prefix := "[" + e.Node.Token().Start.String() + "] "
suffix := contextName(e.Node) + " in " + contextName(e.Context) + ": " + e.Err.Error()
if e.Document != nil && e.Document.Name != "" {
return prefix + e.Document.Name + ": " + suffix
}
return prefix + suffix
}
func contextName(node Node) string {
switch node := node.(type) {
case *Document:
return "main"
case *Section:
return node.Name()
case *Statement:
return node.Name()
default:
return fmt.Sprintf("<%v>", node)
}
}