Профессиональный лексический анализ на регулярных выражениях07.08.2019 10:34
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