commit 9def28e2b172284d2151d20678a684dbeb626470 Author: David Ashby Date: Fri Sep 17 23:02:57 2021 -0400 first pieces diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..77642f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +.idea +*.iml +out \ No newline at end of file diff --git a/src/lox/Expr.java b/src/lox/Expr.java new file mode 100644 index 0000000..7c91ca4 --- /dev/null +++ b/src/lox/Expr.java @@ -0,0 +1,41 @@ +package lox; + +abstract class Expr { + static class Binary extends Expr { + Binary(Expr left, Token operator, Expr right) { + this.left = left; + this.operator = operator; + this.right = right; + } + + final Expr left; + final Token operator; + final Expr right; + } + + static class Grouping extends Expr { + Grouping(Expr expression) { + this.expression = expression; + } + + final Expr expression; + } + + static class Literal extends Expr { + Literal(Object value) { + this.value = value; + } + + final Object value; + } + + static class Unary extends Expr { + Unary(Token operator, Expr right) { + this.operator = operator; + this.right = right; + } + + final Token operator; + final Expr right; + } +} diff --git a/src/lox/Lox.java b/src/lox/Lox.java new file mode 100644 index 0000000..7f99efe --- /dev/null +++ b/src/lox/Lox.java @@ -0,0 +1,67 @@ +package lox; + +import java.io.BufferedReader; +import java.io.IOException; +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) throws IOException { + if (args.length > 1) { + System.out.println("Usage: jlox [script]"); + System.exit(64); + } else if (args.length == 1) { + runFile(args[0]); + } else { + runPrompt(); + } + } + + private static void runFile(String path) throws IOException { + byte[] bytes = Files.readAllBytes(Paths.get(path)); + run(new String(bytes, Charset.defaultCharset())); + + // whoops + if (hadError) { + System.exit(65); + } + } + + private static void runPrompt() throws IOException { + InputStreamReader input = new InputStreamReader(System.in); + BufferedReader reader = new BufferedReader(input); + + for (; ; ) { + System.out.print("> "); + String line = reader.readLine(); + if (line == null) { + break; + } + run(line); + hadError = false; + } + } + + private static void run(String source) { + Scanner scanner = new Scanner(source); + List tokens = scanner.scanTokens(); + + for (Token token : tokens) { + System.out.println(token); + } + } + + static void error(int line, String message) { + report(line, "", message); + } + + private static void report(int line, String where, String message) { + System.err.println("[line " + line + "] Error" + where + ": " + message); + hadError = true; + } +} diff --git a/src/lox/Scanner.java b/src/lox/Scanner.java new file mode 100644 index 0000000..10ed93d --- /dev/null +++ b/src/lox/Scanner.java @@ -0,0 +1,230 @@ +package lox; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static lox.TokenType.*; + +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; + + 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); + } + + Scanner(String source) { + this.source = source; + } + + List scanTokens() { + while (!isAtEnd()) { + start = current; + scanToken(); + } + + tokens.add(new Token(TokenType.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); + break; + case '<': + addToken(match('=') ? LESS_EQUAL : LESS); + break; + case '>': + addToken(match('=') ? GREATER_EQUAL : GREATER); + break; + case '/': + if (match('/')) { + // comments run to the end of the line + while (peek() != '\n' && !isAtEnd()) { + advance(); + } + } else { + addToken(SLASH); + } + break; + case ' ': + case '\r': + case '\t': + break; // skip whitespace + case '\n': + line++; + break; // EOL + 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.getOrDefault(text, IDENTIFIER); + addToken(type); + } + + private void number() { + while (isDigit(peek())) { + advance(); + } + + if (peek() == '.' && isDigit(peekNext())) { + 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(); + + String value = source.substring(start + 1, current - 1); + addToken(STRING, value); + } + + private boolean match(char expected) { + if (isAtEnd()) { // ran off the end of the line + return false; + } + if (source.charAt(current) != expected) { // next char is not our expected extra + return false; + } + + // consume second char + 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 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 boolean isAtEnd() { + return current >= source.length(); + } + + 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)); + } +} diff --git a/src/lox/Token.java b/src/lox/Token.java new file mode 100644 index 0000000..68c6acc --- /dev/null +++ b/src/lox/Token.java @@ -0,0 +1,19 @@ +package lox; + +class Token { + final TokenType type; + final String lexeme; + final Object literal; + final int line; + + Token(TokenType type, String lexeme, Object literal, int line) { + this.type = type; + this.lexeme = lexeme; + this.literal = literal; + this.line = line; + } + + public String toString() { + return type + " " + lexeme + " " + literal; + } +} diff --git a/src/lox/TokenType.java b/src/lox/TokenType.java new file mode 100644 index 0000000..f0a445c --- /dev/null +++ b/src/lox/TokenType.java @@ -0,0 +1,47 @@ +package lox; + +enum TokenType { + LEFT_PAREN, + RIGHT_PAREN, + LEFT_BRACE, + RIGHT_BRACE, + COMMA, + DOT, + MINUS, + PLUS, + SEMICOLON, + SLASH, + STAR, + + BANG, + BANG_EQUAL, + EQUAL, + EQUAL_EQUAL, + GREATER, + GREATER_EQUAL, + LESS, + LESS_EQUAL, + + IDENTIFIER, + STRING, + NUMBER, + + AND, + CLASS, + ELSE, + FALSE, + FUN, + FOR, + IF, + NIL, + OR, + PRINT, + RETURN, + SUPER, + THIS, + TRUE, + VAR, + WHILE, + + EOF +} diff --git a/src/tool/GenerateAST.java b/src/tool/GenerateAST.java new file mode 100644 index 0000000..ebfcbc8 --- /dev/null +++ b/src/tool/GenerateAST.java @@ -0,0 +1,69 @@ +package tool; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; + +public class GenerateAST { + public static void main(String[] args) throws IOException { + if (args.length != 1) { + System.err.println("Usage: generate_ast "); + System.exit(64); + } + String outputDir = args[0]; + defineAST( + outputDir, + "Expr", + Arrays.asList( + "Binary : Expr left, Token operator, Expr right", + "Grouping : Expr expression", + "Literal : Object value", + "Unary : Token operator, Expr right")); + } + + private static void defineAST(String outputDir, String baseName, List types) + throws IOException { + String path = outputDir + "/" + baseName + ".java"; + PrintWriter writer = new PrintWriter(path, Charset.defaultCharset()); + + writer.println("package lox;"); + writer.println(); + writer.println("import java.util.List;"); + writer.println(); + writer.println("abstract class " + baseName + " {"); + + for (String type : types) { + String className = type.split(":")[0].trim(); + String fields = type.split(":")[1].trim(); + defineType(writer, baseName, className, fields); + } + + writer.println("}"); + writer.close(); + } + + private static void defineType( + PrintWriter writer, String baseName, String className, String fieldList) { + writer.println(" static class " + className + " extends " + baseName + " {"); + + writer.println(" " + className + "(" + fieldList + ") { "); + + String[] fields = fieldList.split(", "); + for (String field : fields) { + String name = field.split(" ")[1]; + writer.println(" this." + name + " = " + name + ";"); + } + + writer.println(" }"); + writer.println(); + + for (String field : fields) { + writer.println(" final " + field + ";"); + } + + writer.println(" }"); + writer.println(); + } +}