Skip to content

Commit

Permalink
[pkg/ottl] Add grammar utility to extract paths from statements (#35174)
Browse files Browse the repository at this point in the history
**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
  • Loading branch information
edmocosta authored Oct 7, 2024
1 parent 3fa7d8d commit e95bae2
Show file tree
Hide file tree
Showing 4 changed files with 816 additions and 0 deletions.
163 changes: 163 additions & 0 deletions pkg/ottl/grammar.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ func (b *booleanValue) checkForCustomError() error {
return nil
}

func (b *booleanValue) accept(v grammarVisitor) {
if b.Comparison != nil {
b.Comparison.accept(v)
}
if b.ConstExpr != nil && b.ConstExpr.Converter != nil {
b.ConstExpr.Converter.accept(v)
}
if b.SubExpr != nil {
b.SubExpr.accept(v)
}
}

// opAndBooleanValue represents the right side of an AND boolean expression.
type opAndBooleanValue struct {
Operator string `parser:"@OpAnd"`
Expand All @@ -67,6 +79,12 @@ func (b *opAndBooleanValue) checkForCustomError() error {
return b.Value.checkForCustomError()
}

func (b *opAndBooleanValue) accept(v grammarVisitor) {
if b.Value != nil {
b.Value.accept(v)
}
}

// term represents an arbitrary number of boolean values joined by AND.
type term struct {
Left *booleanValue `parser:"@@"`
Expand All @@ -87,6 +105,17 @@ func (b *term) checkForCustomError() error {
return nil
}

func (b *term) accept(v grammarVisitor) {
if b.Left != nil {
b.Left.accept(v)
}
for _, r := range b.Right {
if r != nil {
r.accept(v)
}
}
}

// opOrTerm represents the right side of an OR boolean expression.
type opOrTerm struct {
Operator string `parser:"@OpOr"`
Expand All @@ -97,6 +126,12 @@ func (b *opOrTerm) checkForCustomError() error {
return b.Term.checkForCustomError()
}

func (b *opOrTerm) accept(v grammarVisitor) {
if b.Term != nil {
b.Term.accept(v)
}
}

// booleanExpression represents a true/false decision expressed
// as an arbitrary number of terms separated by OR.
type booleanExpression struct {
Expand All @@ -118,6 +153,17 @@ func (b *booleanExpression) checkForCustomError() error {
return nil
}

func (b *booleanExpression) accept(v grammarVisitor) {
if b.Left != nil {
b.Left.accept(v)
}
for _, r := range b.Right {
if r != nil {
r.accept(v)
}
}
}

// compareOp is the type of a comparison operator.
type compareOp int

Expand Down Expand Up @@ -187,6 +233,11 @@ func (c *comparison) checkForCustomError() error {
return err
}

func (c *comparison) accept(v grammarVisitor) {
c.Left.accept(v)
c.Right.accept(v)
}

// editor represents the function call of a statement.
type editor struct {
Function string `parser:"@(Lowercase(Uppercase | Lowercase)*)"`
Expand All @@ -210,13 +261,28 @@ func (i *editor) checkForCustomError() error {
return nil
}

func (i *editor) accept(v grammarVisitor) {
v.visitEditor(i)
for _, arg := range i.Arguments {
arg.accept(v)
}
}

// converter represents a converter function call.
type converter struct {
Function string `parser:"@(Uppercase(Uppercase | Lowercase)*)"`
Arguments []argument `parser:"'(' ( @@ ( ',' @@ )* )? ')'"`
Keys []key `parser:"( @@ )*"`
}

func (c *converter) accept(v grammarVisitor) {
if c.Arguments != nil {
for _, a := range c.Arguments {
a.accept(v)
}
}
}

type argument struct {
Name string `parser:"(@(Lowercase(Uppercase | Lowercase)*) Equal)?"`
Value value `parser:"( @@"`
Expand All @@ -227,6 +293,10 @@ func (a *argument) checkForCustomError() error {
return a.Value.checkForCustomError()
}

func (a *argument) accept(v grammarVisitor) {
a.Value.accept(v)
}

// value represents a part of a parsed statement which is resolved to a value of some sort. This can be a telemetry path
// mathExpression, function call, or literal.
type value struct {
Expand All @@ -251,8 +321,27 @@ func (v *value) checkForCustomError() error {
return nil
}

func (v *value) accept(vis grammarVisitor) {
vis.visitValue(v)
if v.Literal != nil {
v.Literal.accept(vis)
}
if v.MathExpression != nil {
v.MathExpression.accept(vis)
}
if v.Map != nil {
v.Map.accept(vis)
}
if v.List != nil {
for _, i := range v.List.Values {
i.accept(vis)
}
}
}

// path represents a telemetry path mathExpression.
type path struct {
Pos lexer.Position
Context string `parser:"(@Lowercase '.')?"`
Fields []field `parser:"@@ ( '.' @@ )*"`
}
Expand All @@ -276,6 +365,14 @@ type mapValue struct {
Values []mapItem `parser:"'{' (@@ ','?)* '}'"`
}

func (m *mapValue) accept(v grammarVisitor) {
for _, i := range m.Values {
if i.Value != nil {
i.Value.accept(v)
}
}
}

type mapItem struct {
Key *string `parser:"@String ':'"`
Value *value `parser:"@@"`
Expand Down Expand Up @@ -326,6 +423,19 @@ func (m *mathExprLiteral) checkForCustomError() error {
return nil
}

func (m *mathExprLiteral) accept(v grammarVisitor) {
v.visitMathExprLiteral(m)
if m.Path != nil {
v.visitPath(m.Path)
}
if m.Editor != nil {
m.Editor.accept(v)
}
if m.Converter != nil {
m.Converter.accept(v)
}
}

type mathValue struct {
Literal *mathExprLiteral `parser:"( @@"`
SubExpression *mathExpression `parser:"| '(' @@ ')' )"`
Expand All @@ -338,6 +448,15 @@ func (m *mathValue) checkForCustomError() error {
return m.SubExpression.checkForCustomError()
}

func (m *mathValue) accept(v grammarVisitor) {
if m.Literal != nil {
m.Literal.accept(v)
}
if m.SubExpression != nil {
m.SubExpression.accept(v)
}
}

type opMultDivValue struct {
Operator mathOp `parser:"@OpMultDiv"`
Value *mathValue `parser:"@@"`
Expand All @@ -347,6 +466,12 @@ func (m *opMultDivValue) checkForCustomError() error {
return m.Value.checkForCustomError()
}

func (m *opMultDivValue) accept(v grammarVisitor) {
if m.Value != nil {
m.Value.accept(v)
}
}

type addSubTerm struct {
Left *mathValue `parser:"@@"`
Right []*opMultDivValue `parser:"@@*"`
Expand All @@ -366,6 +491,17 @@ func (m *addSubTerm) checkForCustomError() error {
return nil
}

func (m *addSubTerm) accept(v grammarVisitor) {
if m.Left != nil {
m.Left.accept(v)
}
for _, r := range m.Right {
if r != nil {
r.accept(v)
}
}
}

type opAddSubTerm struct {
Operator mathOp `parser:"@OpAddSub"`
Term *addSubTerm `parser:"@@"`
Expand All @@ -375,6 +511,12 @@ func (m *opAddSubTerm) checkForCustomError() error {
return m.Term.checkForCustomError()
}

func (m *opAddSubTerm) accept(v grammarVisitor) {
if m.Term != nil {
m.Term.accept(v)
}
}

type mathExpression struct {
Left *addSubTerm `parser:"@@"`
Right []*opAddSubTerm `parser:"@@*"`
Expand All @@ -394,6 +536,19 @@ func (m *mathExpression) checkForCustomError() error {
return nil
}

func (m *mathExpression) accept(v grammarVisitor) {
if m.Left != nil {
m.Left.accept(v)
}
if m.Right != nil {
for _, r := range m.Right {
if r != nil {
r.accept(v)
}
}
}
}

type mathOp int

const (
Expand Down Expand Up @@ -464,3 +619,11 @@ func buildLexer() *lexer.StatefulDefinition {
{Name: "whitespace", Pattern: `\s+`},
})
}

// grammarVisitor allows accessing the grammar AST nodes using the visitor pattern.
type grammarVisitor interface {
visitPath(v *path)
visitEditor(v *editor)
visitValue(v *value)
visitMathExprLiteral(v *mathExprLiteral)
}
Loading

0 comments on commit e95bae2

Please sign in to comment.