From f37f467f42b5813fb10829f4ae2571a76b2bfe15 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 12 Jan 2022 10:43:35 -0800 Subject: [PATCH 1/4] Add test --- Fluid.Tests/ParserTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Fluid.Tests/ParserTests.cs b/Fluid.Tests/ParserTests.cs index d5fb64db..b84a7d3d 100644 --- a/Fluid.Tests/ParserTests.cs +++ b/Fluid.Tests/ParserTests.cs @@ -1029,5 +1029,17 @@ public void ShouldNotParseFunctionCall() Assert.False(parser.TryParse("{{ a() }}", out var template, out var errors)); Assert.Contains(ErrorMessages.FunctionsNotAllowed, errors); } + + [Fact] + public void KeywordsShouldNotConflictWithIdentifiers() + { + // Ensure the parser doesn't read 'empty' when identifiers start with this keywork + // Same for blank, true, false + + var source = "{% assign emptyThing = 'this is not empty' %}{{ emptyThing }}"; + var context = new TemplateContext(); + var template = _parser.Parse(source); + Assert.Equal("this is not empty", template.Render(context)); + } } } From f732e502c3581997901b2a07db285f9e39cb7999 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 12 Jan 2022 10:43:49 -0800 Subject: [PATCH 2/4] Fix keywords conflicts --- Fluid/Ast/MemberExpression.cs | 42 +++++++++++++++++++++++------------ Fluid/FluidParser.cs | 10 +-------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/Fluid/Ast/MemberExpression.cs b/Fluid/Ast/MemberExpression.cs index becb91d6..c3ff2f04 100644 --- a/Fluid/Ast/MemberExpression.cs +++ b/Fluid/Ast/MemberExpression.cs @@ -25,23 +25,37 @@ public override ValueTask EvaluateAsync(TemplateContext context) var initial = Segments[0] as IdentifierSegment; - // Search the initial segment in the local scope first - - FluidValue value = context.LocalScope.GetValue(initial.Identifier); - - // If it was not successful, try again with a member of the model - + FluidValue value = NilValue.Instance; int start = 1; - if (value.IsNil()) + switch (initial.Identifier) { - if (context.Model == null) - { - return new ValueTask(value); - } - - start = 0; - value = context.Model; + case "empty": value = EmptyValue.Instance; break; + case "blank": value = BlankValue.Instance; break; + case "true": value = BooleanValue.True; break; + case "false": value = BooleanValue.False; break; + default: + + // Search the initial segment in the local scope first + + value = context.LocalScope.GetValue(initial.Identifier); + + // If it was not successful, try again with a member of the model + + if (value.IsNil()) + { + if (context.Model == null) + { + return new ValueTask(value); + } + else + { + start = 0; + value = context.Model; + } + } + + break; } for (var i = start; i < Segments.Count; i++) diff --git a/Fluid/FluidParser.cs b/Fluid/FluidParser.cs index f9ff3a07..b0af4b39 100644 --- a/Fluid/FluidParser.cs +++ b/Fluid/FluidParser.cs @@ -35,10 +35,6 @@ public class FluidParser protected static readonly Parser String = Terms.String(StringLiteralQuotes.SingleOrDouble); protected static readonly Parser Number = Terms.Decimal(NumberOptions.AllowSign); - protected static readonly Parser True = Terms.Text("true"); - protected static readonly Parser False = Terms.Text("false"); - protected static readonly Parser Empty = Terms.Text("empty"); - protected static readonly Parser Blank = Terms.Text("blank"); protected static readonly Parser DoubleEquals = Terms.Text("=="); protected static readonly Parser NotEquals = Terms.Text("!="); @@ -116,13 +112,9 @@ public FluidParser(FluidParserOptions parserOptions) .AndSkip(RParen) .Then(x => new RangeExpression(x.Item1, x.Item2)); - // primary => NUMBER | STRING | BOOLEAN | property + // primary => NUMBER | STRING | property Primary.Parser = String.Then(x => new LiteralExpression(StringValue.Create(x))) - .Or(True.Then(x => new LiteralExpression(BooleanValue.True))) - .Or(False.Then(x => new LiteralExpression(BooleanValue.False))) - .Or(Empty.Then(x => new LiteralExpression(EmptyValue.Instance))) - .Or(Blank.Then(x => new LiteralExpression(BlankValue.Instance))) .Or(Member.Then(x => x)) .Or(Number.Then(x => new LiteralExpression(NumberValue.Create(x)))) ; From dc7bb214063e1ef5fc1ce412341737ab1f8f40c0 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 12 Jan 2022 10:53:24 -0800 Subject: [PATCH 3/4] Move keywords logic to parser --- Fluid/Ast/MemberExpression.cs | 42 ++++++++++++----------------------- Fluid/FluidParser.cs | 15 ++++++++++++- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/Fluid/Ast/MemberExpression.cs b/Fluid/Ast/MemberExpression.cs index c3ff2f04..becb91d6 100644 --- a/Fluid/Ast/MemberExpression.cs +++ b/Fluid/Ast/MemberExpression.cs @@ -25,37 +25,23 @@ public override ValueTask EvaluateAsync(TemplateContext context) var initial = Segments[0] as IdentifierSegment; - FluidValue value = NilValue.Instance; + // Search the initial segment in the local scope first + + FluidValue value = context.LocalScope.GetValue(initial.Identifier); + + // If it was not successful, try again with a member of the model + int start = 1; - switch (initial.Identifier) + if (value.IsNil()) { - case "empty": value = EmptyValue.Instance; break; - case "blank": value = BlankValue.Instance; break; - case "true": value = BooleanValue.True; break; - case "false": value = BooleanValue.False; break; - default: - - // Search the initial segment in the local scope first - - value = context.LocalScope.GetValue(initial.Identifier); - - // If it was not successful, try again with a member of the model - - if (value.IsNil()) - { - if (context.Model == null) - { - return new ValueTask(value); - } - else - { - start = 0; - value = context.Model; - } - } - - break; + if (context.Model == null) + { + return new ValueTask(value); + } + + start = 0; + value = context.Model; } for (var i = start; i < Segments.Count; i++) diff --git a/Fluid/FluidParser.cs b/Fluid/FluidParser.cs index b0af4b39..6fa4fd96 100644 --- a/Fluid/FluidParser.cs +++ b/Fluid/FluidParser.cs @@ -115,7 +115,20 @@ public FluidParser(FluidParserOptions parserOptions) // primary => NUMBER | STRING | property Primary.Parser = String.Then(x => new LiteralExpression(StringValue.Create(x))) - .Or(Member.Then(x => x)) + .Or(Member.Then(static x => { + if (x.Segments.Count == 1) + { + switch ((x.Segments[0] as IdentifierSegment).Identifier) + { + case "empty": return new LiteralExpression(EmptyValue.Instance); + case "blank": return new LiteralExpression(BlankValue.Instance); + case "true": return new LiteralExpression(BooleanValue.True); + case "false": return new LiteralExpression(BooleanValue.False); + } + } + + return x; + })) .Or(Number.Then(x => new LiteralExpression(NumberValue.Create(x)))) ; From 435a281dd854dcc740c2b1fe4dd795a149006f0f Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 12 Jan 2022 10:54:45 -0800 Subject: [PATCH 4/4] Add test --- Fluid.Tests/ParserTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Fluid.Tests/ParserTests.cs b/Fluid.Tests/ParserTests.cs index b84a7d3d..ab0e8a91 100644 --- a/Fluid.Tests/ParserTests.cs +++ b/Fluid.Tests/ParserTests.cs @@ -1036,10 +1036,10 @@ public void KeywordsShouldNotConflictWithIdentifiers() // Ensure the parser doesn't read 'empty' when identifiers start with this keywork // Same for blank, true, false - var source = "{% assign emptyThing = 'this is not empty' %}{{ emptyThing }}"; - var context = new TemplateContext(); + var source = "{% assign emptyThing = 'this is not empty' %}{{ emptyThing }}{{ empty.size }}"; + var context = new TemplateContext(new { empty = "eric" }); var template = _parser.Parse(source); - Assert.Equal("this is not empty", template.Render(context)); + Assert.Equal("this is not empty4", template.Render(context)); } } }