Skip to content

Commit

Permalink
Improve TextSpan and For statements (#668)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored May 30, 2024
1 parent 0291471 commit 4f8c5c9
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 90 deletions.
6 changes: 3 additions & 3 deletions Fluid/Ast/ForStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ namespace Fluid.Ast
{
public sealed class ForStatement : TagStatement
{
private readonly bool _isContinueOffset;
private readonly string _continueOffsetLiteral;

public ForStatement(
Expand All @@ -25,7 +24,7 @@ public ForStatement(
Reversed = reversed;
Else = elseStatement;

_isContinueOffset = Offset is MemberExpression l && l.Segments.Count == 1 && ((IdentifierSegment)l.Segments[0]).Identifier == "continue";
OffsetIsContinue = 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;
}

Expand All @@ -36,6 +35,7 @@ public ForStatement(
public Expression Offset { get; }
public bool Reversed { get; }
public ElseStatement Else { get; }
public bool OffsetIsContinue { get; }

public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
{
Expand All @@ -55,7 +55,7 @@ public override async ValueTask<Completion> WriteToAsync(TextWriter writer, Text
var startIndex = 0;
if (Offset is not null)
{
if (_isContinueOffset)
if (OffsetIsContinue)
{
startIndex = (int)context.GetValue(_continueOffsetLiteral).ToNumberValue();
}
Expand Down
182 changes: 96 additions & 86 deletions Fluid/Ast/TextSpanStatement.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
using Parlot;
using Fluid.Utils;
using Parlot;
using System.Text.Encodings.Web;
using Fluid.Utils;

namespace Fluid.Ast
{
public sealed class TextSpanStatement : Statement
{
private bool _isStripped;
private bool _isEmpty;
private bool _isBufferPrepared;
private readonly object _synLock = new();
private TextSpan _text;
private string _buffer;
internal string _preparedBuffer;

public TextSpanStatement(in TextSpan text)
{
Expand All @@ -32,115 +31,128 @@ public TextSpanStatement(string text)

public ref readonly TextSpan Text => ref _text;

public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
public string Buffer => _preparedBuffer;

public void PrepareBuffer(TemplateOptions options)
{
if (!_isStripped)
if (_isBufferPrepared)
{
return;
}

// Prevent two threads from stripping the same statement in case WriteToAsync is called concurrently
lock (_synLock)
{
// Prevent two threads from strsipping the same statement in case WriteToAsync is called concurrently
//
lock (_synLock)
if (!_isBufferPrepared)
{
if (!_isStripped)
var trimming = options.Trimming;
StripLeft |=
(PreviousIsTag && (trimming & TrimmingFlags.TagRight) != 0) ||
(PreviousIsOutput && (trimming & TrimmingFlags.OutputRight) != 0)
;

StripRight |=
(NextIsTag && (trimming & TrimmingFlags.TagLeft) != 0) ||
(NextIsOutput && (trimming & TrimmingFlags.OutputLeft) != 0)
;

var span = _text.Buffer;
var start = 0;
var end = _text.Length - 1;

// Does this text need to have its left part trimmed?
if (StripLeft)
{
var trimming = context.Options.Trimming;
StripLeft |=
(PreviousIsTag && (trimming & TrimmingFlags.TagRight) != 0) ||
(PreviousIsOutput && (trimming & TrimmingFlags.OutputRight) != 0)
;

StripRight |=
(NextIsTag && (trimming & TrimmingFlags.TagLeft) != 0) ||
(NextIsOutput && (trimming & TrimmingFlags.OutputLeft) != 0)
;

var span = _text.Buffer;
var start = 0;
var end = _text.Length - 1;

// Does this text need to have its left part trimmed?
if (StripLeft)
var firstNewLine = -1;

for (var i = start; i <= end; i++)
{
var firstNewLine = -1;
var c = span[_text.Offset + i];

for (var i = start; i <= end; i++)
if (Character.IsWhiteSpaceOrNewLine(c))
{
var c = span[_text.Offset + i];
start++;

if (Character.IsWhiteSpaceOrNewLine(c))
if (firstNewLine == -1 && (c == '\n'))
{
start++;

if (firstNewLine == -1 && (c == '\n'))
{
firstNewLine = start;
}
}
else
{
break;
firstNewLine = start;
}
}

if (!context.Options.Greedy)
else
{
if (firstNewLine != -1)
{
start = firstNewLine;
}
break;
}
}

// Does this text need to have its right part trimmed?
if (StripRight)
if (!options.Greedy)
{
var lastNewLine = -1;

for (var i = end; i >= start; i--)
if (firstNewLine != -1)
{
var c = span[_text.Offset + i];
start = firstNewLine;
}
}
}

if (Character.IsWhiteSpaceOrNewLine(c))
{
if (lastNewLine == -1 && c == '\n')
{
lastNewLine = end;
}
// Does this text need to have its right part trimmed?
if (StripRight)
{
var lastNewLine = -1;

end--;
}
else
{
break;
}
}
for (var i = end; i >= start; i--)
{
var c = span[_text.Offset + i];

if (!context.Options.Greedy)
if (Character.IsWhiteSpaceOrNewLine(c))
{
if (lastNewLine != -1)
if (lastNewLine == -1 && c == '\n')
{
end = lastNewLine;
lastNewLine = end;
}

end--;
}
else
{
break;
}
}
if (end - start + 1 == 0)
{
_isEmpty = true;
}
else if (start != 0 || end != _text.Length - 1)
{
var offset = _text.Offset;
var buffer = _text.Buffer;

_text = new TextSpan(buffer, offset + start, end - start + 1);
if (!options.Greedy)
{
if (lastNewLine != -1)
{
end = lastNewLine;
}
}
}
if (end - start + 1 == 0)
{
_text = "";
}
else if (start != 0 || end != _text.Length - 1)
{
var offset = _text.Offset;
var buffer = _text.Buffer;

_buffer = _text.ToString();
_isStripped = true;
_text = new TextSpan(buffer, offset + start, end - start + 1);
}

_preparedBuffer = _text.ToString();
_isBufferPrepared = true;
}
}
}

protected internal override Statement Accept(AstVisitor visitor) => visitor.VisitTextSpanStatement(this);

if (_isEmpty)
public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
{
if (!_isBufferPrepared)
{
PrepareBuffer(context.Options);
}

if (_preparedBuffer == "")
{
return new ValueTask<Completion>(Completion.Normal);
}
Expand All @@ -157,15 +169,13 @@ static async ValueTask<Completion> Awaited(Task task)
return Completion.Normal;
}

var task = writer.WriteAsync(_buffer);
var task = writer.WriteAsync(_preparedBuffer);
if (!task.IsCompletedSuccessfully())
{
return Awaited(task);
}

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

protected internal override Statement Accept(AstVisitor visitor) => visitor.VisitTextSpanStatement(this);
}
}
}
1 change: 1 addition & 0 deletions Fluid/Values/FluidValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public enum FluidValues
Object = 7,
String = 8,
DateTime = 9,
Function = 10,
}
}
2 changes: 1 addition & 1 deletion Fluid/Values/FunctionValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public FunctionValue(Func<FunctionArguments, TemplateContext, FluidValue> action
_action = (args, c) => new ValueTask<FluidValue>(action(args, c));
}

public override FluidValues Type => FluidValues.Object;
public override FluidValues Type => FluidValues.Function;

public override ValueTask<FluidValue> InvokeAsync(FunctionArguments arguments, TemplateContext context)
{
Expand Down

0 comments on commit 4f8c5c9

Please sign in to comment.