/// <summary> /// Initializes FilterExpressionWrapper with given filterString and options. /// </summary> public FilterExpressionWrapper(string filterString, FilterOptions options) { ValidateArg.NotNullOrEmpty(filterString, "filterString"); this.FilterString = filterString; this.FilterOptions = options; try { // We prefer fast filter when it's available. this.filterExpression = FilterExpression.Parse(filterString, out this.fastFilter); if (UseFastFilter) { this.filterExpression = null; // Property value regex is only supported for fast filter, // so we ignore it if no fast filter is constructed. // TODO: surface an error message to suer. var regexString = options?.FilterRegEx; if (!string.IsNullOrEmpty(regexString)) { Debug.Assert(options.FilterRegExReplacement != null ? options.FilterRegEx != null : true); this.fastFilter.PropertyValueRegex = new Regex(regexString, RegexOptions.Compiled); this.fastFilter.PropertyValueRegexReplacement = options.FilterRegExReplacement; } } } catch (FormatException ex) { this.ParseError = ex.Message; } catch (ArgumentException ex) { this.fastFilter = null; this.ParseError = ex.Message; } }
/// <summary> /// Return FilterExpression after parsing the given filter expression, and a FastFilter when possible. /// </summary> internal static FilterExpression Parse(string filterString, out FastFilter fastFilter) { ValidateArg.NotNull(filterString, nameof(filterString)); // Below parsing doesn't error out on pattern (), so explicitly search for that (empty parethesis). var invalidInput = Regex.Match(filterString, @"\(\s*\)"); if (invalidInput.Success) { throw new FormatException(string.Format(CultureInfo.CurrentCulture, CommonResources.TestCaseFilterFormatException, CommonResources.EmptyParenthesis)); } var tokens = TokenizeFilterExpressionString(filterString); var operatorStack = new Stack <Operator>(); var filterStack = new Stack <FilterExpression>(); var fastFilterBuilder = FastFilter.CreateBuilder(); // This is based on standard parsing of in order expression using two stacks (operand stack and operator stack) // Precedence(And) > Precedence(Or) foreach (var inputToken in tokens) { var token = inputToken.Trim(); if (string.IsNullOrEmpty(token)) { // ignore empty tokens continue; } switch (token) { case "&": case "|": Operator currentOperator = Operator.And; if (string.Equals("|", token)) { currentOperator = Operator.Or; } fastFilterBuilder.AddOperator(currentOperator); // Always put only higher priority operator on stack. // if lesser priority -- pop up the stack and process the operator to maintain operator precedence. // if equal priority -- pop up the stack and process the operator to maintain operator associativity. // OpenBrace is special condition. & or | can come on top of OpenBrace for case like ((a=b)&c=d) while (true) { bool isEmpty = operatorStack.Count == 0; Operator stackTopOperator = isEmpty ? Operator.None : operatorStack.Peek(); if (isEmpty || stackTopOperator == Operator.OpenBrace || stackTopOperator < currentOperator) { operatorStack.Push(currentOperator); break; } stackTopOperator = operatorStack.Pop(); ProcessOperator(filterStack, stackTopOperator); } break; case "(": operatorStack.Push(Operator.OpenBrace); break; case ")": // process operators from the stack till OpenBrace is found. // If stack is empty at any time, than matching OpenBrace is missing from the expression. if (operatorStack.Count == 0) { throw new FormatException(string.Format(CultureInfo.CurrentCulture, CommonResources.TestCaseFilterFormatException, CommonResources.MissingOpenParenthesis)); } Operator temp = operatorStack.Pop(); while (temp != Operator.OpenBrace) { ProcessOperator(filterStack, temp); if (operatorStack.Count == 0) { throw new FormatException(string.Format(CultureInfo.CurrentCulture, CommonResources.TestCaseFilterFormatException, CommonResources.MissingOpenParenthesis)); } temp = operatorStack.Pop(); } break; default: // push the operand to the operand stack. Condition condition = Condition.Parse(token); FilterExpression filter = new FilterExpression(condition); filterStack.Push(filter); fastFilterBuilder.AddCondition(condition); break; } } while (operatorStack.Count != 0) { Operator temp = operatorStack.Pop(); ProcessOperator(filterStack, temp); } if (filterStack.Count != 1) { throw new FormatException(string.Format(CultureInfo.CurrentCulture, CommonResources.TestCaseFilterFormatException, CommonResources.MissingOperator)); } fastFilter = fastFilterBuilder.ToFastFilter(); return(filterStack.Pop()); }