Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/river/printer/walker.go
4096 views
1
package printer
2
3
import (
4
"fmt"
5
"strings"
6
7
"github.com/grafana/agent/pkg/river/ast"
8
"github.com/grafana/agent/pkg/river/token"
9
)
10
11
// A walker walks an AST and sends lexical tokens and formatting information to
12
// a printer.
13
type walker struct {
14
p *printer
15
}
16
17
func (w *walker) Walk(node ast.Node) error {
18
switch node := node.(type) {
19
case *ast.File:
20
w.walkFile(node)
21
case ast.Body:
22
w.walkStmts(node)
23
case ast.Stmt:
24
w.walkStmt(node)
25
case ast.Expr:
26
w.walkExpr(node)
27
default:
28
return fmt.Errorf("unsupported node type %T", node)
29
}
30
31
return nil
32
}
33
34
func (w *walker) walkFile(f *ast.File) {
35
w.p.SetComments(f.Comments)
36
w.walkStmts(f.Body)
37
}
38
39
func (w *walker) walkStmts(ss []ast.Stmt) {
40
for i, s := range ss {
41
var addedSpacing bool
42
43
// Two blocks should always be separated by a blank line.
44
if _, isBlock := s.(*ast.BlockStmt); i > 0 && isBlock {
45
w.p.Write(wsFormfeed)
46
addedSpacing = true
47
}
48
49
// A blank line should always be added if there is a blank line in the
50
// source between two statements.
51
if i > 0 && !addedSpacing {
52
var (
53
prevLine = ast.EndPos(ss[i-1]).Position().Line
54
curLine = ast.StartPos(ss[i-0]).Position().Line
55
56
lineDiff = curLine - prevLine
57
)
58
59
if lineDiff > 1 {
60
w.p.Write(wsFormfeed)
61
}
62
}
63
64
w.walkStmt(s)
65
66
// Statements which cross multiple lines don't belong to the same row run.
67
// Add a formfeed to start a new row run if the node crossed more than one
68
// line, otherwise add the normal newline.
69
if nodeLines(s) > 1 {
70
w.p.Write(wsFormfeed)
71
} else {
72
w.p.Write(wsNewline)
73
}
74
}
75
}
76
77
func nodeLines(n ast.Node) int {
78
var (
79
startLine = ast.StartPos(n).Position().Line
80
endLine = ast.EndPos(n).Position().Line
81
)
82
83
return endLine - startLine + 1
84
}
85
86
func (w *walker) walkStmt(s ast.Stmt) {
87
switch s := s.(type) {
88
case *ast.AttributeStmt:
89
w.walkAttributeStmt(s)
90
case *ast.BlockStmt:
91
w.walkBlockStmt(s)
92
}
93
}
94
95
func (w *walker) walkAttributeStmt(s *ast.AttributeStmt) {
96
w.p.Write(s.Name.NamePos, s.Name, wsVTab, token.ASSIGN, wsBlank)
97
w.walkExpr(s.Value)
98
}
99
100
func (w *walker) walkBlockStmt(s *ast.BlockStmt) {
101
joined := strings.Join(s.Name, ".")
102
103
w.p.Write(
104
s.NamePos,
105
&ast.Ident{Name: joined, NamePos: s.NamePos},
106
)
107
108
if s.Label != "" {
109
label := fmt.Sprintf("%q", s.Label)
110
111
w.p.Write(
112
wsBlank,
113
s.LabelPos,
114
&ast.LiteralExpr{Kind: token.STRING, Value: label},
115
)
116
}
117
118
w.p.Write(
119
wsBlank,
120
s.LCurlyPos, token.LCURLY, wsIndent,
121
)
122
123
if len(s.Body) > 0 {
124
// Add a formfeed to start a new row run before writing any statements.
125
w.p.Write(wsFormfeed)
126
w.walkStmts(s.Body)
127
} else {
128
// There's no statements, but add a blank line between the left and right
129
// curly anyway.
130
w.p.Write(wsBlank)
131
}
132
133
w.p.Write(wsUnindent, s.RCurlyPos, token.RCURLY)
134
}
135
136
func (w *walker) walkExpr(e ast.Expr) {
137
switch e := e.(type) {
138
case *ast.LiteralExpr:
139
w.p.Write(e.ValuePos, e)
140
141
case *ast.ArrayExpr:
142
w.walkArrayExpr(e)
143
144
case *ast.ObjectExpr:
145
w.walkObjectExpr(e)
146
147
case *ast.IdentifierExpr:
148
w.p.Write(e.Ident.NamePos, e.Ident)
149
150
case *ast.AccessExpr:
151
w.walkExpr(e.Value)
152
w.p.Write(token.DOT, e.Name)
153
154
case *ast.IndexExpr:
155
w.walkExpr(e.Value)
156
w.p.Write(e.LBrackPos, token.LBRACK)
157
w.walkExpr(e.Index)
158
w.p.Write(e.RBrackPos, token.RBRACK)
159
160
case *ast.CallExpr:
161
w.walkCallExpr(e)
162
163
case *ast.UnaryExpr:
164
w.p.Write(e.KindPos, e.Kind)
165
w.walkExpr(e.Value)
166
167
case *ast.BinaryExpr:
168
// TODO(rfratto):
169
//
170
// 1. allow RHS to be on a new line
171
//
172
// 2. remove spacing between some operators to make precedence
173
// clearer like Go does
174
w.walkExpr(e.Left)
175
w.p.Write(wsBlank, e.KindPos, e.Kind, wsBlank)
176
w.walkExpr(e.Right)
177
178
case *ast.ParenExpr:
179
w.p.Write(token.LPAREN)
180
w.walkExpr(e.Inner)
181
w.p.Write(token.RPAREN)
182
}
183
}
184
185
func (w *walker) walkArrayExpr(e *ast.ArrayExpr) {
186
w.p.Write(e.LBrackPos, token.LBRACK)
187
prevPos := e.LBrackPos
188
189
for i := 0; i < len(e.Elements); i++ {
190
var addedNewline bool
191
192
elementPos := ast.StartPos(e.Elements[i])
193
194
// Add a newline if this element starts on a different line than the last
195
// element ended.
196
if differentLines(prevPos, elementPos) {
197
// Indent elements inside the array on different lines. The indent is
198
// done *before* the newline to make sure comments written before the
199
// newline are indented properly.
200
w.p.Write(wsIndent, wsFormfeed)
201
addedNewline = true
202
} else if i > 0 {
203
// Make sure a space is injected before the next element if two
204
// successive elements are on the same line.
205
w.p.Write(wsBlank)
206
}
207
prevPos = ast.EndPos(e.Elements[i])
208
209
// Write the expression.
210
w.walkExpr(e.Elements[i])
211
212
// Always add commas in between successive elements.
213
if i+1 < len(e.Elements) {
214
w.p.Write(token.COMMA)
215
}
216
217
if addedNewline {
218
w.p.Write(wsUnindent)
219
}
220
}
221
222
var addedSuffixNewline bool
223
224
// If the closing bracket is on a different line than the final element,
225
// we need to add a trailing comma.
226
if len(e.Elements) > 0 && differentLines(prevPos, e.RBrackPos) {
227
// We add an indentation here so comments after the final element are
228
// indented.
229
w.p.Write(token.COMMA, wsIndent, wsFormfeed)
230
addedSuffixNewline = true
231
}
232
233
if addedSuffixNewline {
234
w.p.Write(wsUnindent)
235
}
236
w.p.Write(e.RBrackPos, token.RBRACK)
237
}
238
239
func (w *walker) walkObjectExpr(e *ast.ObjectExpr) {
240
w.p.Write(e.LCurlyPos, token.LCURLY, wsIndent)
241
242
prevPos := e.LCurlyPos
243
244
for i := 0; i < len(e.Fields); i++ {
245
field := e.Fields[i]
246
elementPos := ast.StartPos(field.Name)
247
248
// Add a newline if this element starts on a different line than the last
249
// element ended.
250
if differentLines(prevPos, elementPos) {
251
// We want to align the equal sign for object attributes if the previous
252
// field only crossed one line.
253
if i > 0 && nodeLines(e.Fields[i-1].Value) == 1 {
254
w.p.Write(wsNewline)
255
} else {
256
w.p.Write(wsFormfeed)
257
}
258
} else if i > 0 {
259
// Make sure a space is injected before the next element if two successive
260
// elements are on the same line.
261
w.p.Write(wsBlank)
262
}
263
prevPos = ast.EndPos(field.Name)
264
265
w.p.Write(field.Name.NamePos)
266
267
// Write the field.
268
if field.Quoted {
269
w.p.Write(&ast.LiteralExpr{
270
Kind: token.STRING,
271
ValuePos: field.Name.NamePos,
272
Value: fmt.Sprintf("%q", field.Name.Name),
273
})
274
} else {
275
w.p.Write(field.Name)
276
}
277
278
w.p.Write(wsVTab, token.ASSIGN, wsBlank)
279
w.walkExpr(field.Value)
280
281
// Always add commas in between successive elements.
282
if i+1 < len(e.Fields) {
283
w.p.Write(token.COMMA)
284
}
285
}
286
287
// If the closing bracket is on a different line than the final element,
288
// we need to add a trailing comma.
289
if len(e.Fields) > 0 && differentLines(prevPos, e.RCurlyPos) {
290
w.p.Write(token.COMMA, wsFormfeed)
291
}
292
293
w.p.Write(wsUnindent, e.RCurlyPos, token.RCURLY)
294
}
295
296
func (w *walker) walkCallExpr(e *ast.CallExpr) {
297
w.walkExpr(e.Value)
298
w.p.Write(token.LPAREN)
299
300
prevPos := e.LParenPos
301
302
for i, arg := range e.Args {
303
var addedNewline bool
304
305
argPos := ast.StartPos(arg)
306
307
// Add a newline if this element starts on a different line than the last
308
// element ended.
309
if differentLines(prevPos, argPos) {
310
w.p.Write(wsFormfeed, wsIndent)
311
addedNewline = true
312
}
313
314
w.walkExpr(arg)
315
prevPos = ast.EndPos(arg)
316
317
if i+1 < len(e.Args) {
318
w.p.Write(token.COMMA, wsBlank)
319
}
320
321
if addedNewline {
322
w.p.Write(wsUnindent)
323
}
324
}
325
326
// Add a final comma if the final argument is on a different line than the
327
// right parenthesis.
328
if differentLines(prevPos, e.RParenPos) {
329
w.p.Write(token.COMMA, wsFormfeed)
330
}
331
332
w.p.Write(token.RPAREN)
333
}
334
335
// differentLines returns true if a and b are on different lines.
336
func differentLines(a, b token.Pos) bool {
337
return a.Position().Line != b.Position().Line
338
}
339
340