private ExpressionTerm createTerm(
                final List<AbstractToken> tokens) {
            ExpressionTerm expressionTerm;

            if (NullSafe.isEmptyCollection(tokens)) {
                throw new RuntimeException("createTerm called with empty list");
            } else if (tokens.size() < 3) {
                throw new TokenException(tokens.getFirst(), "Incomplete term");
            }

            final AbstractToken fieldToken = tokens.get(0);
            final AbstractToken conditionToken = tokens.get(1);

            if (!TokenType.isString(fieldToken) && !TokenType.PARAM.equals(fieldToken.getTokenType())) {
                throw new TokenException(fieldToken, "Expected field string");
            }
            if (!TokenType.ALL_CONDITIONS.contains(conditionToken.getTokenType())) {
                throw new TokenException(conditionToken, "Expected condition token");
            }

            if (TokenType.IN.equals(conditionToken.getTokenType())) {
                expressionTerm = createInTerm(tokens);

            } else {
                final String field = fieldToken.getUnescapedText();
                final StringBuilder value = new StringBuilder();
                int end = addValue(tokens, value, 2);

                if (TokenType.BETWEEN.equals(conditionToken.getTokenType())) {
                    if (tokens.size() == end) {
                        throw new TokenException(tokens.getLast(), "Expected between and");
                    }

                    final AbstractToken betweenAnd = tokens.get(end);
                    if (!TokenType.BETWEEN_AND.equals(betweenAnd.getTokenType())) {
                        throw new TokenException(betweenAnd, "Expected between and");
                    }
                    value.append(", ");
                    end = addValue(tokens, value, end + 1);
                }

                // Check we consumed all tokens to get the value(s).
                if (end < tokens.size()) {
                    throw new TokenException(tokens.get(end), "Unexpected token");
                }

                // If we have a where clause then we expect the next token to contain an expression.
                Condition cond;
                switch (conditionToken.getTokenType()) {
                    case EQUALS -> cond = Condition.EQUALS;
                    case NOT_EQUALS -> cond = Condition.NOT_EQUALS;
                    case GREATER_THAN -> cond = Condition.GREATER_THAN;
                    case GREATER_THAN_OR_EQUAL_TO -> cond = Condition.GREATER_THAN_OR_EQUAL_TO;
                    case LESS_THAN -> cond = Condition.LESS_THAN;
                    case LESS_THAN_OR_EQUAL_TO -> cond = Condition.LESS_THAN_OR_EQUAL_TO;
                    case IS_NULL -> cond = Condition.IS_NULL;
                    case IS_NOT_NULL -> cond = Condition.IS_NOT_NULL;
                    case BETWEEN -> cond = Condition.BETWEEN;
                    default -> throw new TokenException(conditionToken, "Unknown condition: " + conditionToken);
                }

                expressionTerm = ExpressionTerm
                        .builder()
                        .field(field)
                        .condition(cond)
                        .value(value.toString().trim())
                        .build();
            }

            if (inHaving) {
                // Remember the tokens used for having clauses as we need to ensure they are added.
                additionalFields.add(fieldToken);
                if (tokens.size() >= 3) {
                    final AbstractToken valueToken = tokens.get(2);
                    if (TokenType.STRING.equals(valueToken.getTokenType()) ||
                        TokenType.PARAM.equals(valueToken.getTokenType())) {
                        additionalFields.add(valueToken);
                    }
                }
            }

            return expressionTerm;
        }
@param isLenient Ignores any additional child tokens found. This is to support getting just
                 the data source from a partially written query, which is likely to not be
                 a valid query. For example, you may type
                 <pre>{@code from View sel}</pre> and at this point do code completion on 'sel'
                 so if this method is called it will treat 'sel as an extra child.
stroom-query/stroom-query-common/src/main/java/stroom/query/language/SearchRequestFactory.java