private static IExpression ParseExpression(ref StringSlice text)
        {
            ConsumeWhitespace(ref text);
            if (text.Length == 0)
            {
                return(new AllExpression());
            }

            List <IExpression> terms = new List <IExpression>();

            terms.Add(ParseAndExpression(ref text));

            while (true)
            {
                Literal <ExpressionToken> t = StartingToken(ref text);
                if (t?.Value == ExpressionToken.Or)
                {
                    text = text.Substring(t.Text.Length);
                    terms.Add(ParseAndExpression(ref text));
                }
                else
                {
                    break;
                }
            }

            return(terms.Count == 1 ? terms[0] : new OrExpression(terms));
        }
        private static IExpression ParseTerm(ref StringSlice text)
        {
            Literal <ExpressionToken> t = StartingToken(ref text);

            // All?
            if (t?.Value == ExpressionToken.All)
            {
                text = text.Substring(t.Text.Length);
                return(new AllExpression());
            }

            // Parenthesized subexpression?
            if (t?.Value == ExpressionToken.LeftParen)
            {
                text = text.Substring(t.Text.Length);
                IExpression subexpression = ParseExpression(ref text);

                t = StartingToken(ref text);
                if (t?.Value != ExpressionToken.RightParen)
                {
                    throw new QueryParseException("nested expression end paren", text);
                }
                text = text.Substring(t.Text.Length);

                return(subexpression);
            }

            // Not?
            if (t?.Value == ExpressionToken.Not)
            {
                text = text.Substring(t.Text.Length);
                return(new NotExpression(ParseTerm(ref text)));
            }

            // PropertyName CompareOperator Value
            StringSlice     propertyName = ParseString(ref text);
            CompareOperator op           = ParseCompareOperator(ref text);
            StringSlice     value        = ParseString(ref text);

            return(new TermExpression(propertyName.ToString(), op, value.ToString()));
        }
        private static void ConsumeWhitespace(ref StringSlice text)
        {
            int whitespaceCount = 0;

            for (; whitespaceCount < text.Length; ++whitespaceCount)
            {
                if (!IsWhitespace(text[whitespaceCount]))
                {
                    break;
                }
            }

            text = text.Substring(whitespaceCount);
        }
        private static CompareOperator ParseCompareOperator(ref StringSlice text)
        {
            ConsumeWhitespace(ref text);

            foreach (Literal <CompareOperator> op in CompareOperators)
            {
                if (text.StartsWith(op.Text, StringComparison.OrdinalIgnoreCase))
                {
                    text = text.Substring(op.Text.Length);
                    return(op.Value);
                }
            }

            throw new QueryParseException("compare operator", text);
        }
        private static IExpression ParseAndExpression(ref StringSlice text)
        {
            List <IExpression> terms = new List <IExpression>();

            terms.Add(ParseTerm(ref text));

            while (true)
            {
                Literal <ExpressionToken> t = StartingToken(ref text);
                if (t?.Value == ExpressionToken.And)
                {
                    text = text.Substring(t.Text.Length);
                    terms.Add(ParseTerm(ref text));
                }
                else
                {
                    break;
                }
            }

            return(terms.Count == 1 ? terms[0] : new AndExpression(terms));
        }
        private static StringSlice ParseString(ref StringSlice text)
        {
            ConsumeWhitespace(ref text);
            if (text.Length == 0)
            {
                throw new QueryParseException("string", text);
            }

            char start = text[0];

            if (start == '\'' || start == '"')
            {
                char terminator = start;

                // Keep a StringBuilder for unescaping (only if needed) and track what we've copied to it already
                StringBuilder unescapedForm = null;
                int           nextCopyFrom  = 1;

                int terminatorIndex = 1;
                for (; terminatorIndex < text.Length - 1; ++terminatorIndex)
                {
                    if (text[terminatorIndex] == terminator)
                    {
                        // If this wasn't a doubled quote, the string is done
                        if (text[terminatorIndex + 1] != terminator)
                        {
                            break;
                        }

                        // Copy to the StringBuilder without either quote
                        if (unescapedForm == null)
                        {
                            unescapedForm = new StringBuilder();
                        }
                        text.Substring(nextCopyFrom, terminatorIndex - nextCopyFrom).AppendTo(unescapedForm);

                        // Include the second quote next time
                        nextCopyFrom = terminatorIndex + 1;

                        // Skip to look after the second quote next iteration
                        terminatorIndex += 1;
                    }
                }

                // Ensure the string is terminated
                if (terminatorIndex == text.Length || text[terminatorIndex] != terminator)
                {
                    throw new QueryParseException("string terminator", text);
                }

                if (unescapedForm != null)
                {
                    if (nextCopyFrom < terminatorIndex)
                    {
                        text.Substring(nextCopyFrom, terminatorIndex - nextCopyFrom).AppendTo(unescapedForm);
                    }
                    text = text.Substring(terminatorIndex + 1);
                    return(unescapedForm.ToString());
                }
                else
                {
                    StringSlice resultSlice = text.Substring(1, terminatorIndex - 1);
                    text = text.Substring(terminatorIndex + 1);
                    return(resultSlice);
                }
            }
            else
            {
                // Read until whitespace or ')' (so closing a subexpression doesn't require a space)
                int terminatorIndex = 0;
                for (; terminatorIndex < text.Length; ++terminatorIndex)
                {
                    char c = text[terminatorIndex];
                    if (c == ')' || IsWhitespace(c))
                    {
                        break;
                    }
                }

                // Consume and return the string
                StringSlice result = text.Substring(0, terminatorIndex);
                text = text.Substring(terminatorIndex);
                return(result);
            }
        }