internal static FilterExpression Parse()

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();
    }