From 3c77265891e7bb9e7781e594d904dd6bae3cd7a7 Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri <7004080+alirezanet@users.noreply.github.com> Date: Tue, 19 Oct 2021 19:34:11 +0330 Subject: [PATCH] v2.3.1 (#31) * update benchmark results * refactor field indexing * fix build warnings (nullable type) * using lexer instead of regex to parse fieldIndexTokens * update to v2.3.1 --- README.md | 15 ++--- .../LibraryComparisionFilteringBenchmark.cs | 2 +- .../Gridify.EntityFramework.csproj | 2 +- src/Gridify/Gridify.csproj | 2 +- src/Gridify/GridifyMapper.cs | 4 +- src/Gridify/IGridifyMapper.cs | 2 +- src/Gridify/Syntax/FieldExpressionSyntax.cs | 24 +++----- src/Gridify/Syntax/Lexer.cs | 59 +++++++++++++++++-- src/Gridify/Syntax/Parser.cs | 41 ++++++++----- src/Gridify/Syntax/SyntaxKind.cs | 3 +- 10 files changed, 106 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 0839d83b..97592184 100644 --- a/README.md +++ b/README.md @@ -90,16 +90,17 @@ Also, i Should note other features like Pagination and Sorting have almost zero BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1237 (21H1/May2021Update) 11th Gen Intel Core i5-11400F 2.60GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK=5.0.301 +[Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated | -|------------ |-----------:|---------:|---------:|------:|--------:|--------:|--------:|----------:| -| Native LINQ | 724.329 us | 6.4686 us | 6.0507 us | 1.000 | 0.00 | 5.8594 | 2.9297 | 37,392 B | -| Gridify | 736.854 us | 5.7427 us | 5.0907 us | 1.018 | 0.01 | 5.8594 | 2.9297 | 39,924 B | -| DynamicLinq | 886.773 us | 8.4943 us | 7.5299 us | 1.225 | 0.01 | 19.5313 | 9.7656 | 125,122 B | -| Sieve | 961.574 us | 6.6389 us | 5.8852 us | 1.328 | 0.01 | 8.7891 | 3.9063 | 55,186 B | -| Fop | 2,933.332 us | 49.7515 us | 61.0994 us | 4.076 | 0.11 | 46.8750 | 23.4375 | 315,830 B | +| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Allocated | +|------------ |-----------:|---------:|---------:|------:|--------:|--------:|----------:| +| Native LINQ | 740.9 us | 7.80 us | 6.92 us | 1.00 | 5.8594 | 2.9297 | 37 KB | +| Gridify | 762.6 us | 10.06 us | 9.41 us | 1.03 | 5.8594 | 2.9297 | 39 KB | +| DynamicLinq | 902.1 us | 11.56 us | 10.81 us | 1.22 | 19.5313 | 9.7656 | 122 KB | +| Sieve | 977.9 us | 6.80 us | 6.37 us | 1.32 | 7.8125 | 3.9063 | 54 KB | +| Fop | 2,959.8 us | 39.11 us | 36.58 us | 3.99 | 46.8750 | 23.4375 | 306 KB | --- ## Installation diff --git a/benchmark/LibraryComparisionFilteringBenchmark.cs b/benchmark/LibraryComparisionFilteringBenchmark.cs index 89706988..00117fb2 100644 --- a/benchmark/LibraryComparisionFilteringBenchmark.cs +++ b/benchmark/LibraryComparisionFilteringBenchmark.cs @@ -63,7 +63,7 @@ public void Gridify() Ds.ApplyFiltering("Name=Ali", gm).Consume(Consumer); } - [Benchmark] // compiled query (this is not included in our readme benchmarks)w + // [Benchmark] // compiled query (this is not included in our readme benchmarks)w public void GridifyCompiled() { EnumerableDs.Where(compiled1).Consume(Consumer); diff --git a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj index 8f719dfa..3dae83c4 100644 --- a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj +++ b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj @@ -9,7 +9,7 @@ netstandard2.0 Gridify.EntityFramework - 2.3.0 + 2.3.1 Alireza Sabouri TuxTeam Gridify (EntityFramework), Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify/Gridify.csproj b/src/Gridify/Gridify.csproj index 23a18b3b..ce08fce2 100644 --- a/src/Gridify/Gridify.csproj +++ b/src/Gridify/Gridify.csproj @@ -3,7 +3,7 @@ netstandard2.0 Gridify - 2.3.0 + 2.3.1 Alireza Sabouri TuxTeam Gridify, Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify/GridifyMapper.cs b/src/Gridify/GridifyMapper.cs index 1212c64e..af2f9e77 100644 --- a/src/Gridify/GridifyMapper.cs +++ b/src/Gridify/GridifyMapper.cs @@ -118,14 +118,14 @@ public LambdaExpression GetLambdaExpression(string key) return expression!; } - public Expression> GetExpression(string key) + public Expression> GetExpression(string key) { var expression = Configuration.CaseSensitive ? _mappings.FirstOrDefault(q => key.Equals(q.From))?.To : _mappings.FirstOrDefault(q => key.Equals(q.From, StringComparison.InvariantCultureIgnoreCase))?.To; if (expression == null) throw new GridifyMapperException($"Mapping Key `{key}` not found."); - return expression as Expression> ?? throw new GridifyMapperException($"Expression fir the `{key}` not found."); + return expression as Expression> ?? throw new GridifyMapperException($"Expression fir the `{key}` not found."); } public IEnumerable> GetCurrentMaps() diff --git a/src/Gridify/IGridifyMapper.cs b/src/Gridify/IGridifyMapper.cs index 512aa211..e17e77f4 100644 --- a/src/Gridify/IGridifyMapper.cs +++ b/src/Gridify/IGridifyMapper.cs @@ -16,7 +16,7 @@ IGridifyMapper AddMap(string from, Expression> to, Func IGridifyMapper RemoveMap(string propertyName); IGridifyMapper RemoveMap(IGMap gMap); LambdaExpression GetLambdaExpression(string from); - Expression> GetExpression(string key); + Expression> GetExpression(string key); IGMap? GetGMap(string from); bool HasMap(string key); public GridifyMapperConfiguration Configuration { get; } diff --git a/src/Gridify/Syntax/FieldExpressionSyntax.cs b/src/Gridify/Syntax/FieldExpressionSyntax.cs index 55b03173..03ad6ad5 100644 --- a/src/Gridify/Syntax/FieldExpressionSyntax.cs +++ b/src/Gridify/Syntax/FieldExpressionSyntax.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; +using System.Collections.Generic; namespace Gridify.Syntax { @@ -8,21 +6,15 @@ internal sealed class FieldExpressionSyntax : ExpressionSyntax { internal FieldExpressionSyntax(SyntaxToken fieldToken) { - // for performance reason we simply check the last character first - if (fieldToken.Text.EndsWith("]")) - { - // checking indexes from the field names - var regex = new Regex(@"(\w+)\[(\d+)\]"); - var match = regex.Match(fieldToken.Text); - if (!match.Success) throw new ArgumentException($"Invalid filed name '{fieldToken.Text}'"); - IsCollection = true; - Index = int.Parse(match.Groups[2].Value); - FieldToken = new SyntaxToken(SyntaxKind.FieldToken, 0, match.Groups[1].Value); - } - else - FieldToken = fieldToken; + FieldToken = fieldToken; } + public FieldExpressionSyntax(SyntaxToken fieldToken, int index) + { + IsCollection = true; + Index = index; + FieldToken = fieldToken; + } public override SyntaxKind Kind => SyntaxKind.FieldExpression; diff --git a/src/Gridify/Syntax/Lexer.cs b/src/Gridify/Syntax/Lexer.cs index 8f5db06b..356ffc8a 100644 --- a/src/Gridify/Syntax/Lexer.cs +++ b/src/Gridify/Syntax/Lexer.cs @@ -46,44 +46,95 @@ public SyntaxToken NextToken() case '|': return new SyntaxToken(SyntaxKind.Or, _position++, "|"); case '^': + { + _waitingForValue = true; return new SyntaxToken(SyntaxKind.StartsWith, _position++, "^"); + } case '$': + { + _waitingForValue = true; return new SyntaxToken(SyntaxKind.EndsWith, _position++, "$"); + } case '!' when peek == '^': + { + _waitingForValue = true; return new SyntaxToken(SyntaxKind.NotStartsWith, _position += 2, "!^"); + } case '!' when peek == '$': + { + _waitingForValue = true; return new SyntaxToken(SyntaxKind.NotEndsWith, _position += 2, "!$"); + } case '=' when peek == '*': + { + _waitingForValue = true; return new SyntaxToken(SyntaxKind.Like, _position += 2, "=*"); - case '=' : + } + case '=': + { + _waitingForValue = true; return new SyntaxToken(SyntaxKind.Equal, _position ++ , "="); + } case '!' when peek == '=': + { + _waitingForValue = true; return new SyntaxToken(SyntaxKind.NotEqual, _position += 2, "!="); + } case '!' when peek == '*': + { + _waitingForValue = true; return new SyntaxToken(SyntaxKind.NotLike, _position += 2, "!*"); + } case '/' when peek == 'i': return new SyntaxToken(SyntaxKind.CaseInsensitive, _position += 2, "/i"); case '<': + { + _waitingForValue = true; return peek == '=' ? new SyntaxToken(SyntaxKind.LessOrEqualThan, _position += 2, "<=") : new SyntaxToken(SyntaxKind.LessThan, _position++, "<"); + } case '>': + { + _waitingForValue = true; return peek == '=' ? new SyntaxToken(SyntaxKind.GreaterOrEqualThan, _position += 2, ">=") : new SyntaxToken(SyntaxKind.GreaterThan, _position++, ">"); + } + } + + if (Current == '[') + { + Next(); + var start = _position; + while (char.IsDigit(Current)) + Next(); + + var length = _position - start; + var text = _text.Substring(start, length); + + if (Current == ']') + { + _position++; + return new SyntaxToken(SyntaxKind.FieldIndexToken, start, text); + } + + _diagnostics.Add($"bad character input: '{peek.ToString()}' at {_position++.ToString()}. expected ']' "); + return new SyntaxToken(SyntaxKind.BadToken, _position, Current.ToString()); + } if (char.IsLetter(Current) && !_waitingForValue) { var start = _position; - while (char.IsLetterOrDigit(Current) || Current is '_' or '[' or ']') + while (char.IsLetterOrDigit(Current) || Current is '_') Next(); var length = _position - start; var text = _text.Substring(start, length); - - _waitingForValue = true; + return new SyntaxToken(SyntaxKind.FieldToken, start, text); } + if (char.IsWhiteSpace(Current)) { diff --git a/src/Gridify/Syntax/Parser.cs b/src/Gridify/Syntax/Parser.cs index 12137f91..d2d10978 100644 --- a/src/Gridify/Syntax/Parser.cs +++ b/src/Gridify/Syntax/Parser.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; namespace Gridify.Syntax { @@ -92,10 +94,10 @@ private ExpressionSyntax ParseValueExpression() // field= if (Current.Kind != SyntaxKind.ValueToken) return new ValueExpressionSyntax(new SyntaxToken(), false, true); - + var valueToken = Match(SyntaxKind.ValueToken); - var isCaseInsensitive = IsMatch(SyntaxKind.CaseInsensitive); - return new ValueExpressionSyntax(valueToken, isCaseInsensitive,false); + var isCaseInsensitive = IsMatch(SyntaxKind.CaseInsensitive, out _); + return new ValueExpressionSyntax(valueToken, isCaseInsensitive, false); } private SyntaxToken NextToken() @@ -105,10 +107,15 @@ private SyntaxToken NextToken() return current; } - private bool IsMatch(SyntaxKind kind) + private bool IsMatch(SyntaxKind kind, out SyntaxToken token) { - if (Current.Kind != kind) return false; - NextToken(); + if (Current.Kind != kind) + { + token = Current; + return false; + } + + token = NextToken(); return true; } @@ -123,15 +130,21 @@ private SyntaxToken Match(SyntaxKind kind) private ExpressionSyntax ParsePrimaryExpression() { - if (Current.Kind == SyntaxKind.OpenParenthesisToken) - { - var left = NextToken(); - var expression = ParseTerm(); - var right = Match(SyntaxKind.CloseParenthesis); - return new ParenthesizedExpressionSyntax(left, expression, right); - } + if (Current.Kind != SyntaxKind.OpenParenthesisToken) return ParseFieldExpression(); + var left = NextToken(); + var expression = ParseTerm(); + var right = Match(SyntaxKind.CloseParenthesis); + return new ParenthesizedExpressionSyntax(left, expression, right); + } + + private ExpressionSyntax ParseFieldExpression() + { var fieldToken = Match(SyntaxKind.FieldToken); + + if (IsMatch(SyntaxKind.FieldIndexToken, out SyntaxToken fieldSyntaxToken)) + return new FieldExpressionSyntax(fieldToken, int.Parse(fieldSyntaxToken.Text)); + return new FieldExpressionSyntax(fieldToken); } } diff --git a/src/Gridify/Syntax/SyntaxKind.cs b/src/Gridify/Syntax/SyntaxKind.cs index ad3d09c5..fac1a7a7 100644 --- a/src/Gridify/Syntax/SyntaxKind.cs +++ b/src/Gridify/Syntax/SyntaxKind.cs @@ -31,6 +31,7 @@ public enum SyntaxKind ParenthesizedExpression, NotStartsWith, NotEndsWith, - CaseInsensitive + CaseInsensitive, + FieldIndexToken } } \ No newline at end of file