Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement offset continue #465

Merged
merged 1 commit into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Fluid.Tests/ForStatementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ public async Task ForEvaluatesMemberOptionsOffsetOnly()

Assert.Equal("54", sw.ToString());
}

[Fact]
public async Task NegativeTargetShouldNotRenderLoop()
{
Expand Down
17 changes: 17 additions & 0 deletions Fluid.Tests/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1041,5 +1041,22 @@ public void KeywordsShouldNotConflictWithIdentifiers()
var template = _parser.Parse(source);
Assert.Equal("this is not empty4", template.Render(context));
}

[Fact]
public void ShouldContinueForLoop()
{

var source = @"
{%- assign array = (1..6) %}
{%- for item in array limit: 3 %}
{{- item}}
{%- endfor %}
{%- for item in array offset: continue limit: 2 %}
{{- item}}
{%- endfor %}";

var template = _parser.Parse(source);
Assert.Equal("12345", template.Render());
}
}
}
73 changes: 28 additions & 45 deletions Fluid/Ast/ForStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,73 +10,43 @@ namespace Fluid.Ast
{
public class ForStatement : TagStatement
{
public ForStatement(
List<Statement> statements,
string identifier,
MemberExpression member,
Expression limit,
Expression offset,
bool reversed,
ElseStatement elseStatement = null
) : base(statements)
{
Identifier = identifier;
Member = member;
Limit = limit;
Offset = offset;
Reversed = reversed;
Else = elseStatement;
}
private bool _isContinueOffset;
private string _continueOffsetLiteral;

public ForStatement(
List<Statement> statements,
string identifier,
RangeExpression range,
Expression source,
Expression limit,
Expression offset,
bool reversed,
ElseStatement elseStatement = null
) : base(statements)
{
Identifier = identifier;
Range = range;
Source = source;
Limit = limit;
Offset = offset;
Reversed = reversed;
Else = elseStatement;

_isContinueOffset = Offset is MemberExpression l && l.Segments.Count == 1 && ((IdentifierSegment)l.Segments[0]).Identifier == "continue";
_continueOffsetLiteral = source is MemberExpression m ? "for_continue_" + ((IdentifierSegment)m.Segments[0]).Identifier : null;
}

public string Identifier { get; }
public RangeExpression Range { get; }
public MemberExpression Member { get; }
public Expression Source { get; }
public Expression Limit { get; }
public Expression Offset { get; }
public bool Reversed { get; }
public Statement Else { get; }

public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
{
List<FluidValue> list = null;

if (Member != null)
{
var member = await Member.EvaluateAsync(context);
list = member.Enumerate(context).ToList();
}
else if (Range != null)
{
var start = Convert.ToInt32((await Range.From.EvaluateAsync(context)).ToNumberValue());
var end = Convert.ToInt32((await Range.To.EvaluateAsync(context)).ToNumberValue());
var source = (await Source.EvaluateAsync(context)).Enumerate(context).ToList();

list = new List<FluidValue>(Math.Max(1, end - start));

for (var i = start; i <= end; i++)
{
list.Add(NumberValue.Create(i));
}
}

if (list is null || list.Count == 0)
if (source.Count == 0)
{
if (Else != null)
{
Expand All @@ -90,11 +60,19 @@ public override async ValueTask<Completion> WriteToAsync(TextWriter writer, Text
var startIndex = 0;
if (Offset is not null)
{
var offset = (int) (await Offset.EvaluateAsync(context)).ToNumberValue();
startIndex = offset;
if (_isContinueOffset)
{
startIndex = (int) context.GetValue(_continueOffsetLiteral).ToNumberValue();
}
else
{
var offset = (int)(await Offset.EvaluateAsync(context)).ToNumberValue();
startIndex = offset;
}
}

var count = Math.Max(0, list.Count - startIndex);
var count = Math.Max(0, source.Count - startIndex);

if (Limit is not null)
{
var limit = (int) (await Limit.EvaluateAsync(context)).ToNumberValue();
Expand Down Expand Up @@ -122,7 +100,7 @@ public override async ValueTask<Completion> WriteToAsync(TextWriter writer, Text

if (Reversed)
{
list.Reverse(startIndex, count);
source.Reverse(startIndex, count);
}

context.EnterForLoopScope();
Expand All @@ -139,7 +117,7 @@ public override async ValueTask<Completion> WriteToAsync(TextWriter writer, Text
{
context.IncrementSteps();

var item = list[i];
var item = source[i];

context.LocalScope._properties[Identifier] = item;

Expand All @@ -151,6 +129,11 @@ public override async ValueTask<Completion> WriteToAsync(TextWriter writer, Text
forloop.First = i == 0;
forloop.Last = i == length - 1;

if (_continueOffsetLiteral != null)
{
context.SetValue(_continueOffsetLiteral, forloop.Index);
}

Completion completion = Completion.Normal;

for (var index = 0; index < _statements.Count; index++)
Expand Down
48 changes: 46 additions & 2 deletions Fluid/Ast/RangeExpression.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Fluid.Values;
using Fluid.Ast.BinaryExpressions;
using Fluid.Values;
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace Fluid.Ast
Expand All @@ -17,7 +20,48 @@ public RangeExpression(Expression from, Expression to)

public override ValueTask<FluidValue> EvaluateAsync(TemplateContext context)
{
throw new System.NotImplementedException();
int start, end;

var startTask = From.EvaluateAsync(context);
var endTask = To.EvaluateAsync(context);

if (startTask.IsCompletedSuccessfully && endTask.IsCompletedSuccessfully)
{
start = Convert.ToInt32(startTask.Result.ToNumberValue());
end = Convert.ToInt32(endTask.Result.ToNumberValue());

// If end < start, we create an empty array
var list = new FluidValue[Math.Max(0, end - start + 1)];

for (var i = 0; i < list.Length; i++)
{
list[i] = NumberValue.Create(start + i);
}

return new ArrayValue(list);
}
else
{
return Awaited(startTask, endTask);
}
}

[MethodImpl(MethodImplOptions.NoInlining)]
private async ValueTask<FluidValue> Awaited(
ValueTask<FluidValue> leftTask,
ValueTask<FluidValue> rightTask)
{
var start = Convert.ToInt32((await leftTask).ToNumberValue());
var end = Convert.ToInt32((await rightTask).ToNumberValue());

var list = new FluidValue[Math.Max(1, end - start)];

for (var i = start; i <= end; i++)
{
list[i] = NumberValue.Create(i);
}

return new ArrayValue(list);
}
}
}
3 changes: 2 additions & 1 deletion Fluid/FluidParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public FluidParser(FluidParserOptions parserOptions)
.AndSkip(Terms.Text(".."))
.And(OneOf(Integer, Member.Then<Expression>(x => x)))
.AndSkip(RParen)
.Then(x => new RangeExpression(x.Item1, x.Item2));
.Then<Expression>(x => new RangeExpression(x.Item1, x.Item2));

// primary => NUMBER | STRING | property
Primary.Parser =
Expand All @@ -135,6 +135,7 @@ public FluidParser(FluidParserOptions parserOptions)
return x;
}))
.Or(Number.Then<Expression>(x => new LiteralExpression(NumberValue.Create(x))))
.Or(Range)
;

RegisteredOperators["contains"] = (a, b) => new ContainsBinaryExpression(a, b);
Expand Down