From 7847dc9bbc9c3c719b406842967e4687bfc765c9 Mon Sep 17 00:00:00 2001 From: zuckerberg <5-zuckerberg@users.noreply.git.neet.dev> Date: Sat, 14 Aug 2021 10:06:58 -0400 Subject: [PATCH] wip --- .../com/zuckerberg/dbuild/AstPrinter.java | 47 ++++ .../java/com/zuckerberg/dbuild/DBuild.java | 75 +++++++ src/main/java/com/zuckerberg/dbuild/Expr.java | 70 ++++++ src/main/java/com/zuckerberg/dbuild/Main.java | 9 - .../java/com/zuckerberg/dbuild/Parser.java | 12 ++ .../java/com/zuckerberg/dbuild/Scanner.java | 204 ++++++++++++++++++ .../java/com/zuckerberg/dbuild/Token.java | 19 ++ .../java/com/zuckerberg/dbuild/TokenType.java | 22 ++ 8 files changed, 449 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/zuckerberg/dbuild/AstPrinter.java create mode 100644 src/main/java/com/zuckerberg/dbuild/DBuild.java create mode 100644 src/main/java/com/zuckerberg/dbuild/Expr.java delete mode 100644 src/main/java/com/zuckerberg/dbuild/Main.java create mode 100644 src/main/java/com/zuckerberg/dbuild/Parser.java create mode 100644 src/main/java/com/zuckerberg/dbuild/Scanner.java create mode 100644 src/main/java/com/zuckerberg/dbuild/Token.java create mode 100644 src/main/java/com/zuckerberg/dbuild/TokenType.java diff --git a/src/main/java/com/zuckerberg/dbuild/AstPrinter.java b/src/main/java/com/zuckerberg/dbuild/AstPrinter.java new file mode 100644 index 0000000..8d1d70a --- /dev/null +++ b/src/main/java/com/zuckerberg/dbuild/AstPrinter.java @@ -0,0 +1,47 @@ +package com.zuckerberg.dbuild; + +import com.zuckerberg.dbuild.Expr.Binary; +import com.zuckerberg.dbuild.Expr.Grouping; +import com.zuckerberg.dbuild.Expr.Literal; +import com.zuckerberg.dbuild.Expr.Unary; + +class AstPrinter implements Expr.Visitor { + + String print(Expr expr) { + return expr.accept(this); + } + + @Override + public String visitBinaryExpr(Binary expr) { + return parenthesize(expr.operator.lexeme, expr.left, expr.right); + } + + @Override + public String visitGroupingExpr(Grouping expr) { + return parenthesize("group", expr.expression); + } + + @Override + public String visitLiteralExpr(Literal expr) { + if (expr.value == null) return "nil"; + return expr.value.toString(); + } + + @Override + public String visitUnaryExpr(Unary expr) { + return parenthesize(expr.operator.lexeme, expr.right); + } + + private String parenthesize(String name, Expr... exprs) { + StringBuilder builder = new StringBuilder(); + + builder.append("(").append(name); + for (Expr expr : exprs) { + builder.append(" "); + builder.append(expr.accept(this)); + } + builder.append(")"); + + return builder.toString(); + } +} diff --git a/src/main/java/com/zuckerberg/dbuild/DBuild.java b/src/main/java/com/zuckerberg/dbuild/DBuild.java new file mode 100644 index 0000000..015616e --- /dev/null +++ b/src/main/java/com/zuckerberg/dbuild/DBuild.java @@ -0,0 +1,75 @@ +package com.zuckerberg.dbuild; + +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 DBuild +{ + static boolean hadError = false; + + public static void main(String[] args) throws IOException + { + if (args.length > 1) { + System.out.println("Usage: dBuild [script]"); + System.exit(64); + } else if (args.length == 1) { + runFile(args[0]); + } else { + runPrompt(); + } + + // Expr expression = new Expr.Binary( + // new Expr.Unary( + // new Token(TokenType.MINUS, "-", null, 1), + // new Expr.Literal(123)), + // new Token(TokenType.STAR, "*", null, 1), + // new Expr.Grouping( + // new Expr.Literal(45.67))); + + // System.out.println(new AstPrinter().print(expression)); + } + + private static void runFile(String path) throws IOException { + byte[] bytes = Files.readAllBytes(Paths.get(path)); + run(new String(bytes, Charset.defaultCharset())); + + 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 now, just print the tokens. + 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/main/java/com/zuckerberg/dbuild/Expr.java b/src/main/java/com/zuckerberg/dbuild/Expr.java new file mode 100644 index 0000000..e473a86 --- /dev/null +++ b/src/main/java/com/zuckerberg/dbuild/Expr.java @@ -0,0 +1,70 @@ +package com.zuckerberg.dbuild; + +public abstract class Expr { + interface Visitor { + R visitBinaryExpr(Binary binary); + R visitGroupingExpr(Grouping grouping); + R visitLiteralExpr(Literal literal); + R visitUnaryExpr(Unary unary); + } + + abstract R accept(Visitor visitor); + + static class Binary extends Expr { + final Expr left; + final Token operator; + final Expr right; + + public Binary(Expr left, Token operator, Expr right) { + this.left = left; + this.operator = operator; + this.right = right; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitBinaryExpr(this); + } + } + + static class Grouping extends Expr { + final Expr expression; + + public Grouping(Expr expression) { + this.expression = expression; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitGroupingExpr(this); + } + } + + static class Literal extends Expr { + final Object value; + + public Literal(Object value) { + this.value = value; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitLiteralExpr(this); + } + } + + static class Unary extends Expr { + final Token operator; + final Expr right; + + public Unary(Token operator, Expr right) { + this.operator = operator; + this.right = right; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitUnaryExpr(this); + } + } +} diff --git a/src/main/java/com/zuckerberg/dbuild/Main.java b/src/main/java/com/zuckerberg/dbuild/Main.java deleted file mode 100644 index 90750be..0000000 --- a/src/main/java/com/zuckerberg/dbuild/Main.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.zuckerberg.dbuild; - -public class Main -{ - public static void main(String[] args) - { - System.out.println("Hello World!"); - } -} diff --git a/src/main/java/com/zuckerberg/dbuild/Parser.java b/src/main/java/com/zuckerberg/dbuild/Parser.java new file mode 100644 index 0000000..670dc50 --- /dev/null +++ b/src/main/java/com/zuckerberg/dbuild/Parser.java @@ -0,0 +1,12 @@ +package com.zuckerberg.dbuild; + +import java.util.List; + +class Parser { + private final List tokens; + private int current = 0; + + Parser(List tokens) { + this.tokens = tokens; + } +} diff --git a/src/main/java/com/zuckerberg/dbuild/Scanner.java b/src/main/java/com/zuckerberg/dbuild/Scanner.java new file mode 100644 index 0000000..3b11aad --- /dev/null +++ b/src/main/java/com/zuckerberg/dbuild/Scanner.java @@ -0,0 +1,204 @@ +package com.zuckerberg.dbuild; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.zuckerberg.dbuild.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()) { + // We are at the beginning of the next lexeme. + start = current; + scanToken(); + } + + tokens.add(new Token(EOF, "", null, line)); + return tokens; + } + + private boolean isAtEnd() { + return current >= source.length(); + } + + 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('/')) { + // A comment goes until the end of the line. + while (peek() != '\n' && !isAtEnd()) advance(); + } else { + addToken(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 { + DBuild.error(line, "Unexpected character."); + } + break; + } + } + + private void string() { + while (peek() != '"' && !isAtEnd()) { + if (peek() == '\n') line++; + advance(); + } + + if (isAtEnd()) { + DBuild.error(line, "Unterminated string."); + return; + } + + // The closing ". + advance(); + + // 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 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 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 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); + } +} diff --git a/src/main/java/com/zuckerberg/dbuild/Token.java b/src/main/java/com/zuckerberg/dbuild/Token.java new file mode 100644 index 0000000..acb1c0e --- /dev/null +++ b/src/main/java/com/zuckerberg/dbuild/Token.java @@ -0,0 +1,19 @@ +package com.zuckerberg.dbuild; + +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; + } +} \ No newline at end of file diff --git a/src/main/java/com/zuckerberg/dbuild/TokenType.java b/src/main/java/com/zuckerberg/dbuild/TokenType.java new file mode 100644 index 0000000..5dc16ef --- /dev/null +++ b/src/main/java/com/zuckerberg/dbuild/TokenType.java @@ -0,0 +1,22 @@ +package com.zuckerberg.dbuild; + +public enum TokenType { + // Single-character tokens. + LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, + COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, + + // One or two character tokens. + BANG, BANG_EQUAL, + EQUAL, EQUAL_EQUAL, + GREATER, GREATER_EQUAL, + LESS, LESS_EQUAL, + + // Literals. + IDENTIFIER, STRING, NUMBER, + + // Keywords. + AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, + PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, + + EOF +}