Новый лексер

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 static List<Token> tokenize(String input) {
return new Lexer().process(input).getTokens();
return new Lexer(input).process().getTokens();
}
private static final String OPERATOR_CHARS = "=+-<>()[]!$:";
@ -82,146 +82,170 @@ public final class Lexer {
private final List<Token> tokens;
private final StringBuilder buffer;
private TokenizeState state;
private int pos;
private char textStartChar;
private enum TokenizeState {
DEFAULT, NUMBER, OPERATOR, WORD, TEXT, COMMENT
}
private final String input;
private final int length;
private Lexer() {
private int pos;
private Lexer(String input) {
this.input = input;
this.length = input.length();
tokens = new ArrayList<Token>();
buffer = new StringBuilder();
state = TokenizeState.DEFAULT;
}
public List<Token> getTokens() {
return tokens;
}
public Lexer process(String input) {
final int length = input.length();
for (pos = 0; pos < length; pos++) {
tokenize(input.charAt(pos));
public Lexer process() {
pos = 0;
while (pos < length) {
tokenize();
}
tokenize('\0');// EOF
addToken(TokenType.EOF, false);
input = null;
addToken(TokenType.EOF);
return this;
}
private void tokenize(char ch) {
switch (state) {
case DEFAULT: tokenizeDefault(ch); break;
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) {
private void tokenize() {
skipWhitespaces();
final char ch = peek(0);
if (Character.isLetter(ch)) {
// Слово (ключевое слово или команда)
buffer.append(ch);
state = TokenizeState.WORD;
tokenizeWord();
} else if (Character.isDigit(ch)) {
// Число
buffer.append(ch);
state = TokenizeState.NUMBER;
tokenizeNumber();
} else if (ch == '"' || ch == '\'') {
// Текст в "кавычках" или 'одинарных'
textStartChar = ch;
state = TokenizeState.TEXT;
tokenizeText(ch);
} else if (ch == '#') {
clearBuffer();
state = TokenizeState.COMMENT;
tokenizeComment();
} else {
// Операторы и спецсимволы
tokenizeOperator(ch);
tokenizeOperator();
}
}
private void tokenizeWord(char ch) {
if (Character.isLetterOrDigit(ch) || (ch == '_') || (ch == '.')) {
buffer.append(ch);
} else {
if ( (ch == '"' || ch == '\'') && (buffer.length() == 1) && (buffer.charAt(0) == 'u') ) {
// Строка в юникоде u"текст" или u'текст'
clearBuffer();
textStartChar = ch;
state = TokenizeState.TEXT;
private void tokenizeWord() {
char ch = peek(0);
// Строка в юникоде u"текст" или u'текст'
if (ch == 'u') {
final char textStartChar = peek(1);
if (textStartChar == '"' || textStartChar == '\'') {
next(); // u
tokenizeText(textStartChar);
return;
}
final String word = buffer.toString().toLowerCase(Locale.ENGLISH);
addToken(KEYWORDS.containsKey(word) ? KEYWORDS.get(word) : TokenType.WORD);
}
}
private void tokenizeNumber(char ch) {
// Целое или вещественное число.
if (ch == '.') {
// Пропускаем десятичные точки, если они уже были в числе.
if (buffer.indexOf(".") == -1) buffer.append(ch);
} else if (Character.isDigit(ch)) {
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.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);
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) {
final int len = buffer.length();
if (ch == textStartChar) {
// Добавляем токен, если не было экранирования символа кавычки.
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 skipWhitespaces() {
char ch = peek(0);
while(ch != '\0' && Character.isWhitespace(ch)) {
ch = next();
}
}
private void addToken(TokenType type) {
addToken(type, true);
addToken(type, "");
}
private void addToken(TokenType type, boolean reprocessLastChar) {
tokens.add(new Token(buffer.toString(), type));
clearBuffer();
if (reprocessLastChar) pos--;
state = TokenizeState.DEFAULT;
private void addToken(TokenType type, String text) {
tokens.add(new Token(text, type));
}
private void clearBuffer() {
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);
}
}