diff --git a/demos/hello_world.lox b/demos/hello_world.lox new file mode 100644 index 0000000..648148a --- /dev/null +++ b/demos/hello_world.lox @@ -0,0 +1,7 @@ +// Funcion hello world! +fun helloWorld(param) { + variable = "hello world" + param; + print variable; +} + +helloWorld("daniel"); \ No newline at end of file diff --git a/src/main/java/fun/skrd/lox/AstPrinter.java b/src/main/java/fun/skrd/lox/AstPrinter.java new file mode 100644 index 0000000..d5bc3de --- /dev/null +++ b/src/main/java/fun/skrd/lox/AstPrinter.java @@ -0,0 +1,39 @@ +package fun.skrd.lox; + +class AstPrinter implements Expr.Visitor { + String print(Expr expr) { + return expr.accept(this); + } + + @Override + public String visitBinaryExpr(Expr.Binary expr) { + return parenthesize(expr.operator.lexeme, expr.left, expr.right); + } + + @Override + public String visitGroupingExpr(Expr.Grouping expr) { + return parenthesize("group", expr.expression); + } + + @Override + public String visitLiteralExpr(Expr.Literal expr) { + if(expr.value == null) return "nil"; + return expr.value.toString(); + } + + @Override + public String visitUnaryExpr(Expr.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(" ").append(expr.accept(this)); + } + builder.append(")"); + + return builder.toString(); + } +} diff --git a/src/main/java/fun/skrd/lox/Expr.java b/src/main/java/fun/skrd/lox/Expr.java new file mode 100644 index 0000000..01ca33c --- /dev/null +++ b/src/main/java/fun/skrd/lox/Expr.java @@ -0,0 +1,77 @@ +package fun.skrd.lox; + +import java.util.List; + +abstract class Expr { + interface Visitor { + R visitBinaryExpr(Binary expr); + + R visitGroupingExpr(Grouping expr); + + R visitLiteralExpr(Literal expr); + + R visitUnaryExpr(Unary expr); + + } + + static class Binary extends Expr { + 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); + } + + final Expr left; + final Token operator; + final Expr right; + } + + static class Grouping extends Expr { + Grouping(Expr expression) { + this.expression = expression; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitGroupingExpr(this); + } + + final Expr expression; + } + + static class Literal extends Expr { + Literal(Object value) { + this.value = value; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitLiteralExpr(this); + } + + final Object value; + } + + static class Unary extends Expr { + Unary(Token operator, Expr right) { + this.operator = operator; + this.right = right; + } + + @Override + R accept(Visitor visitor) { + return visitor.visitUnaryExpr(this); + } + + final Token operator; + final Expr right; + } + + + abstract R accept(Visitor visitor); +} diff --git a/src/main/java/fun/skrd/Lox.java b/src/main/java/fun/skrd/lox/Lox.java similarity index 79% rename from src/main/java/fun/skrd/Lox.java rename to src/main/java/fun/skrd/lox/Lox.java index 55cdc60..c361457 100644 --- a/src/main/java/fun/skrd/Lox.java +++ b/src/main/java/fun/skrd/lox/Lox.java @@ -1,4 +1,4 @@ -package fun.skrd; +package fun.skrd.lox; import java.io.BufferedReader; import java.io.IOException; @@ -43,16 +43,30 @@ public class Lox { private static void run(String source) { Scanner scanner = new Scanner(source); List tokens = scanner.scanTokens(); + Parser parser = new Parser(tokens); + Expr expression = parser.parse(); + + if(hadError) return; for (Token token : tokens) { System.out.println(token); } + + System.out.println(new AstPrinter().print(expression)); } static void error(int line, String message) { report(line, "", message); } + static void error(Token token, String message) { + if(token.type == TokenType.EOF) { + report(token.line, " at end", message); + } else { + report(token.line, " at '" + token.lexeme + "'", 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/fun/skrd/lox/Parser.java b/src/main/java/fun/skrd/lox/Parser.java new file mode 100644 index 0000000..2e97f62 --- /dev/null +++ b/src/main/java/fun/skrd/lox/Parser.java @@ -0,0 +1,161 @@ +package fun.skrd.lox; + +import java.util.List; + +import static fun.skrd.lox.TokenType.*; + +class Parser { + private static class ParserError extends RuntimeException {} + + private final List tokens; + private int current = 0; + + Parser(List tokens) { + this.tokens = tokens; + } + + Expr parse() { + try { + return expression(); + } catch(ParserError e) { + return null; + } + } + + private Expr expression() { + return equality(); + } + + private Expr equality() { + Expr expr = comparison(); + + while (match(BANG_EQUAL, EQUAl_EQUAL)) { + Token operator = previous(); + Expr right = comparison(); + expr = new Expr.Binary(expr, operator, right); + } + + return expr; + } + + private Expr comparison() { + Expr expr = term(); + + while (match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)) { + Token operator = previous(); + Expr right = term(); + expr = new Expr.Binary(expr, operator, right); + } + + return expr; + } + + private Expr term() { + Expr expr = factor(); + + while (match(MINUS, PLUS)) { + Token operator = previous(); + Expr right = factor(); + expr = new Expr.Binary(expr, operator, right); + } + + return expr; + } + + private Expr factor() { + Expr expr = unary(); + + while (match(SLASH, STAR)) { + Token operator = previous(); + Expr right = unary(); + expr = new Expr.Binary(expr, operator, right); + } + + return expr; + } + + private Expr unary() { + if (match(BANG, MINUS)) { + Token operator = previous(); + Expr right = unary(); + return new Expr.Unary(operator, right); + } + + return primary(); + } + + private Expr primary() { + if (match(FALSE)) return new Expr.Literal(false); + if (match(TRUE)) return new Expr.Literal(true); + if (match(NIL)) return new Expr.Literal(null); + + if (match(NUMBER, STRING)) { + return new Expr.Literal(previous().literal); + } + + if (match(LEFT_PAREN)) { + Expr expr = expression(); + consume(RIGHT_PAREN, "Expect ')' after expression."); + return new Expr.Grouping(expr); + } + + throw error(peek(), "Expect expression."); + } + + private void synchronize() { + advance(); + while (!isAtEnd()) { + if (previous().type == SEMICOLON) return; + + switch (peek().type) { + case CLASS: case FUN: case VAR: case FOR: + case IF: case WHILE: case PRINT: case RETURN: + return; + } + + advance(); + } + } + + private boolean match(TokenType... types) { + for (TokenType type : types) { + if (check(type)) { + advance(); + return true; + } + } + return false; + } + + private Token consume(TokenType type, String message) { + if (check(type)) return advance(); + throw error(peek(), message); + } + + private ParserError error(Token token, String message) { + Lox.error(token, message); + return new ParserError(); + } + + private boolean check(TokenType type) { + if (isAtEnd()) return false; + return peek().type == type; + } + + private Token advance() { + if (!isAtEnd()) current++; + return previous(); + } + + private boolean isAtEnd() { + return peek().type == EOF; + } + + private Token peek() { + return tokens.get(current); + } + + private Token previous() { + return tokens.get(current - 1); + } +} diff --git a/src/main/java/fun/skrd/Scanner.java b/src/main/java/fun/skrd/lox/Scanner.java similarity index 98% rename from src/main/java/fun/skrd/Scanner.java rename to src/main/java/fun/skrd/lox/Scanner.java index 530633a..79334eb 100644 --- a/src/main/java/fun/skrd/Scanner.java +++ b/src/main/java/fun/skrd/lox/Scanner.java @@ -1,11 +1,11 @@ -package fun.skrd; +package fun.skrd.lox; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import static fun.skrd.TokenType.*; +import static fun.skrd.lox.TokenType.*; public class Scanner { private final String source; diff --git a/src/main/java/fun/skrd/Token.java b/src/main/java/fun/skrd/lox/Token.java similarity index 94% rename from src/main/java/fun/skrd/Token.java rename to src/main/java/fun/skrd/lox/Token.java index 8ec45a3..f2a333e 100644 --- a/src/main/java/fun/skrd/Token.java +++ b/src/main/java/fun/skrd/lox/Token.java @@ -1,4 +1,4 @@ -package fun.skrd; +package fun.skrd.lox; class Token { final TokenType type; diff --git a/src/main/java/fun/skrd/TokenType.java b/src/main/java/fun/skrd/lox/TokenType.java similarity index 95% rename from src/main/java/fun/skrd/TokenType.java rename to src/main/java/fun/skrd/lox/TokenType.java index 4e932d8..e856504 100644 --- a/src/main/java/fun/skrd/TokenType.java +++ b/src/main/java/fun/skrd/lox/TokenType.java @@ -1,4 +1,4 @@ -package fun.skrd; +package fun.skrd.lox; enum TokenType { // Single-character tokens. diff --git a/src/main/java/fun/skrd/tool/GenerateAst.java b/src/main/java/fun/skrd/tool/GenerateAst.java new file mode 100644 index 0000000..a62b19c --- /dev/null +++ b/src/main/java/fun/skrd/tool/GenerateAst.java @@ -0,0 +1,97 @@ +package fun.skrd.tool; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; + +public class GenerateAst { + public static void main(String[] args) throws IOException { + if (args.length != 1) { + System.out.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 + File.separator + baseName + ".java"; + PrintWriter writer = new PrintWriter(path); + writer.println("package fun.skrd.lox;"); + writer.println(); + writer.println("import java.util.List;"); + writer.println(); + writer.println("abstract class " + baseName + " {"); + + defineVisitor(writer, baseName, types); + + for(String type : types) { + String className = type.split(":")[0].trim(); + String fields = type.split(":")[1].trim(); + defineType(writer, baseName, className, fields); + } + + writer.println(); + + writer.println(" abstract R accept(Visitor visitor);"); + + writer.println("}"); + writer.close(); + } + + private static void defineVisitor( + PrintWriter writer, String baseName, List types + ){ + writer.println(" interface Visitor {"); + + for (String type : types) { + String typeName = type.split(":")[0].trim(); + writer.println(" R visit" + typeName + baseName + "(" + typeName + " " + baseName.toLowerCase() + ");"); + writer.println(); + } + + writer.println(" }"); + writer.println(); + } + + 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(); + writer.println(" @Override"); + writer.println(" R accept(Visitor visitor) {"); + writer.println(" return visitor.visit"+className+basename+"(this);"); + writer.println(" }"); + writer.println(); + + for (String field : fields) { + writer.println(" final " + field + ";"); + } + + writer.println(" }"); + writer.println(); + } +}