in src/Microsoft.TestPlatform.Common/Filtering/FilterExpression.cs [166:274]
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 parenthesis).
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(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();
}