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

New features: Hexadecimal integers and array initializers #77

Merged
merged 5 commits into from
Apr 28, 2017
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
91 changes: 75 additions & 16 deletions src/System.Linq.Dynamic.Core/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -927,11 +927,15 @@ Expression ParseIntegerLiteral()
string text = _textParser.CurrentToken.Text;
string qualifier = null;
char last = text[text.Length - 1];
bool isHexadecimal = text.StartsWith(text[0] == '-' ? "-0x" : "0x", StringComparison.CurrentCultureIgnoreCase);
char[] qualifierLetters = isHexadecimal
? new[] { 'U', 'u', 'L', 'l' }
: new[] { 'U', 'u', 'L', 'l', 'F', 'f', 'D', 'd' };

if (char.IsLetter(last))
if (qualifierLetters.Contains(last))
{
int pos = text.Length - 1, count = 0;
while (char.IsLetter(text[pos]))
while (qualifierLetters.Contains(text[pos]))
{
++count;
--pos;
Expand All @@ -942,8 +946,13 @@ Expression ParseIntegerLiteral()

if (text[0] != '-')
{
if (isHexadecimal)
{
text = text.Substring(2);
}

ulong value;
if (!ulong.TryParse(text, out value))
if (!ulong.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, CultureInfo.CurrentCulture, out value))
throw ParseError(Res.InvalidIntegerLiteral, text);

_textParser.NextToken();
Expand All @@ -964,10 +973,20 @@ Expression ParseIntegerLiteral()
}
else
{
if (isHexadecimal)
{
text = text.Substring(3);
}

long value;
if (!long.TryParse(text, out value))
if (!long.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, CultureInfo.CurrentCulture, out value))
throw ParseError(Res.InvalidIntegerLiteral, text);

if (isHexadecimal)
{
value = -value;
}

_textParser.NextToken();
if (!string.IsNullOrEmpty(qualifier))
{
Expand Down Expand Up @@ -1172,6 +1191,7 @@ Expression ParseNew()
_textParser.NextToken();
if (_textParser.CurrentToken.Id != TokenId.OpenParen &&
_textParser.CurrentToken.Id != TokenId.OpenCurlyParen &&
_textParser.CurrentToken.Id != TokenId.OpenBracket &&
_textParser.CurrentToken.Id != TokenId.Identifier)
throw ParseError(Res.OpenParenOrIdentifierExpected);

Expand All @@ -1184,34 +1204,49 @@ Expression ParseNew()
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName);
_textParser.NextToken();
if (_textParser.CurrentToken.Id != TokenId.OpenParen &&
_textParser.CurrentToken.Id != TokenId.OpenBracket &&
_textParser.CurrentToken.Id != TokenId.OpenCurlyParen)
throw ParseError(Res.OpenParenExpected);
}

bool arrayInitializer = false;
if (_textParser.CurrentToken.Id == TokenId.OpenBracket)
{
_textParser.NextToken();
_textParser.ValidateToken(TokenId.CloseBracket, Res.CloseBracketExpected);
_textParser.NextToken();
_textParser.ValidateToken(TokenId.OpenCurlyParen, Res.OpenCurlyParenExpected);
arrayInitializer = true;
}

_textParser.NextToken();

var properties = new List<DynamicProperty>();
var expressions = new List<Expression>();

while (true)
while (_textParser.CurrentToken.Id != TokenId.CloseParen
&& _textParser.CurrentToken.Id != TokenId.CloseCurlyParen)
{
int exprPos = _textParser.CurrentToken.Pos;
Expression expr = ParseConditionalOperator();
string propName;
if (TokenIdentifierIs("as"))
{
_textParser.NextToken();
propName = GetIdentifier();
_textParser.NextToken();
}
else
if (!arrayInitializer)
{
if (!TryGetMemberName(expr, out propName))
throw ParseError(exprPos, Res.MissingAsClause);
string propName;
if (TokenIdentifierIs("as"))
{
_textParser.NextToken();
propName = GetIdentifier();
_textParser.NextToken();
}
else
{
if (!TryGetMemberName(expr, out propName)) throw ParseError(exprPos, Res.MissingAsClause);
}

properties.Add(new DynamicProperty(propName, expr.Type));
}

expressions.Add(expr);
properties.Add(new DynamicProperty(propName, expr.Type));

if (_textParser.CurrentToken.Id != TokenId.Comma)
break;
Expand All @@ -1224,9 +1259,33 @@ Expression ParseNew()
throw ParseError(Res.CloseParenOrCommaExpected);
_textParser.NextToken();

if (arrayInitializer)
{
return CreateArrayInitializerExpression(expressions, newType);
}

return CreateNewExpression(properties, expressions, newType);
}

private Expression CreateArrayInitializerExpression(List<Expression> expressions, Type newType)
{
if (expressions.Count == 0)
{
return Expression.NewArrayInit(newType ?? typeof(object));
}

if (newType != null)
{
return Expression.NewArrayInit(
newType,
expressions.Select(expression => PromoteExpression(expression, newType, true, true)));
}

return Expression.NewArrayInit(
expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object),
expressions);
}

private Expression CreateNewExpression(List<DynamicProperty> properties, List<Expression> expressions, Type newType)
{
// http://solutionizing.net/category/linq/
Expand Down
3 changes: 3 additions & 0 deletions src/System.Linq.Dynamic.Core/Res.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,20 @@ internal static class Res
public const string UnterminatedStringLiteral = "Unterminated string literal";
public const string InvalidCharacter = "Syntax error '{0}'";
public const string DigitExpected = "Digit expected";
public const string HexCharExpected = "Hexadecimal character expected";
public const string SyntaxError = "Syntax error";
public const string TokenExpected = "{0} expected";
public const string ParseExceptionFormat = "{0} (at index {1})";
public const string ColonExpected = "':' expected";
public const string OpenParenExpected = "'(' expected";
public const string OpenCurlyParenExpected = "'{' expected";
public const string CloseParenOrOperatorExpected = "')' or operator expected";
public const string CloseParenOrCommaExpected = "')' or ',' expected";
public const string DotOrOpenParenExpected = "'.' or '(' expected";
public const string DotOrOpenParenOrStringLiteralExpected = "'.' or '(' or string literal expected";
public const string OpenBracketExpected = "'[' expected";
public const string CloseBracketOrCommaExpected = "']' or ',' expected";
public const string CloseBracketExpected = "']' expected";
public const string IdentifierExpected = "Identifier expected";
public const string OpenParenOrIdentifierExpected = "'(' or Identifier expected";
public const string IdentifierImplementingInterfaceExpected = "Identifier implementing interface '{0}' expected";
Expand Down
39 changes: 39 additions & 0 deletions src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,19 @@ public void NextToken()
NextChar();
} while (char.IsDigit(_ch));

bool hexInteger = false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code coverage from TextParser.cs dropped a bit, can you please check if you can add some more specific tests in this area?

if (_ch == 'X' || _ch == 'x')
{
NextChar();
ValidateHexChar();
do
{
NextChar();
} while (IsHexChar(_ch));

hexInteger = true;
}

if (_ch == 'U' || _ch == 'L')
{
NextChar();
Expand All @@ -302,6 +315,11 @@ public void NextToken()
break;
}

if (hexInteger)
{
break;
}

if (_ch == NumberDecimalSeparator)
{
tokenId = TokenId.RealLiteral;
Expand Down Expand Up @@ -365,6 +383,11 @@ private void ValidateDigit()
if (!char.IsDigit(_ch)) throw ParseError(_textPos, Res.DigitExpected);
}

private void ValidateHexChar()
{
if (!IsHexChar(_ch)) throw ParseError(_textPos, Res.HexCharExpected);
}

private Exception ParseError(string format, params object[] args)
{
return ParseError(CurrentToken.Pos, format, args);
Expand All @@ -380,5 +403,21 @@ private static TokenId GetAliasedTokenId(TokenId t, string alias)
TokenId id;
return t == TokenId.Identifier && _predefinedAliases.TryGetValue(alias, out id) ? id : t;
}

private static bool IsHexChar(char c)
{
if (char.IsDigit(c))
{
return true;
}

if (c <= '\x007f')
{
c |= (char)0x20;
return c >= 'a' && c <= 'f';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this also work for 'A' and 'F' (capital ?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Binary or with 0x20 makes all letters lowercase.

}

return false;
}
}
}
49 changes: 48 additions & 1 deletion test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@ namespace System.Linq.Dynamic.Core.Tests
{
public class ExpressionTests
{
[Fact]
public void ExpressionTests_ArrayInitializer()
{
//Arrange
var list = new[] { 0, 1, 2, 3, 4 };

//Act
var result1 = list.AsQueryable().SelectMany("new[] {}");
var result2 = list.AsQueryable().SelectMany("new[] { it + 1, it + 2 }");
var result3 = list.AsQueryable().SelectMany("new long[] { it + 1, byte(it + 2), short(it + 3) }");
var result4 = list.AsQueryable().SelectMany("new[] { new[] { it + 1, it + 2 }, new[] { it + 3, it + 4 } }");

//Assert
Assert.Equal(result1.Count(), 0);
Assert.Equal(result2.Cast<int>(), list.SelectMany(item => new[] { item + 1, item + 2 }));
Assert.Equal(result3.Cast<long>(), list.SelectMany(item => new long[] { item + 1, (byte)(item + 2), (short)(item + 3) }));
Assert.Equal(result4.SelectMany("it").Cast<int>(), list.SelectMany(item => new[] { new[] { item + 1, item + 2 }, new[] { item + 3, item + 4 } }).SelectMany(item => item));

Assert.Throws<ParseException>(() => list.AsQueryable().SelectMany("new[ {}"));
Assert.Throws<ParseException>(() => list.AsQueryable().SelectMany("new] {}"));
}

[Fact]
public void ExpressionTests_Cast_To_nullableint()
{
Expand Down Expand Up @@ -453,7 +475,6 @@ public void ExpressionTests_Guid_CompareTo_String()
Assert.Equal(lst[0], result2b.Single());
}


[Fact]
public void ExpressionTests_Guid_CompareTo_Guid()
{
Expand All @@ -471,6 +492,32 @@ public void ExpressionTests_Guid_CompareTo_Guid()
Assert.Equal(testValue, resultb.Single());
}

[Fact]
public void ExpressionTests_HexadecimalInteger()
{
//Arrange
var values = new[] { 1, 2, 3 };

//Act
var result = values.AsQueryable().Select("it * 0x1000abEF").Cast<int>();
var resultNeg = values.AsQueryable().Select("it * -0xaBcDeF").Cast<int>();
var resultU = values.AsQueryable().Select("uint(it) * 0x12345678U").Cast<uint>();
var resultL = values.AsQueryable().Select("it * 0x1234567890abcdefL").Cast<long>();
var resultLNeg = values.AsQueryable().Select("it * -0x0ABCDEF987654321L").Cast<long>();
var resultUL = values.AsQueryable().Select("ulong(it) * 0x1000abEFUL").Cast<ulong>();

//Assert
Assert.Equal(values.Select(it => it * 0x1000abEF), result);
Assert.Equal(values.Select(it => it * -0xaBcDeF), resultNeg);
Assert.Equal(values.Select(it => (uint)it * 0x12345678U), resultU);
Assert.Equal(values.Select(it => it * 0x1234567890abcdefL), resultL);
Assert.Equal(values.Select(it => it * -0x0ABCDEF987654321L), resultLNeg);
Assert.Equal(values.Select(it => (ulong)it * 0x1000abEFUL), resultUL);

Assert.Throws<ParseException>(() => values.AsQueryable().Where("it < 0x 11a"));
Assert.Throws<ParseException>(() => values.AsQueryable().Where("it < 11a"));
}

[Fact]
public void ExpressionTests_In_Enum()
{
Expand Down
21 changes: 19 additions & 2 deletions test/System.Linq.Dynamic.Core.Tests/TextParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public void TextParser_Parse_RealLiteral()
Check.That(textParser.CurrentToken.Id).Equals(TokenId.RealLiteral);
Check.That(textParser.CurrentToken.Pos).Equals(1);
Check.That(textParser.CurrentToken.Text).Equals("1.0E25");

Check.ThatCode(() => new TextParser("1.e25")).Throws<ParseException>();
}

[Fact]
Expand Down Expand Up @@ -103,6 +105,20 @@ public void TextParser_Parse_GreaterThanEqual()
Check.That(textParser.CurrentToken.Text).Equals(">=");
}

[Fact]
public void TextParser_Parse_HexadecimalIntegerLiteral()
{
// Assign + Act
var textParser = new TextParser(" 0x1234567890AbCdEfL ");

// Assert
Check.That(textParser.CurrentToken.Id).Equals(TokenId.IntegerLiteral);
Check.That(textParser.CurrentToken.Pos).Equals(1);
Check.That(textParser.CurrentToken.Text).Equals("0x1234567890AbCdEfL");

Check.ThatCode(() => new TextParser("0xz1234")).Throws<ParseException>();
}

[Fact]
public void TextParser_Parse_LessGreater()
{
Expand Down Expand Up @@ -139,11 +155,12 @@ public void TextParser_Parse_Slash()
Check.That(textParser.CurrentToken.Text).Equals("/");
}

// [Fact]
[Fact]
public void TextParser_Parse_ThrowsException()
{
// Assign + Act + Assert
Check.ThatCode(() => { new TextParser("ಬಾ"); }).Throws<ParseException>();
//Check.ThatCode(() => { new TextParser("ಬಾ"); }).Throws<ParseException>();
Check.ThatCode(() => { new TextParser(";"); }).Throws<ParseException>();
}
}
}