diff --git a/src/com/annimon/everlastingsummer/Lexer.java b/src/com/annimon/everlastingsummer/Lexer.java index f26b689..b2ee6d7 100644 --- a/src/com/annimon/everlastingsummer/Lexer.java +++ b/src/com/annimon/everlastingsummer/Lexer.java @@ -12,7 +12,7 @@ import java.util.Map; public final class Lexer { public static List 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 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(); buffer = new StringBuilder(); - state = TokenizeState.DEFAULT; } public List 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); + } }