Skip to content

Commit

Permalink
Reduce state machines in binary expressions (#311)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Apr 21, 2021
1 parent 8b47e75 commit 2d69b13
Show file tree
Hide file tree
Showing 17 changed files with 188 additions and 202 deletions.
14 changes: 14 additions & 0 deletions Fluid.Benchmarks/TagBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class TagBenchmarks
private readonly TestCase _assign;
private readonly TestCase _else;
private readonly TestCase _textSpan;
private readonly TestCase _binaryExpressions;

public TagBenchmarks()
{
Expand All @@ -25,6 +26,7 @@ public TagBenchmarks()
_else = new TestCase("{% if false %}{% else %}SHOWN{% endif %}");
_assign = new TestCase("{% assign something = 'foo' %} {% assign another = 1234 %} {% assign last = something %}");
_textSpan = new TestCase("foo");
_binaryExpressions = new TestCase("{% if 1 == 'elvis' or 0 == 1 or 1 == 2 or 2 < 1 or 4 > 4 or 1 != 1 or 1 >= 2 or 4 <= 2 or 'abc' contains 'd' or 'abc' startswith 'd' or 'abc' endswith 'd' %}TEXT{% endif %}");

_context = new TemplateContext();
}
Expand Down Expand Up @@ -113,6 +115,18 @@ public string TextSpan_Render()
return _textSpan.Render(_context);
}

[Benchmark]
public object BinaryExpressions_Parse()
{
return _binaryExpressions.Parse();
}

[Benchmark]
public string BinaryExpressions_Render()
{
return _binaryExpressions.Render(_context);
}

private sealed class TestCase
{
private readonly string _source;
Expand Down
42 changes: 40 additions & 2 deletions Fluid/Ast/BinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
namespace Fluid.Ast
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Fluid.Values;

namespace Fluid.Ast
{
public abstract class BinaryExpression : Expression
{
Expand All @@ -11,5 +16,38 @@ protected BinaryExpression(Expression left, Expression right)
public Expression Left { get; }

public Expression Right { get; }

/// <summary>
/// Evaluates two operands and tries to avoid state machines.
/// </summary>
public override ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
{
var leftTask = Left.EvaluateAsync(context);
var rightTask = Right.EvaluateAsync(context);

if (leftTask.IsCompletedSuccessfully && rightTask.IsCompletedSuccessfully)
{
return Evaluate(leftTask.Result, rightTask.Result);
}

return Awaited(leftTask, rightTask);
}

[MethodImpl(MethodImplOptions.NoInlining)]
private async ValueTask<FluidValue> Awaited(
ValueTask<FluidValue> leftTask,
ValueTask<FluidValue> rightTask)
{
var leftValue = await leftTask;
var rightValue = await rightTask;

return Evaluate(leftValue, rightValue);
}

// sub-classes using the default implementation need to override this
internal virtual FluidValue Evaluate(FluidValue leftValue, FluidValue rightValue)
{
throw new NotImplementedException();
}
}
}
}
10 changes: 3 additions & 7 deletions Fluid/Ast/BinaryExpressions/AddBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
using System.Threading.Tasks;
using Fluid.Values;
using Fluid.Values;

namespace Fluid.Ast.BinaryExpressions
{
public class AddBinaryExpression : BinaryExpression
public sealed class AddBinaryExpression : BinaryExpression
{
public AddBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
internal override FluidValue Evaluate(FluidValue leftValue, FluidValue rightValue)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

if (leftValue is StringValue)
{
return new StringValue(leftValue.ToStringValue() + rightValue.ToStringValue());
Expand Down
27 changes: 5 additions & 22 deletions Fluid/Ast/BinaryExpressions/AndBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
using System.Threading.Tasks;
using Fluid.Values;
using Fluid.Values;

namespace Fluid.Ast.BinaryExpressions
{
public class AndBinaryExpression : BinaryExpression
public sealed class AndBinaryExpression : BinaryExpression
{
public AndBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

public override ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
internal override FluidValue Evaluate(FluidValue leftValue, FluidValue rightValue)
{
static async ValueTask<FluidValue> Awaited(ValueTask<FluidValue> leftTask, ValueTask<FluidValue> rightTask)
{
var leftValue = await leftTask;
var rightValue = await rightTask;

return BooleanValue.Create(leftValue.ToBooleanValue() && rightValue.ToBooleanValue());
}

var leftTask = Left.EvaluateAsync(context);
var rightTask = Right.EvaluateAsync(context);

if (leftTask.IsCompletedSuccessfully && rightTask.IsCompletedSuccessfully)
{
return BooleanValue.Create(leftTask.Result.ToBooleanValue() && rightTask.Result.ToBooleanValue());
}

return Awaited(leftTask, rightTask);
return BooleanValue.Create(leftValue.ToBooleanValue() && rightValue.ToBooleanValue());
}
}
}
}
16 changes: 6 additions & 10 deletions Fluid/Ast/BinaryExpressions/ContainsBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
using System.Threading.Tasks;
using Fluid.Values;
using Fluid.Values;

namespace Fluid.Ast.BinaryExpressions
{
public class ContainsBinaryExpression : BinaryExpression
public sealed class ContainsBinaryExpression : BinaryExpression
{
public ContainsBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
internal override FluidValue Evaluate(FluidValue leftValue, FluidValue rightValue)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

return leftValue.Contains(rightValue)
? BooleanValue.True
: BooleanValue.False;
? BooleanValue.True
: BooleanValue.False;
}
}
}
}
10 changes: 3 additions & 7 deletions Fluid/Ast/BinaryExpressions/DivideBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
using System.Threading.Tasks;
using Fluid.Values;
using Fluid.Values;

namespace Fluid.Ast.BinaryExpressions
{
public class DivideBinaryExpression : BinaryExpression
public sealed class DivideBinaryExpression : BinaryExpression
{
public DivideBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
internal override FluidValue Evaluate(FluidValue leftValue, FluidValue rightValue)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

if (leftValue is NumberValue && rightValue is NumberValue)
{
var rightNumber = rightValue.ToNumberValue();
Expand Down
2 changes: 1 addition & 1 deletion Fluid/Ast/BinaryExpressions/EndsWithBinaryExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Fluid.Ast.BinaryExpressions
{
public class EndsWithBinaryExpression : BinaryExpression
public sealed class EndsWithBinaryExpression : BinaryExpression
{
public EndsWithBinaryExpression(Expression left, Expression right) : base(left, right)
{
Expand Down
19 changes: 6 additions & 13 deletions Fluid/Ast/BinaryExpressions/EqualBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
using System.Threading.Tasks;
using Fluid.Values;
using Fluid.Values;

namespace Fluid.Ast.BinaryExpressions
{
public class EqualBinaryExpression : BinaryExpression
public sealed class EqualBinaryExpression : BinaryExpression
{
public EqualBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
internal override FluidValue Evaluate(FluidValue leftValue, FluidValue rightValue)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

if (leftValue.Equals(rightValue))
{
return BooleanValue.True;
}

return BooleanValue.False;
return leftValue.Equals(rightValue)
? BooleanValue.True
: BooleanValue.False;
}
}
}
37 changes: 13 additions & 24 deletions Fluid/Ast/BinaryExpressions/GreaterThanBinaryExpression.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System.Threading.Tasks;
using Fluid.Values;
using Fluid.Values;

namespace Fluid.Ast.BinaryExpressions
{
public class GreaterThanBinaryExpression : BinaryExpression
public sealed class GreaterThanBinaryExpression : BinaryExpression
{
public GreaterThanBinaryExpression(Expression left, Expression right, bool strict) : base(left, right)
{
Expand All @@ -12,24 +11,18 @@ public GreaterThanBinaryExpression(Expression left, Expression right, bool stric

public bool Strict { get; }

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
internal override FluidValue Evaluate(FluidValue leftValue, FluidValue rightValue)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

if (leftValue.IsNil() || rightValue.IsNil())
{
if (Strict)
{
return BooleanValue.False;
}
else
{
return leftValue.IsNil() && rightValue.IsNil()
? BooleanValue.True
: BooleanValue.False
;
}

return leftValue.IsNil() && rightValue.IsNil()
? BooleanValue.True
: BooleanValue.False;
}

if (leftValue is NumberValue)
Expand All @@ -38,19 +31,15 @@ public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext contex
{
return leftValue.ToNumberValue() > rightValue.ToNumberValue()
? BooleanValue.True
: BooleanValue.False
;
}
else
{
return leftValue.ToNumberValue() >= rightValue.ToNumberValue()
? BooleanValue.True
: BooleanValue.False
;
: BooleanValue.False;
}

return leftValue.ToNumberValue() >= rightValue.ToNumberValue()
? BooleanValue.True
: BooleanValue.False;
}

return NilValue.Instance;
}
}
}
}
37 changes: 13 additions & 24 deletions Fluid/Ast/BinaryExpressions/LowerThanExpression.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System.Threading.Tasks;
using Fluid.Values;
using Fluid.Values;

namespace Fluid.Ast.BinaryExpressions
{
public class LowerThanExpression : BinaryExpression
public sealed class LowerThanExpression : BinaryExpression
{
public LowerThanExpression(Expression left, Expression right, bool strict) : base(left, right)
{
Expand All @@ -12,24 +11,18 @@ public LowerThanExpression(Expression left, Expression right, bool strict) : bas

public bool Strict { get; }

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
internal override FluidValue Evaluate(FluidValue leftValue, FluidValue rightValue)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);

if (leftValue.IsNil() || rightValue.IsNil())
{
if (Strict)
{
return BooleanValue.False;
}
else
{
return leftValue.IsNil() && rightValue.IsNil()
? BooleanValue.True
: BooleanValue.False
;
}

return leftValue.IsNil() && rightValue.IsNil()
? BooleanValue.True
: BooleanValue.False;
}

if (leftValue is NumberValue)
Expand All @@ -38,19 +31,15 @@ public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext contex
{
return leftValue.ToNumberValue() < rightValue.ToNumberValue()
? BooleanValue.True
: BooleanValue.False
;
}
else
{
return leftValue.ToNumberValue() <= rightValue.ToNumberValue()
? BooleanValue.True
: BooleanValue.False
;
: BooleanValue.False;
}

return leftValue.ToNumberValue() <= rightValue.ToNumberValue()
? BooleanValue.True
: BooleanValue.False;
}

return NilValue.Instance;
}
}
}
}
Loading

0 comments on commit 2d69b13

Please sign in to comment.