Skip to content

Commit bf213e6

Browse files
authored
Use ParsingConfig.IsCaseSensitive setting in TextParser and KeywordsHelper (#864)
1 parent 87767cb commit bf213e6

File tree

4 files changed

+132
-51
lines changed

4 files changed

+132
-51
lines changed

src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs

+21-19
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,8 @@ internal class KeywordsHelper : IKeywordsHelper
2525

2626
private readonly ParsingConfig _config;
2727

28-
// Keywords are IgnoreCase
29-
private readonly Dictionary<string, AnyOf<string, Expression, Type>> _keywordMapping = new(StringComparer.OrdinalIgnoreCase)
30-
{
31-
{ "true", Expression.Constant(true) },
32-
{ "false", Expression.Constant(false) },
33-
{ "null", Expression.Constant(null) },
34-
35-
{ SYMBOL_IT, SYMBOL_IT },
36-
{ SYMBOL_PARENT, SYMBOL_PARENT },
37-
{ SYMBOL_ROOT, SYMBOL_ROOT },
38-
39-
{ FUNCTION_IIF, FUNCTION_IIF },
40-
{ FUNCTION_ISNULL, FUNCTION_ISNULL },
41-
{ FUNCTION_NEW, FUNCTION_NEW },
42-
{ FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION },
43-
{ FUNCTION_IS, FUNCTION_IS },
44-
{ FUNCTION_AS, FUNCTION_AS },
45-
{ FUNCTION_CAST, FUNCTION_CAST }
46-
};
28+
// Keywords compare case depends on the value from ParsingConfig.IsCaseSensitive
29+
private readonly Dictionary<string, AnyOf<string, Expression, Type>> _keywordMapping;
4730

4831
// PreDefined Types are not IgnoreCase
4932
private static readonly Dictionary<string, Type> PreDefinedTypeMapping = new();
@@ -64,6 +47,25 @@ public KeywordsHelper(ParsingConfig config)
6447
{
6548
_config = Check.NotNull(config);
6649

50+
_keywordMapping = new(config.IsCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase)
51+
{
52+
{ "true", Expression.Constant(true) },
53+
{ "false", Expression.Constant(false) },
54+
{ "null", Expression.Constant(null) },
55+
56+
{ SYMBOL_IT, SYMBOL_IT },
57+
{ SYMBOL_PARENT, SYMBOL_PARENT },
58+
{ SYMBOL_ROOT, SYMBOL_ROOT },
59+
60+
{ FUNCTION_IIF, FUNCTION_IIF },
61+
{ FUNCTION_ISNULL, FUNCTION_ISNULL },
62+
{ FUNCTION_NEW, FUNCTION_NEW },
63+
{ FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION },
64+
{ FUNCTION_IS, FUNCTION_IS },
65+
{ FUNCTION_AS, FUNCTION_AS },
66+
{ FUNCTION_CAST, FUNCTION_CAST }
67+
};
68+
6769
if (config.AreContextKeywordsEnabled)
6870
{
6971
_keywordMapping.Add(KEYWORD_IT, KEYWORD_IT);

src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs

+30-28
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,14 @@ public class TextParser
1212
private const char DefaultNumberDecimalSeparator = '.';
1313
private static readonly char[] EscapeCharacters = { '\\', 'a', 'b', 'f', 'n', 'r', 't', 'v' };
1414

15-
// These aliases are supposed to simply the where clause and make it more human readable
16-
private static readonly Dictionary<string, TokenId> PredefinedOperatorAliases = new(StringComparer.OrdinalIgnoreCase)
17-
{
18-
{ "eq", TokenId.Equal },
19-
{ "equal", TokenId.Equal },
20-
{ "ne", TokenId.ExclamationEqual },
21-
{ "notequal", TokenId.ExclamationEqual },
22-
{ "neq", TokenId.ExclamationEqual },
23-
{ "lt", TokenId.LessThan },
24-
{ "LessThan", TokenId.LessThan },
25-
{ "le", TokenId.LessThanEqual },
26-
{ "LessThanEqual", TokenId.LessThanEqual },
27-
{ "gt", TokenId.GreaterThan },
28-
{ "GreaterThan", TokenId.GreaterThan },
29-
{ "ge", TokenId.GreaterThanEqual },
30-
{ "GreaterThanEqual", TokenId.GreaterThanEqual },
31-
{ "and", TokenId.DoubleAmpersand },
32-
{ "AndAlso", TokenId.DoubleAmpersand },
33-
{ "or", TokenId.DoubleBar },
34-
{ "OrElse", TokenId.DoubleBar },
35-
{ "not", TokenId.Exclamation },
36-
{ "mod", TokenId.Percent }
37-
};
38-
3915
private readonly char _numberDecimalSeparator;
4016
private readonly string _text;
4117
private readonly int _textLen;
4218
private readonly ParsingConfig _parsingConfig;
4319

20+
// These aliases simplify the "Where"-clause and make it more human-readable.
21+
private readonly Dictionary<string, TokenId> _predefinedOperatorAliases;
22+
4423
private int _textPos;
4524
private char _ch;
4625

@@ -58,6 +37,29 @@ public TextParser(ParsingConfig config, string text)
5837
{
5938
_parsingConfig = config;
6039

40+
_predefinedOperatorAliases = new(config.IsCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase)
41+
{
42+
{ "eq", TokenId.Equal },
43+
{ "equal", TokenId.Equal },
44+
{ "ne", TokenId.ExclamationEqual },
45+
{ "notequal", TokenId.ExclamationEqual },
46+
{ "neq", TokenId.ExclamationEqual },
47+
{ "lt", TokenId.LessThan },
48+
{ "LessThan", TokenId.LessThan },
49+
{ "le", TokenId.LessThanEqual },
50+
{ "LessThanEqual", TokenId.LessThanEqual },
51+
{ "gt", TokenId.GreaterThan },
52+
{ "GreaterThan", TokenId.GreaterThan },
53+
{ "ge", TokenId.GreaterThanEqual },
54+
{ "GreaterThanEqual", TokenId.GreaterThanEqual },
55+
{ "and", TokenId.DoubleAmpersand },
56+
{ "AndAlso", TokenId.DoubleAmpersand },
57+
{ "or", TokenId.DoubleBar },
58+
{ "OrElse", TokenId.DoubleBar },
59+
{ "not", TokenId.Exclamation },
60+
{ "mod", TokenId.Percent }
61+
};
62+
6163
_numberDecimalSeparator = config.NumberParseCulture?.NumberFormat.NumberDecimalSeparator[0] ?? DefaultNumberDecimalSeparator;
6264

6365
_text = text;
@@ -529,14 +531,14 @@ private Exception ParseError(string format, params object[] args)
529531
return ParseError(CurrentToken.Pos, format, args);
530532
}
531533

532-
private static Exception ParseError(int pos, string format, params object[] args)
534+
private TokenId GetAliasedTokenId(TokenId tokenId, string alias)
533535
{
534-
return new ParseException(string.Format(CultureInfo.CurrentCulture, format, args), pos);
536+
return tokenId == TokenId.Identifier && _predefinedOperatorAliases.TryGetValue(alias, out TokenId id) ? id : tokenId;
535537
}
536538

537-
private static TokenId GetAliasedTokenId(TokenId tokenId, string alias)
539+
private static Exception ParseError(int pos, string format, params object[] args)
538540
{
539-
return tokenId == TokenId.Identifier && PredefinedOperatorAliases.TryGetValue(alias, out TokenId id) ? id : tokenId;
541+
return new ParseException(string.Format(CultureInfo.CurrentCulture, format, args), pos);
540542
}
541543

542544
private static bool IsHexChar(char c)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Linq.Dynamic.Core.CustomTypeProviders;
4+
using FluentAssertions;
5+
using Xunit;
6+
7+
namespace System.Linq.Dynamic.Core.Tests.CustomTypeProviders
8+
{
9+
[SuppressMessage("ReSharper", "InconsistentNaming")]
10+
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
11+
public class CustomTypeProviderTests
12+
{
13+
private readonly ParsingConfig _config = new()
14+
{
15+
CustomTypeProvider = new MyCustomTypeProvider(ParsingConfig.Default, typeof(IS), typeof(IS.NOT), typeof(IST), typeof(IST.NOT)),
16+
IsCaseSensitive = true
17+
};
18+
19+
[Theory]
20+
[InlineData("IS.NULL(null)", true)]
21+
[InlineData("IS.NOT.NULL(null)", false)]
22+
[InlineData("IST.NULL(null)", true)]
23+
[InlineData("IST.NOT.NULL(null)", false)]
24+
public void Test(string expression, bool expected)
25+
{
26+
// Act 1
27+
var lambdaExpression = DynamicExpressionParser.ParseLambda(
28+
_config,
29+
false,
30+
[],
31+
typeof(bool),
32+
expression
33+
);
34+
35+
// Act 2
36+
var result = (bool?)lambdaExpression.Compile().DynamicInvoke();
37+
38+
// Assert
39+
result.Should().Be(expected);
40+
}
41+
42+
public class MyCustomTypeProvider : DefaultDynamicLinqCustomTypeProvider
43+
{
44+
private readonly HashSet<Type> _types;
45+
46+
public MyCustomTypeProvider(ParsingConfig config, params Type[] types) : base(config)
47+
{
48+
_types = new HashSet<Type>(types);
49+
}
50+
51+
public override HashSet<Type> GetCustomTypes()
52+
{
53+
return _types;
54+
}
55+
}
56+
57+
public static class IS
58+
{
59+
public static bool NULL(object? value) => value == null;
60+
61+
public static class NOT
62+
{
63+
public static bool NULL(object? value) => value != null;
64+
}
65+
}
66+
67+
public static class IST
68+
{
69+
public static bool NULL(object? value) => value == null;
70+
71+
public static class NOT
72+
{
73+
public static bool NULL(object? value) => value != null;
74+
}
75+
}
76+
}
77+
}

test/System.Linq.Dynamic.Core.Tests/DefaultDynamicLinqCustomTypeProviderTests.cs test/System.Linq.Dynamic.Core.Tests/CustomTypeProviders/DefaultDynamicLinqCustomTypeProviderTests.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
using NFluent;
55
using Xunit;
66

7-
namespace System.Linq.Dynamic.Core.Tests;
7+
namespace System.Linq.Dynamic.Core.Tests.CustomTypeProviders;
88

99
public class DefaultDynamicLinqCustomTypeProviderTests
1010
{
11-
private readonly IDynamicLinkCustomTypeProvider _sut;
11+
private readonly DefaultDynamicLinqCustomTypeProvider _sut;
1212

1313
public DefaultDynamicLinqCustomTypeProviderTests()
1414
{
@@ -19,7 +19,7 @@ public DefaultDynamicLinqCustomTypeProviderTests()
1919
public void DefaultDynamicLinqCustomTypeProvider_ResolveSystemType()
2020
{
2121
// Act
22-
var type = _sut.ResolveType(typeof(DirectoryInfo).FullName);
22+
var type = _sut.ResolveType(typeof(DirectoryInfo).FullName!);
2323

2424
// Assert
2525
type.Should().Be(typeof(DirectoryInfo));
@@ -49,7 +49,7 @@ public void DefaultDynamicLinqCustomTypeProvider_ResolveType_UnknownReturnsNull(
4949
public void DefaultDynamicLinqCustomTypeProvider_ResolveType_DefinedReturnsType()
5050
{
5151
// Act
52-
var result = _sut.ResolveType(typeof(DefaultDynamicLinqCustomTypeProviderTests).FullName);
52+
var result = _sut.ResolveType(typeof(DefaultDynamicLinqCustomTypeProviderTests).FullName!);
5353

5454
// Assert
5555
Check.That(result).IsNotNull();

0 commit comments

Comments
 (0)