Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ParsingConfig.IsCaseSensitive setting in TextParser and KeywordsHelper #864

Merged
merged 1 commit into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 21 additions & 19 deletions src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,8 @@ internal class KeywordsHelper : IKeywordsHelper

private readonly ParsingConfig _config;

// Keywords are IgnoreCase
private readonly Dictionary<string, AnyOf<string, Expression, Type>> _keywordMapping = new(StringComparer.OrdinalIgnoreCase)
{
{ "true", Expression.Constant(true) },
{ "false", Expression.Constant(false) },
{ "null", Expression.Constant(null) },

{ SYMBOL_IT, SYMBOL_IT },
{ SYMBOL_PARENT, SYMBOL_PARENT },
{ SYMBOL_ROOT, SYMBOL_ROOT },

{ FUNCTION_IIF, FUNCTION_IIF },
{ FUNCTION_ISNULL, FUNCTION_ISNULL },
{ FUNCTION_NEW, FUNCTION_NEW },
{ FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION },
{ FUNCTION_IS, FUNCTION_IS },
{ FUNCTION_AS, FUNCTION_AS },
{ FUNCTION_CAST, FUNCTION_CAST }
};
// Keywords compare case depends on the value from ParsingConfig.IsCaseSensitive
private readonly Dictionary<string, AnyOf<string, Expression, Type>> _keywordMapping;

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

_keywordMapping = new(config.IsCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase)
{
{ "true", Expression.Constant(true) },
{ "false", Expression.Constant(false) },
{ "null", Expression.Constant(null) },

{ SYMBOL_IT, SYMBOL_IT },
{ SYMBOL_PARENT, SYMBOL_PARENT },
{ SYMBOL_ROOT, SYMBOL_ROOT },

{ FUNCTION_IIF, FUNCTION_IIF },
{ FUNCTION_ISNULL, FUNCTION_ISNULL },
{ FUNCTION_NEW, FUNCTION_NEW },
{ FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION },
{ FUNCTION_IS, FUNCTION_IS },
{ FUNCTION_AS, FUNCTION_AS },
{ FUNCTION_CAST, FUNCTION_CAST }
};

if (config.AreContextKeywordsEnabled)
{
_keywordMapping.Add(KEYWORD_IT, KEYWORD_IT);
Expand Down
58 changes: 30 additions & 28 deletions src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,14 @@ public class TextParser
private const char DefaultNumberDecimalSeparator = '.';
private static readonly char[] EscapeCharacters = { '\\', 'a', 'b', 'f', 'n', 'r', 't', 'v' };

// These aliases are supposed to simply the where clause and make it more human readable
private static readonly Dictionary<string, TokenId> PredefinedOperatorAliases = new(StringComparer.OrdinalIgnoreCase)
{
{ "eq", TokenId.Equal },
{ "equal", TokenId.Equal },
{ "ne", TokenId.ExclamationEqual },
{ "notequal", TokenId.ExclamationEqual },
{ "neq", TokenId.ExclamationEqual },
{ "lt", TokenId.LessThan },
{ "LessThan", TokenId.LessThan },
{ "le", TokenId.LessThanEqual },
{ "LessThanEqual", TokenId.LessThanEqual },
{ "gt", TokenId.GreaterThan },
{ "GreaterThan", TokenId.GreaterThan },
{ "ge", TokenId.GreaterThanEqual },
{ "GreaterThanEqual", TokenId.GreaterThanEqual },
{ "and", TokenId.DoubleAmpersand },
{ "AndAlso", TokenId.DoubleAmpersand },
{ "or", TokenId.DoubleBar },
{ "OrElse", TokenId.DoubleBar },
{ "not", TokenId.Exclamation },
{ "mod", TokenId.Percent }
};

private readonly char _numberDecimalSeparator;
private readonly string _text;
private readonly int _textLen;
private readonly ParsingConfig _parsingConfig;

// These aliases simplify the "Where"-clause and make it more human-readable.
private readonly Dictionary<string, TokenId> _predefinedOperatorAliases;

private int _textPos;
private char _ch;

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

_predefinedOperatorAliases = new(config.IsCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase)
{
{ "eq", TokenId.Equal },
{ "equal", TokenId.Equal },
{ "ne", TokenId.ExclamationEqual },
{ "notequal", TokenId.ExclamationEqual },
{ "neq", TokenId.ExclamationEqual },
{ "lt", TokenId.LessThan },
{ "LessThan", TokenId.LessThan },
{ "le", TokenId.LessThanEqual },
{ "LessThanEqual", TokenId.LessThanEqual },
{ "gt", TokenId.GreaterThan },
{ "GreaterThan", TokenId.GreaterThan },
{ "ge", TokenId.GreaterThanEqual },
{ "GreaterThanEqual", TokenId.GreaterThanEqual },
{ "and", TokenId.DoubleAmpersand },
{ "AndAlso", TokenId.DoubleAmpersand },
{ "or", TokenId.DoubleBar },
{ "OrElse", TokenId.DoubleBar },
{ "not", TokenId.Exclamation },
{ "mod", TokenId.Percent }
};

_numberDecimalSeparator = config.NumberParseCulture?.NumberFormat.NumberDecimalSeparator[0] ?? DefaultNumberDecimalSeparator;

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

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

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

private static bool IsHexChar(char c)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Dynamic.Core.CustomTypeProviders;
using FluentAssertions;
using Xunit;

namespace System.Linq.Dynamic.Core.Tests.CustomTypeProviders
{
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "MemberHidesStaticFromOuterClass")]
public class CustomTypeProviderTests
{
private readonly ParsingConfig _config = new()
{
CustomTypeProvider = new MyCustomTypeProvider(ParsingConfig.Default, typeof(IS), typeof(IS.NOT), typeof(IST), typeof(IST.NOT)),
IsCaseSensitive = true
};

[Theory]
[InlineData("IS.NULL(null)", true)]
[InlineData("IS.NOT.NULL(null)", false)]
[InlineData("IST.NULL(null)", true)]
[InlineData("IST.NOT.NULL(null)", false)]
public void Test(string expression, bool expected)
{
// Act 1
var lambdaExpression = DynamicExpressionParser.ParseLambda(
_config,
false,
[],
typeof(bool),
expression
);

// Act 2
var result = (bool?)lambdaExpression.Compile().DynamicInvoke();

// Assert
result.Should().Be(expected);
}

public class MyCustomTypeProvider : DefaultDynamicLinqCustomTypeProvider
{
private readonly HashSet<Type> _types;

public MyCustomTypeProvider(ParsingConfig config, params Type[] types) : base(config)
{
_types = new HashSet<Type>(types);
}

public override HashSet<Type> GetCustomTypes()
{
return _types;
}
}

public static class IS
{
public static bool NULL(object? value) => value == null;

public static class NOT
{
public static bool NULL(object? value) => value != null;
}
}

public static class IST
{
public static bool NULL(object? value) => value == null;

public static class NOT
{
public static bool NULL(object? value) => value != null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
using NFluent;
using Xunit;

namespace System.Linq.Dynamic.Core.Tests;
namespace System.Linq.Dynamic.Core.Tests.CustomTypeProviders;

public class DefaultDynamicLinqCustomTypeProviderTests
{
private readonly IDynamicLinkCustomTypeProvider _sut;
private readonly DefaultDynamicLinqCustomTypeProvider _sut;

public DefaultDynamicLinqCustomTypeProviderTests()
{
Expand All @@ -19,7 +19,7 @@ public DefaultDynamicLinqCustomTypeProviderTests()
public void DefaultDynamicLinqCustomTypeProvider_ResolveSystemType()
{
// Act
var type = _sut.ResolveType(typeof(DirectoryInfo).FullName);
var type = _sut.ResolveType(typeof(DirectoryInfo).FullName!);

// Assert
type.Should().Be(typeof(DirectoryInfo));
Expand Down Expand Up @@ -49,7 +49,7 @@ public void DefaultDynamicLinqCustomTypeProvider_ResolveType_UnknownReturnsNull(
public void DefaultDynamicLinqCustomTypeProvider_ResolveType_DefinedReturnsType()
{
// Act
var result = _sut.ResolveType(typeof(DefaultDynamicLinqCustomTypeProviderTests).FullName);
var result = _sut.ResolveType(typeof(DefaultDynamicLinqCustomTypeProviderTests).FullName!);

// Assert
Check.That(result).IsNotNull();
Expand Down
Loading