|
|
@@ -0,0 +1,108 @@
|
|
|
+/* Copyright (C) 2019-2023 Hangzhou HSH Co. Ltd.
|
|
|
+ * All right reserved.*/
|
|
|
+package com.craftinginterpreters.lox;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+import static com.craftinginterpreters.lox.TokenType.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author simon
|
|
|
+ * @date 2023-06-04 16:30
|
|
|
+ * @desc
|
|
|
+ */
|
|
|
+public class Scanner {
|
|
|
+ private final String source;
|
|
|
+ private final List<Token> tokens = new ArrayList<>();
|
|
|
+ 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;
|
|
|
+
|
|
|
+ public Scanner(String source) {
|
|
|
+ this.source = source;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<Token> scanTokens() {
|
|
|
+ while (!isAtEnd()) {
|
|
|
+ // We are at the beginning of the next lexeme.
|
|
|
+ start = current;
|
|
|
+ scanToken();
|
|
|
+ }
|
|
|
+
|
|
|
+ // add finish token to the end of list
|
|
|
+ tokens.add(new Token(EOF, "", null, line));
|
|
|
+ return tokens;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void scanToken() {
|
|
|
+ char c = advance();
|
|
|
+ switch (c) {
|
|
|
+ case '(' -> addToken(LEFT_PARAM);
|
|
|
+ case ')' -> addToken(RIGHT_PARAM);
|
|
|
+ case '{' -> addToken(LEFT_BRACE);
|
|
|
+ case '}' -> addToken(RIGHT_BRACE);
|
|
|
+ case ',' -> addToken(COMMA);
|
|
|
+ case '.' -> addToken(DOT);
|
|
|
+ case '-' -> addToken(MINUS);
|
|
|
+ case '+' -> addToken(PLUS);
|
|
|
+ case ';' -> addToken(SEMICOLON);
|
|
|
+ case '*' -> addToken(STAR);
|
|
|
+ // 两个字符 != == >= <=
|
|
|
+ case '!' -> addToken(match('=') ? BANG_EQUAL : BANG);
|
|
|
+ case '=' -> addToken(match('=') ? EQUAL_EQUAL : EQUAL);
|
|
|
+ case '<' -> addToken(match('=') ? LESS_EQUAL : LESS);
|
|
|
+ case '>' -> addToken(match('=') ? GRATER_EQUAL : GRATER);
|
|
|
+ case '/' -> {
|
|
|
+ if (match('/')) {
|
|
|
+ // A comment goes until the end of the line.
|
|
|
+ while (peek() != '\n' && !isAtEnd()) advance();
|
|
|
+ } else {
|
|
|
+ addToken(SLASH);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Ignore whitespace
|
|
|
+ case ' ', '\r', '\t' -> {
|
|
|
+ }
|
|
|
+ case '\n' -> line++;
|
|
|
+
|
|
|
+ default -> Lox.error(line, "Unexpected character: " + c);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private char peek() {
|
|
|
+ if (isAtEnd()) return '\0';
|
|
|
+ return source.charAt(current);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void addToken(TokenType type) {
|
|
|
+ addToken(type, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void addToken(TokenType type, Object literal) {
|
|
|
+ String text = source.substring(start, current);
|
|
|
+ tokens.add(new Token(type, text, literal, line));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * consumes the next character in the source file and returns it
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private char advance() {
|
|
|
+ return source.charAt(current++);
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isAtEnd() {
|
|
|
+ return current >= source.length();
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean match(char expected) {
|
|
|
+ if (isAtEnd()) return false;
|
|
|
+ if (source.charAt(current) != expected) return false;
|
|
|
+
|
|
|
+ current++;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+}
|