Профессиональный лексический анализ на регулярных выражениях

?v=1
package org.example;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Токенайзер на регулярных выражениях.
 * Использует именованные группы, чтобы не зависеть от способа группировки базовых регулярных выражений.
 */
public class RegexTokenizer implements Enumeration {

    // Строка, которую необходимо разбить на лексемы
    private final String content;

    // Массив типов токенов с регулярными выражениями для анализа
    private final ITokenType[] tokenTypes;

    private final Matcher matcher;

    // Текущая позиция анализа
    private int currentPosition = 0;

    /**
     * @param content строка для анализа
     * @param tokenTypes Массив типов токенов с регулярными выражениями для анализа
     */
    public RegexTokenizer(String content, ITokenType[] tokenTypes) {
        this.content = content;
        this.tokenTypes = tokenTypes;

        // Формируем общее регулярное выражение для анализа
        List regexList = new ArrayList<>();
        for (int i = 0; i < tokenTypes.length; i++) {
            ITokenType tokenType = tokenTypes[i];
            regexList.add("(?" + tokenType.getRegex() + ")");
        }
        String regex = regexList.stream().collect(Collectors.joining("|"));

        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        // Запускаем первый поиск
        matcher = pattern.matcher(content);
        matcher.find();
    }

    @Override
    public boolean hasMoreElements() {
        return currentPosition < content.length();
    }

    @Override
    public Token nextElement() {
        boolean found = currentPosition > matcher.start() ? matcher.find() : true;

        int start = found ? matcher.start() : content.length();
        int end = found ? matcher.end() : content.length();

        if(found && currentPosition == start) {
            currentPosition = end;
            // Лексема найдена- определяем тип
            for (int i = 0; i < tokenTypes.length; i++) {
                String si = "g" + i;
                if (matcher.start(si) == start && matcher.end(si) == end) {
                    return createToken(content, tokenTypes[i], start, end);
                }
            }
        }
        throw new IllegalStateException("Не удается определить лексему в позиции " + currentPosition);
    }

    /**
     * Создаем токен-лексему по параметрам, можно переопределить этот метод, чтобы например ускорить работу алгоритма,
     * например не вырезать текст токена из оригинальной строки для некоторых токенов (пробел, комментарий)
     * @param content оригинальная строка для анализа
     * @param tokenType тип токена
     * @param start начало токена в оригинальной строке
     * @param end конец токена в оригинальной строке
     * @return объект токена-лексемы
     */
    protected Token createToken(String content, ITokenType tokenType, int start, int end) {
        return new Token(content.substring(start, end), tokenType, start);
    }

    /**
     * Список типов токенов для SQL
     */
    public enum SQLTokenType implements ITokenType {
        KEYWORD("\\b(?:select|from|where|group|by|order|or|and|not|exists|having|join|left|right|inner)\\b"),
        ID("[A-Za-z][A-Za-z0-9]*"),
        REAL_NUMBER("[0-9]+\\.[0-9]*"),
        NUMBER("[0-9]+"),
        STRING("'[^']*'"),
        SPACE("\\s+"),
        COMMENT("\\-\\-[^\\n\\r]*"),
        OPERATION("[+\\-\\*/.=\\(\\)]");

        private final String regex;

        SQLTokenType(String regex) {
            this.regex = regex;
        }

        @Override
        public String getRegex() {
            return regex;
        }
    }

    public static void main(String[] args) {
        String s = "select count(id) -- count of 'Johns' \n" +
                "FROM person\n" +
                "where name = 'John'";
        RegexTokenizer tokenizer = new RegexTokenizer(s, SQLTokenType.values());
        while(tokenizer.hasMoreElements()) {
            Token token = tokenizer.nextElement();
            System.out.println(token.getText() + " : " + token.getType());
        }
    }
}

/**
 * Класс- токен (лексема)
 */
public class Token {
    // Текст токена
    private final String text;
    // Тип токена
    private final ITokenType type;
    // Начало токена в исходном тексте
    private final int start;

    public Token(String text, ITokenType type, int start) {
        this.text = text;
        this.type = type;
        this.start = start;
    }

    public String getText() {
        return text;
    }

    public ITokenType getType() {
        return type;
    }

    public int getStart() {
        return start;
    }
}
/**
 * Интерфейс для типа токена
 */
public interface ITokenType {
    /**
     * Регулярное выражение для данного типа токена
     */
    String getRegex();
}

© Habrahabr.ru