Новый лексер
This commit is contained in:
parent
ff328046a3
commit
0b2f414098
@ -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 final String input;
|
||||
private final int length;
|
||||
|
||||
private int pos;
|
||||
private char textStartChar;
|
||||
|
||||
private enum TokenizeState {
|
||||
DEFAULT, NUMBER, OPERATOR, WORD, TEXT, COMMENT
|
||||
}
|
||||
private Lexer(String input) {
|
||||
this.input = input;
|
||||
this.length = input.length();
|
||||
|
||||
private Lexer() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user