def parse(self, filter_string: str) -> SearchFilter:
        """
        Parse a filter string into a SearchFilter object.
        The escape character: \\ will not be encoded into the SearchFilter.value or SearchFilter.value_list
        (unless it is escaped itself).
        """
        self._reset_state()

        if filter_string == '':
            raise FilterStringParsingError('Cannot parse empty filter string')

        stack: List[Union[str, Operator, Term]] = []
        parenthesis_count = 0
        escape = False
        read_space_until = None  # ignore space until reaching the value of this variable

        i = 0
        while i < len(filter_string):
            c = filter_string[i]

            # Stop ignoring white spaces if we are not in a list after a comma
            if c != ' ':
                self._in_term_value_after_comma = False

            # Special processing if we are reading a term value
            if self._read_term_value and (
                    c not in [' ', ')'] or
                    read_space_until != None or
                    self._in_term_value_after_comma or
                    escape
            ):
                if self._reached_term_end:
                    self._error(f"Expected end of term, but found '{c}'", filter_string, i)

                if escape:
                    self._current_token.append(c)
                    self._current_raw_token.append(c)
                    self._append_to_term_value(c)
                    escape = False
                elif c == '\\':
                    # Not added to current token or term value
                    self._current_raw_token.append(c)
                    escape = True
                elif c == ',' and self._term_type == MarqoFilterStringParser._TermType.In:
                    # Comma only has special meaning in IN term lists.
                    # Break into the next list for term_value.
                    self._term_value.append([])
                    self._current_token.append(c)
                    self._current_raw_token.append(c)
                    self._in_term_value_after_comma = True
                elif self._in_term_value_after_comma and c == ' ':
                    # Ignore all whitespace after comma in IN term lists.
                    pass
                else:
                    if c == read_space_until:
                        read_space_until = None
                        # Term end is reached if ) or ] are reached, EXCEPT for grouped items in IN lists.
                        # Example: a IN (1, 2, (3, 4))
                        # In this case, reaching the ) after 4 does NOT end the term.
                        if not (self._term_type == MarqoFilterStringParser._TermType.In and c == ')'):
                            self._reached_term_end = True
                    elif len(self._get_current_term_value()) == 0 and c == '(' and not read_space_until:  # start of term value
                        read_space_until = ')'
                    elif len(self._get_current_term_value()) == 0 and c == '[' and not read_space_until:  # start of term value
                        read_space_until = ']'
                        if self._term_type != MarqoFilterStringParser._TermType.In:
                            self._term_type = MarqoFilterStringParser._TermType.Range
                        else:
                            self._error('Unexpected [ after IN operator.', filter_string, i)
                    else:
                        self._append_to_term_value(c)

                    self._current_token.append(c)
                    self._current_raw_token.append(c)

                # Increment i for loop
                i += 1
                continue

            if escape:
                self._current_token.append(c)
                self._current_raw_token.append(c)
                escape = False
            elif c == ':':
                self._read_term_value = True
                self._term_type = MarqoFilterStringParser._TermType.Equality
                self._term_field = ''.join(self._current_token)
                self._current_token.append(c)
                self._current_raw_token.append(c)
            elif c == '(':
                if len(self._current_token) > 0:
                    self._error('Unexpected (', filter_string, i)

                stack.append(c)
                parenthesis_count += 1
            elif c == ')':
                parenthesis_count -= 1
                # Last parenthesis in an IN list
                # Example: a IN (1, 2, 3)
                # Note: token is NOT pushed at this state. Just note that term has ended.
                if self._term_type == MarqoFilterStringParser._TermType.In and not self._reached_term_end:
                    self._current_token.append(c)
                    self._current_raw_token.append(c)
                    self._reached_term_end = True
                else:
                    # Grouping parenthesis for expressions (grouping multiple terms)
                    # Example: (a:1 AND b in (2)) OR c:3
                    self._push_token(
                        stack,
                        filter_string,
                        self._current_token,
                        self._current_raw_token,
                        self._term_field,
                        self._term_value,
                        self._term_type,
                        i
                    )
                    self._reset_state()
                    self._merge_expression(stack, filter_string, i)

            elif c == '\\':
                self._current_raw_token.append(c)
                escape = True
            elif c == ' ':
                # Found the ' IN ' operator. Look for a list starting with '(' on the next pass.
                if self._term_divider_is_IN(i, filter_string):
                    self._read_term_value = True
                    self._term_type = MarqoFilterStringParser._TermType.In
                    self._term_value = [[]]
                    self._term_field = ''.join(self._current_token)
                    self._current_token.append(self.IN_TERM_DIVIDER)
                    self._current_raw_token.append(self.IN_TERM_DIVIDER)
                    parenthesis_count += 1

                    # Skip the next 4 characters (they were used in ' IN (' operator).
                    i += len(self.IN_TERM_DIVIDER) - 1

                elif not self._reached_term_end and self._term_type == MarqoFilterStringParser._TermType.In:
                    # Found UNGROUPED white space in IN term list. This should be an error.
                    self._error('Unexpected white space in IN term list', filter_string, i)
                else:
                    # Found the space AFTER a full term/token.
                    self._push_token(
                        stack,
                        filter_string,
                        self._current_token,
                        self._current_raw_token,
                        self._term_field,
                        self._term_value,
                        self._term_type,
                        i
                    )
                    self._reset_state()
            else:
                self._current_token.append(c)
                self._current_raw_token.append(c)
            # Increment i for loop
            i += 1

        # Post-parse loop cleanup
        if len(self._current_token) > 0:
            self._push_token(
                stack,
                filter_string,
                self._current_token,
                self._current_raw_token,
                self._term_field,
                self._term_value,
                self._term_type,
                len(filter_string)
            )

        if parenthesis_count != 0:
            # merge_stack will catch this, but this is a more specific error message
            self._error('Unbalanced parentheses')

        self._merge_stack(stack, filter_string)

        if len(stack) != 1:
            # This should be unreachable
            self._error('Failed to parse filter string')

        root = stack.pop()

        return SearchFilter(root)

        Parse a filter string into a SearchFilter object.
        The escape character: \ will not be encoded into the SearchFilter.value or SearchFilter.value_list
        (unless it is escaped itself).
        
src/marqo/core/search/search_filter.py