Новый лексер
This commit is contained in:
parent
ff328046a3
commit
0b2f414098
@ -12,7 +12,7 @@ import java.util.Map;
|
|||||||
public final class Lexer {
|
public final class Lexer {
|
||||||
|
|
||||||
public static List<Token> tokenize(String input) {
|
public static List<Token> tokenize(String input) {
|
||||||
return new Lexer().process(input).getTokens();
|
return new Lexer(input).process().getTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String OPERATOR_CHARS = "=+-<>()[]!$:";
|
private static final String OPERATOR_CHARS = "=+-<>()[]!$:";
|
||||||
@ -82,146 +82,170 @@ public final class Lexer {
|
|||||||
private final List<Token> tokens;
|
private final List<Token> tokens;
|
||||||
private final StringBuilder buffer;
|
private final StringBuilder buffer;
|
||||||
|
|
||||||
private TokenizeState state;
|
private final String input;
|
||||||
|
private final int length;
|
||||||
|
|
||||||
private int pos;
|
private int pos;
|
||||||
private char textStartChar;
|
|
||||||
|
|
||||||
private enum TokenizeState {
|
private Lexer(String input) {
|
||||||
DEFAULT, NUMBER, OPERATOR, WORD, TEXT, COMMENT
|
this.input = input;
|
||||||
}
|
this.length = input.length();
|
||||||
|
|
||||||
private Lexer() {
|
|
||||||
tokens = new ArrayList<Token>();
|
tokens = new ArrayList<Token>();
|
||||||
buffer = new StringBuilder();
|
buffer = new StringBuilder();
|
||||||
state = TokenizeState.DEFAULT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Token> getTokens() {
|
public List<Token> getTokens() {
|
||||||
return tokens;
|
return tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Lexer process(String input) {
|
public Lexer process() {
|
||||||
final int length = input.length();
|
pos = 0;
|
||||||
for (pos = 0; pos < length; pos++) {
|
while (pos < length) {
|
||||||
tokenize(input.charAt(pos));
|
tokenize();
|
||||||
}
|
}
|
||||||
tokenize('\0');// EOF
|
addToken(TokenType.EOF);
|
||||||
addToken(TokenType.EOF, false);
|
|
||||||
input = null;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tokenize(char ch) {
|
private void tokenize() {
|
||||||
switch (state) {
|
skipWhitespaces();
|
||||||
case DEFAULT: tokenizeDefault(ch); break;
|
final char ch = peek(0);
|
||||||
case WORD: tokenizeWord(ch); break;
|
|
||||||
case NUMBER: tokenizeNumber(ch); break;
|
|
||||||
case OPERATOR: tokenizeOperator(ch); break;
|
|
||||||
case TEXT: tokenizeText(ch); break;
|
|
||||||
case COMMENT: tokenizeComment(ch); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tokenizeDefault(char ch) {
|
|
||||||
if (Character.isLetter(ch)) {
|
if (Character.isLetter(ch)) {
|
||||||
// Слово (ключевое слово или команда)
|
// Слово (ключевое слово или команда)
|
||||||
buffer.append(ch);
|
tokenizeWord();
|
||||||
state = TokenizeState.WORD;
|
|
||||||
} else if (Character.isDigit(ch)) {
|
} else if (Character.isDigit(ch)) {
|
||||||
// Число
|
// Число
|
||||||
buffer.append(ch);
|
tokenizeNumber();
|
||||||
state = TokenizeState.NUMBER;
|
|
||||||
} else if (ch == '"' || ch == '\'') {
|
} else if (ch == '"' || ch == '\'') {
|
||||||
// Текст в "кавычках" или 'одинарных'
|
// Текст в "кавычках" или 'одинарных'
|
||||||
textStartChar = ch;
|
tokenizeText(ch);
|
||||||
state = TokenizeState.TEXT;
|
|
||||||
} else if (ch == '#') {
|
} else if (ch == '#') {
|
||||||
clearBuffer();
|
tokenizeComment();
|
||||||
state = TokenizeState.COMMENT;
|
|
||||||
} else {
|
} else {
|
||||||
// Операторы и спецсимволы
|
// Операторы и спецсимволы
|
||||||
tokenizeOperator(ch);
|
tokenizeOperator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tokenizeWord(char ch) {
|
private void tokenizeWord() {
|
||||||
if (Character.isLetterOrDigit(ch) || (ch == '_') || (ch == '.')) {
|
char ch = peek(0);
|
||||||
buffer.append(ch);
|
|
||||||
} else {
|
|
||||||
if ( (ch == '"' || ch == '\'') && (buffer.length() == 1) && (buffer.charAt(0) == 'u') ) {
|
|
||||||
// Строка в юникоде u"текст" или u'текст'
|
// Строка в юникоде u"текст" или u'текст'
|
||||||
clearBuffer();
|
if (ch == 'u') {
|
||||||
textStartChar = ch;
|
final char textStartChar = peek(1);
|
||||||
state = TokenizeState.TEXT;
|
if (textStartChar == '"' || textStartChar == '\'') {
|
||||||
|
next(); // u
|
||||||
|
tokenizeText(textStartChar);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final String word = buffer.toString().toLowerCase(Locale.ENGLISH);
|
}
|
||||||
addToken(KEYWORDS.containsKey(word) ? KEYWORDS.get(word) : TokenType.WORD);
|
|
||||||
|
clearBuffer();
|
||||||
|
while(Character.isLetterOrDigit(ch) || (ch == '_') || (ch == '.')) {
|
||||||
|
buffer.append(ch);
|
||||||
|
ch = next();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String word = buffer.toString();
|
||||||
|
final String key = word.toLowerCase(Locale.ENGLISH);
|
||||||
|
if (KEYWORDS.containsKey(key)) {
|
||||||
|
addToken(KEYWORDS.get(key));
|
||||||
|
} else {
|
||||||
|
addToken(TokenType.WORD, word);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tokenizeNumber(char ch) {
|
private void tokenizeNumber() {
|
||||||
|
char ch = peek(0);
|
||||||
|
clearBuffer();
|
||||||
|
boolean decimal = false;
|
||||||
|
while (true) {
|
||||||
// Целое или вещественное число.
|
// Целое или вещественное число.
|
||||||
if (ch == '.') {
|
if (ch == '.') {
|
||||||
// Пропускаем десятичные точки, если они уже были в числе.
|
// Пропускаем десятичные точки, если они уже были в числе.
|
||||||
if (buffer.indexOf(".") == -1) buffer.append(ch);
|
if (!decimal) buffer.append(ch);
|
||||||
} else if (Character.isDigit(ch)) {
|
decimal = true;
|
||||||
buffer.append(ch);
|
} else if (!Character.isDigit(ch)) {
|
||||||
} else {
|
break;
|
||||||
addToken(TokenType.NUMBER);
|
|
||||||
}
|
}
|
||||||
|
buffer.append(ch);
|
||||||
|
ch = next();
|
||||||
|
}
|
||||||
|
addToken(TokenType.NUMBER, buffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tokenizeOperator(char ch) {
|
private void tokenizeOperator() {
|
||||||
|
final char ch = peek(0);
|
||||||
final int index = OPERATOR_CHARS.indexOf(ch);
|
final int index = OPERATOR_CHARS.indexOf(ch);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
addToken(OPERATOR_TYPES[index], false);
|
addToken(OPERATOR_TYPES[index]);
|
||||||
}
|
}
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tokenizeText(char ch) {
|
private void tokenizeText(final char textStartChar) {
|
||||||
final int len = buffer.length();
|
clearBuffer();
|
||||||
if (ch == textStartChar) {
|
char ch = next(); // пропускаем открывающую кавычку
|
||||||
// Добавляем токен, если не было экранирования символа кавычки.
|
while(true) {
|
||||||
if (len == 0 ||
|
if (ch == textStartChar) break;
|
||||||
( (len > 0) && (buffer.charAt(len - 1) != '\\') )) {
|
if (ch == '\0') break; // не закрыта кавычка, но мы добавим то, что есть
|
||||||
addToken(TokenType.TEXT, false);
|
if (ch == '\\') {
|
||||||
return;
|
ch = next();
|
||||||
}
|
|
||||||
// Экранируем символ кавычки.
|
|
||||||
if (len > 0) {
|
|
||||||
buffer.setCharAt(len - 1, textStartChar);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Экранирование символов
|
|
||||||
if ( (len > 0) && (buffer.charAt(len - 1) == '\\') ) {
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'n': buffer.setCharAt(len - 1, '\n'); return;
|
case 'n': ch = next(); buffer.append('\n'); continue;
|
||||||
case 't': buffer.setCharAt(len - 1, '\t'); return;
|
case 't': ch = next(); buffer.append('\t'); continue;
|
||||||
|
default:
|
||||||
|
if (ch == textStartChar) {
|
||||||
|
ch = next();
|
||||||
|
buffer.append('"');
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
buffer.append('\\');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
buffer.append(ch);
|
buffer.append(ch);
|
||||||
|
ch = next();
|
||||||
|
}
|
||||||
|
next(); // пропускаем закрывающую кавычку
|
||||||
|
addToken(TokenType.TEXT, buffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tokenizeComment(char ch) {
|
private void tokenizeComment() {
|
||||||
if (ch == '\n' || ch == '\r') {
|
char ch = peek(0);
|
||||||
state = TokenizeState.DEFAULT;
|
while("\n\r\0".indexOf(ch) == -1) {
|
||||||
|
ch = next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipWhitespaces() {
|
||||||
|
char ch = peek(0);
|
||||||
|
while(ch != '\0' && Character.isWhitespace(ch)) {
|
||||||
|
ch = next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToken(TokenType type) {
|
private void addToken(TokenType type) {
|
||||||
addToken(type, true);
|
addToken(type, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToken(TokenType type, boolean reprocessLastChar) {
|
private void addToken(TokenType type, String text) {
|
||||||
tokens.add(new Token(buffer.toString(), type));
|
tokens.add(new Token(text, type));
|
||||||
clearBuffer();
|
|
||||||
if (reprocessLastChar) pos--;
|
|
||||||
state = TokenizeState.DEFAULT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearBuffer() {
|
private void clearBuffer() {
|
||||||
buffer.setLength(0);
|
buffer.setLength(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private char next() {
|
||||||
|
pos++;
|
||||||
|
if (pos >= length) return '\0';
|
||||||
|
return input.charAt(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private char peek(int relativePosition) {
|
||||||
|
int tempPos = pos + relativePosition;
|
||||||
|
if (tempPos >= length) return '\0';
|
||||||
|
return input.charAt(tempPos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user