Новый лексер

This commit is contained in:
Victor 2015-06-15 21:30:03 +03:00
parent ff328046a3
commit 0b2f414098

View File

@ -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); // Строка в юникоде u"текст" или u'текст'
} else { if (ch == 'u') {
if ( (ch == '"' || ch == '\'') && (buffer.length() == 1) && (buffer.charAt(0) == 'u') ) { final char textStartChar = peek(1);
// Строка в юникоде u"текст" или u'текст' if (textStartChar == '"' || textStartChar == '\'') {
clearBuffer(); next(); // u
textStartChar = ch; tokenizeText(textStartChar);
state = TokenizeState.TEXT;
return; return;
} }
final String word = buffer.toString().toLowerCase(Locale.ENGLISH);
addToken(KEYWORDS.containsKey(word) ? KEYWORDS.get(word) : TokenType.WORD);
} }
}
private void tokenizeNumber(char ch) { clearBuffer();
// Целое или вещественное число. while(Character.isLetterOrDigit(ch) || (ch == '_') || (ch == '.')) {
if (ch == '.') {
// Пропускаем десятичные точки, если они уже были в числе.
if (buffer.indexOf(".") == -1) buffer.append(ch);
} else if (Character.isDigit(ch)) {
buffer.append(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 { } else {
addToken(TokenType.NUMBER); addToken(TokenType.WORD, word);
} }
} }
private void tokenizeOperator(char ch) { private void tokenizeNumber() {
char ch = peek(0);
clearBuffer();
boolean decimal = false;
while (true) {
// Целое или вещественное число.
if (ch == '.') {
// Пропускаем десятичные точки, если они уже были в числе.
if (!decimal) buffer.append(ch);
decimal = true;
} else if (!Character.isDigit(ch)) {
break;
}
buffer.append(ch);
ch = next();
}
addToken(TokenType.NUMBER, buffer.toString());
}
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(final char textStartChar) {
clearBuffer();
char ch = next(); // пропускаем открывающую кавычку
while(true) {
if (ch == textStartChar) break;
if (ch == '\0') break; // не закрыта кавычка, но мы добавим то, что есть
if (ch == '\\') {
ch = next();
switch (ch) {
case 'n': ch = next(); buffer.append('\n'); continue;
case 't': ch = next(); buffer.append('\t'); continue;
default:
if (ch == textStartChar) {
ch = next();
buffer.append('"');
continue;
}
}
buffer.append('\\');
continue;
}
buffer.append(ch);
ch = next();
}
next(); // пропускаем закрывающую кавычку
addToken(TokenType.TEXT, buffer.toString());
}
private void tokenizeComment() {
char ch = peek(0);
while("\n\r\0".indexOf(ch) == -1) {
ch = next();
} }
} }
private void tokenizeText(char ch) { private void skipWhitespaces() {
final int len = buffer.length(); char ch = peek(0);
if (ch == textStartChar) { while(ch != '\0' && Character.isWhitespace(ch)) {
// Добавляем токен, если не было экранирования символа кавычки. ch = next();
if (len == 0 ||
( (len > 0) && (buffer.charAt(len - 1) != '\\') )) {
addToken(TokenType.TEXT, false);
return;
}
// Экранируем символ кавычки.
if (len > 0) {
buffer.setCharAt(len - 1, textStartChar);
return;
}
}
// Экранирование символов
if ( (len > 0) && (buffer.charAt(len - 1) == '\\') ) {
switch (ch) {
case 'n': buffer.setCharAt(len - 1, '\n'); return;
case 't': buffer.setCharAt(len - 1, '\t'); return;
}
}
buffer.append(ch);
}
private void tokenizeComment(char ch) {
if (ch == '\n' || ch == '\r') {
state = TokenizeState.DEFAULT;
} }
} }
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);
}
} }