From a13db8e29cef3885c1b6c0411c087032638de6be Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Wed, 12 Feb 2025 14:20:34 +0100 Subject: [PATCH] chapter 10 in java --- java/lox-interpreter/pom.xml | 12 ++++ .../java/ch/vanwa/lox_interpreter/Expr.java | 28 ++++---- .../ch/vanwa/lox_interpreter/Interpreter.java | 63 +++++++++++++++- .../ch/vanwa/lox_interpreter/LoxCallable.java | 9 +++ .../ch/vanwa/lox_interpreter/LoxFunction.java | 41 +++++++++++ .../java/ch/vanwa/lox_interpreter/Parser.java | 71 ++++++++++++++++++- .../java/ch/vanwa/lox_interpreter/Return.java | 10 +++ .../java/ch/vanwa/lox_interpreter/Stmt.java | 39 +++++++--- .../main/java/ch/vanwa/tool/GenerateAst.java | 4 ++ 9 files changed, 254 insertions(+), 23 deletions(-) create mode 100644 java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/LoxCallable.java create mode 100644 java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/LoxFunction.java create mode 100644 java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Return.java diff --git a/java/lox-interpreter/pom.xml b/java/lox-interpreter/pom.xml index dabfda5..9bf0b34 100644 --- a/java/lox-interpreter/pom.xml +++ b/java/lox-interpreter/pom.xml @@ -116,6 +116,18 @@ java + + ch.vanwa.lox_interpreter.App + + ${script} + + + + + jlox-repl + + java + ch.vanwa.lox_interpreter.App diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Expr.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Expr.java index 02909ff..4106d02 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Expr.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Expr.java @@ -5,20 +5,14 @@ import java.util.List; abstract class Expr { interface Visitor { R visitAssignExpr(Assign expr); - R visitBinaryExpr(Binary expr); - + R visitCallExpr(Call expr); R visitGroupingExpr(Grouping expr); - R visitLiteralExpr(Literal expr); - R visitLogicalExpr(Logical expr); - R visitUnaryExpr(Unary expr); - R visitVariableExpr(Variable expr); } - static class Assign extends Expr { Assign(Token name, Expr value) { this.name = name; @@ -33,7 +27,6 @@ abstract class Expr { final Token name; final Expr value; } - static class Binary extends Expr { Binary(Expr left, Token operator, Expr right) { this.left = left; @@ -50,7 +43,22 @@ abstract class Expr { final Token operator; final Expr right; } + static class Call extends Expr { + Call(Expr callee, Token paren, List arguments) { + this.callee = callee; + this.paren = paren; + this.arguments = arguments; + } + @Override + R accept(Visitor visitor) { + return visitor.visitCallExpr(this); + } + + final Expr callee; + final Token paren; + final List arguments; + } static class Grouping extends Expr { Grouping(Expr expression) { this.expression = expression; @@ -63,7 +71,6 @@ abstract class Expr { final Expr expression; } - static class Literal extends Expr { Literal(Object value) { this.value = value; @@ -76,7 +83,6 @@ abstract class Expr { final Object value; } - static class Logical extends Expr { Logical(Expr left, Token operator, Expr right) { this.left = left; @@ -93,7 +99,6 @@ abstract class Expr { final Token operator; final Expr right; } - static class Unary extends Expr { Unary(Token operator, Expr right) { this.operator = operator; @@ -108,7 +113,6 @@ abstract class Expr { final Token operator; final Expr right; } - static class Variable extends Expr { Variable(Token name) { this.name = name; diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Interpreter.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Interpreter.java index 2221a0f..3c7164e 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Interpreter.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Interpreter.java @@ -1,13 +1,34 @@ package ch.vanwa.lox_interpreter; +import java.util.ArrayList; import java.util.List; /** * Evaluate Lox expressions. */ class Interpreter implements Expr.Visitor, Stmt.Visitor { + final Environment globals = new Environment(); + private Environment environment = globals; - private Environment environment = new Environment(); + Interpreter() { + globals.define("clock", new LoxCallable() { + @Override + public int arity() { + return 0; + } + + @Override + public Object call(Interpreter interpreter, + List arguments) { + return (double) System.currentTimeMillis() / 1000.0; + } + + @Override + public String toString() { + return ""; + } + }); + } void interpret(List statements) { try { @@ -82,6 +103,13 @@ class Interpreter implements Expr.Visitor, Stmt.Visitor { return null; } + @Override + public Void visitFunctionStmt(Stmt.Function stmt) { + LoxFunction function = new LoxFunction(stmt, environment); + environment.define(stmt.name.lexeme(), function); + return null; + } + @Override public Void visitIfStmt(Stmt.If stmt) { if (isTruthy(evaluate(stmt.condition))) { @@ -99,6 +127,15 @@ class Interpreter implements Expr.Visitor, Stmt.Visitor { return null; } + @Override + public Void visitReturnStmt(Stmt.Return stmt) { + Object value = null; + if (stmt.value != null) + value = evaluate(stmt.value); + + throw new Return(value); + } + @Override public Void visitVarStmt(Stmt.Var stmt) { Object value = null; @@ -172,6 +209,30 @@ class Interpreter implements Expr.Visitor, Stmt.Visitor { return null; } + @Override + public Object visitCallExpr(Expr.Call expr) { + Object callee = evaluate(expr.callee); + + List arguments = new ArrayList<>(); + for (Expr argument : expr.arguments) { + arguments.add(evaluate(argument)); + } + + if (!(callee instanceof LoxCallable)) { + throw new RuntimeError(expr.paren, + "Can only call functions and classes."); + } + + LoxCallable function = (LoxCallable) callee; + if (arguments.size() != function.arity()) { + throw new RuntimeError(expr.paren, "Expected " + + function.arity() + " arguments but got " + + arguments.size() + "."); + } + + return function.call(this, arguments); + } + @Override public Object visitUnaryExpr(Expr.Unary expr) { Object right = evaluate(expr.right); diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/LoxCallable.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/LoxCallable.java new file mode 100644 index 0000000..b0ac838 --- /dev/null +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/LoxCallable.java @@ -0,0 +1,9 @@ +package ch.vanwa.lox_interpreter; + +import java.util.List; + +interface LoxCallable { + int arity(); + + Object call(Interpreter interpreter, List arguments); +} diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/LoxFunction.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/LoxFunction.java new file mode 100644 index 0000000..c3d3dca --- /dev/null +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/LoxFunction.java @@ -0,0 +1,41 @@ +package ch.vanwa.lox_interpreter; + +import java.util.List; + +class LoxFunction implements LoxCallable { + private final Stmt.Function declaration; + private final Environment closure; + + LoxFunction(Stmt.Function declaration, Environment closure) { + this.closure = closure; + this.declaration = declaration; + } + + @Override + public String toString() { + return ""; + } + + @Override + public int arity() { + return declaration.params.size(); + } + + @Override + public Object call(Interpreter interpreter, + List arguments) { + Environment environment = new Environment(closure); + for (int i = 0; i < declaration.params.size(); i++) { + environment.define(declaration.params.get(i).lexeme(), + arguments.get(i)); + } + + try { + interpreter.executeBlock(declaration.body, environment); + } catch (Return returnValue) { + return returnValue.value; + } + + return null; + } +} diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Parser.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Parser.java index 81c05aa..2d06a9d 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Parser.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Parser.java @@ -58,6 +58,9 @@ class Parser { private Stmt declaration() { try { + if (match(FUN)) { + return function("function"); + } if (match(VAR)) { return varDeclaration(); } @@ -79,6 +82,9 @@ class Parser { if (match(PRINT)) { return printStatement(); } + if (match(RETURN)) { + return returnStatement(); + } if (match(WHILE)) { return whileStatement(); } @@ -166,6 +172,17 @@ class Parser { return new Stmt.Var(name, initializer); } + private Stmt returnStatement() { + Token keyword = previous(); + Expr value = null; + if (!check(SEMICOLON)) { + value = expression(); + } + + consume(SEMICOLON, "Expect ';' after return value."); + return new Stmt.Return(keyword, value); + } + private Stmt whileStatement() { consume(LEFT_PAREN, "Expect '(' after 'while'."); Expr condition = expression(); @@ -181,6 +198,27 @@ class Parser { return new Stmt.Expression(expr); } + private Stmt.Function function(String kind) { + Token name = consume(IDENTIFIER, "Expect " + kind + " name."); + consume(LEFT_PAREN, "Expect '(' after " + kind + " name."); + List parameters = new ArrayList<>(); + if (!check(RIGHT_PAREN)) { + do { + if (parameters.size() >= 255) { + error(peek(), "Can't have more than 255 parameters."); + } + + parameters.add( + consume(IDENTIFIER, "Expect parameter name.")); + } while (match(COMMA)); + } + consume(RIGHT_PAREN, "Expect ')' after parameters."); + + consume(LEFT_BRACE, "Expect '{' before " + kind + " body."); + List body = block(); + return new Stmt.Function(name, parameters, body); + } + private List block() { List statements = new ArrayList<>(); @@ -289,7 +327,38 @@ class Parser { return new Expr.Unary(operator, right); } - return primary(); + return call(); + } + + private Expr call() { + Expr expr = primary(); + + while (true) { + if (match(LEFT_PAREN)) { + expr = finishCall(expr); + } else { + break; + } + } + + return expr; + } + + private Expr finishCall(Expr callee) { + List arguments = new ArrayList<>(); + if (!check(RIGHT_PAREN)) { + do { + if (arguments.size() >= 255) { + error(peek(), "Can't have more than 255 arguments."); + } + arguments.add(expression()); + } while (match(COMMA)); + } + + Token paren = consume(RIGHT_PAREN, + "Expect ')' after arguments."); + + return new Expr.Call(callee, paren, arguments); } private Expr primary() { diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Return.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Return.java new file mode 100644 index 0000000..45adeb6 --- /dev/null +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Return.java @@ -0,0 +1,10 @@ +package ch.vanwa.lox_interpreter; + +class Return extends RuntimeException { + final Object value; + + Return(Object value) { + super(null, null, false, false); + this.value = value; + } +} diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Stmt.java b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Stmt.java index c7f41a1..7381854 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Stmt.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/lox_interpreter/Stmt.java @@ -5,18 +5,14 @@ import java.util.List; abstract class Stmt { interface Visitor { R visitBlockStmt(Block stmt); - R visitExpressionStmt(Expression stmt); - + R visitFunctionStmt(Function stmt); R visitIfStmt(If stmt); - R visitPrintStmt(Print stmt); - + R visitReturnStmt(Return stmt); R visitVarStmt(Var stmt); - R visitWhileStmt(While stmt); } - static class Block extends Stmt { Block(List statements) { this.statements = statements; @@ -29,7 +25,6 @@ abstract class Stmt { final List statements; } - static class Expression extends Stmt { Expression(Expr expression) { this.expression = expression; @@ -42,7 +37,22 @@ abstract class Stmt { final Expr expression; } + static class Function extends Stmt { + Function(Token name, List params, List body) { + this.name = name; + this.params = params; + this.body = body; + } + @Override + R accept(Visitor visitor) { + return visitor.visitFunctionStmt(this); + } + + final Token name; + final List params; + final List body; + } static class If extends Stmt { If(Expr condition, Stmt thenBranch, Stmt elseBranch) { this.condition = condition; @@ -59,7 +69,6 @@ abstract class Stmt { final Stmt thenBranch; final Stmt elseBranch; } - static class Print extends Stmt { Print(Expr expression) { this.expression = expression; @@ -72,7 +81,20 @@ abstract class Stmt { final Expr expression; } + static class Return extends Stmt { + Return(Token keyword, Expr value) { + this.keyword = keyword; + this.value = value; + } + @Override + R accept(Visitor visitor) { + return visitor.visitReturnStmt(this); + } + + final Token keyword; + final Expr value; + } static class Var extends Stmt { Var(Token name, Expr initializer) { this.name = name; @@ -87,7 +109,6 @@ abstract class Stmt { final Token name; final Expr initializer; } - static class While extends Stmt { While(Expr condition, Stmt body) { this.condition = condition; diff --git a/java/lox-interpreter/src/main/java/ch/vanwa/tool/GenerateAst.java b/java/lox-interpreter/src/main/java/ch/vanwa/tool/GenerateAst.java index 8d1e96d..3023e6e 100644 --- a/java/lox-interpreter/src/main/java/ch/vanwa/tool/GenerateAst.java +++ b/java/lox-interpreter/src/main/java/ch/vanwa/tool/GenerateAst.java @@ -16,6 +16,7 @@ public class GenerateAst { defineAst(outputDir, "Expr", Arrays.asList( "Assign : Token name, Expr value", "Binary : Expr left, Token operator, Expr right", + "Call : Expr callee, Token paren, List arguments", "Grouping : Expr expression", "Literal : Object value", "Logical : Expr left, Token operator, Expr right", @@ -25,9 +26,12 @@ public class GenerateAst { defineAst(outputDir, "Stmt", Arrays.asList( "Block : List statements", "Expression : Expr expression", + "Function : Token name, List params," + + " List body", "If : Expr condition, Stmt thenBranch," + " Stmt elseBranch", "Print : Expr expression", + "Return : Token keyword, Expr value", "Var : Token name, Expr initializer", "While : Expr condition, Stmt body")); }