CharacterScanner.java

package org.sterling.source.scanner;

import static java.util.regex.Pattern.compile;
import static org.sterling.source.scanner.ScannerUtil.invalidEscapeSequence;
import static org.sterling.source.scanner.ScannerUtil.unexpectedInput;
import static org.sterling.source.scanner.ScannerUtil.unterminatedCharacter;

import java.util.regex.Pattern;
import org.sterling.SterlingException;
import org.sterling.source.syntax.CharacterToken;
import org.sterling.source.syntax.NodeKind;
import org.sterling.source.syntax.Token;

public class CharacterScanner implements ScannerDelegate {

    private final Pattern asciiEscape;
    private final Pattern hexEscape;
    private final Pattern octalEscape;

    public CharacterScanner() {
        asciiEscape = compile("^\\\\[btnfr\"'\\\\]");
        hexEscape = compile("^\\\\[uU][a-fA-F0-9]{4}");
        octalEscape = compile("^\\\\[0-3]?[0-7]{2}");
    }

    @Override
    public boolean expect(NodeKind kind, InputReader reader) {
        return reader.expect('\'');
    }

    @Override
    public Token require(NodeKind kind, InputReader reader) throws SterlingException {
        if (!expect(kind, reader)) {
            throw unexpectedInput(reader, kind);
        }
        reader.store();
        if (!reader.store(asciiEscape, hexEscape, octalEscape)) {
            validateEscapeSequence(kind, reader);
            reader.store();
        }
        if (reader.expect('\'')) {
            reader.store();
        } else {
            throw unterminatedCharacter(reader, kind);
        }
        Token token = reader.accept(kind);
        char charValue = EscapeUtil.unescapeString(token.getValue()).charAt(1);
        return new CharacterToken(token, charValue);
    }

    private void validateEscapeSequence(NodeKind kind, InputReader reader) throws SterlingException {
        if (reader.expect('\\')) {
            throw invalidEscapeSequence(reader, kind);
        } else if (reader.expect('\'')) {
            throw unexpectedInput(reader, kind);
        } else if (reader.expect('\n')) {
            throw unterminatedCharacter(reader, kind);
        }
    }
}