-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
result.go
192 lines (166 loc) · 6.58 KB
/
result.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
185
186
187
188
189
190
191
192
package rat
import (
"fmt"
"strings"
"golang.org/x/exp/slices"
)
// Result contains the result of an evaluated Rule function along with
// its own []rune slice (R).
//
// N (for "Name") is a string name for a result mapped to the x.Name
// rule. This makes for easy reading and walking of results trees since
// the string name is included in the output JSON (see String).
// Normally, mixing x.ID and x.Name rules is avoided.
//
// I (for "ID") is an integer mapped to the x.ID rule. Integer IDs are
// preferable to names (N) in cases where the use of names would
// increase the parse tree output JSON (see String) beyond acceptable
// levels since integer identifiers rarely take more than 2 runes each.
// Normally, mixing x.ID and x.Name rules is avoided.
//
// B (for "beginning") is the inclusive beginning position index of the
// first rune in Buf that matches.
//
// E (for "ending") is the exclusive ending position index of the end of
// the match (and Beg of next match). End must be advanced to the
// farthest possible point even if the rule fails (and an Err set). This
// allows recovery attempts from that position.
//
// C (for "children") contains results within this result, sub-matches,
// equivalent to parenthesized patterns of a regular expression.
//
// X contains any error encountered while parsing.
//
// Note that B == E does not indicate failure. E is usually greater than
// B, but not necessarily, for example, for lookahead rules are
// successful without advancing the position at all. E is also greater
// than B if a partial match was made that still resulted in an error.
// Only checking X can absolutely confirm a rule failure.
//
// Avoid taking reference to Result
//
// A Result is already made up of references so no further dereferencing
// is required. The buffer (R) is a slice and therefore all slices point
// to the same underlying array in memory. And no actual string data is
// saved in any Result. Rather, the beginning and ending positions
// within the buffer data are stored and retrieved when needed with
// methods such as Text().
//
type Result struct {
N string // string name (x.Name)
I int // integer identifier (x.ID)
B int // beginning (inclusive)
E int // ending (non-inclusive)
X error // error, eXpected something else
C []Result // children, results within this result
R []rune // reference data (underlying slice array shared)
}
// MarshalJSON fulfills the encoding.JSONMarshaler interface. The begin
// (B), end (E) are always included. The name (N), id (I), buffer (R),
// error (X) and child sub-matches (C) are only included if not empty.
// Child sub-matches omit the buffer (R). The order of fields is
// guaranteed not to change. Output is always a single line. There is
// no dependency on the reflect package. The buffer (R) is rendered as
// a quoted string (%q) with no further escaping (unlike built-in Go
// JSON marshaling which escapes things unnecessarily producing
// unreadable output). The buffer (R) is never included for children
// (which is the same). An error is never returned.
func (m Result) MarshalJSON() ([]byte, error) {
s := "{"
if m.N != "" {
s += fmt.Sprintf(`"N":%q,`, m.N)
}
if m.I > 0 {
s += fmt.Sprintf(`"I":%v,`, m.I)
}
s += fmt.Sprintf(`"B":%v,"E":%v`, m.B, m.E)
if m.X != nil {
s += fmt.Sprintf(`,"X":%q`, m.X)
}
if len(m.C) > 0 {
results := []string{}
for _, c := range m.C {
results = append(results, Result{c.N, c.I, c.B, c.E, c.X, c.C, nil}.String())
}
s += `,"C":[` + strings.Join(results, ",") + `]`
}
if m.R != nil {
s += fmt.Sprintf(`,"R":%q`, string(m.R))
}
s += "}"
return []byte(s), nil
}
// String fulfills the fmt.Stringer interface as JSON by calling
// MarshalJSON. If JSON marshaling fails for any reason a "null" string
// is returned.
func (m Result) String() string {
buf, err := m.MarshalJSON()
if err != nil {
return "null"
}
return string(buf)
}
// Print is shortcut for fmt.Println(String).
func (m Result) Print() { fmt.Println(m) }
// PrintText is short for fmt.Println(m.Text()).
func (m Result) PrintText() { fmt.Println(m.Text()) }
// PrintError is short for fmt.Println(m.X) but adds position.
func (m Result) PrintError() { fmt.Println(m.X) }
// Text returns the text between beginning (B) and ending (E)
// (non-inclusively) It is a shortcut for
// string(res.R[res.B:res.E]).
func (m Result) Text() string {
return string(m.R[m.B:m.E])
}
// FlatFunc is function that returns a flattened rooted-node tree.
type FlatFunc func(root Result) []Result
// VisitFunc is a first-class function passed one result. Typically
// these functions will enclose variables, contexts, or a channel
// outside of its own scope to be updated for each visit. Functional
// recursion is usually used, which may present some limitations
// depending on the depth required.
type VisitFunc func(a Result)
// DefaultFlatFunc is the default FlatFunc to use when filtering for results
// using With* methods.
var DefaultFlatFunc = ByDepth
// ByDepth flattens a rooted node tree of Result structs by
// traversing in a synchronous, depth-first, preorder way.
func ByDepth(root Result) []Result {
results := []Result{root}
for _, child := range root.C {
results = append(results, []Result(ByDepth(child))...)
}
return results
}
// Walk calls WalkBy(DefaultFlatFunc, root, do). Use this when the
// order of processing matters more than speed (ASTs, etc.). Also see
// WalkAsync.
func Walk(root Result, do VisitFunc) { WalkBy(DefaultFlatFunc, root, do) }
// WalkBy takes a function to flatten a rooted node tree (FlatFunc),
// creates a flattened slice of Results starting from root Result, and
// then passes each synchronously to the VisitFunc waiting for it to
// complete before doing the next.
// Walk calls WalkBy(DefaultFlatFunc, root, do). Use this when the
// order of processing matters more than speed (ASTs, etc.). Also see
func WalkBy(flatten FlatFunc, root Result, do VisitFunc) {
for _, result := range flatten(root) {
do(result)
}
}
// MaxGoroutines set the maximum number of goroutines by any method or
// function in this package (WalkByAsync, for example). By default,
// there is no limit (0).
var MaxGoroutines int
// WithName returns all results with any of the passed names. Returns
// zero length slice if no results. As a convenience, multiple names may
// be passed and all matches for each will be grouped together in the
// order provided. See WalkDefault for details on the algorithm used.
func (m Result) WithName(names ...string) []Result {
results := []Result{}
Walk(m, func(r Result) {
if slices.Contains(names, r.N) {
results = append(results, r)
}
})
return results
}