TokenizerRepeater.java

package net.morimekta.lexer;

import net.morimekta.strings.EscapeUtil;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;
import static net.morimekta.strings.EscapeUtil.javaEscape;

/**
 * the tokenizer repeater is meant to repeat a set of tokens in the same
 * sequence.
 *
 * @param <TT> TokenType type.
 * @param <T> Token type.
 */
public class TokenizerRepeater<TT, T extends Token<TT>> implements Tokenizer<TT, T> {
    private final Queue<T> unread;
    private       T        lastToken;

    /**
     * @param tokens List of tokens.
     */
    public TokenizerRepeater(List<T> tokens) {
        if (requireNonNull(tokens, "tokens == null").isEmpty()) {
            throw new IllegalArgumentException("Empty token list cannot be repeated");
        }

        unread = new ArrayDeque<>();
        unread.addAll(tokens);
    }

    @Override
    public T parseNextToken() {
        if (unread.size() > 0) {
            lastToken = unread.poll();
            return lastToken;
        }
        return null;
    }

    @Override
    public T readUntil(CharSequence terminator, TT type, boolean allowEof) throws IOException {
        if (unread.size() > 0) {
            if (!Objects.equals(unread.peek().type(), type)) {
                throw new LexerException("type mismatch: " + type + " != " + lastToken.type());
            }
            lastToken = requireNonNull(unread.poll());
            return lastToken;
        }
        if (allowEof) return null;
        throw new LexerException(currentLine(), currentLineNo(), currentLinePos(), 1,
                                 "End of stream reading until '" + terminator + "'");
    }

    @Override
    public int currentLineNo() {
        if (lastToken != null) {
            return lastToken.lineNo();
        }
        T next = requireNonNull(unread.peek());
        return next.lineNo();
    }

    @Override
    public int currentLinePos() {
        if (lastToken != null) {
            return lastToken.linePos() + lastToken.length();
        }
        T next = requireNonNull(unread.peek());
        return next.linePos();
    }

    @Override
    public CharSequence currentLine() {
        if (lastToken != null) {
            return lastToken.line();
        }
        T next = requireNonNull(unread.peek());
        return next.line();
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "{" +
               unread.stream()
                     .map(TokenizerRepeater::quote)
                     .collect(Collectors.joining(", ")) + "}";
    }

    private static String quote(Token<?> t) {
        return "'" + javaEscape(t) + "'";
    }
}