private List <MetricFilterExpressionToken> TokenizeFilterString(string filterString) { List <MetricFilterExpressionToken> tokenList = new List <MetricFilterExpressionToken>(); int pos = 0; // preliminary tokenizing loop while (pos < filterString.Length) { char c = filterString[pos]; // Whitespace Token if (char.IsWhiteSpace(c)) { StringBuilder sb = new StringBuilder(); for (sb.Append(c); ++pos < filterString.Length && char.IsWhiteSpace(c = filterString[pos]); sb.Append(c)) { } // The whitespace tokens will be removed later, but are required to separate other tokens for now tokenList.Add(new MetricFilterExpressionToken(sb.ToString(), MetricFilterExpressionToken.TokenType.Whitespace)); } else if (char.IsLetter(c) || c == '_') { // Identifier Token (keywords are handled later) StringBuilder sb = new StringBuilder(); for (sb.Append(c); ++pos < filterString.Length && (char.IsLetterOrDigit(c = filterString[pos]) || c == '_' || c == '.'); sb.Append(c)) { } tokenList.Add(new MetricFilterExpressionToken(sb.ToString(), MetricFilterExpressionToken.TokenType.Identifier)); } else if (char.IsDigit(c) || c == '-') { // only supported token starting with a number is DateTimeOffset StringBuilder sb = new StringBuilder(); for (sb.Append(c); ++pos < filterString.Length && (char.IsDigit(c = filterString[pos]) || "TZ:.-+".Contains(c)); sb.Append(c)) { } tokenList.Add(new MetricFilterExpressionToken(sb.ToString(), MetricFilterExpressionToken.TokenType.DateTimeOffsetValue)); } else { switch (c) { case '\'': StringBuilder sb = new StringBuilder(); // slight variation here to avoid capturing opening quote for (pos++; pos < filterString.Length && (c = filterString[pos]) != '\''; pos++) { sb.Append(c); } // verify and step over closing quote if (pos++ >= filterString.Length) { throw new FormatException("Unclosed StringValue token"); } tokenList.Add(new MetricFilterExpressionToken(sb.ToString(), MetricFilterExpressionToken.TokenType.StringValue)); break; case ')': case '(': tokenList.Add(new MetricFilterExpressionToken( filterString.Substring(pos++, 1), c == '(' ? MetricFilterExpressionToken.TokenType.OpenParen : MetricFilterExpressionToken.TokenType.CloseParen)); break; default: throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Unexpected character encountered '{0}'", c)); } } } // Second pass identifies keywords (operators) and duration values for (int i = 0; i < tokenList.Count; i++) { MetricFilterExpressionToken t = tokenList[i]; if (t.Type == MetricFilterExpressionToken.TokenType.Whitespace) { tokenList.RemoveAt(i--); continue; } if (t.Type != MetricFilterExpressionToken.TokenType.Identifier) { continue; } // The OData spec appears to be case sensitive here. switch (t.Value) { case "and": t.Type = MetricFilterExpressionToken.TokenType.AndOperator; continue; case "or": t.Type = MetricFilterExpressionToken.TokenType.OrOperator; continue; case "eq": t.Type = MetricFilterExpressionToken.TokenType.EqOperator; continue; case "duration": if (i + 1 < tokenList.Count && tokenList[i + 1].Type == MetricFilterExpressionToken.TokenType.StringValue) { t.Type = MetricFilterExpressionToken.TokenType.DurationValue; t.Value = tokenList[i + 1].Value; tokenList.RemoveAt(i + 1); } continue; } } return(tokenList); }
// Parse filter clause (or parenthesized expression) corresponds to F -> ID EQ VALUE | (E) private static MetricFilterExpressionTree ParseFilterClause(MetricFilterExpressionTokenizer tokenizer) { if (tokenizer == null || tokenizer.IsEmpty) { throw GenerateFilterParseException(null, MetricFilterExpressionToken.TokenType.Identifier); } MetricFilterExpressionToken token = tokenizer.Current; switch (token.Type) { case MetricFilterExpressionToken.TokenType.Identifier: // F -> ID EQ VALUE // validate and store the parameter name FilterParameter parameter = ParseParameter(token.Value); // Consume name tokenizer.Advance(); // Verify and comsume EQ if (tokenizer.IsEmpty || tokenizer.Current.Type != MetricFilterExpressionToken.TokenType.EqOperator) { throw GenerateFilterParseException(tokenizer.Current, MetricFilterExpressionToken.TokenType.EqOperator); } tokenizer.Advance(); MetricFilterExpressionToken.TokenType expectedType = GetExpectedTokenTypeForParameter(parameter); // Verify, store, and consume value if (tokenizer.IsEmpty || tokenizer.Current.Type != expectedType) { throw GenerateFilterParseException(tokenizer.Current, expectedType); } string value = tokenizer.Current.Value; tokenizer.Advance(); return(new MetricFilterExpressionTree() { Value = new KeyValuePair <FilterParameter, string>(ParseParameter(token.Value), value) }); case MetricFilterExpressionToken.TokenType.OpenParen: // F -> (E) // Consume ( tokenizer.Advance(); // Match Expression MetricFilterExpressionTree node = ParseFilterExpression(tokenizer); // Verify and consume ) if (tokenizer.IsEmpty || tokenizer.Current.Type != MetricFilterExpressionToken.TokenType.CloseParen) { throw GenerateFilterParseException(tokenizer.Current, MetricFilterExpressionToken.TokenType.CloseParen); } tokenizer.Advance(); return(node); default: throw GenerateFilterParseException(token, MetricFilterExpressionToken.TokenType.Identifier); } }
private static FormatException GenerateFilterParseException(MetricFilterExpressionToken encountered, MetricFilterExpressionToken.TokenType expected) { return(new FormatException( string.Format(CultureInfo.InvariantCulture, "Failed to parse expression. Expected {0} token (encountered {1}).", expected, encountered))); }
private static FormatException GenerateFilterParseException(MetricFilterExpressionToken encountered, MetricFilterExpressionToken.TokenType expected) { return new FormatException( string.Format(CultureInfo.InvariantCulture, "Failed to parse expression. Expected {0} token (encountered {1}).", expected, encountered)); }