From 4b592b8ce8012e0cfee7c3c409d9cb9d87505c36 Mon Sep 17 00:00:00 2001 From: Erik Winter Date: Wed, 24 Jul 2024 15:33:30 +0200 Subject: [PATCH] 4.7 --- .../craftinginterpreters/lox}/Lox.java | 3 +- src/com/craftinginterpreters/lox/Scanner.java | 207 ++++++++++++++++++ .../craftinginterpreters/lox}/Token.java | 2 + .../craftinginterpreters/lox}/TokenType.java | 2 + 4 files changed, 213 insertions(+), 1 deletion(-) rename src/{ => com/craftinginterpreters/lox}/Lox.java (97%) create mode 100644 src/com/craftinginterpreters/lox/Scanner.java rename src/{ => com/craftinginterpreters/lox}/Token.java (90%) rename src/{ => com/craftinginterpreters/lox}/TokenType.java (92%) diff --git a/src/Lox.java b/src/com/craftinginterpreters/lox/Lox.java similarity index 97% rename from src/Lox.java rename to src/com/craftinginterpreters/lox/Lox.java index b18b585..fa06c7b 100644 --- a/src/Lox.java +++ b/src/com/craftinginterpreters/lox/Lox.java @@ -1,3 +1,5 @@ +package com.craftinginterpreters.lox; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -5,7 +7,6 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; -import java.util.Scanner; public class Lox { static boolean hadError = false; diff --git a/src/com/craftinginterpreters/lox/Scanner.java b/src/com/craftinginterpreters/lox/Scanner.java new file mode 100644 index 0000000..9b9497e --- /dev/null +++ b/src/com/craftinginterpreters/lox/Scanner.java @@ -0,0 +1,207 @@ +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.*; + +public class Scanner { + private final String source; + private final List tokens = new ArrayList<>(); + private int start = 0; + private int current = 0; + private int line = 1; + private static final Map keywords; + + Scanner(String source) { + this.source = source; + } + + List ScanTokens() { + while(!isAtEnd()) { + start = current; + scanToken(); + } + + tokens.add(new Token(EOF, "", null, line)); + return tokens; + } + + private void scanToken() { + char c = advance(); + switch (c) { + case '(': addToken(LEFT_PAREN); break; + case ')': addToken(RIGHT_PAREN); break; + case '{': addToken(LEFT_BRACE); break; + case '}': addToken(RIGHT_BRACE); break; + case ',': addToken(COMMA); break; + case '.': addToken(DOT); break; + case '-': addToken(MINUS); break; + case '+': addToken(PLUS); break; + case ';': addToken(SEMICOLON); break; + case '*': addToken(STAR); break; + case '!': + addToken(match('=') ? BANG_EQUAL : BANG); + break; + case '=': + addToken(match('=') ? EQUAL_EQUAL : EQUAL); + case '<': + addToken(match('=') ? LESS_EQUAL : LESS); + case '>': + addToken(match('=') ? GREATER_EQUAL : GREATER); + case '/': + if (match('/')) { + // A comment goes until the end of the line + while(peek() != '\n' && !isAtEnd()) { + advance(); + } + } else { + addToken(SLASH); + } + case ' ': + case '\r': + case '\t': + // Ignore whitespace + break; + case '\n': + line++; + break; + case '"': string(); break; + default: + if (isDigit(c)) { + number(); + } else if (isAlpha(c)) { + identifier(); + } else { + Lox.error(line, "Unexpected character."); + } + } + } + + private boolean isAtEnd() { + return current >= source.length(); + } + + private char advance() { + return source.charAt(current++); + } + + private boolean match(char expected) { + if (isAtEnd()) { + return false; + } + if (source.charAt(current) != expected) { + return false; + } + + current++; + return true; + } + + private char peek() { + if (isAtEnd()) { + return '\0'; + } + return source.charAt(current); + } + + private char peekNext() { + if (current + 1 < source.length()) { + return '\0'; + } + return source.charAt(current + 1); + } + + 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); + } + + private void number() { + while(isDigit(peek())) { + advance(); + } + + // Look for a fractional part. + if (peek() == '.' && isDigit(peekNext())) { + // Consume the "." + advance(); + while (isDigit(peek())) { + advance(); + } + } + + addToken(NUMBER, Double.parseDouble(source.substring(start, current))); + } + + private void string() { + while (peek() != '"' && !isAtEnd()) { + if (peek() == '\n') { + line++; + advance(); + } + } + + if (isAtEnd()) { + Lox.error(line, "Unterminated string."); + return; + } + + advance(); // The closing ". + + // Trim the surrounding quotes. + String value = source.substring(start + 1, current - 1); + addToken(STRING, value); + } + + 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); + } + + 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)); + } + + static { + keywords = new HashMap<>(); + keywords.put("and", AND); + keywords.put("class", CLASS); + keywords.put("else", ELSE); + keywords.put("false", FALSE); + keywords.put("for", FOR); + 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); + } +} diff --git a/src/Token.java b/src/com/craftinginterpreters/lox/Token.java similarity index 90% rename from src/Token.java rename to src/com/craftinginterpreters/lox/Token.java index 405ac20..84762a4 100644 --- a/src/Token.java +++ b/src/com/craftinginterpreters/lox/Token.java @@ -1,3 +1,5 @@ +package com.craftinginterpreters.lox; + class Token { final TokenType type; final String lexeme; diff --git a/src/TokenType.java b/src/com/craftinginterpreters/lox/TokenType.java similarity index 92% rename from src/TokenType.java rename to src/com/craftinginterpreters/lox/TokenType.java index 4d9fc56..555acb7 100644 --- a/src/TokenType.java +++ b/src/com/craftinginterpreters/lox/TokenType.java @@ -1,3 +1,5 @@ +package com.craftinginterpreters.lox; + enum TokenType { // Single-character tokens LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE,