Skip to content

Commit

Permalink
Avoid state machines (#303)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Apr 14, 2021
1 parent 41e2705 commit 458b66b
Show file tree
Hide file tree
Showing 16 changed files with 443 additions and 70 deletions.
134 changes: 134 additions & 0 deletions Fluid.Benchmarks/TagBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;

namespace Fluid.Benchmarks
{
[MemoryDiagnoser]
public class TagBenchmarks
{
private static readonly FluidParser _parser = new();
private readonly TemplateContext _context;
private readonly TestCase _rawTag;
private readonly TestCase _ifWithAnds;
private readonly TestCase _ifWithOrs;
private readonly TestCase _elseIf;
private readonly TestCase _assign;
private readonly TestCase _else;
private readonly TestCase _textSpan;

public TagBenchmarks()
{
_rawTag = new TestCase("before {% raw %} {{ TEST 3 }} {% endraw %} after");
_ifWithAnds = new TestCase("{% if true and false and false == false %}HIDDEN{% endif %}");
_ifWithOrs = new TestCase("{% if true == false or false or false %}HIDDEN{% endif %}");
_elseIf = new TestCase("{% if false %}{% elsif true == false or false or false %}HIDDEN{% endif %}");
_else = new TestCase("{% if false %}{% else %}SHOWN{% endif %}");
_assign = new TestCase("{% assign something = 'foo' %} {% assign another = 1234 %} {% assign last = something %}");
_textSpan = new TestCase("foo");

_context = new TemplateContext();
}

[Benchmark]
public object RawTag_Parse()
{
return _rawTag.Parse();
}

[Benchmark]
public string RawTag_Render()
{
return _rawTag.Render(_context);
}

[Benchmark]
public object IfStatement_Ands_Parse()
{
return _ifWithAnds.Parse();
}

[Benchmark]
public string IfStatement_Ands_Render()
{
return _ifWithAnds.Render(_context);
}

[Benchmark]
public object IfStatement_Ors_Parse()
{
return _ifWithOrs.Parse();
}

[Benchmark]
public string IfStatement_Ors_Render()
{
return _ifWithOrs.Render(_context);
}

[Benchmark]
public object ElseIfStatement_Parse()
{
return _elseIf.Parse();
}

[Benchmark]
public string ElseIfStatement_Render()
{
return _elseIf.Render(_context);
}

[Benchmark]
public object Assign_Parse()
{
return _assign.Parse();
}

[Benchmark]
public string Assign_Render()
{
return _assign.Render(_context);
}

[Benchmark]
public object Else_Parse()
{
return _else.Parse();
}

[Benchmark]
public string Else_Render()
{
return _else.Render(_context);
}

[Benchmark]
public object TextSpan_Parse()
{
return _textSpan.Parse();
}

[Benchmark]
public string TextSpan_Render()
{
return _textSpan.Render(_context);
}

private sealed class TestCase
{
private readonly string _source;
private readonly IFluidTemplate _template;

public TestCase(string source)
{
_source = source;
_template = _parser.Parse(source);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string Render(TemplateContext context) => _template.Render(context);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IFluidTemplate Parse() => _parser.Parse(_source);
}
}
}
20 changes: 16 additions & 4 deletions Fluid/Ast/AssignStatement.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Fluid.Values;

namespace Fluid.Ast
{
Expand All @@ -16,14 +17,25 @@ public AssignStatement(string identifier, Expression value)

public Expression Value { get; }

public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
{
static async ValueTask<Completion> Awaited(ValueTask<FluidValue> task, TemplateContext context, string identifier)
{
var value = await task;
context.SetValue(identifier, value);
return Completion.Normal;
}

context.IncrementSteps();

var value = await Value.EvaluateAsync(context);
context.SetValue(Identifier, value);
var task = Value.EvaluateAsync(context);
if (!task.IsCompletedSuccessfully)
{
return Awaited(task, context, Identifier);
}

return Completion.Normal;
context.SetValue(Identifier, task.Result);
return new ValueTask<Completion>(Completion.Normal);
}
}
}
2 changes: 1 addition & 1 deletion Fluid/Ast/BinaryExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
public abstract class BinaryExpression : Expression
{
public BinaryExpression(Expression left, Expression right)
protected BinaryExpression(Expression left, Expression right)
{
Left = left;
Right = right;
Expand Down
21 changes: 17 additions & 4 deletions Fluid/Ast/BinaryExpressions/AndBinaryExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,25 @@ public AndBinaryExpression(Expression left, Expression right) : base(left, right
{
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
public override ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);
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());
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);
}
}
}
21 changes: 17 additions & 4 deletions Fluid/Ast/BinaryExpressions/OrBinaryExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,25 @@ public OrBinaryExpression(Expression left, Expression right) : base(left, right)
{
}

public override async ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
public override ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
{
var leftValue = await Left.EvaluateAsync(context);
var rightValue = await Right.EvaluateAsync(context);
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());
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);
}
}
}
41 changes: 38 additions & 3 deletions Fluid/Ast/ElseIfStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,50 @@ public ElseIfStatement(Expression condition, List<Statement> statements) : base(

public Expression Condition { get; }

public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
{
// Process statements until next block or end of statements
for (var index = 0; index < _statements.Count; index++)
for (var i = 0; i < _statements.Count; i++)
{
context.IncrementSteps();

var completion = await _statements[index].WriteToAsync(writer, encoder, context);
var task = _statements[i].WriteToAsync(writer, encoder, context);
if (!task.IsCompletedSuccessfully)
{
return Awaited(task, i + 1, writer, encoder, context);
}

var completion = task.Result;
if (completion != Completion.Normal)
{
// Stop processing the block statements
// We return the completion to flow it to the outer loop
return new ValueTask<Completion>(completion);
}
}

return new ValueTask<Completion>(Completion.Normal);
}

private async ValueTask<Completion> Awaited(
ValueTask<Completion> task,
int startIndex,
TextWriter writer,
TextEncoder encoder,
TemplateContext context)
{
var completion = await task;
if (completion != Completion.Normal)
{
// Stop processing the block statements
// We return the completion to flow it to the outer loop
return completion;
}
// Process statements until next block or end of statements
for (var index = startIndex; index < _statements.Count; index++)
{
context.IncrementSteps();
completion = await _statements[index].WriteToAsync(writer, encoder, context);
if (completion != Completion.Normal)
{
// Stop processing the block statements
Expand Down
45 changes: 42 additions & 3 deletions Fluid/Ast/ElseStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,53 @@ public ElseStatement(List<Statement> statements) : base(statements)
{
}

public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
{
for (var i = 0; i < _statements.Count; i++)
{
context.IncrementSteps();

var statement = _statements[i];
var completion = await statement.WriteToAsync(writer, encoder, context);
var task = _statements[i].WriteToAsync(writer, encoder, context);

if (!task.IsCompletedSuccessfully)
{
return Awaited(task, i + 1, writer, encoder, context);
}

var completion = task.Result;

if (completion != Completion.Normal)
{
// Stop processing the block statements
// We return the completion to flow it to the outer loop
return new ValueTask<Completion>(completion);
}
}

return new ValueTask<Completion>(Completion.Normal);
}

private async ValueTask<Completion> Awaited(
ValueTask<Completion> task,
int startIndex,
TextWriter writer,
TextEncoder encoder,
TemplateContext context)
{
var completion = await task;

if (completion != Completion.Normal)
{
// Stop processing the block statements
// We return the completion to flow it to the outer loop
return completion;
}

for (var i = startIndex; i < _statements.Count; i++)
{
context.IncrementSteps();

completion = await _statements[i].WriteToAsync(writer, encoder, context);

if (completion != Completion.Normal)
{
Expand Down
Loading

0 comments on commit 458b66b

Please sign in to comment.