/// <summary>Validates the current token is of the specified kind.</summary> /// <param name="t">Expected token kind.</param> internal void ValidateToken(ExpressionTokenKind t) { if (this.token.Kind != t) { throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); } }
/// <summary> /// Get type-prefixed literals with quoted values duration, binary and spatial types. /// </summary> /// <param name="tokenText">Token text</param> /// <returns>ExpressionTokenKind</returns> /// <example>geometry'POINT (79 84)'. 'geometry' is the tokenText </example> private ExpressionTokenKind GetBuiltInTypesLiteralPrefixWithQuotedValue(string tokenText) { if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixDuration, StringComparison.OrdinalIgnoreCase)) { return(ExpressionTokenKind.DurationLiteral); } else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixBinary, StringComparison.OrdinalIgnoreCase)) { return(ExpressionTokenKind.BinaryLiteral); } else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixGeography, StringComparison.OrdinalIgnoreCase)) { return(ExpressionTokenKind.GeographyLiteral); } else if (String.Equals(tokenText, ExpressionConstants.LiteralPrefixGeometry, StringComparison.OrdinalIgnoreCase)) { return(ExpressionTokenKind.GeometryLiteral); } else if (string.Equals(tokenText, ExpressionConstants.KeywordNull, StringComparison.OrdinalIgnoreCase)) { // typed null literals are not supported. throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); } else { // treat as quoted literal return(ExpressionTokenKind.QuotedLiteral); } }
/// <summary>Validates the current character is a digit.</summary> private void ValidateDigit() { if (!this.IsValidDigit) { throw ParseError(ODataErrorStrings.ExpressionLexer_DigitExpected(this.textPos, this.Text)); } }
public void UnsupportedTokenBinderErrorTest() { IEdmModel model = QueryTestMetadata.BuildTestMetadata(this.PrimitiveTypeResolver, this.UntypedDataServiceProviderFactory); var binder = new ErrorMetadataBinder(model); var token = new LiteralToken("d"); Action action = () => binder.Bind(token); action.ShouldThrow <ODataException>().WithMessage(ODataErrorStrings.MetadataBinder_BoundNodeCannotBeNull(token.Kind)); }
/// <summary> /// Create a InNode /// </summary> /// <param name="left">The left operand.</param> /// <param name="right">The right operand.</param> /// <exception cref="System.ArgumentNullException">Throws if the left or right inputs are null.</exception> /// <exception cref="ODataException">Throws if the right operand single item type isn't the same type as the left operand.</exception> public InNode(SingleValueNode left, CollectionNode right) { ExceptionUtils.CheckArgumentNotNull(left, "left"); ExceptionUtils.CheckArgumentNotNull(right, "right"); this.left = left; this.right = right; if (!this.left.GetEdmTypeReference().IsAssignableFrom(this.right.ItemType) && !this.right.ItemType.IsAssignableFrom(this.left.GetEdmTypeReference())) { throw new ArgumentException(ODataErrorStrings.Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType( this.right.ItemType.FullName(), this.left.GetEdmTypeReference().FullName())); } }
/// <summary> /// Starting from an identifier, reads a sequence of dots and /// identifiers, and returns the text for it, with whitespace /// stripped. /// </summary> /// <param name="acceptStar">do we allow a star in this identifier</param> /// <returns>The dotted identifier starting at the current identifier.</returns> internal string ReadDottedIdentifier(bool acceptStar) { this.ValidateToken(ExpressionTokenKind.Identifier); StringBuilder builder = null; string result = this.CurrentToken.Text; this.NextToken(); while (this.CurrentToken.Kind == ExpressionTokenKind.Dot) { this.NextToken(); if (this.CurrentToken.Kind != ExpressionTokenKind.Identifier && this.CurrentToken.Kind != ExpressionTokenKind.QuotedLiteral) { if (this.CurrentToken.Kind == ExpressionTokenKind.Star) { // if we accept a star and this is the last token in the identifier, then we're ok... otherwise we throw. if (!acceptStar || (this.PeekNextToken().Kind != ExpressionTokenKind.End && this.PeekNextToken().Kind != ExpressionTokenKind.Comma)) { throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); } } else { throw ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); } } if (builder == null) { builder = new StringBuilder(result, result.Length + 1 + this.CurrentToken.Text.Length); } builder.Append('.'); builder.Append(this.CurrentToken.Text); this.NextToken(); } if (builder != null) { result = builder.ToString(); } return(result); }
private void HandleQuotedValues() { int startPosition = this.token.Position; do { do { this.NextChar(); }while (this.ch.HasValue && this.ch != '\''); if (this.ch == null) { throw ParseError(ODataErrorStrings.ExpressionLexer_UnterminatedLiteral(this.textPos, this.Text)); } this.NextChar(); }while (this.ch.HasValue && this.ch == '\''); // Update token.Text to include the literal + the quoted value this.token.Text = this.Text.Substring(startPosition, this.textPos - startPosition); }
/// <summary> /// If the source node is not of the specified type, then we check if type promotion is possible and inject a convert node. /// If the source node is the same type as the target type (or if the target type is null), we just return the source node as is. /// </summary> /// <param name="source">The source node to apply the conversion to.</param> /// <param name="targetTypeReference">The target primitive type. May be null - this method will do nothing in that case.</param> /// <returns>The converted query node, or the original source node unchanged.</returns> internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference) { Debug.Assert(source != null, "source != null"); if (targetTypeReference == null) { return(source); } if (source.TypeReference != null) { if (source.TypeReference.IsEquivalentTo(targetTypeReference)) { // For source is type definition, if source's underlying type == target type. // We create a conversion node from source to its underlying type (target type) // so that the service can convert value of source clr type to underlying clr type. if (source.TypeReference.IsTypeDefinition()) { return(new ConvertNode(source, targetTypeReference)); } return(source); } // Structured type in url will be translated into a node with raw string value. // We create a conversion node from string to structured type. if (targetTypeReference.IsStructured() || targetTypeReference.IsStructuredCollectionType()) { return(new ConvertNode(source, targetTypeReference)); } ConstantNode constantNode = source as ConstantNode; if (constantNode != null && constantNode.Value != null && source.TypeReference.IsString() && targetTypeReference.IsEnum()) { string memberName = constantNode.Value.ToString(); IEdmEnumType enumType = targetTypeReference.Definition as IEdmEnumType; if (enumType.Members.Any(m => string.Compare(m.Name, memberName, StringComparison.Ordinal) == 0)) { string literalText = ODataUriUtils.ConvertToUriLiteral(constantNode.Value, default(ODataVersion)); return(new ConstantNode(new ODataEnumValue(constantNode.Value.ToString(), targetTypeReference.Definition.ToString()), literalText, targetTypeReference)); } else { throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(memberName)); } } if (!TypePromotionUtils.CanConvertTo(source, source.TypeReference, targetTypeReference)) { throw new ODataException(ODataErrorStrings.MetadataBinder_CannotConvertToType(source.TypeReference.FullName(), targetTypeReference.FullName())); } else { if (source.TypeReference.IsEnum() && constantNode != null) { return(new ConstantNode(constantNode.Value, ODataUriUtils.ConvertToUriLiteral(constantNode.Value, ODataVersion.V4), targetTypeReference)); } object originalPrimitiveValue; if (MetadataUtilsCommon.TryGetConstantNodePrimitiveLDMF(source, out originalPrimitiveValue) && (originalPrimitiveValue != null)) { // L F D M types : directly create a ConvertNode. // 1. NodeToExpressionTranslator.cs won't allow implicitly converting single/double to decimal, which should be done here at Node tree level. // 2. And prevent losing precision in float -> double, e.g. (double)1.234f => 1.2339999675750732d not 1.234d object targetPrimitiveValue = ODataUriConversionUtils.CoerceNumericType(originalPrimitiveValue, targetTypeReference.AsPrimitive().Definition as IEdmPrimitiveType); if (string.IsNullOrEmpty(constantNode.LiteralText)) { return(new ConstantNode(targetPrimitiveValue)); } var candidate = new ConstantNode(targetPrimitiveValue, constantNode.LiteralText); var decimalType = candidate.TypeReference as IEdmDecimalTypeReference; if (decimalType != null) { var targetDecimalType = (IEdmDecimalTypeReference)targetTypeReference; return(decimalType.Precision == targetDecimalType.Precision && decimalType.Scale == targetDecimalType.Scale ? (SingleValueNode)candidate : (SingleValueNode)(new ConvertNode(candidate, targetTypeReference))); } else { return(candidate); } } else { // other type conversion : ConvertNode return(new ConvertNode(source, targetTypeReference)); } } } else { // If the source doesn't have a type (possibly an open property), then it's possible to convert it // cause we don't know for sure. return(new ConvertNode(source, targetTypeReference)); } }
protected virtual ExpressionToken NextTokenImplementation(out Exception error) { error = null; if (this.ignoreWhitespace) { this.ParseWhitespace(); } ExpressionTokenKind t; int tokenPos = this.textPos; switch (this.ch) { case '(': this.NextChar(); t = ExpressionTokenKind.OpenParen; break; case ')': this.NextChar(); t = ExpressionTokenKind.CloseParen; break; case ',': this.NextChar(); t = ExpressionTokenKind.Comma; break; case '-': bool hasNext = this.textPos + 1 < this.TextLen; if (hasNext && Char.IsDigit(this.Text[this.textPos + 1])) { // don't separate '-' and its following digits : -2147483648 is valid int.MinValue, but 2147483648 is long. t = this.ParseFromDigit(); if (ExpressionLexerUtils.IsNumeric(t)) { break; } // If it looked like a numeric but wasn't (because it was a binary 0x... value for example), // we'll rewind and fall through to a simple '-' token. this.SetTextPos(tokenPos); } else if (hasNext && this.Text[tokenPos + 1] == ExpressionConstants.InfinityLiteral[0]) { this.NextChar(); this.ParseIdentifier(); string currentIdentifier = this.Text.Substring(tokenPos + 1, this.textPos - tokenPos - 1); if (ExpressionLexerUtils.IsInfinityLiteralDouble(currentIdentifier)) { t = ExpressionTokenKind.DoubleLiteral; break; } else if (ExpressionLexerUtils.IsInfinityLiteralSingle(currentIdentifier)) { t = ExpressionTokenKind.SingleLiteral; break; } // If it looked like '-INF' but wasn't we'll rewind and fall through to a simple '-' token. this.SetTextPos(tokenPos); } this.NextChar(); t = ExpressionTokenKind.Minus; break; case '=': this.NextChar(); t = ExpressionTokenKind.Equal; break; case '/': this.NextChar(); t = ExpressionTokenKind.Slash; break; case '?': this.NextChar(); t = ExpressionTokenKind.Question; break; case '.': this.NextChar(); t = ExpressionTokenKind.Dot; break; case '\'': char quote = this.ch.Value; do { this.AdvanceToNextOccuranceOf(quote); if (this.textPos == this.TextLen) { error = ParseError(ODataErrorStrings.ExpressionLexer_UnterminatedStringLiteral(this.textPos, this.Text)); } this.NextChar(); }while (this.ch.HasValue && (this.ch.Value == quote)); t = ExpressionTokenKind.StringLiteral; break; case '*': this.NextChar(); t = ExpressionTokenKind.Star; break; case ':': this.NextChar(); t = ExpressionTokenKind.Colon; break; case '{': this.NextChar(); this.AdvanceThroughBalancedExpression('{', '}'); t = ExpressionTokenKind.BracedExpression; break; case '[': this.NextChar(); this.AdvanceThroughBalancedExpression('[', ']'); t = ExpressionTokenKind.BracketedExpression; break; default: if (this.IsValidWhiteSpace) { Debug.Assert(!this.ignoreWhitespace, "should not hit ws while ignoring it"); this.ParseWhitespace(); t = ExpressionTokenKind.Unknown; break; } if (this.IsValidStartingCharForIdentifier) { this.ParseIdentifier(); // Guids will have '-' in them // guidValue = 8HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 4HEXDIG "-" 12HEXDIG if (this.ch == '-' && this.TryParseGuid(tokenPos)) { t = ExpressionTokenKind.GuidLiteral; break; } t = ExpressionTokenKind.Identifier; break; } if (this.IsValidDigit) { t = this.ParseFromDigit(); break; } if (this.textPos == this.TextLen) { t = ExpressionTokenKind.End; break; } if (this.useSemicolonDelimeter && this.ch == ';') { this.NextChar(); t = ExpressionTokenKind.SemiColon; break; } if (this.parsingFunctionParameters && this.ch == '@') { this.NextChar(); if (this.textPos == this.TextLen) { error = ParseError(ODataErrorStrings.ExpressionLexer_SyntaxError(this.textPos, this.Text)); t = ExpressionTokenKind.Unknown; break; } if (!this.IsValidStartingCharForIdentifier) { error = ParseError(ODataErrorStrings.ExpressionLexer_InvalidCharacter(this.ch, this.textPos, this.Text)); t = ExpressionTokenKind.Unknown; break; } this.ParseIdentifier(); t = ExpressionTokenKind.ParameterAlias; break; } error = ParseError(ODataErrorStrings.ExpressionLexer_InvalidCharacter(this.ch, this.textPos, this.Text)); t = ExpressionTokenKind.Unknown; break; } this.token.Kind = t; this.token.Text = this.Text.Substring(tokenPos, this.textPos - tokenPos); this.token.Position = tokenPos; this.HandleTypePrefixedLiterals(); return(this.token); }
/// <summary> /// Makes best guess on numeric string without trailing letter like L, F, M, D /// </summary> /// <param name="numericStr">The numeric string.</param> /// <param name="guessedKind">The possbile kind (IntegerLiteral or DoubleLiteral) from ParseFromDigit() method.</param> /// <returns>A more accurate ExpressionTokenKind</returns> private static ExpressionTokenKind MakeBestGuessOnNoSuffixStr(string numericStr, ExpressionTokenKind guessedKind) { // no suffix, so // (1) make a best guess (note: later we support promoting each to later one: int32->int64->single->double->decimal). // look at value: "2147483647" may be Int32/long, "2147483649" must be long. // look at precision: "3258.67876576549" may be sinle/double/decimal, "3258.678765765489753678965390" must be decimal. // (2) then let MetadataUtilsCommon.CanConvertPrimitiveTypeTo() method does further promotion when knowing expected sematics type. int tmpInt = 0; long tmpLong = 0; float tmpFloat = 0; double tmpDouble = 0; decimal tmpDecimal = 0; if (guessedKind == ExpressionTokenKind.IntegerLiteral) { if (int.TryParse(numericStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpInt)) { return(ExpressionTokenKind.IntegerLiteral); } if (long.TryParse(numericStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out tmpLong)) { return(ExpressionTokenKind.Int64Literal); } } bool canBeSingle = float.TryParse(numericStr, NumberStyles.Float, CultureInfo.InvariantCulture, out tmpFloat); bool canBeDouble = double.TryParse(numericStr, NumberStyles.Float, CultureInfo.InvariantCulture, out tmpDouble); bool canBeDecimal = decimal.TryParse(numericStr, NumberStyles.Integer | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out tmpDecimal); // 1. try high precision -> low precision if (canBeDouble && canBeDecimal) { decimal doubleToDecimalR; decimal doubleToDecimalN; // To keep the full precision of the current value, which if necessary is all 17 digits of precision supported by the Double type. bool doubleCanBeDecimalR = decimal.TryParse(tmpDouble.ToString("R", CultureInfo.InvariantCulture), NumberStyles.Float, CultureInfo.InvariantCulture, out doubleToDecimalR); // To cover the scientific notation case, such as 1e+19 in the tmpDouble bool doubleCanBeDecimalN = decimal.TryParse(tmpDouble.ToString("N29", CultureInfo.InvariantCulture), NumberStyles.Number, CultureInfo.InvariantCulture, out doubleToDecimalN); if ((doubleCanBeDecimalR && doubleToDecimalR != tmpDecimal) || (!doubleCanBeDecimalR && doubleCanBeDecimalN && doubleToDecimalN != tmpDecimal)) { // losing precision as double, so choose decimal return(ExpressionTokenKind.DecimalLiteral); } } // here can't use normal casting like the above double VS decimal. // prevent losing precision in float -> double, e.g. (double)1.234f will be 1.2339999675750732d not 1.234d if (canBeSingle && canBeDouble && (double.Parse(tmpFloat.ToString("R", CultureInfo.InvariantCulture), CultureInfo.InvariantCulture) != tmpDouble)) { // losing precision as single, so choose double return(ExpressionTokenKind.DoubleLiteral); } // 2. try most compatible -> least compatible if (canBeSingle) { return(ExpressionTokenKind.SingleLiteral); } if (canBeDouble) { return(ExpressionTokenKind.DoubleLiteral); } throw new ODataException(ODataErrorStrings.ExpressionLexer_InvalidNumericString(numericStr)); }