From 0ff3987f8dfd7a0aa459f75cb9319b5d02672fc0 Mon Sep 17 00:00:00 2001 From: nigel barink Date: Sat, 23 Mar 2024 17:27:33 +0100 Subject: [PATCH] scanner complete --- app/src/main/java/org/barink/Lox.java | 3 +- app/src/main/java/org/barink/Scanner.java | 170 +++++++++++++++++++++- 2 files changed, 171 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/barink/Lox.java b/app/src/main/java/org/barink/Lox.java index bb82800..652b6fd 100644 --- a/app/src/main/java/org/barink/Lox.java +++ b/app/src/main/java/org/barink/Lox.java @@ -9,12 +9,13 @@ import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.List; public class Lox { static boolean hadError = false; - public static void main(String[] args) { + public static void main(String[] args) throws IOException { if (args.length > 1){ System.out.println("Usage: jlox [script]"); System.exit(64); diff --git a/app/src/main/java/org/barink/Scanner.java b/app/src/main/java/org/barink/Scanner.java index 3e59d58..e06f960 100644 --- a/app/src/main/java/org/barink/Scanner.java +++ b/app/src/main/java/org/barink/Scanner.java @@ -1,7 +1,11 @@ package org.barink; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import org.barink.TokenType; class Scanner { private final String source; @@ -9,6 +13,27 @@ class Scanner { private int start = 0; private int current = 0; private int line = 1; + private static final Map keywords; + + static { + keywords = new HashMap<>(); + keywords.put("and", TokenType.AND); + keywords.put("class", TokenType.CLASS); + keywords.put("else", TokenType.ELSE); + keywords.put("false", TokenType.FALSE); + keywords.put("for", TokenType.FOR); + keywords.put("fun", TokenType.FUN); + keywords.put("if", TokenType.IF); + keywords.put("nil", TokenType.NIL); + keywords.put("or", TokenType.OR); + keywords.put("print", TokenType.PRINT); + keywords.put("return", TokenType.RETURN); + keywords.put("super", TokenType.SUPER); + keywords.put("this", TokenType.THIS); + keywords.put("true", TokenType.TRUE); + keywords.put("var", TokenType.TRUE); + keywords.put("while", TokenType.WHILE); + } Scanner(String source){ this.source = source; @@ -21,9 +46,152 @@ class Scanner { scanToken(); } tokens.add(new Token(TokenType.EOF, "", null, line)); - return tokens + return tokens; } + private void scanToken () { + char c = advance(); + switch(c){ + case '(': addToken(TokenType.LEFT_PAREN); break; + case ')': addToken(TokenType.RIGHT_PAREN); break; + case '{': addToken(TokenType.LEFT_BRACE); break; + case '}': addToken(TokenType.RIGHT_BRACE); break; + case ',': addToken(TokenType.COMMA); break; + case '.': addToken(TokenType.DOT); break; + case '-': addToken(TokenType.MINUS); break; + case '+': addToken(TokenType.PLUS); break; + case ';': addToken(TokenType.SEMICOLON); break; + case '*': addToken(TokenType.STAR); break; + case '!': + addToken(match('=')? TokenType.BANG_EQUAL : TokenType.BANG); + break; + case '=': + addToken(match('=')? TokenType.EQUAL_EQUAL : TokenType.EQUAL); + break; + case '<': + addToken(match('=')? TokenType.LESS_EQUAL : TokenType.LESS); + break; + case '>': + addToken(match('=')? TokenType.GREATER_EQUAL : TokenType.EQUAL); + break; + case '/': + if (match('/')){ + // A comment goes until the end of the line. + while (peek() != '\n' && !isAtEnd()) advance(); + } else { + addToken(TokenType.SLASH); + } break; + 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."); + } + break; + } + + } + + private void identifier(){ + while(isAlphaNumeric(peek())) advance(); + + String text = source.substring(start, current); + TokenType type = keywords.get(text); + if (type == null) type = TokenType.IDENTIFIER; + addToken(type); + } + + 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 boolean isDigit(char c){ + return c >= '0' && c <= '9'; + } + + private void number(){ + while( isDigit(peek())) advance(); + + // Look for a fractional part. + if (peek() == '.' && isDigit(peekNext())){ + // Consume the "." + advance(); + + while (isDigit(peek())) advance(); + + } + + addToken(TokenType.NUMBER, Double.parseDouble(source.substring(start, current))); + } + + private char peekNext(){ + if (current + 1 >= source.length()) return '\0'; + return source.charAt(current + 1); + } + + private void string(){ + while(peek() != '"' && !isAtEnd()){ + if (peek() == '\n') line++; + advance(); + } + + if(isAtEnd()){ + Lox.error(line, "Unterminated string."); + return; + } + + // The closing ". + advance(); + + // Trim the surrounding quotes. + String value = source.substring(start + 1, current - 1); + addToken(TokenType.STRING, value); + } + + private char peek(){ + if (isAtEnd()) return '\0'; + 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 advance(){ + 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 )); + } private boolean isAtEnd(){ return current >= source.length(); }