Skip to content

Commit e95bae2

Browse files
authored
[pkg/ottl] Add grammar utility to extract paths from statements (#35174)
**Description:** <Describe what has changed.> <!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> This PR is part of #29017, and adds a grammar utility to allow extracting `ottl.path` from parsed statements and expressions. The new functions will power the context inferrer utility (see [draft](#35050)), which implementation will basically gather all declared statement's `Path.Context`, and choose the highest priority one. For the statements rewriter utility purpose, it changes the `grammar.go` file including a new field `Pos lexer.Position` into the `ottl.path` struct. This value is automatically set by the `participle` lexer and holds the path's offsets, being useful for identifying their positions in the raw statement, without using regexes an being coupled to the grammar's definition. **Additional changes**: This proposal uses the visitor pattern to gather all path's from the parsed statements. Considering the grammar's custom error mechanism (`checkForCustomError`) also works with a similar pattern, I've added the visitor implementation as part of the grammar objects, and refactored all `checkForCustomError` functions to be part of a validator visitor. Differently of the current implementation, it joins and displays all errors at once instead of one by one, IMO, it's specially useful for statements with more than one error, for example: `set(name, int(2), float(1))`. *&ast;* We can change it back do be error-by-error if necessary. *&ast;* If modifying the custom validator is not desired, I can roll those changes back keeping only the necessary code to extract the path's + `Pos` field. **Link to tracking Issue:** #29017 **Testing:** Unit tests were added **Documentation:** No changes
1 parent 3fa7d8d commit e95bae2

File tree

4 files changed

+816
-0
lines changed

4 files changed

+816
-0
lines changed

pkg/ottl/grammar.go

+163
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ func (b *booleanValue) checkForCustomError() error {
5757
return nil
5858
}
5959

60+
func (b *booleanValue) accept(v grammarVisitor) {
61+
if b.Comparison != nil {
62+
b.Comparison.accept(v)
63+
}
64+
if b.ConstExpr != nil && b.ConstExpr.Converter != nil {
65+
b.ConstExpr.Converter.accept(v)
66+
}
67+
if b.SubExpr != nil {
68+
b.SubExpr.accept(v)
69+
}
70+
}
71+
6072
// opAndBooleanValue represents the right side of an AND boolean expression.
6173
type opAndBooleanValue struct {
6274
Operator string `parser:"@OpAnd"`
@@ -67,6 +79,12 @@ func (b *opAndBooleanValue) checkForCustomError() error {
6779
return b.Value.checkForCustomError()
6880
}
6981

82+
func (b *opAndBooleanValue) accept(v grammarVisitor) {
83+
if b.Value != nil {
84+
b.Value.accept(v)
85+
}
86+
}
87+
7088
// term represents an arbitrary number of boolean values joined by AND.
7189
type term struct {
7290
Left *booleanValue `parser:"@@"`
@@ -87,6 +105,17 @@ func (b *term) checkForCustomError() error {
87105
return nil
88106
}
89107

108+
func (b *term) accept(v grammarVisitor) {
109+
if b.Left != nil {
110+
b.Left.accept(v)
111+
}
112+
for _, r := range b.Right {
113+
if r != nil {
114+
r.accept(v)
115+
}
116+
}
117+
}
118+
90119
// opOrTerm represents the right side of an OR boolean expression.
91120
type opOrTerm struct {
92121
Operator string `parser:"@OpOr"`
@@ -97,6 +126,12 @@ func (b *opOrTerm) checkForCustomError() error {
97126
return b.Term.checkForCustomError()
98127
}
99128

129+
func (b *opOrTerm) accept(v grammarVisitor) {
130+
if b.Term != nil {
131+
b.Term.accept(v)
132+
}
133+
}
134+
100135
// booleanExpression represents a true/false decision expressed
101136
// as an arbitrary number of terms separated by OR.
102137
type booleanExpression struct {
@@ -118,6 +153,17 @@ func (b *booleanExpression) checkForCustomError() error {
118153
return nil
119154
}
120155

156+
func (b *booleanExpression) accept(v grammarVisitor) {
157+
if b.Left != nil {
158+
b.Left.accept(v)
159+
}
160+
for _, r := range b.Right {
161+
if r != nil {
162+
r.accept(v)
163+
}
164+
}
165+
}
166+
121167
// compareOp is the type of a comparison operator.
122168
type compareOp int
123169

@@ -187,6 +233,11 @@ func (c *comparison) checkForCustomError() error {
187233
return err
188234
}
189235

236+
func (c *comparison) accept(v grammarVisitor) {
237+
c.Left.accept(v)
238+
c.Right.accept(v)
239+
}
240+
190241
// editor represents the function call of a statement.
191242
type editor struct {
192243
Function string `parser:"@(Lowercase(Uppercase | Lowercase)*)"`
@@ -210,13 +261,28 @@ func (i *editor) checkForCustomError() error {
210261
return nil
211262
}
212263

264+
func (i *editor) accept(v grammarVisitor) {
265+
v.visitEditor(i)
266+
for _, arg := range i.Arguments {
267+
arg.accept(v)
268+
}
269+
}
270+
213271
// converter represents a converter function call.
214272
type converter struct {
215273
Function string `parser:"@(Uppercase(Uppercase | Lowercase)*)"`
216274
Arguments []argument `parser:"'(' ( @@ ( ',' @@ )* )? ')'"`
217275
Keys []key `parser:"( @@ )*"`
218276
}
219277

278+
func (c *converter) accept(v grammarVisitor) {
279+
if c.Arguments != nil {
280+
for _, a := range c.Arguments {
281+
a.accept(v)
282+
}
283+
}
284+
}
285+
220286
type argument struct {
221287
Name string `parser:"(@(Lowercase(Uppercase | Lowercase)*) Equal)?"`
222288
Value value `parser:"( @@"`
@@ -227,6 +293,10 @@ func (a *argument) checkForCustomError() error {
227293
return a.Value.checkForCustomError()
228294
}
229295

296+
func (a *argument) accept(v grammarVisitor) {
297+
a.Value.accept(v)
298+
}
299+
230300
// value represents a part of a parsed statement which is resolved to a value of some sort. This can be a telemetry path
231301
// mathExpression, function call, or literal.
232302
type value struct {
@@ -251,8 +321,27 @@ func (v *value) checkForCustomError() error {
251321
return nil
252322
}
253323

324+
func (v *value) accept(vis grammarVisitor) {
325+
vis.visitValue(v)
326+
if v.Literal != nil {
327+
v.Literal.accept(vis)
328+
}
329+
if v.MathExpression != nil {
330+
v.MathExpression.accept(vis)
331+
}
332+
if v.Map != nil {
333+
v.Map.accept(vis)
334+
}
335+
if v.List != nil {
336+
for _, i := range v.List.Values {
337+
i.accept(vis)
338+
}
339+
}
340+
}
341+
254342
// path represents a telemetry path mathExpression.
255343
type path struct {
344+
Pos lexer.Position
256345
Context string `parser:"(@Lowercase '.')?"`
257346
Fields []field `parser:"@@ ( '.' @@ )*"`
258347
}
@@ -276,6 +365,14 @@ type mapValue struct {
276365
Values []mapItem `parser:"'{' (@@ ','?)* '}'"`
277366
}
278367

368+
func (m *mapValue) accept(v grammarVisitor) {
369+
for _, i := range m.Values {
370+
if i.Value != nil {
371+
i.Value.accept(v)
372+
}
373+
}
374+
}
375+
279376
type mapItem struct {
280377
Key *string `parser:"@String ':'"`
281378
Value *value `parser:"@@"`
@@ -326,6 +423,19 @@ func (m *mathExprLiteral) checkForCustomError() error {
326423
return nil
327424
}
328425

426+
func (m *mathExprLiteral) accept(v grammarVisitor) {
427+
v.visitMathExprLiteral(m)
428+
if m.Path != nil {
429+
v.visitPath(m.Path)
430+
}
431+
if m.Editor != nil {
432+
m.Editor.accept(v)
433+
}
434+
if m.Converter != nil {
435+
m.Converter.accept(v)
436+
}
437+
}
438+
329439
type mathValue struct {
330440
Literal *mathExprLiteral `parser:"( @@"`
331441
SubExpression *mathExpression `parser:"| '(' @@ ')' )"`
@@ -338,6 +448,15 @@ func (m *mathValue) checkForCustomError() error {
338448
return m.SubExpression.checkForCustomError()
339449
}
340450

451+
func (m *mathValue) accept(v grammarVisitor) {
452+
if m.Literal != nil {
453+
m.Literal.accept(v)
454+
}
455+
if m.SubExpression != nil {
456+
m.SubExpression.accept(v)
457+
}
458+
}
459+
341460
type opMultDivValue struct {
342461
Operator mathOp `parser:"@OpMultDiv"`
343462
Value *mathValue `parser:"@@"`
@@ -347,6 +466,12 @@ func (m *opMultDivValue) checkForCustomError() error {
347466
return m.Value.checkForCustomError()
348467
}
349468

469+
func (m *opMultDivValue) accept(v grammarVisitor) {
470+
if m.Value != nil {
471+
m.Value.accept(v)
472+
}
473+
}
474+
350475
type addSubTerm struct {
351476
Left *mathValue `parser:"@@"`
352477
Right []*opMultDivValue `parser:"@@*"`
@@ -366,6 +491,17 @@ func (m *addSubTerm) checkForCustomError() error {
366491
return nil
367492
}
368493

494+
func (m *addSubTerm) accept(v grammarVisitor) {
495+
if m.Left != nil {
496+
m.Left.accept(v)
497+
}
498+
for _, r := range m.Right {
499+
if r != nil {
500+
r.accept(v)
501+
}
502+
}
503+
}
504+
369505
type opAddSubTerm struct {
370506
Operator mathOp `parser:"@OpAddSub"`
371507
Term *addSubTerm `parser:"@@"`
@@ -375,6 +511,12 @@ func (m *opAddSubTerm) checkForCustomError() error {
375511
return m.Term.checkForCustomError()
376512
}
377513

514+
func (m *opAddSubTerm) accept(v grammarVisitor) {
515+
if m.Term != nil {
516+
m.Term.accept(v)
517+
}
518+
}
519+
378520
type mathExpression struct {
379521
Left *addSubTerm `parser:"@@"`
380522
Right []*opAddSubTerm `parser:"@@*"`
@@ -394,6 +536,19 @@ func (m *mathExpression) checkForCustomError() error {
394536
return nil
395537
}
396538

539+
func (m *mathExpression) accept(v grammarVisitor) {
540+
if m.Left != nil {
541+
m.Left.accept(v)
542+
}
543+
if m.Right != nil {
544+
for _, r := range m.Right {
545+
if r != nil {
546+
r.accept(v)
547+
}
548+
}
549+
}
550+
}
551+
397552
type mathOp int
398553

399554
const (
@@ -464,3 +619,11 @@ func buildLexer() *lexer.StatefulDefinition {
464619
{Name: "whitespace", Pattern: `\s+`},
465620
})
466621
}
622+
623+
// grammarVisitor allows accessing the grammar AST nodes using the visitor pattern.
624+
type grammarVisitor interface {
625+
visitPath(v *path)
626+
visitEditor(v *editor)
627+
visitValue(v *value)
628+
visitMathExprLiteral(v *mathExprLiteral)
629+
}

0 commit comments

Comments
 (0)