From 0fd6f67b49727cb32679b35ce8f14f4a5598933a Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 13 Dec 2024 19:26:03 -0800 Subject: [PATCH] Improve expression trees --- src/Parlot/Compilation/CompilationResult.cs | 2 +- src/Parlot/Fluent/Deferred.cs | 11 ++-- src/Parlot/Fluent/If.cs | 1 - src/Parlot/Fluent/NumberLiteral.cs | 20 ++++++- src/Parlot/Fluent/NumberLiteralBase.cs | 58 ++++++++++++------- src/Parlot/Fluent/Parser.Compile.cs | 9 ++- .../Calc/FluentParserCompiledTests.cs | 14 +++++ test/Parlot.Tests/CompileTests.cs | 3 + 8 files changed, 81 insertions(+), 37 deletions(-) create mode 100644 test/Parlot.Tests/Calc/FluentParserCompiledTests.cs diff --git a/src/Parlot/Compilation/CompilationResult.cs b/src/Parlot/Compilation/CompilationResult.cs index 58b0814c..da6ce723 100644 --- a/src/Parlot/Compilation/CompilationResult.cs +++ b/src/Parlot/Compilation/CompilationResult.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq.Expressions; diff --git a/src/Parlot/Fluent/Deferred.cs b/src/Parlot/Fluent/Deferred.cs index 5e3a7197..e7e723a3 100644 --- a/src/Parlot/Fluent/Deferred.cs +++ b/src/Parlot/Fluent/Deferred.cs @@ -104,14 +104,15 @@ public CompilationResult Compile(CompilationContext context) type: typeof(ValueTuple), variables: parserCompileResult.Variables.Append(resultExpression), Expression.Block(parserCompileResult.Body), - Expression.Assign(resultExpression, Expression.New( - typeof(ValueTuple).GetConstructor([typeof(bool), typeof(T)])!, - parserCompileResult.Success, - context.DiscardResult ? Expression.Default(parserCompileResult.Value.Type) : parserCompileResult.Value)), + Expression.Assign(Expression.Field(resultExpression, nameof(ValueTuple.Item1)), parserCompileResult.Success), + context.DiscardResult + ? Expression.Empty() + : Expression.Assign(Expression.Field(resultExpression, nameof(ValueTuple.Item2)), parserCompileResult.Value), returnExpression, returnLabel), + name: $"Deferred_{context.NextNumber}", true, - context.ParseContext + [context.ParseContext] ); // Store the source lambda for debugging diff --git a/src/Parlot/Fluent/If.cs b/src/Parlot/Fluent/If.cs index efef27ad..84f30f71 100644 --- a/src/Parlot/Fluent/If.cs +++ b/src/Parlot/Fluent/If.cs @@ -76,7 +76,6 @@ public CompilationResult Compile(CompilationContext context) // var start = context.DeclarePositionVariable(result); -// parserCompileResult.Success var block = Expression.Block( Expression.IfThen( diff --git a/src/Parlot/Fluent/NumberLiteral.cs b/src/Parlot/Fluent/NumberLiteral.cs index 9791dc06..60efb0ef 100644 --- a/src/Parlot/Fluent/NumberLiteral.cs +++ b/src/Parlot/Fluent/NumberLiteral.cs @@ -38,8 +38,8 @@ public NumberLiteral(NumberOptions numberOptions = NumberOptions.Number, char de _groupSeparator = groupSeparator; _numberStyles = numberOptions.ToNumberStyles(); - if (decimalSeparator != NumberLiterals.DefaultDecimalSeparator || - groupSeparator != NumberLiterals.DefaultGroupSeparator) + if (_decimalSeparator != NumberLiterals.DefaultDecimalSeparator || + _groupSeparator != NumberLiterals.DefaultGroupSeparator) { _culture = (CultureInfo)CultureInfo.InvariantCulture.Clone(); _culture.NumberFormat.NumberDecimalSeparator = decimalSeparator.ToString(); @@ -107,11 +107,25 @@ public CompilationResult Compile(CompilationContext context) var reset = context.DeclarePositionVariable(result); + // Automatically converted to e.g., numberStyles16 = NumberStyles.AllowLeadingSign|NumberStyles.AllowDecimalPoint|NumberStyles.AllowExponent; var numberStyles = result.DeclareVariable($"numberStyles{context.NextNumber}", Expression.Constant(_numberStyles)); - var culture = result.DeclareVariable($"culture{context.NextNumber}", Expression.Constant(_culture)); + var culture = result.DeclareVariable($"culture{context.NextNumber}"); var numberSpan = result.DeclareVariable($"number{context.NextNumber}", typeof(ReadOnlySpan)); var end = result.DeclareVariable($"end{context.NextNumber}"); + if (_decimalSeparator != NumberLiterals.DefaultDecimalSeparator || _groupSeparator != NumberLiterals.DefaultGroupSeparator) + { + // culture = CultureInfo.InvariantCulture.Clone(); + result.Body.Add(Expression.Assign(culture, Expression.Convert(Expression.Call(Expression.Property(null, typeof(CultureInfo), nameof(CultureInfo.InvariantCulture)), methodName: nameof(CultureInfo.Clone), Array.Empty()), typeof(CultureInfo)))); + result.Body.Add(Expression.Assign(Expression.Property(Expression.Property(culture, nameof(CultureInfo.NumberFormat)), nameof(NumberFormatInfo.NumberDecimalSeparator)), Expression.Constant(_decimalSeparator.ToString()))); + result.Body.Add(Expression.Assign(Expression.Property(Expression.Property(culture, nameof(CultureInfo.NumberFormat)), nameof(NumberFormatInfo.NumberGroupSeparator)), Expression.Constant(_groupSeparator.ToString()))); + } + else + { + // culture = CultureInfo.InvariantCulture; + result.Body.Add(Expression.Assign(culture, Expression.Property(null, typeof(CultureInfo), nameof(CultureInfo.InvariantCulture)))); + } + // if (context.Scanner.ReadDecimal(_numberOptions, out var numberSpan, _decimalSeparator, _groupSeparator)) // { // var end = context.Scanner.Cursor.Offset; diff --git a/src/Parlot/Fluent/NumberLiteralBase.cs b/src/Parlot/Fluent/NumberLiteralBase.cs index 4301db26..6a7fdc0b 100644 --- a/src/Parlot/Fluent/NumberLiteralBase.cs +++ b/src/Parlot/Fluent/NumberLiteralBase.cs @@ -41,8 +41,8 @@ public NumberLiteralBase(NumberOptions numberOptions = NumberOptions.Number, cha _tryParseMethodInfo = tryParseMethodInfo ?? _defaultTryParseMethodInfo; _numberStyles = numberOptions.ToNumberStyles(); - if (decimalSeparator != NumberLiterals.DefaultDecimalSeparator || - groupSeparator != NumberLiterals.DefaultGroupSeparator) + if (_decimalSeparator != NumberLiterals.DefaultDecimalSeparator || + _groupSeparator != NumberLiterals.DefaultGroupSeparator) { _culture = (CultureInfo)CultureInfo.InvariantCulture.Clone(); _culture.NumberFormat.NumberDecimalSeparator = decimalSeparator.ToString(); @@ -110,11 +110,25 @@ public CompilationResult Compile(CompilationContext context) var reset = context.DeclarePositionVariable(result); + // Automatically converted to e.g., numberStyles16 = NumberStyles.AllowLeadingSign|NumberStyles.AllowDecimalPoint|NumberStyles.AllowExponent; var numberStyles = result.DeclareVariable($"numberStyles{context.NextNumber}", Expression.Constant(_numberStyles)); - var culture = result.DeclareVariable($"culture{context.NextNumber}", Expression.Constant(_culture)); + var culture = result.DeclareVariable($"culture{context.NextNumber}"); var numberSpan = result.DeclareVariable($"number{context.NextNumber}", typeof(ReadOnlySpan)); var end = result.DeclareVariable($"end{context.NextNumber}"); + if (_decimalSeparator != NumberLiterals.DefaultDecimalSeparator || _groupSeparator != NumberLiterals.DefaultGroupSeparator) + { + // culture = CultureInfo.InvariantCulture.Clone(); + result.Body.Add(Expression.Assign(culture, Expression.Convert(Expression.Call(Expression.Property(null, typeof(CultureInfo), nameof(CultureInfo.InvariantCulture)), methodName: nameof(CultureInfo.Clone), Array.Empty()), typeof(CultureInfo)))); + result.Body.Add(Expression.Assign(Expression.Property(Expression.Property(culture, nameof(CultureInfo.NumberFormat)), nameof(NumberFormatInfo.NumberDecimalSeparator)), Expression.Constant(_decimalSeparator.ToString()))); + result.Body.Add(Expression.Assign(Expression.Property(Expression.Property(culture, nameof(CultureInfo.NumberFormat)), nameof(NumberFormatInfo.NumberGroupSeparator)), Expression.Constant(_groupSeparator.ToString()))); + } + else + { + // culture = CultureInfo.InvariantCulture; + result.Body.Add(Expression.Assign(culture, Expression.Property(null, typeof(CultureInfo), nameof(CultureInfo.InvariantCulture)))); + } + // if (context.Scanner.ReadDecimal(_numberOptions, out var numberSpan, _decimalSeparator, _groupSeparator)) // { // var end = context.Scanner.Cursor.Offset; @@ -129,25 +143,25 @@ public CompilationResult Compile(CompilationContext context) // var block = - Expression.IfThen( - context.ReadDecimal(Expression.Constant(_allowLeadingSign), - Expression.Constant(_allowDecimalSeparator), - Expression.Constant(_allowGroupSeparator), - Expression.Constant(_allowExponent), - numberSpan, Expression.Constant(_decimalSeparator), Expression.Constant(_groupSeparator)), - Expression.Block( - Expression.Assign(end, context.Offset()), - Expression.Assign(result.Success, - Expression.Call( - _tryParseMethodInfo, - // This class is only used before NET7.0, when there is no overload for TryParse that takes a ReadOnlySpan - Expression.Call(numberSpan, ExpressionHelper.ReadOnlySpan_ToString), - numberStyles, - culture, - result.Value) - ) - ) - ); + Expression.IfThen( + context.ReadDecimal(Expression.Constant(_allowLeadingSign), + Expression.Constant(_allowDecimalSeparator), + Expression.Constant(_allowGroupSeparator), + Expression.Constant(_allowExponent), + numberSpan, Expression.Constant(_decimalSeparator), Expression.Constant(_groupSeparator)), + Expression.Block( + Expression.Assign(end, context.Offset()), + Expression.Assign(result.Success, + Expression.Call( + _tryParseMethodInfo, + // This class is only used before NET7.0, when there is no overload for TryParse that takes a ReadOnlySpan + Expression.Call(numberSpan, ExpressionHelper.ReadOnlySpan_ToString), + numberStyles, + culture, + result.Value) + ) + ) + ); result.Body.Add(block); diff --git a/src/Parlot/Fluent/Parser.Compile.cs b/src/Parlot/Fluent/Parser.Compile.cs index 81bfd73c..0bfa3162 100644 --- a/src/Parlot/Fluent/Parser.Compile.cs +++ b/src/Parlot/Fluent/Parser.Compile.cs @@ -3,14 +3,11 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using System.Reflection; namespace Parlot.Fluent; public abstract partial class Parser { - private static readonly ConstructorInfo _valueTupleConstructor = typeof(ValueTuple).GetConstructor([typeof(bool), typeof(T)])!; - /// /// Compiles the current parser. /// @@ -38,7 +35,8 @@ public Parser Compile() compilationResult.Variables.Add(resultExpression); compilationResult.Body.Add( Expression.Block( - Expression.Assign(resultExpression, Expression.New(_valueTupleConstructor, compilationResult.Success, compilationResult.Value)), + Expression.Assign(Expression.Field(resultExpression, nameof(ValueTuple.Item1)), compilationResult.Success), + Expression.Assign(Expression.Field(resultExpression, nameof(ValueTuple.Item2)), compilationResult.Value), returnExpression, returnLabel ) @@ -58,6 +56,7 @@ public Parser Compile() var allExpressions = new List(); allExpressions.AddRange(compilationContext.GlobalExpressions); + //allExpressions.AddRange(compilationContext.Lambdas); allExpressions.AddRange(compilationResult.Body); var body = Expression.Block( @@ -66,7 +65,7 @@ public Parser Compile() allExpressions ); - var result = Expression.Lambda>>(body, compilationContext.ParseContext); + var result = Expression.Lambda>>(body, name: $"Parse_{compilationContext.NextNumber}", parameters: [compilationContext.ParseContext]); // In Debug mode, inspect the generated code with // result.ToCSharpString(); diff --git a/test/Parlot.Tests/Calc/FluentParserCompiledTests.cs b/test/Parlot.Tests/Calc/FluentParserCompiledTests.cs new file mode 100644 index 00000000..22be7213 --- /dev/null +++ b/test/Parlot.Tests/Calc/FluentParserCompiledTests.cs @@ -0,0 +1,14 @@ +using Parlot.Fluent; + +namespace Parlot.Tests.Calc; + +public class FluentParserCompiledTests : CalcTests +{ + static Parser _compiled = FluentParser.Expression.Compile(); + + protected override decimal Evaluate(string text) + { + _compiled.TryParse(text, out var expression); + return expression.Evaluate(); + } +} diff --git a/test/Parlot.Tests/CompileTests.cs b/test/Parlot.Tests/CompileTests.cs index c024a61b..016df1d8 100644 --- a/test/Parlot.Tests/CompileTests.cs +++ b/test/Parlot.Tests/CompileTests.cs @@ -461,6 +461,9 @@ public void CompiledWhenShouldResetPositionWhenFalse() [Fact] public void CompiledIfShouldNotInvokeParserWhenFalse() { + // Add a test to ensure state can be a custom object (Expression.Constant might not work with that) + Assert.False(true); + bool invoked = false; var evenState = If(predicate: (context, x) => x % 2 == 0, state: 0, parser: Literals.Integer().Then(x => invoked = true)).Compile();