-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtree.go
More file actions
354 lines (314 loc) · 7.14 KB
/
tree.go
File metadata and controls
354 lines (314 loc) · 7.14 KB
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
package dot
import (
"bufio"
"fmt"
"io"
"strings"
"github.com/teleivo/dot/internal/assert"
"github.com/teleivo/dot/token"
)
// Format specifies the output representation for rendering a [Tree].
type Format int
const (
// Default renders the formatted output as indented text.
Default Format = iota
// Scheme renders the tree as S-expressions with position annotations. Each node is rendered
// as (NodeType (@ startLine startCol endLine endCol) children...) and tokens are rendered as
// ('token' (@ startLine startCol endLine endCol)).
Scheme
)
var formats = map[string]Format{
"default": Default,
"scheme": Scheme,
}
var validFormats = [...]string{"default", "scheme"}
// NewFormat converts a string to a [Format] constant. Valid values are "default" and "scheme".
// Returns an error if the format string is invalid.
func NewFormat(format string) (Format, error) {
if f, ok := formats[format]; ok {
return f, nil
}
return Default, fmt.Errorf("invalid format string: %q, valid ones are: %q", format, validFormats)
}
// TreeKind represents the type of syntax tree node (non-terminals).
type TreeKind uint32
const (
KindErrorTree TreeKind = 1 << iota
// Graph structure
KindFile
KindGraph
KindSubgraph
// Statements
KindStmtList
KindNodeStmt
KindEdgeStmt
KindAttrStmt
// Node and Edge components
KindNodeID
KindPort
KindCompassPoint
// Attributes
KindAttrList
KindAList
KindAttribute
KindAttrName
KindAttrValue
KindID
)
// String returns the name of the tree kind.
func (tk TreeKind) String() string {
switch tk {
case KindErrorTree:
return "ErrorTree"
case KindFile:
return "File"
case KindGraph:
return "Graph"
case KindSubgraph:
return "Subgraph"
case KindStmtList:
return "StmtList"
case KindNodeStmt:
return "NodeStmt"
case KindEdgeStmt:
return "EdgeStmt"
case KindAttrStmt:
return "AttrStmt"
case KindNodeID:
return "NodeID"
case KindPort:
return "Port"
case KindCompassPoint:
return "CompassPoint"
case KindAttrList:
return "AttrList"
case KindAList:
return "AList"
case KindAttribute:
return "Attribute"
case KindAttrName:
return "AttrName"
case KindAttrValue:
return "AttrValue"
case KindID:
return "ID"
default:
panic(fmt.Errorf("TreeKind Stringer missing case for %d", tk))
}
}
// Tree represents a node in the concrete syntax tree.
//
// Type identifies the syntactic construct (e.g., [Graph], [NodeStmt], [ID]). Children contains the
// node's children in source order, which may be either [TreeChild] (subtrees) or [TokenChild]
// (tokens). Start and End mark the source positions.
type Tree struct {
Kind TreeKind
Children []Child
Start, End token.Position
}
func (tree *Tree) appendToken(child token.Token) {
if len(tree.Children) == 0 {
tree.Start = child.Start
}
tree.End = child.End
tree.Children = append(tree.Children, TokenChild{child})
}
func (tree *Tree) appendTree(child *Tree) {
if len(tree.Children) == 0 {
tree.Start = child.Start
}
tree.End = child.End
tree.Children = append(tree.Children, TreeChild{child})
}
// String returns the tree formatted using the [Default] format.
func (tree *Tree) String() string {
if tree == nil {
return ""
}
var sb strings.Builder
_ = tree.Render(&sb, Default)
return sb.String()
}
// renderDefault writes the tree in default format (indented text without positions or parentheses)
// to the buffered writer.
func renderDefault(bw *bufio.Writer, tree *Tree, indent int) error {
if tree == nil {
return nil
}
err := writeIndentBuffered(bw, indent)
if err != nil {
return err
}
_, err = bw.WriteString(tree.Kind.String())
if err != nil {
return err
}
for _, child := range tree.Children {
err = bw.WriteByte('\n')
if err != nil {
return err
}
switch c := child.(type) {
case TokenChild:
err = writeIndentBuffered(bw, indent+1)
if err != nil {
return err
}
err = bw.WriteByte('\'')
if err != nil {
return err
}
_, err = bw.WriteString(c.String())
if err != nil {
return err
}
err = bw.WriteByte('\'')
if err != nil {
return err
}
case TreeChild:
err = renderDefault(bw, c.Tree, indent+1)
if err != nil {
return err
}
}
}
return nil
}
func writeIndentBuffered(bw *bufio.Writer, columns int) error {
for range columns {
err := bw.WriteByte('\t')
if err != nil {
return err
}
}
return nil
}
// Render writes the tree to w in the specified format. See [Format] for available formats.
func (tree *Tree) Render(w io.Writer, format Format) error {
if tree == nil {
return nil
}
bw := bufio.NewWriter(w)
var err error
switch format {
case Default:
err = renderDefault(bw, tree, 0)
case Scheme:
err = renderScheme(bw, tree, 0)
default:
panic(fmt.Errorf("rendering tree in format %d is not implemented", format))
}
if err != nil {
return err
}
err = bw.WriteByte('\n')
if err != nil {
return err
}
return bw.Flush()
}
// renderScheme writes the tree in scheme format (S-expressions with position annotations) to the
// buffered writer.
func renderScheme(bw *bufio.Writer, tree *Tree, indent int) error {
if tree == nil {
return nil
}
err := writeIndentBuffered(bw, indent)
if err != nil {
return err
}
err = bw.WriteByte('(')
if err != nil {
return err
}
_, err = bw.WriteString(tree.Kind.String())
if err != nil {
return err
}
err = renderPosition(bw, tree.Start, tree.End)
if err != nil {
return err
}
for _, child := range tree.Children {
err = bw.WriteByte('\n')
if err != nil {
return err
}
switch c := child.(type) {
case TokenChild:
err = writeIndentBuffered(bw, indent+1)
if err != nil {
return err
}
err = bw.WriteByte('(')
if err != nil {
return err
}
err = bw.WriteByte('\'')
if err != nil {
return err
}
_, err = bw.WriteString(c.String())
if err != nil {
return err
}
err = bw.WriteByte('\'')
if err != nil {
return err
}
err = renderPosition(bw, c.Start, c.End)
if err != nil {
return err
}
err = bw.WriteByte(')')
if err != nil {
return err
}
case TreeChild:
err = renderScheme(bw, c.Tree, indent+1)
if err != nil {
return err
}
}
}
err = bw.WriteByte(')')
if err != nil {
return err
}
return nil
}
func renderPosition(bw *bufio.Writer, start, end token.Position) error {
assert.That(start.IsValid() == end.IsValid(), "tree position invariant violated: both Start and End must be valid or both invalid, got Start=%v End=%v", start, end)
if !start.IsValid() && !end.IsValid() { // empty File will not have positions
return nil
}
_, err := bw.WriteString(" (@ ")
if err != nil {
return err
}
_, err = fmt.Fprintf(bw, "%d %d %d %d", start.Line, start.Column, end.Line, end.Column)
if err != nil {
return err
}
err = bw.WriteByte(')')
if err != nil {
return err
}
return nil
}
// Child is a marker interface for tree node children. Implementations are [TreeChild] and
// [TokenChild].
type Child interface {
child()
}
// TreeChild wraps a [Tree] as a child of another tree node.
type TreeChild struct {
*Tree
}
func (TreeChild) child() {}
// TokenChild wraps a [token.Token] as a child of a tree node.
type TokenChild struct {
token.Token
}
func (TokenChild) child() {}