|
|
@@ -3,7 +3,9 @@
|
|
|
package com.craftinginterpreters.lox;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
|
|
|
import static com.craftinginterpreters.lox.TokenType.*;
|
|
|
|
|
|
@@ -18,6 +20,26 @@ public class Scanner {
|
|
|
private int start = 0; // points to the first character in the lexeme being scanned
|
|
|
private int current = 0; // points at the character currently being considered.
|
|
|
private int line = 1;
|
|
|
+ private static final Map<String, TokenType> keywords;
|
|
|
+
|
|
|
+ static {
|
|
|
+ keywords = new HashMap<>();
|
|
|
+ keywords.put("and", AND);
|
|
|
+ keywords.put("class", CLASS);
|
|
|
+ keywords.put("else", ELSE);
|
|
|
+ keywords.put("false", FALSE);
|
|
|
+ keywords.put("fun", FUN);
|
|
|
+ keywords.put("if", IF);
|
|
|
+ keywords.put("nil", NIL);
|
|
|
+ keywords.put("or", OR);
|
|
|
+ keywords.put("print", PRINT);
|
|
|
+ keywords.put("return", RETURN);
|
|
|
+ keywords.put("super", SUPER);
|
|
|
+ keywords.put("this", THIS);
|
|
|
+ keywords.put("true", TRUE);
|
|
|
+ keywords.put("var", VAR);
|
|
|
+ keywords.put("while", WHILE);
|
|
|
+ }
|
|
|
|
|
|
public Scanner(String source) {
|
|
|
this.source = source;
|
|
|
@@ -67,8 +89,70 @@ public class Scanner {
|
|
|
}
|
|
|
case '\n' -> line++;
|
|
|
|
|
|
- default -> Lox.error(line, "Unexpected character: " + c);
|
|
|
+ case '"' -> string();
|
|
|
+
|
|
|
+ default -> {
|
|
|
+ if (isDigit(c)) {
|
|
|
+ number();
|
|
|
+ } else if (isAlpha(c)) {
|
|
|
+ identifier();
|
|
|
+ } else {
|
|
|
+ Lox.error(line, "Unexpected character: " + c);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } // end switch
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * keyword or user-defined identifier
|
|
|
+ */
|
|
|
+ private void identifier() {
|
|
|
+ while (isAlphaNumeric(peek())) advance();
|
|
|
+
|
|
|
+ String text = source.substring(start, current);
|
|
|
+ TokenType type = keywords.get(text);
|
|
|
+ if (type == null) type = IDENTIFIER;
|
|
|
+ addToken(type);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Number literals
|
|
|
+ * 1234
|
|
|
+ * 12.34
|
|
|
+ */
|
|
|
+ private void number() {
|
|
|
+ while (isDigit(peek())) advance();
|
|
|
+
|
|
|
+ // Look for a fractional part.
|
|
|
+ if (peek() == '.' && isDigit(peekNext())) {
|
|
|
+ // consume .
|
|
|
+ advance();
|
|
|
+
|
|
|
+ while (isDigit(peek())) advance();
|
|
|
}
|
|
|
+
|
|
|
+ addToken(NUMBER, Double.parseDouble(source.substring(start, current)));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * String literals
|
|
|
+ */
|
|
|
+ private void string() {
|
|
|
+ while (peek() != '"' && !isAtEnd()) {
|
|
|
+ if (peek() == '\n') line++;
|
|
|
+ advance();
|
|
|
+ }
|
|
|
+ if (isAtEnd()) {
|
|
|
+ Lox.error(line, "Unterminated string.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取最后的 “
|
|
|
+ advance();
|
|
|
+
|
|
|
+ // Trim the surrounding quotes.
|
|
|
+ String value = source.substring(start + 1, current - 1);
|
|
|
+ addToken(STRING, value);
|
|
|
}
|
|
|
|
|
|
private char peek() {
|
|
|
@@ -76,6 +160,11 @@ public class Scanner {
|
|
|
return source.charAt(current);
|
|
|
}
|
|
|
|
|
|
+ private char peekNext() {
|
|
|
+ if (current + 1 >= source.length()) return '\0';
|
|
|
+ return source.charAt(current + 1);
|
|
|
+ }
|
|
|
+
|
|
|
private void addToken(TokenType type) {
|
|
|
addToken(type, null);
|
|
|
}
|
|
|
@@ -105,4 +194,18 @@ public class Scanner {
|
|
|
current++;
|
|
|
return true;
|
|
|
}
|
|
|
+
|
|
|
+ private boolean isDigit(char c) {
|
|
|
+ return c >= '0' && c <= '9';
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isAlpha(char c) {
|
|
|
+ return (c >= 'a' && c <= 'z') ||
|
|
|
+ (c >= 'A' && c <= 'Z') ||
|
|
|
+ c == '_';
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isAlphaNumeric(char c) {
|
|
|
+ return isAlpha(c) || isDigit(c);
|
|
|
+ }
|
|
|
}
|