Skip to content

Commit

Permalink
- Fixing a bug that meant Duration was considered a valid Expr
Browse files Browse the repository at this point in the history
- Introduced a depth-first visitor that can extract all descendant `Expr` nodes from a given `Expr`
  • Loading branch information
djluck committed Nov 24, 2021
1 parent f9c37ea commit 20ca794
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 3 deletions.
3 changes: 1 addition & 2 deletions src/PromQL.Parser/Ast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public interface IPromQlNode
[Closed(
typeof(AggregateExpr),
typeof(BinaryExpr),
typeof(Duration),
typeof(FunctionCall),
typeof(MatrixSelector),
typeof(NumberLiteral),
Expand Down Expand Up @@ -174,7 +173,7 @@ public record NumberLiteral(double Value) : Expr
public void Accept(IVisitor visitor) => visitor.Visit(this);
}

public record Duration(TimeSpan Value) : Expr
public record Duration(TimeSpan Value) : IPromQlNode
{
public void Accept(IVisitor visitor) => visitor.Visit(this);
}
Expand Down
88 changes: 88 additions & 0 deletions src/PromQL.Parser/DepthFirstExpressionVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Collections;
using System.Collections.Generic;
using PromQL.Parser.Ast;

namespace PromQL.Parser
{
/// <summary>
/// A depth-first visitor that can find all descendant <see cref="Expr"/> nodes from a given <see cref="Expr"/>.
/// </summary>
public class DepthFirstExpressionVisitor : IVisitor
{
private List<Expr> _expressions = new();

void IVisitor.Visit(StringLiteral expr) => _expressions.Add(expr);

void IVisitor.Visit(SubqueryExpr sq)
{
_expressions.Add(sq);
sq.Expr.Accept(this);
}

void IVisitor.Visit(Duration d) { }

void IVisitor.Visit(NumberLiteral n) => _expressions.Add(n);

void IVisitor.Visit(MetricIdentifier mi) { }

void IVisitor.Visit(LabelMatcher expr) { }

void IVisitor.Visit(UnaryExpr unary)
{
_expressions.Add(unary);
unary.Expr.Accept(this);
}

void IVisitor.Visit(MatrixSelector ms)
{
_expressions.Add(ms);
// No need to visit vector selector, it's accessible from matrix selector
}

void IVisitor.Visit(OffsetExpr offset)
{
_expressions.Add(offset);
offset.Expr.Accept(this);
}

void IVisitor.Visit(ParenExpression paren)
{
_expressions.Add(paren);
paren.Expr.Accept(this);
}

void IVisitor.Visit(FunctionCall fnCall)
{
_expressions.Add(fnCall);
foreach (var a in fnCall.Args)
a.Accept(this);
}

void IVisitor.Visit(VectorMatching vm) { }

void IVisitor.Visit(BinaryExpr expr)
{
_expressions.Add(expr);
expr.LeftHandSide.Accept(this);
expr.RightHandSide.Accept(this);
}

void IVisitor.Visit(AggregateExpr expr)
{
_expressions.Add(expr);
expr.Param?.Accept(this);
expr.Expr.Accept(this);
}

void IVisitor.Visit(VectorSelector vs) => _expressions.Add(vs);

void IVisitor.Visit(LabelMatchers lms) { }

public IEnumerable<Expr> GetExpressions(Expr expr)
{
_expressions.Clear();
expr.Accept(this);
return _expressions;
}
}
}
2 changes: 1 addition & 1 deletion src/PromQL.Parser/IVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public interface IVisitor
void Visit(BinaryExpr expr);
void Visit(AggregateExpr expr);
void Visit(VectorSelector vs);
void Visit(LabelMatchers fnCall);
void Visit(LabelMatchers lms);
}
}
51 changes: 51 additions & 0 deletions tests/PromQL.Parser.Tests/DepthFirstExpressionVisitorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using PromQL.Parser.Ast;

namespace PromQL.Parser.Tests
{
[TestFixture]
public class DepthFirstExpressionVisitorTests
{
[Test]
public void Visit_Basic_Types()
{
// Not semantically valid but syntactically valid!
const string toParse = "'hello' + 1";
var expr = Parser.ParseExpression(toParse);
var visitor = new DepthFirstExpressionVisitor();
visitor.GetExpressions(expr)
.Select(x => x.GetType())
.Should()
.Equal(
typeof(BinaryExpr),
typeof(StringLiteral),
typeof(NumberLiteral)
);
}

[Test]
public void Visit_Complex_Expression()
{
const string toParse = "sum(rate(my_vector[1m] offset 5m)) + -(some_metric[1m:])";
var expr = Parser.ParseExpression(toParse);
var visitor = new DepthFirstExpressionVisitor();
visitor.GetExpressions(expr)
.Select(x => x.GetType())
.Should()
.Equal(
typeof(BinaryExpr),
typeof(AggregateExpr),
typeof(FunctionCall),
typeof(OffsetExpr),
typeof(MatrixSelector),
typeof(UnaryExpr),
typeof(ParenExpression),
typeof(SubqueryExpr),
typeof(VectorSelector)
)
;
}
}
}

0 comments on commit 20ca794

Please sign in to comment.