private void processWindow(final KeywordGroup keywordGroup,
                                   final TableSettings.Builder builder) {
            final List<AbstractToken> children = new ArrayList<>(keywordGroup.getChildren());

            final HoppingWindow.Builder hoppingWindowBuilder = HoppingWindow.builder();

            // Get field name.
            if (!children.isEmpty()) {
                final AbstractToken token = children.getFirst();
                if (!TokenType.isString(token)) {
                    throw new TokenException(token, "Syntax exception");
                }
                final String field = token.getUnescapedText();
                hoppingWindowBuilder.timeField(field);
                children.removeFirst();
            } else {
                throw new TokenException(keywordGroup, "Expected field");
            }

            // Get `by` and duration.
            final int byIndex = getTokenIndex(children, token -> TokenType.BY.equals(token.getTokenType()));
            if (byIndex == -1) {
                throw new TokenException(keywordGroup, "Syntax exception, expected by");
            } else if (children.size() > byIndex + 1) {
                final AbstractToken token = children.get(byIndex + 1);
                if (!TokenType.DURATION.equals(token.getTokenType())) {
                    throw new TokenException(token, "Syntax exception, expected valid window duration");
                }
                final String durationString = token.getUnescapedText();
                hoppingWindowBuilder.windowSize(durationString);
                hoppingWindowBuilder.advanceSize(durationString);

                // We found the duration so remove the tokens.
                children.remove(byIndex + 1);
                children.remove(byIndex);
            } else {
                throw new TokenException(children.get(byIndex), "Syntax exception, expected window duration");
            }

            // Get `advance` and duration.
            final int advanceIndex = getTokenIndex(children, token -> TokenType.isString(token) &&
                                                                      token.getUnescapedText().equalsIgnoreCase(
                                                                              "advance"));
            if (advanceIndex != -1) {
                if (children.size() > advanceIndex + 1) {
                    final AbstractToken token = children.get(advanceIndex + 1);
                    if (!TokenType.DURATION.equals(token.getTokenType())) {
                        throw new TokenException(token, "Syntax exception, expected valid advance duration");
                    }
                    final String advanceString = token.getUnescapedText();
                    hoppingWindowBuilder.advanceSize(advanceString);

                    // We found the duration so remove the tokens.
                    children.remove(advanceIndex + 1);
                    children.remove(advanceIndex);
                } else {
                    throw new TokenException(children.get(advanceIndex), "Syntax exception, expected advance duration");
                }
            }

            // Get `using` and function.
            final int usingIndex = getTokenIndex(children, token -> TokenType.isString(token) &&
                                                                    token.getUnescapedText().equalsIgnoreCase("using"));
            if (usingIndex != -1) {
                if (children.size() > usingIndex + 1) {
                    final AbstractToken token = children.get(usingIndex + 1);
//                    if (!TokenType.FUNCTION_GROUP.equals(token.getTokenType())) {
//                        throw new TokenException(token, "Syntax exception, expected valid using function");
//                    }
                    final String function = token.getUnescapedText();
                    hoppingWindowBuilder.function(function);

                    // We found the duration so remove the tokens.
                    children.remove(usingIndex + 1);
                    children.remove(usingIndex);
                } else {
                    throw new TokenException(children.get(usingIndex), "Syntax exception, expected using function");
                }
            }

            if (!children.isEmpty()) {
                throw new TokenException(children.getFirst(), "Unexpected token");
            }

            final Window window = hoppingWindowBuilder.build();
            final CompiledWindow compiledWindow = CompiledWindow.create(window);
            // Add time window fields, so they can be used by other expressions.
            compiledWindow.addWindowFields(expressionContext, fieldIndex, expressionMap);
            optionalCompiledWindow = Optional.of(compiledWindow);
            builder.window(window);
        }
@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