From e15b211cc53425c1d92389b34e5ef1bb82c6dcf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Thu, 11 Jul 2024 08:30:36 -0700 Subject: [PATCH] ReadOnlySpan, CharMap, nullable and new Compilation helpers (#138) --- Directory.Packages.props | 2 +- NuGet.config | 5 +- Parlot.sln | 3 +- src/Parlot/CharMap.cs | 89 ++++++++++ src/Parlot/Character.cs | 16 +- src/Parlot/Compilation/CompilationContext.cs | 39 +++++ src/Parlot/Compilation/CompilationResult.cs | 29 +++- src/Parlot/Compilation/ExpressionHelper.cs | 98 ++++------- src/Parlot/Cursor.cs | 52 ++---- src/Parlot/Fluent/Always.cs | 12 +- src/Parlot/Fluent/Between.cs | 11 +- src/Parlot/Fluent/Capture.cs | 9 +- src/Parlot/Fluent/CharLiteral.cs | 9 +- src/Parlot/Fluent/Deferred.cs | 21 +-- src/Parlot/Fluent/Discard.cs | 13 +- src/Parlot/Fluent/Else.cs | 9 +- src/Parlot/Fluent/Eof.cs | 10 +- src/Parlot/Fluent/Error.cs | 19 +-- src/Parlot/Fluent/Identifier.cs | 25 +-- src/Parlot/Fluent/NonWhiteSpaceLiteral.cs | 9 +- src/Parlot/Fluent/Not.cs | 7 +- src/Parlot/Fluent/NumberLiteral.cs | 49 ++++-- src/Parlot/Fluent/NumberLiteralBase.cs | 25 ++- src/Parlot/Fluent/NumberLiterals.cs | 26 +-- src/Parlot/Fluent/OneOf.ABT.cs | 13 +- src/Parlot/Fluent/OneOf.cs | 162 +++++++++++++++---- src/Parlot/Fluent/OneOrMany.cs | 11 +- src/Parlot/Fluent/ParseContext.cs | 4 +- src/Parlot/Fluent/Parser.Compile.cs | 15 +- src/Parlot/Fluent/Parser.TryParse.cs | 14 +- src/Parlot/Fluent/Parser.cs | 2 +- src/Parlot/Fluent/Parsers.And.cs | 32 ++-- src/Parlot/Fluent/Parsers.AndSkip.cs | 42 +++-- src/Parlot/Fluent/Parsers.OneOf.cs | 6 +- src/Parlot/Fluent/Parsers.SkipAnd.cs | 34 ++-- src/Parlot/Fluent/Parsers.Structure.cs | 28 ++-- src/Parlot/Fluent/Parsers.cs | 23 +-- src/Parlot/Fluent/PatternLiteral.cs | 9 +- src/Parlot/Fluent/Separated.cs | 34 ++-- src/Parlot/Fluent/SequenceAndSkip.cs | 118 ++++++++++++-- src/Parlot/Fluent/SequenceCompileHelper.cs | 15 +- src/Parlot/Fluent/SequenceSkipAnd.cs | 115 +++++++++++-- src/Parlot/Fluent/SkipWhiteSpace.cs | 43 +++-- src/Parlot/Fluent/StringLiteral.cs | 16 +- src/Parlot/Fluent/Switch.cs | 13 +- src/Parlot/Fluent/TextBefore.cs | 13 +- src/Parlot/Fluent/TextLiteral.cs | 15 +- src/Parlot/Fluent/Then.cs | 34 ++-- src/Parlot/Fluent/When.cs | 9 +- src/Parlot/Fluent/WhiteSpaceLiteral.cs | 9 +- src/Parlot/Fluent/ZeroOrMany.cs | 22 +-- src/Parlot/Fluent/ZeroOrOne.cs | 11 +- src/Parlot/Parlot.csproj | 3 +- src/Parlot/ParseError.cs | 2 +- src/Parlot/Scanner.cs | 106 +++++++++++- src/Parlot/TextSpan.cs | 37 ++++- src/Parlot/ThrowHelper.cs | 18 --- test/Parlot.Benchmarks/ParlotBenchmarks.cs | 21 +++ test/Parlot.Tests/BenchmarksTests.cs | 2 +- test/Parlot.Tests/Calc/FluentParserTests.cs | 2 - test/Parlot.Tests/CompileTests.cs | 19 ++- test/Parlot.Tests/CursorTests.cs | 2 - test/Parlot.Tests/FluentTests.cs | 1 - test/Parlot.Tests/Json/JsonParserTests.cs | 1 - test/Parlot.Tests/RewriteTests.cs | 12 -- 65 files changed, 1055 insertions(+), 620 deletions(-) create mode 100644 src/Parlot/CharMap.cs delete mode 100644 src/Parlot/ThrowHelper.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index f66e9a8..9f68705 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ - + diff --git a/NuGet.config b/NuGet.config index 128d95e..7647d1f 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,4 +1,4 @@ - + @@ -8,5 +8,8 @@ + + + diff --git a/Parlot.sln b/Parlot.sln index 18d3077..df1f156 100644 --- a/Parlot.sln +++ b/Parlot.sln @@ -18,11 +18,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig .github\workflows\build.yml = .github\workflows\build.yml Directory.Packages.props = Directory.Packages.props + NuGet.config = NuGet.config .github\workflows\publish.yml = .github\workflows\publish.yml README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "src\Samples\Samples.csproj", "{B9A796FE-4BEB-499A-B506-25F20C749527}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples", "src\Samples\Samples.csproj", "{B9A796FE-4BEB-499A-B506-25F20C749527}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Parlot/CharMap.cs b/src/Parlot/CharMap.cs new file mode 100644 index 0000000..5ceb6ee --- /dev/null +++ b/src/Parlot/CharMap.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Parlot; + +internal sealed class CharMap where T : class +{ + private readonly T[] _asciiMap = new T[128]; + private Dictionary? _nonAsciiMap; + + public CharMap() + { + ExpectedChars = Array.Empty(); + } + + public CharMap(IEnumerable> map) + { + var charSet = new HashSet(); + + foreach (var item in map) + { + charSet.Add(item.Key); + } + + ExpectedChars = [.. charSet]; + Array.Sort(ExpectedChars); + + foreach (var item in map) + { + var c = item.Key; + if (c < 128) + { + _asciiMap[c] ??= item.Value; + } + else + { + _nonAsciiMap ??= []; + + if (!_nonAsciiMap.ContainsKey(c)) + { + _nonAsciiMap[c] = item.Value; + } + } + } + } + + public void Set(char c, T value) + { + ExpectedChars = new HashSet([c, .. ExpectedChars]).ToArray(); + Array.Sort(ExpectedChars); + + if (c < 128) + { + _asciiMap[c] ??= value; + } + else + { + _nonAsciiMap ??= []; + + if (!_nonAsciiMap.ContainsKey(c)) + { + _nonAsciiMap[c] = value; + } + } + } + + public char[] ExpectedChars { get; private set; } + + public T? this[uint c] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + T[] asciiMap = _asciiMap; + if (c < (uint)asciiMap.Length) + { + return asciiMap[c]; + } + else + { + T? map = null; + _nonAsciiMap?.TryGetValue(c, out map); + return map; + } + } + } +} diff --git a/src/Parlot/Character.cs b/src/Parlot/Character.cs index 082fd1b..2604c68 100644 --- a/src/Parlot/Character.cs +++ b/src/Parlot/Character.cs @@ -38,18 +38,6 @@ public static bool IsWhiteSpace(char ch) return (_characterData[ch] & (byte) CharacterMask.WhiteSpace) != 0; } - public static bool IsWhiteSpaceNonAscii(char ch) - { - return (ch >= 0x1680 && ( - ch == 0x1680 || - ch == 0x180E || - (ch >= 0x2000 && ch <= 0x200A) || - ch == 0x202F || - ch == 0x205F || - ch == 0x3000 || - ch == 0xFEFF)); - } - public static bool IsWhiteSpaceOrNewLine(char ch) { return (_characterData[ch] & (byte) CharacterMask.WhiteSpaceOrNewLine) != 0; @@ -85,7 +73,7 @@ public static char ScanHexEscape(string text, int index, out int length) public static TextSpan DecodeString(TextSpan span) { // Nothing to do if the string doesn't have any escape char - if (span.Buffer.AsSpan(span.Offset, span.Length).IndexOf('\\') == -1) + if (string.IsNullOrEmpty(span.Buffer) || span.Buffer.AsSpan(span.Offset, span.Length).IndexOf('\\') == -1) { return span; } @@ -99,7 +87,7 @@ public static TextSpan DecodeString(TextSpan span) // The assumption is that the new string will be shorter since escapes results are smaller than their source var dataIndex = 0; - var buffer = source.Buffer; + var buffer = source.Buffer!; var start = source.Offset; var end = source.Offset + source.Length; diff --git a/src/Parlot/Compilation/CompilationContext.cs b/src/Parlot/Compilation/CompilationContext.cs index a858a72..ef44827 100644 --- a/src/Parlot/Compilation/CompilationContext.cs +++ b/src/Parlot/Compilation/CompilationContext.cs @@ -1,4 +1,5 @@ using Parlot.Fluent; +using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -51,5 +52,43 @@ public CompilationContext() /// This is done to optimize compiled parser that are usually used for pattern matching only. /// public bool DiscardResult { get; set; } = false; + + /// + /// Creates a instance with a and + /// variables. + /// + /// The type of the value returned by the parser instance. + /// The default value of the variable. + /// The default value of the variable. + /// + public CompilationResult CreateCompilationResult(bool defaultSuccess = false, Expression? defaultValue = null) + { + return CreateCompilationResult(typeof(TValue), defaultSuccess, defaultValue); + } + + /// + /// Creates a instance with a and + /// variables. + /// + /// The type of the value returned by the parser instance. + /// The default value of the variable. + /// The default value of the variable. + /// + public CompilationResult CreateCompilationResult(Type valueType, bool defaultSuccess = false, Expression? defaultValue = null) + { + var successVariable = Expression.Variable(typeof(bool), $"success{this.NextNumber}"); + var valueVariable = Expression.Variable(valueType, $"value{this.NextNumber}"); + + var result = new CompilationResult { Success = successVariable, Value = valueVariable }; + + result.Variables.Add(successVariable); + result.Variables.Add(valueVariable); + + result.Body.Add(Expression.Assign(successVariable, Expression.Constant(defaultSuccess, typeof(bool)))); + result.Body.Add(Expression.Assign(valueVariable, defaultValue ?? Expression.Default(valueType))); + + return result; + } + } } diff --git a/src/Parlot/Compilation/CompilationResult.cs b/src/Parlot/Compilation/CompilationResult.cs index 0a9e36f..fb45204 100644 --- a/src/Parlot/Compilation/CompilationResult.cs +++ b/src/Parlot/Compilation/CompilationResult.cs @@ -9,29 +9,48 @@ namespace Parlot.Compilation /// to parse the expected input. /// The convention is that these statements are returned in the property, and any variable that needs to be declared in the block /// that the is used in are set in the list. - /// The property reprsents the variable that contains the success of the statements once executed, and if true then + /// The property represents the variable that contains the success of the statements once executed, and if true then /// the property contains the result. /// public class CompilationResult { + internal CompilationResult() + { + } + /// /// Gets the list of representing the variables used by the compiled result. /// - public List Variables { get; } = new(); + public List Variables { get; } = []; /// /// Gets the list of representing the body of the compiled results. /// - public List Body { get; } = new(); + public List Body { get; } = []; /// /// Gets or sets the of the variable representing the success of the parsing statements. /// - public ParameterExpression Success { get; set; } + public required ParameterExpression Success { get; set; } /// /// Gets or sets the of the variable representing the value of the parsing statements. /// - public ParameterExpression Value { get; set; } + public required ParameterExpression Value { get; set; } + + public ParameterExpression DeclareVariable(string name, Expression? defaultValue = null) + { + return DeclareVariable(name, typeof(T), defaultValue); + } + + public ParameterExpression DeclareVariable(string name, Type type, Expression? defaultValue = null) + { + var variable = Expression.Variable(type, name); + + Variables.Add(variable); + Body.Add(Expression.Assign(variable, defaultValue ?? Expression.Default(type))); + + return variable; + } } } diff --git a/src/Parlot/Compilation/ExpressionHelper.cs b/src/Parlot/Compilation/ExpressionHelper.cs index ef0432e..6ccc165 100644 --- a/src/Parlot/Compilation/ExpressionHelper.cs +++ b/src/Parlot/Compilation/ExpressionHelper.cs @@ -9,41 +9,48 @@ namespace Parlot.Compilation public static class ExpressionHelper { - internal static MethodInfo ParserContext_SkipWhiteSpaceMethod = typeof(ParseContext).GetMethod(nameof(ParseContext.SkipWhiteSpace), Array.Empty()); - internal static MethodInfo Scanner_ReadText_NoResult = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadText), [typeof(string), typeof(StringComparison)]); - internal static MethodInfo Scanner_ReadChar = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadChar), [typeof(char)]); - internal static MethodInfo Scanner_ReadDecimal = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadDecimal), []); - internal static MethodInfo Scanner_ReadDecimalAllArguments = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadDecimal), [typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(ReadOnlySpan).MakeByRefType(), typeof(char), typeof(char)]); - internal static MethodInfo Scanner_ReadInteger = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadInteger), []); - internal static MethodInfo Scanner_ReadNonWhiteSpace = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadNonWhiteSpace), []); - internal static MethodInfo Scanner_ReadNonWhiteSpaceOrNewLine = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadNonWhiteSpaceOrNewLine), []); - internal static MethodInfo Scanner_SkipWhiteSpace = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.SkipWhiteSpace), []); - internal static MethodInfo Scanner_SkipWhiteSpaceOrNewLine = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.SkipWhiteSpaceOrNewLine), []); - internal static MethodInfo Scanner_ReadSingleQuotedString = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadSingleQuotedString), []); - internal static MethodInfo Scanner_ReadDoubleQuotedString = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadDoubleQuotedString), []); - internal static MethodInfo Scanner_ReadQuotedString = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadQuotedString), []); - - internal static MethodInfo Cursor_Advance = typeof(Cursor).GetMethod(nameof(Parlot.Cursor.Advance), Array.Empty()); - internal static MethodInfo Cursor_AdvanceNoNewLines = typeof(Cursor).GetMethod(nameof(Parlot.Cursor.AdvanceNoNewLines), [typeof(int)]); - - internal static ConstructorInfo TextSpan_Constructor = typeof(TextSpan).GetConstructor([typeof(string), typeof(int), typeof(int)]); - //internal static ConstructorInfo GetOptionalResult_Constructor() => typeof(OptionalResult).GetConstructor([typeof(bool), typeof(T)]); + internal static readonly MethodInfo ParserContext_SkipWhiteSpaceMethod = typeof(ParseContext).GetMethod(nameof(ParseContext.SkipWhiteSpace), Array.Empty())!; + internal static readonly MethodInfo ParserContext_WhiteSpaceParser = typeof(ParseContext).GetProperty(nameof(ParseContext.WhiteSpaceParser))?.GetGetMethod()!; + internal static readonly MethodInfo Scanner_ReadText_NoResult = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadText), [typeof(ReadOnlySpan), typeof(StringComparison)])!; + internal static readonly MethodInfo Scanner_ReadChar = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadChar), [typeof(char)])!; + internal static readonly MethodInfo Scanner_ReadDecimal = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadDecimal), [])!; + internal static readonly MethodInfo Scanner_ReadDecimalAllArguments = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadDecimal), [typeof(bool), typeof(bool), typeof(bool), typeof(bool), typeof(ReadOnlySpan).MakeByRefType(), typeof(char), typeof(char)])!; + internal static readonly MethodInfo Scanner_ReadInteger = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadInteger), [])!; + internal static readonly MethodInfo Scanner_ReadNonWhiteSpace = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadNonWhiteSpace), [])!; + internal static readonly MethodInfo Scanner_ReadNonWhiteSpaceOrNewLine = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadNonWhiteSpaceOrNewLine), [])!; + internal static readonly MethodInfo Scanner_SkipWhiteSpace = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.SkipWhiteSpace), [])!; + internal static readonly MethodInfo Scanner_SkipWhiteSpaceOrNewLine = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.SkipWhiteSpaceOrNewLine), [])!; + internal static readonly MethodInfo Scanner_ReadSingleQuotedString = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadSingleQuotedString), [])!; + internal static readonly MethodInfo Scanner_ReadDoubleQuotedString = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadDoubleQuotedString), [])!; + internal static readonly MethodInfo Scanner_ReadQuotedString = typeof(Scanner).GetMethod(nameof(Parlot.Scanner.ReadQuotedString), [])!; + + internal static readonly MethodInfo Cursor_Advance = typeof(Cursor).GetMethod(nameof(Parlot.Cursor.Advance), Array.Empty())!; + internal static readonly MethodInfo Cursor_AdvanceNoNewLines = typeof(Cursor).GetMethod(nameof(Parlot.Cursor.AdvanceNoNewLines), [typeof(int)])!; + internal static readonly MethodInfo Cursor_ResetPosition = typeof(Cursor).GetMethod("ResetPosition")!; + + internal static readonly ConstructorInfo Exception_ToString = typeof(Exception).GetConstructor([typeof(string)])!; + + internal static ConstructorInfo TextSpan_Constructor = typeof(TextSpan).GetConstructor([typeof(string), typeof(int), typeof(int)])!; + + internal static MethodInfo MemoryExtensions_AsSpan = typeof(MemoryExtensions).GetMethod(nameof(MemoryExtensions.AsSpan), [typeof(string)])!; public static Expression ArrayEmpty() => ((Expression>)(() => Array.Empty())).Body; public static Expression New() where T : new() => ((Expression>)(() => new T())).Body; + + public static Expression> CharacterIsInRange = (cursor,b,c) => Character.IsInRange(cursor.Current, b, c); //public static Expression NewOptionalResult(this CompilationContext _, Expression hasValue, Expression value) => Expression.New(GetOptionalResult_Constructor(), [hasValue, value]); public static Expression NewTextSpan(this CompilationContext _, Expression buffer, Expression offset, Expression count) => Expression.New(TextSpan_Constructor, [buffer, offset, count]); public static MemberExpression Scanner(this CompilationContext context) => Expression.Field(context.ParseContext, "Scanner"); public static MemberExpression Cursor(this CompilationContext context) => Expression.Field(context.Scanner(), "Cursor"); public static MemberExpression Position(this CompilationContext context) => Expression.Property(context.Cursor(), "Position"); - public static Expression ResetPosition(this CompilationContext context, Expression position) => Expression.Call(context.Cursor(), typeof(Cursor).GetMethod("ResetPosition"), position); + public static Expression ResetPosition(this CompilationContext context, Expression position) => Expression.Call(context.Cursor(), Cursor_ResetPosition, position); public static MemberExpression Offset(this CompilationContext context) => Expression.Property(context.Cursor(), "Offset"); public static MemberExpression Offset(this CompilationContext _, Expression textPosition) => Expression.Field(textPosition, nameof(TextPosition.Offset)); public static MemberExpression Current(this CompilationContext context) => Expression.Property(context.Cursor(), "Current"); public static MemberExpression Eof(this CompilationContext context) => Expression.Property(context.Cursor(), "Eof"); public static MemberExpression Buffer(this CompilationContext context) => Expression.Field(context.Scanner(), "Buffer"); - public static Expression ThrowObject(this CompilationContext _, Expression o) => Expression.Throw(Expression.New(typeof(Exception).GetConstructor([typeof(string)]), Expression.Call(o, o.Type.GetMethod("ToString", [])))); + public static Expression ThrowObject(this CompilationContext _, Expression o) => Expression.Throw(Expression.New(Exception_ToString, Expression.Call(o, o.Type.GetMethod("ToString", [])!))); public static Expression ThrowParseException(this CompilationContext context, Expression message) => Expression.Throw(Expression.New(typeof(ParseException).GetConstructors().First(), [message, context.Position()] )); public static MethodCallExpression ReadSingleQuotedString(this CompilationContext context) => Expression.Call(context.Scanner(), Scanner_ReadSingleQuotedString); @@ -60,55 +67,6 @@ public static class ExpressionHelper public static MethodCallExpression Advance(this CompilationContext context) => Expression.Call(context.Cursor(), Cursor_Advance); public static MethodCallExpression AdvanceNoNewLine(this CompilationContext context, Expression count) => Expression.Call(context.Cursor(), Cursor_AdvanceNoNewLines, [count]); - public static ParameterExpression DeclareSuccessVariable(this CompilationContext context, CompilationResult result, bool defaultValue) - { - result.Success = Expression.Variable(typeof(bool), $"success{context.NextNumber}"); - result.Variables.Add(result.Success); - result.Body.Add(Expression.Assign(result.Success, Expression.Constant(defaultValue, typeof(bool)))); - return result.Success; - } - - public static ParameterExpression DeclareVariable(this CompilationContext _, CompilationResult result, string name, Expression defaultValue = null) - { - var variable = Expression.Variable(typeof(T), name); - result.Variables.Add(variable); - - result.Body.Add(Expression.Assign(variable, defaultValue ?? Expression.Constant(default(T), typeof(T)))); - - return variable; - } - - public static ParameterExpression DeclareVariable(this CompilationContext _, CompilationResult result, string name, Type type, Expression defaultValue = null) - { - var variable = Expression.Variable(type, name); - result.Variables.Add(variable); - - if (defaultValue != null) - { - result.Body.Add(Expression.Assign(variable, defaultValue)); - } - - return variable; - } - - public static ParameterExpression DeclareValueVariable(this CompilationContext context, CompilationResult result) - { - return DeclareValueVariable(context, result, Expression.Default(typeof(T))); - } - - public static ParameterExpression DeclareValueVariable(this CompilationContext context, CompilationResult result, Expression defaultValue, Type variableType = null) - { - result.Value = Expression.Variable(variableType ?? defaultValue.Type, $"value{context.NextNumber}"); - - if (!context.DiscardResult) - { - result.Variables.Add(result.Value); - result.Body.Add(Expression.Assign(result.Value, defaultValue)); - } - - return result.Value; - } - public static ParameterExpression DeclarePositionVariable(this CompilationContext context, CompilationResult result) { var start = Expression.Variable(typeof(TextPosition), $"position{context.NextNumber}"); diff --git a/src/Parlot/Cursor.cs b/src/Parlot/Cursor.cs index eb0bb97..f2ad2a8 100644 --- a/src/Parlot/Cursor.cs +++ b/src/Parlot/Cursor.cs @@ -31,6 +31,11 @@ public Cursor(string buffer) : this(buffer, TextPosition.Start) public TextPosition Position => new(_offset, _line, _column); + /// + /// Returns the value of the at the current offset. + /// + public ReadOnlySpan Span => _buffer.AsSpan(_offset); + /// /// Advances the cursor by one character. /// @@ -206,69 +211,44 @@ public bool Match(char c) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MatchAnyOf(string s) + public bool MatchAnyOf(ReadOnlySpan s) { - if (s == null) - { - ThrowHelper.ThrowArgumentNullException(nameof(s)); - } - if (Eof) { return false; } - var length = s.Length; - - if (length == 0) - { - return true; - } - - return s.IndexOf(_current) != -1; + return s.Length == 0 || s.IndexOf(_current) > -1; } /// /// Whether a string is at the current position. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Match(string s) + public bool Match(ReadOnlySpan s) { // Equivalent to StringComparison.Ordinal comparison - var sSpan = s.AsSpan(); - var bufferSpan = _buffer.AsSpan(_offset); - - return bufferSpan.StartsWith(sSpan); + if (_textLength < _offset + s.Length) + { + return false; + } + + return Span.StartsWith(s); } /// /// Whether a string is at the current position. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Match(string s, StringComparison comparisonType) + public bool Match(ReadOnlySpan s, StringComparison comparisonType) { if (_textLength < _offset + s.Length) { return false; } - var sSpan = s.AsSpan(); - var bufferSpan = _buffer.AsSpan(_offset); - - if (comparisonType == StringComparison.Ordinal && bufferSpan.Length > 0) - { - var length = sSpan.Length - 1; - - if (bufferSpan[0] != sSpan[0] || bufferSpan[length] != sSpan[length]) - { - return false; - } - } - - // StringComparison.Ordinal is an optimized code path in Span.StartsWith - - return bufferSpan.StartsWith(sSpan, comparisonType); + return Span.StartsWith(s, comparisonType); } } } diff --git a/src/Parlot/Fluent/Always.cs b/src/Parlot/Fluent/Always.cs index 0804873..f5f8ad9 100644 --- a/src/Parlot/Fluent/Always.cs +++ b/src/Parlot/Fluent/Always.cs @@ -10,11 +10,6 @@ public sealed class Always : Parser, ICompilable { private readonly T _value; - public Always() - { - _value = default; - } - public Always(T value) { _value = value; @@ -31,12 +26,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - _ = context.DeclareSuccessVariable(result, true); - _ = context.DeclareValueVariable(result, Expression.Constant(_value)); - - return result; + return context.CreateCompilationResult(true, Expression.Constant(_value, typeof(T))); } } } diff --git a/src/Parlot/Fluent/Between.cs b/src/Parlot/Fluent/Between.cs index 66ff2f1..2388157 100644 --- a/src/Parlot/Fluent/Between.cs +++ b/src/Parlot/Fluent/Between.cs @@ -66,10 +66,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); // start = context.Scanner.Cursor.Position; // @@ -118,16 +115,16 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThen( afterCompileResult.Success, Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, parserCompileResult.Value) + : Expression.Assign(result.Value, parserCompileResult.Value) ) ) ) ), Expression.IfThen( - Expression.Not(success), + Expression.Not(result.Success), context.ResetPosition(start) ) ) diff --git a/src/Parlot/Fluent/Capture.cs b/src/Parlot/Fluent/Capture.cs index c08f30a..7721e5a 100644 --- a/src/Parlot/Fluent/Capture.cs +++ b/src/Parlot/Fluent/Capture.cs @@ -38,10 +38,7 @@ public override bool Parse(ParseContext context, ref ParseResult resul public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(TextSpan))); + var result = context.CreateCompilationResult(); // var start = context.Scanner.Cursor.Position; var start = context.DeclarePositionVariable(result); @@ -80,13 +77,13 @@ public CompilationResult Compile(CompilationContext context) Expression.Block( context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, + : Expression.Assign(result.Value, context.NewTextSpan( context.Buffer(), startOffset, Expression.Subtract(context.Offset(), startOffset) )), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) ), context.ResetPosition(start) ) diff --git a/src/Parlot/Fluent/CharLiteral.cs b/src/Parlot/Fluent/CharLiteral.cs index 798a2d4..5f22ac8 100644 --- a/src/Parlot/Fluent/CharLiteral.cs +++ b/src/Parlot/Fluent/CharLiteral.cs @@ -39,10 +39,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(char))); + var result = context.CreateCompilationResult(); // if (context.Scanner.ReadChar(Char)) // { @@ -54,10 +51,10 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThen( context.ReadChar(Char), Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, Expression.Constant(Char, typeof(char))) + : Expression.Assign(result.Value, Expression.Constant(Char, typeof(char))) ) ) ); diff --git a/src/Parlot/Fluent/Deferred.cs b/src/Parlot/Fluent/Deferred.cs index 1ea54c4..ee6a7b7 100644 --- a/src/Parlot/Fluent/Deferred.cs +++ b/src/Parlot/Fluent/Deferred.cs @@ -7,8 +7,7 @@ namespace Parlot.Fluent { public sealed class Deferred : Parser, ICompilable { - - public Parser Parser { get; set; } + public Parser? Parser { get; set; } public Deferred() { @@ -21,6 +20,11 @@ public Deferred(Func, Parser> parser) public override bool Parse(ParseContext context, ref ParseResult result) { + if (Parser is null) + { + throw new ArgumentNullException(nameof(Parser)); + } + return Parser.Parse(context, ref result); } @@ -29,7 +33,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) private class Closure { - public object Func; + public object? Func; } public CompilationResult Compile(CompilationContext context) @@ -39,10 +43,7 @@ public CompilationResult Compile(CompilationContext context) throw new InvalidOperationException("Can't compile a Deferred Parser until it is fully initialized"); } - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); // Create the body of this parser only once if (!_initialized) @@ -71,7 +72,7 @@ public CompilationResult Compile(CompilationContext context) parserCompileResult.Variables.Append(resultExpression), Expression.Block(parserCompileResult.Body), Expression.Assign(resultExpression, Expression.New( - typeof(ValueTuple).GetConstructor([typeof(bool), typeof(T)]), + typeof(ValueTuple).GetConstructor([typeof(bool), typeof(T)])!, parserCompileResult.Success, context.DiscardResult ? Expression.Default(parserCompileResult.Value.Type) : parserCompileResult.Value)), returnLabelExpression), @@ -103,11 +104,11 @@ public CompilationResult Compile(CompilationContext context) // success = def.Item1; // value = def.Item2; - result.Body.Add(Expression.Assign(success, Expression.Field(deferred, "Item1"))); + result.Body.Add(Expression.Assign(result.Success, Expression.Field(deferred, "Item1"))); result.Body.Add( context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, Expression.Field(deferred, "Item2")) + : Expression.Assign(result.Value, Expression.Field(deferred, "Item2")) ); return result; diff --git a/src/Parlot/Fluent/Discard.cs b/src/Parlot/Fluent/Discard.cs index 286c0e7..6453216 100644 --- a/src/Parlot/Fluent/Discard.cs +++ b/src/Parlot/Fluent/Discard.cs @@ -11,12 +11,6 @@ public sealed class Discard : Parser, ICompilable private readonly Parser _parser; private readonly U _value; - public Discard(Parser parser) - { - _value = default; - _parser = parser; - } - public Discard(Parser parser, U value) { _parser = parser; @@ -40,10 +34,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - _ = context.DeclareValueVariable(result, Expression.Constant(_value)); + var result = context.CreateCompilationResult(false, Expression.Constant(_value, typeof(U))); var parserCompileResult = _parser.Build(context); @@ -58,7 +49,7 @@ public CompilationResult Compile(CompilationContext context) Expression.Block( parserCompileResult.Variables, Expression.Block(parserCompileResult.Body), - Expression.Assign(success, parserCompileResult.Success) + Expression.Assign(result.Success, parserCompileResult.Success) ) ); diff --git a/src/Parlot/Fluent/Else.cs b/src/Parlot/Fluent/Else.cs index 3ee4777..bd7fdef 100644 --- a/src/Parlot/Fluent/Else.cs +++ b/src/Parlot/Fluent/Else.cs @@ -31,10 +31,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, true); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T)), typeof(T)); + var result = context.CreateCompilationResult(true); var parserCompileResult = _parser.Build(context); @@ -59,8 +56,8 @@ public CompilationResult Compile(CompilationContext context) ? Expression.Empty() : Expression.IfThenElse( parserCompileResult.Success, - Expression.Assign(value, parserCompileResult.Value), - Expression.Assign(value, Expression.Constant(_value, typeof(T))) + Expression.Assign(result.Value, parserCompileResult.Value), + Expression.Assign(result.Value, Expression.Constant(_value, typeof(T))) ) ) ); diff --git a/src/Parlot/Fluent/Eof.cs b/src/Parlot/Fluent/Eof.cs index bda331e..d3fa6b5 100644 --- a/src/Parlot/Fluent/Eof.cs +++ b/src/Parlot/Fluent/Eof.cs @@ -1,5 +1,4 @@ using Parlot.Compilation; -using System; using System.Linq.Expressions; namespace Parlot.Fluent @@ -30,10 +29,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); // parse1 instructions // @@ -54,8 +50,8 @@ public CompilationResult Compile(CompilationContext context) Expression.Block( context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, parserCompileResult.Value), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) + : Expression.Assign(result.Value, parserCompileResult.Value), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) ) ) ) diff --git a/src/Parlot/Fluent/Error.cs b/src/Parlot/Fluent/Error.cs index edf4524..91997ae 100644 --- a/src/Parlot/Fluent/Error.cs +++ b/src/Parlot/Fluent/Error.cs @@ -31,10 +31,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - _ = context.DeclareSuccessVariable(result, true); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(true); // parse1 instructions // success = true @@ -57,13 +54,13 @@ public CompilationResult Compile(CompilationContext context) .Append( context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, parserCompileResult.Value)) + : Expression.Assign(result.Value, parserCompileResult.Value)) .Append( Expression.IfThenElse( parserCompileResult.Success, context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, parserCompileResult.Value), + : Expression.Assign(result.Value, parserCompileResult.Value), context.ThrowParseException(Expression.Constant(_message)) @@ -101,10 +98,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - _ = context.DeclareSuccessVariable(result, false); - _ = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); // parse1 instructions // success = false; @@ -161,10 +155,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - _ = context.DeclareSuccessVariable(result, false); - _ = context.DeclareValueVariable(result, Expression.Default(typeof(U))); + var result = context.CreateCompilationResult(); // parse1 instructions // success = false; diff --git a/src/Parlot/Fluent/Identifier.cs b/src/Parlot/Fluent/Identifier.cs index 67458c5..dc17d6f 100644 --- a/src/Parlot/Fluent/Identifier.cs +++ b/src/Parlot/Fluent/Identifier.cs @@ -1,15 +1,19 @@ using Parlot.Compilation; using System; using System.Linq.Expressions; +using System.Reflection; namespace Parlot.Fluent { public sealed class Identifier : Parser, ICompilable { - private readonly Func _extraStart; - private readonly Func _extraPart; + private static readonly MethodInfo _isIdentifierStartMethodInfo = typeof(Character).GetMethod(nameof(Character.IsIdentifierStart))!; + private static readonly MethodInfo _isIdentifierPartMethodInfo = typeof(Character).GetMethod(nameof(Character.IsIdentifierPart))!; - public Identifier(Func extraStart = null, Func extraPart = null) + private readonly Func? _extraStart; + private readonly Func? _extraPart; + + public Identifier(Func? extraStart = null, Func? extraPart = null) { _extraStart = extraStart; _extraPart = extraPart; @@ -45,10 +49,7 @@ public override bool Parse(ParseContext context, ref ParseResult resul public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(TextSpan))); + var result = context.CreateCompilationResult(); // var first = context.Scanner.Cursor.Current; @@ -58,7 +59,7 @@ public CompilationResult Compile(CompilationContext context) // // success = false; - // Textspan value; + // TextSpan value; // // if (Character.IsIdentifierStart(first) [_extraStart != null] || _extraStart(first)) // { @@ -82,7 +83,7 @@ public CompilationResult Compile(CompilationContext context) var block = Expression.Block( Expression.IfThen( Expression.OrElse( - Expression.Call(typeof(Character).GetMethod(nameof(Character.IsIdentifierStart)), first), + Expression.Call(_isIdentifierStartMethodInfo, first), _extraStart != null ? Expression.Invoke(Expression.Constant(_extraStart), first) : Expression.Constant(false, typeof(bool)) @@ -96,7 +97,7 @@ public CompilationResult Compile(CompilationContext context) /* if */ Expression.AndAlso( Expression.Not(context.Eof()), Expression.OrElse( - Expression.Call(typeof(Character).GetMethod(nameof(Character.IsIdentifierPart)), context.Current()), + Expression.Call(_isIdentifierPartMethodInfo, context.Current()), _extraPart != null ? Expression.Invoke(Expression.Constant(_extraPart), context.Current()) : Expression.Constant(false, typeof(bool)) @@ -109,8 +110,8 @@ public CompilationResult Compile(CompilationContext context) ), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, context.NewTextSpan(context.Buffer(), start, Expression.Subtract(context.Offset(), start))), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) + : Expression.Assign(result.Value, context.NewTextSpan(context.Buffer(), start, Expression.Subtract(context.Offset(), start))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) ) ) ); diff --git a/src/Parlot/Fluent/NonWhiteSpaceLiteral.cs b/src/Parlot/Fluent/NonWhiteSpaceLiteral.cs index e670480..e070364 100644 --- a/src/Parlot/Fluent/NonWhiteSpaceLiteral.cs +++ b/src/Parlot/Fluent/NonWhiteSpaceLiteral.cs @@ -43,10 +43,7 @@ public override bool Parse(ParseContext context, ref ParseResult resul public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(TextSpan))); + var result = context.CreateCompilationResult(); // if (!context.Scanner.Cursor.Eof) // { @@ -82,10 +79,10 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThen( Expression.NotEqual(start, end), Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, context.NewTextSpan(context.Buffer(), start, Expression.Subtract(end, start)) + : Expression.Assign(result.Value, context.NewTextSpan(context.Buffer(), start, Expression.Subtract(end, start)) ) ) ))) diff --git a/src/Parlot/Fluent/Not.cs b/src/Parlot/Fluent/Not.cs index 3d3f518..7cde793 100644 --- a/src/Parlot/Fluent/Not.cs +++ b/src/Parlot/Fluent/Not.cs @@ -30,10 +30,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - _ = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); // var start = context.Scanner.Cursor.Position; @@ -62,7 +59,7 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThenElse( parserCompileResult.Success, context.ResetPosition(start), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) ) ) ); diff --git a/src/Parlot/Fluent/NumberLiteral.cs b/src/Parlot/Fluent/NumberLiteral.cs index aa526e8..9b21990 100644 --- a/src/Parlot/Fluent/NumberLiteral.cs +++ b/src/Parlot/Fluent/NumberLiteral.cs @@ -1,5 +1,6 @@ #if NET8_0_OR_GREATER using Parlot.Compilation; +using Parlot.Rewriting; using System; using System.Globalization; using System.Linq.Expressions; @@ -8,12 +9,14 @@ namespace Parlot.Fluent { - public sealed class NumberLiteral : Parser, ICompilable + public sealed class NumberLiteral : Parser, ICompilable, ISeekable where T : INumber { private const char DefaultDecimalSeparator = '.'; private const char DefaultGroupSeparator = ','; + private static readonly MethodInfo _tryParseMethodInfo = typeof(T).GetMethod(nameof(INumber.TryParse), [typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider), typeof(T).MakeByRefType()])!; + private readonly char _decimalSeparator; private readonly char _groupSeparator; private readonly NumberStyles _numberStyles; @@ -23,7 +26,11 @@ public sealed class NumberLiteral : Parser, ICompilable private readonly bool _allowGroupSeparator; private readonly bool _allowExponent; - private static readonly MethodInfo _tryParseMethodInfo = typeof(T).GetMethod(nameof(INumber.TryParse), [typeof(ReadOnlySpan), typeof(NumberStyles), typeof(IFormatProvider), typeof(T).MakeByRefType()]); + public bool CanSeek { get; } = true; + + public char[] ExpectedChars { get; } + + public bool SkipWhitespace { get; } = false; public NumberLiteral(NumberOptions numberOptions = NumberOptions.Number, char decimalSeparator = DefaultDecimalSeparator, char groupSeparator = DefaultGroupSeparator) { @@ -43,6 +50,25 @@ public NumberLiteral(NumberOptions numberOptions = NumberOptions.Number, char de _allowDecimalSeparator = (numberOptions & NumberOptions.AllowDecimalSeparator) != 0; _allowGroupSeparator = (numberOptions & NumberOptions.AllowGroupSeparators) != 0; _allowExponent = (numberOptions & NumberOptions.AllowExponent) != 0; + + ExpectedChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + + if (_allowLeadingSign) + { + ExpectedChars = [..ExpectedChars, '+', '-']; + } + + if (_allowDecimalSeparator) + { + ExpectedChars = [.. ExpectedChars, decimalSeparator]; + } + + if (_allowGroupSeparator) + { + ExpectedChars = [.. ExpectedChars, groupSeparator]; + } + + // Exponent can't be a starting char } public override bool Parse(ParseContext context, ref ParseResult result) @@ -70,10 +96,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result); + var result = context.CreateCompilationResult(); // var start = context.Scanner.Cursor.Offset; // var reset = context.Scanner.Cursor.Position; @@ -81,10 +104,10 @@ public CompilationResult Compile(CompilationContext context) var start = context.DeclareOffsetVariable(result); var reset = context.DeclarePositionVariable(result); - var numberStyles = context.DeclareVariable(result, $"numberStyles{context.NextNumber}", Expression.Constant(_numberStyles)); - var culture = context.DeclareVariable(result, $"culture{context.NextNumber}", Expression.Constant(_culture)); - var numberSpan = context.DeclareVariable(result, $"number{context.NextNumber}", typeof(ReadOnlySpan)); - var end = context.DeclareVariable(result, $"end{context.NextNumber}"); + var numberStyles = result.DeclareVariable($"numberStyles{context.NextNumber}", Expression.Constant(_numberStyles)); + var culture = result.DeclareVariable($"culture{context.NextNumber}", Expression.Constant(_culture)); + var numberSpan = result.DeclareVariable($"number{context.NextNumber}", typeof(ReadOnlySpan)); + var end = result.DeclareVariable($"end{context.NextNumber}"); // if (context.Scanner.ReadDecimal(_numberOptions, out var numberSpan, _decimalSeparator, _groupSeparator)) // { @@ -111,13 +134,13 @@ public CompilationResult Compile(CompilationContext context) numberSpan, Expression.Constant(_decimalSeparator), Expression.Constant(_groupSeparator)), Expression.Block( Expression.Assign(end, context.Offset()), - Expression.Assign(success, + Expression.Assign(result.Success, Expression.Call( _tryParseMethodInfo, numberSpan, numberStyles, culture, - value) + result.Value) ) ) ); @@ -126,7 +149,7 @@ public CompilationResult Compile(CompilationContext context) result.Body.Add( Expression.IfThen( - Expression.Not(success), + Expression.Not(result.Success), context.ResetPosition(reset) ) ); diff --git a/src/Parlot/Fluent/NumberLiteralBase.cs b/src/Parlot/Fluent/NumberLiteralBase.cs index 0f020ab..89773d1 100644 --- a/src/Parlot/Fluent/NumberLiteralBase.cs +++ b/src/Parlot/Fluent/NumberLiteralBase.cs @@ -13,8 +13,8 @@ namespace Parlot.Fluent /// public abstract class NumberLiteralBase : Parser, ICompilable { - private static readonly MethodInfo _defaultTryParseMethodInfo = typeof(T).GetMethod("TryParse", [typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(T).MakeByRefType()]); - private static readonly MethodInfo _rosToString = typeof(ReadOnlySpan).GetMethod(nameof(ToString), []); + private static readonly MethodInfo _defaultTryParseMethodInfo = typeof(T).GetMethod("TryParse", [typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(T).MakeByRefType()])!; + private static readonly MethodInfo _rosToString = typeof(ReadOnlySpan).GetMethod(nameof(ToString), [])!; private readonly char _decimalSeparator; private readonly char _groupSeparator; @@ -30,7 +30,7 @@ public abstract class NumberLiteralBase : Parser, ICompilable public abstract bool TryParseNumber(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out T value); - public NumberLiteralBase(NumberOptions numberOptions = NumberOptions.Number, char decimalSeparator = NumberLiterals.DefaultDecimalSeparator, char groupSeparator = NumberLiterals.DefaultGroupSeparator, MethodInfo tryParseMethodInfo = null) + public NumberLiteralBase(NumberOptions numberOptions = NumberOptions.Number, char decimalSeparator = NumberLiterals.DefaultDecimalSeparator, char groupSeparator = NumberLiterals.DefaultGroupSeparator, MethodInfo? tryParseMethodInfo = null) { _decimalSeparator = decimalSeparator; _groupSeparator = groupSeparator; @@ -78,10 +78,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); // var start = context.Scanner.Cursor.Offset; // var reset = context.Scanner.Cursor.Position; @@ -89,10 +86,10 @@ public CompilationResult Compile(CompilationContext context) var start = context.DeclareOffsetVariable(result); var reset = context.DeclarePositionVariable(result); - var numberStyles = context.DeclareVariable(result, $"numberStyles{context.NextNumber}", Expression.Constant(_numberStyles)); - var culture = context.DeclareVariable(result, $"culture{context.NextNumber}", Expression.Constant(_culture)); - var numberSpan = context.DeclareVariable(result, $"number{context.NextNumber}", typeof(ReadOnlySpan)); - var end = context.DeclareVariable(result, $"end{context.NextNumber}"); + var numberStyles = result.DeclareVariable($"numberStyles{context.NextNumber}", Expression.Constant(_numberStyles)); + var culture = result.DeclareVariable($"culture{context.NextNumber}", Expression.Constant(_culture)); + var numberSpan = result.DeclareVariable($"number{context.NextNumber}", typeof(ReadOnlySpan)); + var end = result.DeclareVariable($"end{context.NextNumber}"); // if (context.Scanner.ReadDecimal(_numberOptions, out var numberSpan, _decimalSeparator, _groupSeparator)) // { @@ -115,13 +112,13 @@ public CompilationResult Compile(CompilationContext context) numberSpan, Expression.Constant(_decimalSeparator), Expression.Constant(_groupSeparator)), Expression.Block( Expression.Assign(end, context.Offset()), - Expression.Assign(success, + Expression.Assign(result.Success, Expression.Call( _tryParseMethodInfo, Expression.Call(numberSpan, _rosToString), numberStyles, culture, - value) + result.Value) ) ) ); @@ -130,7 +127,7 @@ public CompilationResult Compile(CompilationContext context) result.Body.Add( Expression.IfThen( - Expression.Not(success), + Expression.Not(result.Success), context.ResetPosition(reset) ) ); diff --git a/src/Parlot/Fluent/NumberLiterals.cs b/src/Parlot/Fluent/NumberLiterals.cs index 8ebd47d..86e55eb 100644 --- a/src/Parlot/Fluent/NumberLiterals.cs +++ b/src/Parlot/Fluent/NumberLiterals.cs @@ -17,69 +17,69 @@ public static Parser CreateNumberLiteralParser(NumberOptions numberOptions if (typeof(T) == typeof(byte)) { var literal = new ByteNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(sbyte)) { var literal = new SByteNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(int)) { var literal = new IntNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(uint)) { var literal = new UIntNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(long)) { var literal = new LongNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(ulong)) { var literal = new ULongNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(short)) { var literal = new ShortNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(ushort)) { var literal = new UShortNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(decimal)) { var literal = new DecimalNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(double)) { var literal = new DoubleNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else if (typeof(T) == typeof(float)) { var literal = new FloatNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } #if NET6_0_OR_GREATER else if (typeof(T) == typeof(Half)) { var literal = new HalfNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } #endif else if (typeof(T) == typeof(BigInteger)) { var literal = new BigIntegerNumberLiteral(numberOptions, decimalSeparator, groupSeparator); - return literal as NumberLiteralBase; + return (literal as NumberLiteralBase)!; } else { diff --git a/src/Parlot/Fluent/OneOf.ABT.cs b/src/Parlot/Fluent/OneOf.ABT.cs index 8cca861..e5b1797 100644 --- a/src/Parlot/Fluent/OneOf.ABT.cs +++ b/src/Parlot/Fluent/OneOf.ABT.cs @@ -44,10 +44,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); // T value; // @@ -79,10 +76,10 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThenElse( parser1CompileResult.Success, Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, Expression.Convert(parser1CompileResult.Value, typeof(T))) + : Expression.Assign(result.Value, Expression.Convert(parser1CompileResult.Value, typeof(T))) ), Expression.Block( parser2CompileResult.Variables, @@ -90,10 +87,10 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThen( parser2CompileResult.Success, Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, Expression.Convert(parser2CompileResult.Value, typeof(T))) + : Expression.Assign(result.Value, Expression.Convert(parser2CompileResult.Value, typeof(T))) ) ) ) diff --git a/src/Parlot/Fluent/OneOf.cs b/src/Parlot/Fluent/OneOf.cs index 8e61ed2..a1040f9 100644 --- a/src/Parlot/Fluent/OneOf.cs +++ b/src/Parlot/Fluent/OneOf.cs @@ -2,7 +2,6 @@ using Parlot.Rewriting; using System; using System.Collections.Generic; -using System.Collections.Frozen; using System.Linq; using System.Linq.Expressions; @@ -16,7 +15,7 @@ namespace Parlot.Fluent public sealed class OneOf : Parser, ICompilable, ISeekable { internal readonly Parser[] _parsers; - internal readonly FrozenDictionary>> _lookupTable; + internal readonly CharMap>>? _map; public OneOf(Parser[] parsers) { @@ -35,7 +34,7 @@ public OneOf(Parser[] parsers) foreach (var parser in _parsers) { - var expectedChars = (parser as ISeekable).ExpectedChars; + var expectedChars = (parser as ISeekable)!.ExpectedChars; foreach (var c in expectedChars) { @@ -69,10 +68,9 @@ public OneOf(Parser[] parsers) if (lookupTable != null) { - _lookupTable = lookupTable.ToFrozenDictionary(); - CanSeek = true; - ExpectedChars = _lookupTable.Keys.ToArray(); + _map = new CharMap>>(lookupTable); + ExpectedChars = _map.ExpectedChars.ToArray(); } } } @@ -91,7 +89,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) var cursor = context.Scanner.Cursor; - if (_lookupTable != null) + if (_map != null) { if (SkipWhitespace) { @@ -99,9 +97,11 @@ public override bool Parse(ParseContext context, ref ParseResult result) context.SkipWhiteSpace(); - if (_lookupTable.TryGetValue(cursor.Current, out var seekableParsers)) + var seekableParsers = _map[cursor.Current]; + + if (seekableParsers != null) { - var length = seekableParsers.Count; + var length = seekableParsers!.Count; for (var i = 0; i < length; i++) { @@ -116,9 +116,11 @@ public override bool Parse(ParseContext context, ref ParseResult result) } else { - if (_lookupTable.TryGetValue(cursor.Current, out var seekableParsers)) + var seekableParsers = _map[cursor.Current]; + + if (seekableParsers != null) { - var length = seekableParsers.Count; + var length = seekableParsers!.Count; for (var i = 0; i < length; i++) { @@ -149,14 +151,14 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); Expression block = Expression.Empty(); - if (_lookupTable != null) + //if (_map != null) + // For now don't use lookup maps for compiled code as there is no fast option in that case. + + if (false) { // Lookup table is converted to a switch expression @@ -177,12 +179,15 @@ public CompilationResult Compile(CompilationContext context) // ... // } - var cases = _lookupTable.Select(kvp => +#pragma warning disable CS0162 // Unreachable code detected + var cases = _map.ExpectedChars.Select(key => { Expression group = Expression.Empty(); + var parsers = _map[key]; + // The list is reversed since the parsers are unwrapped - foreach (var parser in kvp.Value.ToArray().Reverse()) + foreach (var parser in parsers!.ToArray().Reverse()) { var groupResult = parser.Build(context); @@ -192,46 +197,137 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThenElse( groupResult.Success, Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, groupResult.Value) + : Expression.Assign(result.Value, groupResult.Value) ), group ) ); } - return Expression.SwitchCase( - group, - Expression.Constant(kvp.Key) - ); + return (Key: (uint)key, Body: group); + }).ToArray(); +#pragma warning restore CS0162 // Unreachable code detected + // Creating the switch expression if we need it SwitchExpression switchExpr = Expression.Switch( - context.Current(), + Expression.Convert(context.Current(), typeof(uint)), Expression.Empty(), // no match => success = false - cases + cases.Select(c => Expression.SwitchCase( + c.Body, + Expression.Constant(c.Key) + )).ToArray() ); + // Implement binary tree comparison + // Still slow with a few elements + + var current = Expression.Variable(typeof(uint), $"current{context.NextNumber}"); + var binarySwitch = Expression.Block( + [current], + Expression.Assign(current, Expression.Convert(context.Current(), typeof(uint))), + BinarySwitch(current, cases) + ); + + static Expression BinarySwitch(Expression num, (uint Key, Expression Body)[] cases) + { + if (cases.Length > 3) + { + // Split comparison in two recursive comparisons for each part of the cases + + var lowerCount = (int)Math.Round((double)cases.Length / 2, MidpointRounding.ToEven); + var lowerValues = cases.Take(lowerCount).ToArray(); + var higherValues = cases.Skip(lowerCount).ToArray(); + + return Expression.IfThenElse( + Expression.LessThanOrEqual(num, Expression.Constant(lowerValues[^1].Key)), + // This value or lower + BinarySwitch(num, lowerValues), + // Higher values + BinarySwitch(num, higherValues) + ); + } + else if (cases.Length == 1) + { + return Expression.IfThen( + Expression.Equal(Expression.Constant(cases[0].Key), num), + cases[0].Body + ); + } + else if (cases.Length == 2) + { + return Expression.IfThenElse( + Expression.NotEqual(Expression.Constant(cases[0].Key), num), + Expression.IfThen( + Expression.Equal(Expression.Constant(cases[1].Key), num), + cases[1].Body), + cases[0].Body); + } + else // cases.Length == 3 + { + return Expression.IfThenElse( + Expression.NotEqual(Expression.Constant(cases[0].Key), num), + Expression.IfThenElse( + Expression.NotEqual(Expression.Constant(cases[1].Key), num), + Expression.IfThen( + Expression.Equal(Expression.Constant(cases[2].Key), num), + cases[2].Body), + cases[1].Body), + cases[0].Body); + } + } + + // Implement lookup + // Doesn't work since each method can update the main state with the result (closure issue?) + + var table = Expression.Variable(typeof(CharMap), $"table{context.NextNumber}"); + + var indexerMethodInfo = typeof(CharMap).GetMethod("get_Item", [typeof(uint)])!; + + context.GlobalVariables.Add(table); + var action = result.DeclareVariable($"action{context.NextNumber}"); + + var lookupBlock = Expression.Block( + [current, action, table], + [ + Expression.Assign(current, Expression.Convert(context.Current(), typeof(uint))), + // Initialize lookup table once + Expression.IfThen( + Expression.Equal(Expression.Constant(null, typeof(object)), table), + Expression.Block([ + Expression.Assign(table, ExpressionHelper.New>()), + ..cases.Select(c => Expression.Call(table, typeof(CharMap).GetMethod("Set", [typeof(char), typeof(Action)])!, [Expression.Convert(Expression.Constant(c.Key), typeof(char)), Expression.Lambda(c.Body)])) + ] + ) + ), + Expression.Assign(action, Expression.Call(table, indexerMethodInfo, [current])), + Expression.IfThen( + Expression.NotEqual(Expression.Constant(null), action), + //ExpressionHelper.ThrowObject(context, current) + Expression.Invoke(action) + ) + ] + ); + if (SkipWhitespace) { var start = context.DeclarePositionVariable(result); block = Expression.Block( context.ParserSkipWhiteSpace(), - switchExpr, + binarySwitch, Expression.IfThen( - Expression.IsFalse(success), + Expression.IsFalse(result.Success), context.ResetPosition(start)) ); } else { - block = Expression.Block( - switchExpr - ); + block = binarySwitch; } } else @@ -266,10 +362,10 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThenElse( parserCompileResult.Success, Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, parserCompileResult.Value) + : Expression.Assign(result.Value, parserCompileResult.Value) ), block ) diff --git a/src/Parlot/Fluent/OneOrMany.cs b/src/Parlot/Fluent/OneOrMany.cs index a7babc4..c6ba1a2 100644 --- a/src/Parlot/Fluent/OneOrMany.cs +++ b/src/Parlot/Fluent/OneOrMany.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Reflection; namespace Parlot.Fluent { public sealed class OneOrMany : Parser>, ICompilable, ISeekable { private readonly Parser _parser; + private static readonly MethodInfo _listAddMethodInfo = typeof(List).GetMethod("Add")!; public OneOrMany(Parser parser) { @@ -57,10 +59,7 @@ public override bool Parse(ParseContext context, ref ParseResult))); + var result = context.CreateCompilationResult>(false, Expression.New(typeof(List))); // value = new List(); // @@ -103,8 +102,8 @@ public CompilationResult Compile(CompilationContext context) Expression.Block( context.DiscardResult ? Expression.Empty() - : Expression.Call(value, typeof(List).GetMethod("Add"), parserCompileResult.Value), - Expression.Assign(success, Expression.Constant(true)) + : Expression.Call(result.Value, _listAddMethodInfo, parserCompileResult.Value), + Expression.Assign(result.Success, Expression.Constant(true)) ), Expression.Break(breakLabel) ), diff --git a/src/Parlot/Fluent/ParseContext.cs b/src/Parlot/Fluent/ParseContext.cs index b970ff1..00122fa 100644 --- a/src/Parlot/Fluent/ParseContext.cs +++ b/src/Parlot/Fluent/ParseContext.cs @@ -34,12 +34,12 @@ public ParseContext(Scanner scanner, bool useNewLines = false) /// /// Delegate that is executed whenever a parser is invoked. /// - public Action OnEnterParser { get; set; } + public Action? OnEnterParser { get; set; } /// /// The parser that is used to parse whitespaces and comments. /// - public Parser WhiteSpaceParser { get; set;} + public Parser? WhiteSpaceParser { get; set;} public void SkipWhiteSpace() { diff --git a/src/Parlot/Fluent/Parser.Compile.cs b/src/Parlot/Fluent/Parser.Compile.cs index 9aa7c17..d0ec327 100644 --- a/src/Parlot/Fluent/Parser.Compile.cs +++ b/src/Parlot/Fluent/Parser.Compile.cs @@ -3,11 +3,14 @@ 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. /// @@ -26,7 +29,7 @@ public Parser Compile() // return value; var returnLabelTarget = Expression.Label(typeof(ValueTuple)); - var returnLabelExpression = Expression.Label(returnLabelTarget, Expression.New(typeof(ValueTuple).GetConstructor([typeof(bool), typeof(T)]), compilationResult.Success, compilationResult.Value)); + var returnLabelExpression = Expression.Label(returnLabelTarget, Expression.New(_valueTupleConstructor, compilationResult.Success, compilationResult.Value)); compilationResult.Body.Add(returnLabelExpression); @@ -99,9 +102,7 @@ public CompilationResult Build(CompilationContext context, bool requireResult = private CompilationResult BuildAsNonCompilableParser(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); + var result = context.CreateCompilationResult(false); // // T value; @@ -124,10 +125,10 @@ private CompilationResult BuildAsNonCompilableParser(CompilationContext context) // success = parser.Parse(context.ParseContext, ref parseResult) result.Body.Add( - Expression.Assign(success, + Expression.Assign(result.Success, Expression.Call( Expression.Constant(this), - GetType().GetMethod("Parse", [typeof(ParseContext), typeof(ParseResult).MakeByRefType()]), + GetType().GetMethod("Parse", [typeof(ParseContext), typeof(ParseResult).MakeByRefType()])!, context.ParseContext, parseResult)) ); @@ -139,7 +140,7 @@ private CompilationResult BuildAsNonCompilableParser(CompilationContext context) result.Body.Add( Expression.IfThen( - success, + result.Success, Expression.Assign(value, Expression.Field(parseResult, "Value")) ) ); diff --git a/src/Parlot/Fluent/Parser.TryParse.cs b/src/Parlot/Fluent/Parser.TryParse.cs index 8553405..502a294 100644 --- a/src/Parlot/Fluent/Parser.TryParse.cs +++ b/src/Parlot/Fluent/Parser.TryParse.cs @@ -5,16 +5,16 @@ public abstract partial class Parser { private int _invocations = 0; - private volatile Parser _compiledParser; + private volatile Parser? _compiledParser; - public T Parse(string text) + public T? Parse(string text) { var context = new ParseContext(new Scanner(text)); return Parse(context); } - public T Parse(ParseContext context) + public T? Parse(ParseContext context) { var localResult = new ParseResult(); @@ -47,17 +47,17 @@ private Parser CheckCompiled(ParseContext context) return this; } - public bool TryParse(string text, out T value) + public bool TryParse(string text, out T? value) { return TryParse(text, out value, out _); } - public bool TryParse(string text, out T value, out ParseError error) + public bool TryParse(string text, out T value, out ParseError? error) { return TryParse(new ParseContext(new Scanner(text)), out value, out error); } - public bool TryParse(ParseContext context, out T value, out ParseError error) + public bool TryParse(ParseContext context, out T value, out ParseError? error) { error = null; @@ -82,7 +82,7 @@ public bool TryParse(ParseContext context, out T value, out ParseError error) }; } - value = default; + value = default!; return false; } } diff --git a/src/Parlot/Fluent/Parser.cs b/src/Parlot/Fluent/Parser.cs index 6c02398..8e39d83 100644 --- a/src/Parlot/Fluent/Parser.cs +++ b/src/Parlot/Fluent/Parser.cs @@ -69,7 +69,7 @@ public abstract partial class Parser /// /// Builds a parser that discards the previous result and replaces it by the specified type or value. /// - public Parser Discard() => new Discard(this); + public Parser Discard() => new Discard(this, default); /// /// Builds a parser that discards the previous result and replaces it by the specified type or value. diff --git a/src/Parlot/Fluent/Parsers.And.cs b/src/Parlot/Fluent/Parsers.And.cs index 5b162d4..bd89f59 100644 --- a/src/Parlot/Fluent/Parsers.And.cs +++ b/src/Parlot/Fluent/Parsers.And.cs @@ -5,41 +5,41 @@ public static partial class Parsers /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Sequence And(this Parser parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Parser parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. /// - public static Sequence And(this Sequence parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceAndSkip parser, Parser and) => new Sequence(parser, and); - public static Sequence And(this SequenceSkipAnd parser, Parser and) => new Sequence(parser, and); + public static Sequence And(this Sequence parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static Sequence And(this SequenceSkipAnd parser, Parser and) => new(parser, and); } } diff --git a/src/Parlot/Fluent/Parsers.AndSkip.cs b/src/Parlot/Fluent/Parsers.AndSkip.cs index 4543e25..b3b2b6e 100644 --- a/src/Parlot/Fluent/Parsers.AndSkip.cs +++ b/src/Parlot/Fluent/Parsers.AndSkip.cs @@ -1,54 +1,52 @@ -using System; - -namespace Parlot.Fluent +namespace Parlot.Fluent { public static partial class Parsers { /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceAndSkip AndSkip(this Parser parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Parser parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new SequenceAndSkip(parser, and); - public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new SequenceAndSkip(parser, and); + public static SequenceAndSkip AndSkip(this Sequence parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceAndSkip AndSkip(this SequenceSkipAnd parser, Parser and) => new(parser, and); } } diff --git a/src/Parlot/Fluent/Parsers.OneOf.cs b/src/Parlot/Fluent/Parsers.OneOf.cs index e45d3c3..fa780ed 100644 --- a/src/Parlot/Fluent/Parsers.OneOf.cs +++ b/src/Parlot/Fluent/Parsers.OneOf.cs @@ -1,6 +1,4 @@ -using System.Linq; - -namespace Parlot.Fluent +namespace Parlot.Fluent { // We don't care about the performance of these helpers since they are called only once // during the parser tree creation @@ -18,7 +16,7 @@ public static Parser Or(this Parser parser, Parser or) if (parser is OneOf oneOf) { // Return a single OneOf instance with this new one - return new OneOf(oneOf.Parsers.Append(or).ToArray()); + return new OneOf([.. oneOf.Parsers, or]); } else { diff --git a/src/Parlot/Fluent/Parsers.SkipAnd.cs b/src/Parlot/Fluent/Parsers.SkipAnd.cs index 071e52b..96dcf8d 100644 --- a/src/Parlot/Fluent/Parsers.SkipAnd.cs +++ b/src/Parlot/Fluent/Parsers.SkipAnd.cs @@ -6,46 +6,46 @@ public static partial class Parsers /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceSkipAnd SkipAnd(this Parser parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Parser parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd ASkipAndnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new(parser, and); /// /// Builds a parser that ensure the specified parsers match consecutively. The last parser's result is then ignored. /// - public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new SequenceSkipAnd(parser, and); - public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new SequenceSkipAnd(parser, and); + public static SequenceSkipAnd SkipAnd(this Sequence parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceAndSkip parser, Parser and) => new(parser, and); + public static SequenceSkipAnd SkipAnd(this SequenceSkipAnd parser, Parser and) => new(parser, and); } } diff --git a/src/Parlot/Fluent/Parsers.Structure.cs b/src/Parlot/Fluent/Parsers.Structure.cs index dd91d48..020c36c 100644 --- a/src/Parlot/Fluent/Parsers.Structure.cs +++ b/src/Parlot/Fluent/Parsers.Structure.cs @@ -23,9 +23,9 @@ public static partial class Parsers /// public static Parser LeftAssociative(this Parser parser, params (Parser op, Func factory)[] list) { - var choices = list.Select(l => new Then>(l.op, l.factory)); + var choices = list.Select(l => new Then>(l.op, l.factory)).ToArray(); - return parser.And(ZeroOrMany(new OneOf>(choices.ToArray()).And(parser))) + return parser.And(ZeroOrMany(new OneOf>(choices).And(parser))) .Then(static x => { // multiplicative @@ -59,9 +59,9 @@ public static Parser LeftAssociative(this Parser parser, params /// public static Parser LeftAssociative(this Parser parser, params (Parser op, Func factory)[] list) { - var choices = list.Select(l => new Then>(l.op, l.factory)); + var choices = list.Select(l => new Then>(l.op, l.factory)).ToArray(); - return parser.And(ZeroOrMany(new OneOf>(choices.ToArray()).And(parser))) + return parser.And(ZeroOrMany(new OneOf>(choices).And(parser))) .Then(static (context, x) => { // multiplicative @@ -94,16 +94,16 @@ public static Parser LeftAssociative(this Parser parser, params /// public static Parser RightAssociative(this Parser parser, params (Parser op, Func factory)[] list) { - var choices = list.Select(l => new Then>(l.op, l.factory)); + var choices = list.Select(l => new Then>(l.op, l.factory)).ToArray(); - return parser.And(ZeroOrMany(new OneOf>(choices.ToArray()).And(parser))) + return parser.And(ZeroOrMany(new OneOf>(choices).And(parser))) .Then(static x => { // a; (=, b); (^, c) -> = (a, ^(b, c)) var operations = x.Item2; - T result = default; + T result; if (operations.Count > 0) { @@ -142,16 +142,16 @@ public static Parser RightAssociative(this Parser parser, param /// public static Parser RightAssociative(this Parser parser, params (Parser op, Func factory)[] list) { - var choices = list.Select(l => new Then>(l.op, l.factory)); + var choices = list.Select(l => new Then>(l.op, l.factory)).ToArray(); - return parser.And(ZeroOrMany(new OneOf>(choices.ToArray()).And(parser))) + return parser.And(ZeroOrMany(new OneOf>(choices).And(parser))) .Then(static (context, x) => { // a; (=, b); (^, c) -> = (a, ^(b, c)) var operations = x.Item2; - T result = default; + T result; if (operations.Count > 0) { @@ -185,8 +185,8 @@ public static Parser Unary(this Parser parser, params (Parser((u) => { - var choices = list.Select(l => new Then(l.op.SkipAnd(u), l.factory)); - return new OneOf(choices.ToArray()).Or(parser); + var choices = list.Select(l => new Then(l.op.SkipAnd(u), l.factory)).ToArray(); + return new OneOf(choices).Or(parser); }); } @@ -202,8 +202,8 @@ public static Parser Unary(this Parser parser, params (Parser((u) => { - var choices = list.Select(l => new Then(l.op.SkipAnd(u), l.factory)); - return new OneOf(choices.ToArray()).Or(parser); + var choices = list.Select(l => new Then(l.op.SkipAnd(u), l.factory)).ToArray(); + return new OneOf([.. choices, parser]); }); } } diff --git a/src/Parlot/Fluent/Parsers.cs b/src/Parlot/Fluent/Parsers.cs index e87dad6..cfe73ee 100644 --- a/src/Parlot/Fluent/Parsers.cs +++ b/src/Parlot/Fluent/Parsers.cs @@ -30,7 +30,12 @@ public static partial class Parsers /// /// Builds a parser that looks for zero or one time the specified parser. /// - public static Parser ZeroOrOne(Parser parser, T defaultValue = default) => new ZeroOrOne(parser, defaultValue); + public static Parser ZeroOrOne(Parser parser, T defaultValue) => new ZeroOrOne(parser, defaultValue); + + /// + /// Builds a parser that looks for zero or one time the specified parser. + /// + public static Parser ZeroOrOne(Parser parser) where T : notnull => new ZeroOrOne(parser, default!); /// /// Builds a parser that looks for zero or many times the specified parser. @@ -76,12 +81,12 @@ public static partial class Parsers /// /// Builds a parser that always succeeds. /// - public static Parser Always() => new Always(); + public static Parser Always() => Always(); /// /// Builds a parser that always succeeds. /// - public static Parser Always() => new Always(); + public static Parser Always() => new Always(default); /// /// Builds a parser that always succeeds. @@ -151,7 +156,7 @@ public Parser Number(NumberOptions numberOptions = NumberOptions.Number, c /// /// Builds a parser that matches an identifier. /// - public Parser Identifier(Func extraStart = null, Func extraPart = null) => new Identifier(extraStart, extraPart); + public Parser Identifier(Func? extraStart = null, Func? extraPart = null) => new Identifier(extraStart, extraPart); /// /// Builds a parser that matches a char against a predicate. @@ -191,24 +196,24 @@ public Parser Number(NumberOptions numberOptions = NumberOptions.Number, c /// /// Builds a parser that matches an integer with an option leading sign. /// - public Parser Integer(NumberOptions numberOptions = NumberOptions.Integer) => Parsers.SkipWhiteSpace(Number(numberOptions)); + public Parser Integer(NumberOptions numberOptions = NumberOptions.Integer) => Number(numberOptions); /// /// Builds a parser that matches a floating point number represented as a value. /// - public Parser Decimal(NumberOptions numberOptions = NumberOptions.Float) => Parsers.SkipWhiteSpace(Number(numberOptions)); + public Parser Decimal(NumberOptions numberOptions = NumberOptions.Float) => Number(numberOptions); /// /// Builds a parser that matches a floating point number represented as a value. /// [Obsolete("Prefer Number(NumberOptions.Float) instead.")] - public Parser Float(NumberOptions numberOptions = NumberOptions.Float) => Parsers.SkipWhiteSpace(Number(numberOptions)); + public Parser Float(NumberOptions numberOptions = NumberOptions.Float) => Number(numberOptions); /// /// Builds a parser that matches a floating point number represented as a value. /// [Obsolete("Prefer Number(NumberOptions.Float) instead.")] - public Parser Double(NumberOptions numberOptions = NumberOptions.Float) => Parsers.SkipWhiteSpace(Number(numberOptions)); + public Parser Double(NumberOptions numberOptions = NumberOptions.Float) => Number(numberOptions); /// /// Builds a parser that matches an quoted string that can be escaped. @@ -218,7 +223,7 @@ public Parser Number(NumberOptions numberOptions = NumberOptions.Number, c /// /// Builds a parser that matches an identifier. /// - public Parser Identifier(Func extraStart = null, Func extraPart = null) => Parsers.SkipWhiteSpace(new Identifier(extraStart, extraPart)); + public Parser Identifier(Func? extraStart = null, Func? extraPart = null) => Parsers.SkipWhiteSpace(new Identifier(extraStart, extraPart)); /// /// Builds a parser that matches a char against a predicate. diff --git a/src/Parlot/Fluent/PatternLiteral.cs b/src/Parlot/Fluent/PatternLiteral.cs index 2d369c6..0e06125 100644 --- a/src/Parlot/Fluent/PatternLiteral.cs +++ b/src/Parlot/Fluent/PatternLiteral.cs @@ -54,10 +54,7 @@ public override bool Parse(ParseContext context, ref ParseResult resul public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(TextSpan))); + var result = context.CreateCompilationResult(); // var start = context.Scanner.Cursor.Position; @@ -141,13 +138,13 @@ public CompilationResult Compile(CompilationContext context) Expression.Block( context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, + : Expression.Assign(result.Value, context.NewTextSpan( context.Buffer(), startOffset, Expression.Subtract(context.Offset(), startOffset) )), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) ) ) ); diff --git a/src/Parlot/Fluent/Separated.cs b/src/Parlot/Fluent/Separated.cs index f18232e..625703f 100644 --- a/src/Parlot/Fluent/Separated.cs +++ b/src/Parlot/Fluent/Separated.cs @@ -3,11 +3,14 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Reflection; namespace Parlot.Fluent { public sealed class Separated : Parser>, ICompilable, ISeekable { + private static readonly MethodInfo _listAddMethodInfo = typeof(List).GetMethod("Add")!; + private readonly Parser _separator; private readonly Parser _parser; @@ -34,7 +37,7 @@ public override bool Parse(ParseContext context, ref ParseResult results = null; + List? results = null; var start = 0; var end = context.Scanner.Cursor.Position; @@ -72,12 +75,12 @@ public override bool Parse(ParseContext context, ref ParseResult(); + results = []; start = parsed.Start; first = false; } - results.Add(parsed.Value); + results!.Add(parsed.Value); } result.Set(start, end.Offset, results ?? []); @@ -86,12 +89,9 @@ public override bool Parse(ParseContext context, ref ParseResult(), typeof(IReadOnlyList)); - - var results = context.DeclareVariable>(result, $"results{context.NextNumber}"); + var result = context.CreateCompilationResult>(false, ExpressionHelper.ArrayEmpty()); + var first = result.DeclareVariable($"first{context.NextNumber}", Expression.Constant(true)); + var results = result.DeclareVariable>($"results{context.NextNumber}"); var end = context.DeclarePositionVariable(result); @@ -107,7 +107,12 @@ public CompilationResult Compile(CompilationContext context) // if (parser1.Success) // { // success = true; - // if (results == null) results = new List(); + // if (first) + // { + // results = new List(); + // first = false; + // value = results; + // } // results.Add(parse1.Value); // end = currenPosition; // } @@ -144,15 +149,16 @@ public CompilationResult Compile(CompilationContext context) ? Expression.Empty() : Expression.Block( Expression.IfThen( - Expression.Equal(results, Expression.Constant(null, typeof(List))), + Expression.IsTrue(first), Expression.Block( + Expression.Assign(first, Expression.Constant(false)), Expression.Assign(results, ExpressionHelper.New>()), - Expression.Assign(value, results) + Expression.Assign(result.Value, results) ) ), - Expression.Call(results, typeof(List).GetMethod("Add"), parserCompileResult.Value) + Expression.Call(results, _listAddMethodInfo, parserCompileResult.Value) ), - Expression.Assign(success, Expression.Constant(true)), + Expression.Assign(result.Success, Expression.Constant(true)), Expression.Assign(end, context.Position()) ), Expression.Break(breakLabel) diff --git a/src/Parlot/Fluent/SequenceAndSkip.cs b/src/Parlot/Fluent/SequenceAndSkip.cs index 740f9fe..55423d7 100644 --- a/src/Parlot/Fluent/SequenceAndSkip.cs +++ b/src/Parlot/Fluent/SequenceAndSkip.cs @@ -1,11 +1,12 @@ using Parlot.Compilation; +using Parlot.Rewriting; using System; using System.Linq; using System.Linq.Expressions; namespace Parlot.Fluent { - public sealed class SequenceAndSkip : Parser, ICompilable, ISkippableSequenceParser + public sealed class SequenceAndSkip : Parser, ICompilable, ISkippableSequenceParser, ISeekable { internal readonly Parser _parser1; internal readonly Parser _parser2; @@ -14,8 +15,21 @@ public SequenceAndSkip(Parser parser1, Parser parser2) { _parser1 = parser1 ?? throw new ArgumentNullException(nameof(parser1)); _parser2 = parser2 ?? throw new ArgumentNullException(nameof(parser2)); + + if (_parser1 is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult result) { context.EnterParser(this); @@ -53,10 +67,7 @@ public CompilationResult Compile(CompilationContext context) { // The common skippable sequence compilation helper can't be reused since this doesn't return a tuple - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T1))); + var result = context.CreateCompilationResult(); // T value; // @@ -101,8 +112,8 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThenElse( parser2CompileResult.Success, Expression.Block( - context.DiscardResult ? Expression.Empty() : Expression.Assign(value, parser1CompileResult.Value), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) + context.DiscardResult ? Expression.Empty() : Expression.Assign(result.Value, parser1CompileResult.Value), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) ), context.ResetPosition(start) ) @@ -115,7 +126,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -127,8 +138,21 @@ Parser lastParser { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -174,7 +198,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -183,8 +207,21 @@ public SequenceAndSkip(Parser> parser, Parser lastPar { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -231,7 +268,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -240,8 +277,21 @@ public SequenceAndSkip(Parser> parser, Parser las { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -289,7 +339,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -298,8 +348,22 @@ public SequenceAndSkip(Parser> parser, Parser { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -349,7 +413,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -358,8 +422,22 @@ public SequenceAndSkip(Parser> parser, Parser { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -410,7 +488,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceAndSkip : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -419,8 +497,22 @@ public SequenceAndSkip(Parser> parser, Pa { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); diff --git a/src/Parlot/Fluent/SequenceCompileHelper.cs b/src/Parlot/Fluent/SequenceCompileHelper.cs index bf1b457..03ddf37 100644 --- a/src/Parlot/Fluent/SequenceCompileHelper.cs +++ b/src/Parlot/Fluent/SequenceCompileHelper.cs @@ -11,14 +11,11 @@ internal static class SequenceCompileHelper public static CompilationResult CreateSequenceCompileResult(SkippableCompilationResult[] parserCompileResults, CompilationContext context) { - var result = new CompilationResult(); - var nonSkippableResults = parserCompileResults.Where(x => !x.Skip).ToArray(); var parserTypes = nonSkippableResults.Select(x => x.CompilationResult.Value.Type).ToArray(); var resultType = GetValueTuple(nonSkippableResults.Length).MakeGenericType(parserTypes); - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.New(resultType)); + var result = context.CreateCompilationResult(resultType, false, Expression.New(resultType)); // var start = context.Scanner.Cursor.Position; @@ -50,18 +47,18 @@ static Type GetValueTuple(int length) 6 => typeof(ValueTuple<,,,,,>), 7 => typeof(ValueTuple<,,,,,,>), 8 => typeof(ValueTuple<,,,,,,,>), - _ => null + _ => throw new NotSupportedException("Unsupported number of type arguments") }; } - var valueTupleConstructor = resultType.GetConstructor(parserTypes); + var valueTupleConstructor = resultType.GetConstructor(parserTypes)!; // Initialize the block variable with the inner else statement var block = Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, Expression.New(valueTupleConstructor, nonSkippableResults.Select(x => x.CompilationResult.Value).ToArray())) + : Expression.Assign(result.Value, Expression.New(valueTupleConstructor, nonSkippableResults.Select(x => x.CompilationResult.Value).ToArray())) ); for (var i = parserCompileResults.Length - 1; i >= 0; i--) @@ -88,7 +85,7 @@ static Type GetValueTuple(int length) // } result.Body.Add(Expression.IfThen( - Expression.Not(success), + Expression.Not(result.Success), context.ResetPosition(start) )); diff --git a/src/Parlot/Fluent/SequenceSkipAnd.cs b/src/Parlot/Fluent/SequenceSkipAnd.cs index 69da6f9..f2d1135 100644 --- a/src/Parlot/Fluent/SequenceSkipAnd.cs +++ b/src/Parlot/Fluent/SequenceSkipAnd.cs @@ -1,11 +1,12 @@ using Parlot.Compilation; +using Parlot.Rewriting; using System; using System.Linq; using System.Linq.Expressions; namespace Parlot.Fluent { - public sealed class SequenceSkipAnd : Parser, ICompilable, ISkippableSequenceParser + public sealed class SequenceSkipAnd : Parser, ICompilable, ISkippableSequenceParser, ISeekable { internal readonly Parser _parser1; internal readonly Parser _parser2; @@ -14,8 +15,21 @@ public SequenceSkipAnd(Parser parser1, Parser parser2) { _parser1 = parser1 ?? throw new ArgumentNullException(nameof(parser1)); _parser2 = parser2 ?? throw new ArgumentNullException(nameof(parser2)); + + if (_parser1 is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult result) { context.EnterParser(this); @@ -53,10 +67,7 @@ public CompilationResult Compile(CompilationContext context) { // The common skippable sequence compilation helper can't be reused since this doesn't return a tuple - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T2))); + var result = context.CreateCompilationResult(); // T value; // @@ -101,8 +112,8 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThenElse( parser2CompileResult.Success, Expression.Block( - context.DiscardResult ? Expression.Empty() : Expression.Assign(value, parser2CompileResult.Value), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) + context.DiscardResult ? Expression.Empty() : Expression.Assign(result.Value, parser2CompileResult.Value), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) ), context.ResetPosition(start) ) @@ -115,7 +126,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -127,8 +138,21 @@ Parser lastParser { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -177,7 +201,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -186,8 +210,21 @@ public SequenceSkipAnd(Parser> parser, Parser lastPar { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -237,7 +274,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -246,8 +283,21 @@ public SequenceSkipAnd(Parser> parser, Parser las { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -298,7 +348,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -307,8 +357,21 @@ public SequenceSkipAnd(Parser> parser, Parser { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -361,7 +424,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -370,8 +433,21 @@ public SequenceSkipAnd(Parser> parser, Parser { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); @@ -425,7 +501,7 @@ public CompilationResult Compile(CompilationContext context) } } - public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser + public sealed class SequenceSkipAnd : Parser>, ICompilable, ISkippableSequenceParser, ISeekable { private readonly Parser> _parser; internal readonly Parser _lastParser; @@ -434,8 +510,21 @@ public SequenceSkipAnd(Parser> parser, Pa { _parser = parser; _lastParser = lastParser ?? throw new ArgumentNullException(nameof(lastParser)); + + if (_parser is ISeekable seekable) + { + CanSeek = seekable.CanSeek; + ExpectedChars = seekable.ExpectedChars; + SkipWhitespace = seekable.SkipWhitespace; + } } + public bool CanSeek { get; } + + public char[] ExpectedChars { get; } = []; + + public bool SkipWhitespace { get; } + public override bool Parse(ParseContext context, ref ParseResult> result) { context.EnterParser(this); diff --git a/src/Parlot/Fluent/SkipWhiteSpace.cs b/src/Parlot/Fluent/SkipWhiteSpace.cs index d2e43d3..53d8bbe 100644 --- a/src/Parlot/Fluent/SkipWhiteSpace.cs +++ b/src/Parlot/Fluent/SkipWhiteSpace.cs @@ -30,7 +30,16 @@ public override bool Parse(ParseContext context, ref ParseResult result) { context.EnterParser(this); - var start = context.Scanner.Cursor.Position; + var cursor = context.Scanner.Cursor; + + // If we know there is no custom whitespace parser we can skip the skipper by checking if the + // current char is not part of the common alphanumeric chars + if (context.WhiteSpaceParser is null && Character.IsInRange(cursor.Current, (char)33, (char)126)) + { + return _parser.Parse(context, ref result); + } + + var start = cursor.Position; // Use the scanner's logic to ignore whitespaces since it knows about multi-line grammars context.SkipWhiteSpace(); @@ -40,17 +49,14 @@ public override bool Parse(ParseContext context, ref ParseResult result) return true; } - context.Scanner.Cursor.ResetPosition(start); + cursor.ResetPosition(start); return false; } public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); var start = context.DeclarePositionVariable(result); @@ -59,15 +65,22 @@ public CompilationResult Compile(CompilationContext context) result.Body.Add( Expression.Block( parserCompileResult.Variables, - context.ParserSkipWhiteSpace(), - Expression.Block(parserCompileResult.Body), - Expression.IfThenElse( - parserCompileResult.Success, - Expression.Block( - context.DiscardResult ? Expression.Empty() : Expression.Assign(value, parserCompileResult.Value), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) - ), - context.ResetPosition(start) + Expression.IfThen( + test: Expression.IsFalse(Expression.And( + Expression.Equal(Expression.Call(context.ParseContext, ExpressionHelper.ParserContext_WhiteSpaceParser), Expression.Default(typeof(Parser))), + Expression.Invoke(ExpressionHelper.CharacterIsInRange, [ExpressionHelper.Cursor(context), Expression.Constant((char)33), Expression.Constant((char)126)]))), + ifTrue: context.ParserSkipWhiteSpace() + ), + Expression.Block( + Expression.Block(parserCompileResult.Body), + Expression.IfThenElse( + parserCompileResult.Success, + Expression.Block( + context.DiscardResult ? Expression.Empty() : Expression.Assign(result.Value, parserCompileResult.Value), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) + ), + context.ResetPosition(start) + ) ) ) ); diff --git a/src/Parlot/Fluent/StringLiteral.cs b/src/Parlot/Fluent/StringLiteral.cs index eb8fd51..84e0522 100644 --- a/src/Parlot/Fluent/StringLiteral.cs +++ b/src/Parlot/Fluent/StringLiteral.cs @@ -2,6 +2,7 @@ using Parlot.Rewriting; using System; using System.Linq.Expressions; +using System.Reflection; namespace Parlot.Fluent { @@ -14,6 +15,8 @@ public enum StringLiteralQuotes public sealed class StringLiteral : Parser, ICompilable, ISeekable { + private static readonly MethodInfo _decodeStringMethodInfo = typeof(Character).GetMethod("DecodeString", [typeof(TextSpan)])!; + static readonly char[] SingleQuotes = ['\'']; static readonly char[] DoubleQuotes = ['\"']; static readonly char[] SingleOrDoubleQuotes = ['\'', '\"']; @@ -71,10 +74,7 @@ public override bool Parse(ParseContext context, ref ParseResult resul public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(TextSpan))); + var result = context.CreateCompilationResult(); // var start = context.Scanner.Cursor.Offset; @@ -100,19 +100,17 @@ public CompilationResult Compile(CompilationContext context) var end = Expression.Variable(typeof(int), $"end{context.NextNumber}"); - var decodeStringMethodInfo = typeof(Character).GetMethod("DecodeString", [typeof(TextSpan)]); - result.Body.Add( Expression.IfThen( parseStringExpression, Expression.Block( [end], Expression.Assign(end, context.Offset()), - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, - Expression.Call(decodeStringMethodInfo, + : Expression.Assign(result.Value, + Expression.Call(_decodeStringMethodInfo, context.NewTextSpan( context.Buffer(), Expression.Add(start, Expression.Constant(1)), diff --git a/src/Parlot/Fluent/Switch.cs b/src/Parlot/Fluent/Switch.cs index 4a3b067..6d1eee7 100644 --- a/src/Parlot/Fluent/Switch.cs +++ b/src/Parlot/Fluent/Switch.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Reflection; namespace Parlot.Fluent { @@ -11,6 +12,7 @@ namespace Parlot.Fluent /// public sealed class Switch : Parser, ICompilable { + private static readonly MethodInfo _uParse = typeof(Parser).GetMethod("Parse", [typeof(ParseContext), typeof(ParseResult).MakeByRefType()])!; private readonly Parser _previousParser; private readonly Func> _action; @@ -49,10 +51,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(U))); + var result = context.CreateCompilationResult(); // previousParser instructions // @@ -88,15 +87,15 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThen( Expression.NotEqual(Expression.Constant(null, typeof(Parser)), nextParser), Expression.Block( - Expression.Assign(success, + Expression.Assign(result.Success, Expression.Call( nextParser, - typeof(Parser).GetMethod("Parse", [typeof(ParseContext), typeof(ParseResult).MakeByRefType()]), + _uParse, context.ParseContext, parseResult)), context.DiscardResult ? Expression.Empty() - : Expression.IfThen(success, Expression.Assign(value, Expression.Field(parseResult, "Value"))) + : Expression.IfThen(result.Success, Expression.Assign(result.Value, Expression.Field(parseResult, "Value"))) ) ) ) diff --git a/src/Parlot/Fluent/TextBefore.cs b/src/Parlot/Fluent/TextBefore.cs index ce36468..41def33 100644 --- a/src/Parlot/Fluent/TextBefore.cs +++ b/src/Parlot/Fluent/TextBefore.cs @@ -76,10 +76,7 @@ public override bool Parse(ParseContext context, ref ParseResult resul public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(TextSpan))); + var result = context.CreateCompilationResult(); // var start = context.Scanner.Cursor.Position; // @@ -159,8 +156,8 @@ public CompilationResult Compile(CompilationContext context) _canBeEmpty ? Expression.Empty() : Expression.IfThen(Expression.Equal(length, Expression.Constant(0)), Expression.Break(breakLabel)), - Expression.Assign(success, Expression.Constant(true)), - context.DiscardResult ? Expression.Empty() : Expression.Assign(value, context.NewTextSpan(context.Buffer(), context.Offset(start), length)), + Expression.Assign(result.Success, Expression.Constant(true)), + context.DiscardResult ? Expression.Empty() : Expression.Assign(result.Value, context.NewTextSpan(context.Buffer(), context.Offset(start), length)), Expression.Break(breakLabel) ) ), @@ -177,8 +174,8 @@ public CompilationResult Compile(CompilationContext context) _canBeEmpty ? Expression.Empty() : Expression.IfThen(Expression.Equal(length, Expression.Constant(0)), Expression.Break(breakLabel)), - Expression.Assign(success, Expression.Constant(true)), - context.DiscardResult ? Expression.Empty() : Expression.Assign(value, context.NewTextSpan(context.Buffer(), context.Offset(start), length)), + Expression.Assign(result.Success, Expression.Constant(true)), + context.DiscardResult ? Expression.Empty() : Expression.Assign(result.Value, context.NewTextSpan(context.Buffer(), context.Offset(start), length)), Expression.Break(breakLabel) ) ), diff --git a/src/Parlot/Fluent/TextLiteral.cs b/src/Parlot/Fluent/TextLiteral.cs index ac62811..b3236b4 100644 --- a/src/Parlot/Fluent/TextLiteral.cs +++ b/src/Parlot/Fluent/TextLiteral.cs @@ -15,7 +15,7 @@ public TextLiteral(string text, StringComparison comparisonType) { Text = text ?? throw new ArgumentNullException(nameof(text)); _comparisonType = comparisonType; - _hasNewLines = text.Any(x => Character.IsNewLine(x)); + _hasNewLines = text.Any(Character.IsNewLine); if (CanSeek = Text.Length > 0) { @@ -59,7 +59,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) var cursor = context.Scanner.Cursor; - if (cursor.Match(Text, _comparisonType)) + if (cursor.Match(Text.AsSpan(), _comparisonType)) { var start = cursor.Offset; @@ -81,10 +81,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(string))); + var result = context.CreateCompilationResult(); // if (context.Scanner.ReadText(Text, _comparer, null)) // { @@ -102,14 +99,14 @@ public CompilationResult Compile(CompilationContext context) Expression.Call( Expression.Field(context.ParseContext, "Scanner"), ExpressionHelper.Scanner_ReadText_NoResult, - Expression.Constant(Text, typeof(string)), + Expression.Call(ExpressionHelper.MemoryExtensions_AsSpan, Expression.Constant(Text)), Expression.Constant(_comparisonType, typeof(StringComparison)) ), Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, Expression.Constant(Text, typeof(string))) + : Expression.Assign(result.Value, Expression.Constant(Text, typeof(string))) ) ); diff --git a/src/Parlot/Fluent/Then.cs b/src/Parlot/Fluent/Then.cs index 68ab91a..97bd94c 100644 --- a/src/Parlot/Fluent/Then.cs +++ b/src/Parlot/Fluent/Then.cs @@ -14,9 +14,9 @@ namespace Parlot.Fluent /// The output parser type. public sealed class Then : Parser, ICompilable, ISeekable { - private readonly Func _action1; - private readonly Func _action2; - private readonly U _value = default; + private readonly Func? _action1; + private readonly Func? _action2; + private readonly U? _value = default; private readonly Parser _parser; private Then(Parser parser) @@ -70,7 +70,8 @@ public override bool Parse(ParseContext context, ref ParseResult result) } else { - result.Set(parsed.Start, parsed.End, _value); + // _value can't be null if action1 and action2 are null + result.Set(parsed.Start, parsed.End, _value!); } return true; @@ -81,10 +82,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(U))); + var result = context.CreateCompilationResult(false, Expression.Default(typeof(U))); // parse1 instructions // @@ -96,19 +94,25 @@ public CompilationResult Compile(CompilationContext context) var parserCompileResult = _parser.Build(context, requireResult: true); - Expression transformation; + Expression assignValue; if (_action1 != null) { - transformation = Expression.Invoke(Expression.Constant(_action1), new [] { parserCompileResult.Value }); + assignValue = context.DiscardResult + ? Expression.Invoke(Expression.Constant(_action1), [parserCompileResult.Value]) + : Expression.Assign(result.Value, Expression.Invoke(Expression.Constant(_action1), [parserCompileResult.Value])); } else if (_action2 != null) { - transformation = Expression.Invoke(Expression.Constant(_action2), context.ParseContext, parserCompileResult.Value); + assignValue = context.DiscardResult + ? Expression.Invoke(Expression.Constant(_action2), [context.ParseContext, parserCompileResult.Value]) + : Expression.Assign(result.Value, Expression.Invoke(Expression.Constant(_action2), [context.ParseContext, parserCompileResult.Value])); } else { - transformation = Expression.Constant(_value, typeof(U)); + assignValue = context.DiscardResult + ? Expression.Empty() + : Expression.Assign(result.Value, Expression.Constant(_value, typeof(U))); } var block = Expression.Block( @@ -118,10 +122,8 @@ public CompilationResult Compile(CompilationContext context) Expression.IfThen( parserCompileResult.Success, Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), - context.DiscardResult - ? Expression.Empty() - : Expression.Assign(value, transformation) + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), + assignValue ) ) ) diff --git a/src/Parlot/Fluent/When.cs b/src/Parlot/Fluent/When.cs index e611382..43058e2 100644 --- a/src/Parlot/Fluent/When.cs +++ b/src/Parlot/Fluent/When.cs @@ -38,10 +38,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(T))); + var result = context.CreateCompilationResult(); var parserCompileResult = _parser.Build(context, requireResult: true); @@ -73,10 +70,10 @@ public CompilationResult Compile(CompilationContext context) Expression.Invoke(Expression.Constant(_action), new[] { parserCompileResult.Value }) ), Expression.Block( - Expression.Assign(success, Expression.Constant(true, typeof(bool))), + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))), context.DiscardResult ? Expression.Empty() - : Expression.Assign(value, parserCompileResult.Value) + : Expression.Assign(result.Value, parserCompileResult.Value) ), context.ResetPosition(start) ) diff --git a/src/Parlot/Fluent/WhiteSpaceLiteral.cs b/src/Parlot/Fluent/WhiteSpaceLiteral.cs index 89464b2..553b9d2 100644 --- a/src/Parlot/Fluent/WhiteSpaceLiteral.cs +++ b/src/Parlot/Fluent/WhiteSpaceLiteral.cs @@ -41,10 +41,7 @@ public override bool Parse(ParseContext context, ref ParseResult resul public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, false); - var value = context.DeclareValueVariable(result, Expression.Default(typeof(TextSpan))); + var result = context.CreateCompilationResult(); var start = context.DeclareOffsetVariable(result); @@ -60,9 +57,9 @@ public CompilationResult Compile(CompilationContext context) Expression.Block( Expression.IfThen( Expression.NotEqual(start, end), - Expression.Assign(success, Expression.Constant(true, typeof(bool))) + Expression.Assign(result.Success, Expression.Constant(true, typeof(bool))) ), - context.DiscardResult ? Expression.Empty() : Expression.Assign(value, context.NewTextSpan(context.Buffer(), start, Expression.Subtract(end, start))) + context.DiscardResult ? Expression.Empty() : Expression.Assign(result.Value, context.NewTextSpan(context.Buffer(), start, Expression.Subtract(end, start))) ) ); diff --git a/src/Parlot/Fluent/ZeroOrMany.cs b/src/Parlot/Fluent/ZeroOrMany.cs index d978345..ef6783b 100644 --- a/src/Parlot/Fluent/ZeroOrMany.cs +++ b/src/Parlot/Fluent/ZeroOrMany.cs @@ -3,11 +3,14 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Reflection; namespace Parlot.Fluent { public sealed class ZeroOrMany : Parser>, ICompilable, ISeekable { + private static readonly MethodInfo _listAdd = typeof(List).GetMethod("Add")!; + private readonly Parser _parser; public ZeroOrMany(Parser parser) @@ -32,7 +35,7 @@ public override bool Parse(ParseContext context, ref ParseResult results = null; + List? results = null; var start = 0; var end = 0; @@ -57,18 +60,16 @@ public override bool Parse(ParseContext context, ref ParseResult)[]); return true; } public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var _ = context.DeclareSuccessVariable(result, true); - var value = context.DeclareValueVariable(result, ExpressionHelper.ArrayEmpty(), typeof(IReadOnlyList)); + var result = context.CreateCompilationResult>(true, ExpressionHelper.ArrayEmpty()); - var results = context.DeclareVariable>(result, $"results{context.NextNumber}"); + var results = result.DeclareVariable>($"results{context.NextNumber}"); + var first = result.DeclareVariable($"first{context.NextNumber}", Expression.Constant(true)); // success = true; // @@ -116,13 +117,14 @@ public CompilationResult Compile(CompilationContext context) ? Expression.Empty() : Expression.Block( Expression.IfThen( - Expression.Equal(results, Expression.Constant(null, typeof(List))), + Expression.IsTrue(first), Expression.Block( + Expression.Assign(first, Expression.Constant(false)), Expression.Assign(results, ExpressionHelper.New>()), - Expression.Assign(value, results) + Expression.Assign(result.Value, results) ) ), - Expression.Call(results, typeof(List).GetMethod("Add"), parserCompileResult.Value) + Expression.Call(results, _listAdd, parserCompileResult.Value) ), Expression.Break(breakLabel) ), diff --git a/src/Parlot/Fluent/ZeroOrOne.cs b/src/Parlot/Fluent/ZeroOrOne.cs index 96d42a4..fd577fc 100644 --- a/src/Parlot/Fluent/ZeroOrOne.cs +++ b/src/Parlot/Fluent/ZeroOrOne.cs @@ -10,7 +10,7 @@ public sealed class ZeroOrOne : Parser, ICompilable, ISeekable private readonly Parser _parser; private readonly T _defaultValue; - public ZeroOrOne(Parser parser, T defaultValue = default) + public ZeroOrOne(Parser parser, T defaultValue) { _parser = parser ?? throw new ArgumentNullException(nameof(parser)); _defaultValue = defaultValue; @@ -44,10 +44,7 @@ public override bool Parse(ParseContext context, ref ParseResult result) public CompilationResult Compile(CompilationContext context) { - var result = new CompilationResult(); - - var success = context.DeclareSuccessVariable(result, true); - var value = context.DeclareValueVariable(result, Expression.Constant(_defaultValue, typeof(T))); + var result = context.CreateCompilationResult(true, Expression.Constant(_defaultValue, typeof(T))); // T value = _defaultValue; // @@ -66,8 +63,8 @@ public CompilationResult Compile(CompilationContext context) ? Expression.Empty() : Expression.IfThenElse( parserCompileResult.Success, - Expression.Assign(value, parserCompileResult.Value), - Expression.Assign(value, Expression.Constant(_defaultValue, typeof(T))) + Expression.Assign(result.Value, parserCompileResult.Value), + Expression.Assign(result.Value, Expression.Constant(_defaultValue, typeof(T))) ) ) ); diff --git a/src/Parlot/Parlot.csproj b/src/Parlot/Parlot.csproj index 828240c..849fcc7 100644 --- a/src/Parlot/Parlot.csproj +++ b/src/Parlot/Parlot.csproj @@ -1,7 +1,7 @@  - net462;netstandard2.0;net6.0;net8.0 + net472;netstandard2.0;net6.0;net8.0 Parser combinator for .NET Parlot is a fast, lightweight and simple to use .NET parser combinator. parser;interpreter; @@ -11,6 +11,7 @@ true $(NoWarn);1591 true + enable diff --git a/src/Parlot/ParseError.cs b/src/Parlot/ParseError.cs index 0183179..b10f9fc 100644 --- a/src/Parlot/ParseError.cs +++ b/src/Parlot/ParseError.cs @@ -2,7 +2,7 @@ { public class ParseError { - public string Message { get; set; } + public string? Message { get; set; } public TextPosition Position { get; set; } } } diff --git a/src/Parlot/Scanner.cs b/src/Parlot/Scanner.cs index 5f73bd8..a8e9404 100644 --- a/src/Parlot/Scanner.cs +++ b/src/Parlot/Scanner.cs @@ -3,6 +3,7 @@ namespace Parlot { using Parlot.Fluent; + using System.Buffers; using System.Runtime.CompilerServices; /// @@ -10,6 +11,10 @@ namespace Parlot /// public class Scanner { +#if NET8_0_OR_GREATER + private static readonly SearchValues _decimalDigits = SearchValues.Create("0123456789"); +#endif + public readonly string Buffer; public readonly Cursor Cursor; @@ -117,10 +122,6 @@ public bool ReadIdentifier(out ReadOnlySpan result) return ReadFirstThenOthers(static x => Character.IsIdentifierStart(x), static x => Character.IsIdentifierPart(x), out result); } - public bool ReadBinaryNumber() => false; - - public bool ReadHexNumber() => false; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ReadDecimal() => ReadDecimal(out _); @@ -214,6 +215,35 @@ public bool ReadDecimal(bool allowLeadingSign, bool allowDecimalSeparator, bool [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ReadInteger() => ReadInteger(out _); +#if NET8_0_OR_GREATER + public bool ReadInteger(out ReadOnlySpan result) + { + var span = Cursor.Span; + + var noDigitIndex = span.IndexOfAnyExcept(_decimalDigits); + + // If first char is not a digit, fail + if (noDigitIndex == 0 || span.IsEmpty) + { + result = []; + return false; + } + + // If all chars are digits + if (noDigitIndex == -1) + { + result = span; + } + else + { + result = span[..noDigitIndex]; + } + + Cursor.AdvanceNoNewLines(result.Length); + + return true; + } +#else public bool ReadInteger(out ReadOnlySpan result) { var next = 0; @@ -234,6 +264,7 @@ public bool ReadInteger(out ReadOnlySpan result) return true; } +#endif /// /// Reads a token while the specific predicate is valid. @@ -319,12 +350,12 @@ public bool ReadChar(char c, out ReadOnlySpan result) /// Reads the specific expected text. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ReadText(string text, StringComparison comparisonType) => ReadText(text, comparisonType, out _); + public bool ReadText(ReadOnlySpan text, StringComparison comparisonType) => ReadText(text, comparisonType, out _); /// /// Reads the specific expected text. /// - public bool ReadText(string text, StringComparison comparisonType, out ReadOnlySpan result) + public bool ReadText(ReadOnlySpan text, StringComparison comparisonType, out ReadOnlySpan result) { if (!Cursor.Match(text, comparisonType)) { @@ -339,17 +370,76 @@ public bool ReadText(string text, StringComparison comparisonType, out ReadOnlyS return true; } + /// + /// Reads the specific expected chars. + /// + public bool ReadAnyOf(ReadOnlySpan chars, StringComparison comparisonType, out ReadOnlySpan result) + { + var current = Cursor.Buffer.AsSpan(Cursor.Offset, 1); + + var index = chars.IndexOf(current, comparisonType); + + if (index == -1) + { + result = []; + return false; + } + + int start = Cursor.Offset; + Cursor.Advance(index + 1); + result = Cursor.Buffer.AsSpan(start, index + 1); + + return true; + } + +#if NET8_0_OR_GREATER + /// + /// Reads the specific expected chars. + /// + /// + /// This overload uses as this shouldn't be created on every call. The actual implementation of + /// is chosen based on the constituents of the list. The caller should thus reuse the instance. + /// + public bool ReadAnyOf(SearchValues values, out ReadOnlySpan result) + { + var span = Cursor.Span; + + var notInRangeIndex = span.IndexOfAnyExcept(values); + + // If first char is not in range + if (notInRangeIndex == 0 || span.IsEmpty) + { + result = []; + return false; + } + + // All chars match + if (notInRangeIndex == -1) + { + result = span; + } + else + { + result = span[..notInRangeIndex]; + } + + Cursor.Advance(result.Length); + + return true; + } +#endif + /// /// Reads the specific expected text. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ReadText(string text) => ReadText(text, out _); + public bool ReadText(ReadOnlySpan text) => ReadText(text, out _); /// /// Reads the specific expected text. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ReadText(string text, out ReadOnlySpan result) => ReadText(text, comparisonType: StringComparison.Ordinal, out result); + public bool ReadText(ReadOnlySpan text, out ReadOnlySpan result) => ReadText(text, comparisonType: StringComparison.Ordinal, out result); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ReadSingleQuotedString() => ReadSingleQuotedString(out _); diff --git a/src/Parlot/TextSpan.cs b/src/Parlot/TextSpan.cs index b6a5757..189ec67 100644 --- a/src/Parlot/TextSpan.cs +++ b/src/Parlot/TextSpan.cs @@ -1,17 +1,18 @@ using System; +using System.Globalization; namespace Parlot { public readonly struct TextSpan : IEquatable, IEquatable { - public TextSpan(string value) + public TextSpan(string? value) { Buffer = value; Offset = 0; Length = value == null ? 0 : value.Length; } - public TextSpan(string buffer, int offset, int count) + public TextSpan(string? buffer, int offset, int count) { Buffer = buffer; Offset = offset; @@ -20,16 +21,16 @@ public TextSpan(string buffer, int offset, int count) public readonly int Length; public readonly int Offset; - public readonly string Buffer; + public readonly string? Buffer; - public ReadOnlySpan Span => Buffer == null ? ReadOnlySpan.Empty : Buffer.AsSpan(Offset, Length); + public ReadOnlySpan Span => Buffer == null ? [] : Buffer.AsSpan(Offset, Length); - public override string ToString() + public override string? ToString() { return Buffer?.Substring(Offset, Length); } - public bool Equals(string other) + public bool Equals(string? other) { if (other == null) { @@ -48,5 +49,29 @@ public static implicit operator TextSpan(string s) { return new TextSpan(s); } + + public override bool Equals(object? obj) + { + return obj is TextSpan t && Equals(t); + } + + public override int GetHashCode() + { +#if NET6_0_OR_GREATER + return CultureInfo.InvariantCulture.CompareInfo.GetHashCode(Span, CompareOptions.Ordinal); +#else + return (ToString() ?? "").GetHashCode(); +#endif + } + + public static bool operator ==(TextSpan left, TextSpan right) + { + return left.Equals(right); + } + + public static bool operator !=(TextSpan left, TextSpan right) + { + return !(left == right); + } } } diff --git a/src/Parlot/ThrowHelper.cs b/src/Parlot/ThrowHelper.cs deleted file mode 100644 index 24c70ce..0000000 --- a/src/Parlot/ThrowHelper.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -#if NET6_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif - -namespace Parlot -{ - internal static class ThrowHelper - { -#if NET6_0_OR_GREATER - [DoesNotReturn] -#endif - public static void ThrowArgumentNullException(string paramName) - { - throw new ArgumentNullException(paramName); - } - } -} diff --git a/test/Parlot.Benchmarks/ParlotBenchmarks.cs b/test/Parlot.Benchmarks/ParlotBenchmarks.cs index 7a025f2..ff013ad 100644 --- a/test/Parlot.Benchmarks/ParlotBenchmarks.cs +++ b/test/Parlot.Benchmarks/ParlotBenchmarks.cs @@ -169,5 +169,26 @@ public IJson WideJsonCompiled() { return _jsonBench.WideJson_ParlotCompiled(); } + + [Benchmark, BenchmarkCategory("Constructors")] + public Cursor CursorCtor() + { + return new Cursor("hello", TextPosition.Start); + } + + [Benchmark, BenchmarkCategory("Constructors")] + public Scanner ScannerCtor() + { + return new Scanner("hello"); + } + + private Scanner _scanner = new Scanner("hello"); + + [Benchmark, BenchmarkCategory("Constructors")] + public ParseContext ParseContextCtor() + { + return new ParseContext(_scanner); + } + } } diff --git a/test/Parlot.Tests/BenchmarksTests.cs b/test/Parlot.Tests/BenchmarksTests.cs index 0ce3342..1ae2f3a 100644 --- a/test/Parlot.Tests/BenchmarksTests.cs +++ b/test/Parlot.Tests/BenchmarksTests.cs @@ -211,7 +211,7 @@ public void WideJson() { var benchmarks = new JsonBench(); benchmarks.Setup(); - var result = benchmarks.WideJson_Parlot(); + benchmarks.WideJson_Parlot(); } [Fact] diff --git a/test/Parlot.Tests/Calc/FluentParserTests.cs b/test/Parlot.Tests/Calc/FluentParserTests.cs index 7a9ef30..244c5ea 100644 --- a/test/Parlot.Tests/Calc/FluentParserTests.cs +++ b/test/Parlot.Tests/Calc/FluentParserTests.cs @@ -1,5 +1,3 @@ -using Parlot.Fluent; - namespace Parlot.Tests.Calc { public class FluentParserTests : CalcTests diff --git a/test/Parlot.Tests/CompileTests.cs b/test/Parlot.Tests/CompileTests.cs index 9fdcee3..0699d9c 100644 --- a/test/Parlot.Tests/CompileTests.cs +++ b/test/Parlot.Tests/CompileTests.cs @@ -1,7 +1,6 @@ using Parlot.Fluent; using System; using System.Collections.Generic; -using System.Globalization; using System.Numerics; using Xunit; using static Parlot.Fluent.Parsers; @@ -122,7 +121,7 @@ public void ShouldCompileDeferreds() } [Fact] - public void ShouldCompileCyclicDeferreds() + public void ShouldCompileCyclicDeferred() { var openParen = Terms.Char('('); var closeParen = Terms.Char(')'); @@ -258,7 +257,7 @@ public void ShouldCompileExpressionParser() var result = parser.Parse("(2 + 1) * 3"); - Assert.Equal(9, result.Evaluate()); + Assert.Equal(9, result.Evaluate()); } [Fact] @@ -554,7 +553,7 @@ public void ShouldCompileSkipAndWithAnd() } [Fact] - public void BetweenCompiledShouldresetPosition() + public void BetweenCompiledShouldResetPosition() { Assert.True(Between(Terms.Char('['), Terms.Text("abcd"), Terms.Char(']')).Then(x => x.ToString()).Or(Literals.Text(" [abc").Compile()).TryParse(" [abc]", out var result1)); Assert.Equal(" [abc", result1); @@ -949,5 +948,17 @@ public void NumberParsesCustomGroupSeparator() { Assert.Equal((decimal)123456, Literals.Number(NumberOptions.Any, groupSeparator: '|').Compile().Parse("123|456")); } + + [Theory] + [InlineData("")] + [InlineData("+")] + [InlineData("+++")] + public void ZeroOrManyShouldSucceed(string source) + { + var parser = ZeroOrMany(Literals.Char('+')).Compile(); + + Assert.True(parser.TryParse(source, out var result)); + Assert.Equal(source.Length, result.Count); + } } } diff --git a/test/Parlot.Tests/CursorTests.cs b/test/Parlot.Tests/CursorTests.cs index aeadb87..58b3286 100644 --- a/test/Parlot.Tests/CursorTests.cs +++ b/test/Parlot.Tests/CursorTests.cs @@ -251,8 +251,6 @@ public void MatchAnyOfShouldMatchAny() { var c = new Cursor("1234"); - Assert.Throws(() => c.MatchAnyOf(null)); - Assert.True(c.MatchAnyOf("")); Assert.True(c.MatchAnyOf("1")); Assert.True(c.MatchAnyOf("abc1")); diff --git a/test/Parlot.Tests/FluentTests.cs b/test/Parlot.Tests/FluentTests.cs index 05e48f1..18c613b 100644 --- a/test/Parlot.Tests/FluentTests.cs +++ b/test/Parlot.Tests/FluentTests.cs @@ -1,5 +1,4 @@ using Parlot.Fluent; -using Parlot.Tests.Calc; using System; using System.Collections.Generic; using System.Globalization; diff --git a/test/Parlot.Tests/Json/JsonParserTests.cs b/test/Parlot.Tests/Json/JsonParserTests.cs index b45bdc7..896ffa1 100644 --- a/test/Parlot.Tests/Json/JsonParserTests.cs +++ b/test/Parlot.Tests/Json/JsonParserTests.cs @@ -1,4 +1,3 @@ -using Parlot.Fluent; using Xunit; namespace Parlot.Tests.Json diff --git a/test/Parlot.Tests/RewriteTests.cs b/test/Parlot.Tests/RewriteTests.cs index 6fa1b86..48f130f 100644 --- a/test/Parlot.Tests/RewriteTests.cs +++ b/test/Parlot.Tests/RewriteTests.cs @@ -66,18 +66,6 @@ public void OneOfShouldRewriteAllSeekableCompiled() Assert.Null(helloOrGoodbye.Parse("yo!")); } - [Fact] - public void OneOfShouldRewriteAllSeekableSkipwhiteSpaceCompiled() - { - var hello = new FakeSeekable { CanSeek = true, ExpectedChars = new[] { 'a' }, SkipWhitespace = false, Success = true, Text = "hello" }; - var goodbye = new FakeSeekable { CanSeek = true, ExpectedChars = new[] { 'b' }, SkipWhitespace = false, Success = true, Text = "goodbye" }; - var oneof = Parsers.OneOf(hello, goodbye).Compile(); - - Assert.Equal("hello", oneof.Parse("a")); - Assert.Equal("goodbye", oneof.Parse("b")); - Assert.Null(oneof.Parse("hello")); - } - [Fact] public void OneOfShouldNotRewriteIfOneIsNotSeekable() {