From 12b0d6d0dba1a0333c3ea3243c309776983ef83f Mon Sep 17 00:00:00 2001 From: ctsk <9384305+ctsk@users.noreply.github.com> Date: Sat, 10 Sep 2022 21:09:53 +0200 Subject: [PATCH] [jlox] Add function declaration --- .../main/java/xyz/ctsk/lox/Interpreter.java | 34 +++++++++++-- .../main/java/xyz/ctsk/lox/LoxFunction.java | 37 ++++++++++++++ .../src/main/java/xyz/ctsk/lox/Parser.java | 48 ++++++++++++++++++- .../src/main/java/xyz/ctsk/lox/Return.java | 10 ++++ .../main/java/xyz/ctsk/lox/package-info.java | 2 + 5 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 jlox/lox/src/main/java/xyz/ctsk/lox/LoxFunction.java create mode 100644 jlox/lox/src/main/java/xyz/ctsk/lox/Return.java diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java index 94f02c0..3fb0233 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Interpreter.java @@ -3,7 +3,29 @@ package xyz.ctsk.lox; import java.util.List; public class Interpreter implements Expr.Visitor, Stmt.Visitor { - private Environment environment = new Environment(); + final Environment globals = new Environment(); + private Environment environment = globals; + + + 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 { @@ -17,7 +39,7 @@ public class Interpreter implements Expr.Visitor, Stmt.Visitor { stmt.accept(this); } - private void executeBlock(List statements, Environment environment) { + void executeBlock(List statements, Environment environment) { Environment previous = this.environment; try { this.environment = environment; @@ -78,6 +100,12 @@ public class Interpreter implements Expr.Visitor, Stmt.Visitor { return null; } + @Override + public Void visitReturnStmt(Stmt.Return stmt) { + var value = stmt.value() == null ? null : evaluate(stmt.value()); + throw new Return(value); + } + @Override public Void visitVarStmt(Stmt.Var stmt) { Object value = stmt.initializer() == null ? null : evaluate(stmt.initializer()); @@ -151,7 +179,7 @@ public class Interpreter implements Expr.Visitor, Stmt.Visitor { throw new RuntimeError(expr.paren(), "Can only call functions and classes."); } - return null; + return function.call(this, arguments); } @Override diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/LoxFunction.java b/jlox/lox/src/main/java/xyz/ctsk/lox/LoxFunction.java new file mode 100644 index 0000000..9c2c29f --- /dev/null +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/LoxFunction.java @@ -0,0 +1,37 @@ +package xyz.ctsk.lox; + +import java.util.List; + +public class LoxFunction implements LoxCallable { + private final Stmt.Function declaration; + + public LoxFunction(Stmt.Function declaration) { + this.declaration = declaration; + } + + @Override + public int arity() { + return declaration.params().size(); + } + + @Override + public Object call(Interpreter interpreter, List arguments) { + Environment environment = new Environment(interpreter.globals); + 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 ret) { + return ret.value; + } + return null; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java index ee85476..3e381c8 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java @@ -1,6 +1,5 @@ package xyz.ctsk.lox; -import java.sql.Statement; import java.util.ArrayList; import java.util.List; @@ -11,13 +10,18 @@ import static xyz.ctsk.lox.TokenType.*; * A recursive descent parser for the following grammar: *

* program → declaration* EOF ; - * declaration → varDecl + * declaration → funDecl + * | varDecl * | statement ; + * funDecl → "fun" function ; + * function → IDENTIFIER "(" parameters? ")" block ; + * parameters → IDENTIFIER ( "," IDENTIFIER )* ; * varDecl → "var" IDENTIFIER ( "=" expression )? ";" ; * statement → exprStmt * | forStmt * | ifStmt * | printStmt + * | returnStmt * | whileStmt * | block ; * block → "{" declaration* "}" ; @@ -27,6 +31,7 @@ import static xyz.ctsk.lox.TokenType.*; * ifStmt → "if" "(" expression ")" statement ( "else" statement )? ; * whileStmt → "while" "(" expression ")" statement ; * printStmt → "print" expression ";" ; + * returnStmt → "return" expression? ";" ; * exprStmt → expression ";" ; * expression → equality ; * expression → assignment ; @@ -67,6 +72,8 @@ public class Parser { private Stmt declaration() { try { + if (match(FUN)) return function(FunctionType.FUNCTION); + if (match(VAR)) return varDeclaration(); return match(VAR) ? varDeclaration() : statement(); } catch (ParseError error) { synchronize(); @@ -74,6 +81,28 @@ public class Parser { } } + private Stmt.Function function(FunctionType kind) { + Token name = consume(IDENTIFIER, "Expect %s name.".formatted(kind.toString().toLowerCase())); + + consume(LEFT_PAREN, "Expect '(' after %s name.".formatted(kind.toString().toLowerCase())); + List params = new ArrayList<>(); + if (!check(RIGHT_PAREN)) { + do { + if (params.size() >= 255) { + // DO NOT PANIC + //noinspection ThrowableNotThrown + error(peek(), "Can't have more than 255 parameters."); + } + params.add(consume(IDENTIFIER, "Expect parameter name.")); + } while (match(COMMA)); + } + consume(RIGHT_PAREN, "Expect ')' after parameters"); + + consume(LEFT_BRACE, "Expect '{' before %s body.".formatted(kind.toString().toLowerCase())); + List body = blockStatement(); + return new Stmt.Function(name, params, body); + } + private Stmt varDeclaration() { Token name = consume(IDENTIFIER, "Expect variable name."); @@ -91,11 +120,13 @@ public class Parser { if (match(FOR)) return forStatement(); if (match(IF)) return ifStatement(); if (match(PRINT)) return printStatement(); + if (match(RETURN)) return returnStatement(); if (match(WHILE)) return whileStatement(); if (match(LEFT_BRACE)) return new Stmt.Block(blockStatement()); return expressionStatement(); } + private Stmt forStatement() { consume(LEFT_PAREN, "Expect '(' after 'for'."); @@ -150,6 +181,18 @@ public class Parser { return new Stmt.Print(value); } + + 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(); @@ -387,4 +430,5 @@ public class Parser { } private static class ParseError extends RuntimeException {} + private enum FunctionType { FUNCTION } } diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Return.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Return.java new file mode 100644 index 0000000..c1b7df9 --- /dev/null +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Return.java @@ -0,0 +1,10 @@ +package xyz.ctsk.lox; + +public class Return extends RuntimeException { + final Object value; + + Return(Object value) { + super(null, null, false, false); + this.value = value; + } +} diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/package-info.java b/jlox/lox/src/main/java/xyz/ctsk/lox/package-info.java index 1c6bc8c..e6fcba2 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/package-info.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/package-info.java @@ -14,8 +14,10 @@ rules = { @Rule(head = "Block", body = {"List statements"}), @Rule(head = "Expression", body = {"Expr expression"}), + @Rule(head = "Function", body = {"Token name", "List params", "List body"}), @Rule(head = "If", body = {"Expr condition", "Stmt thenBranch", "Stmt elseBranch"}), @Rule(head = "Print", body = {"Expr expression"}), + @Rule(head = "Return", body = {"Token keyword", "Expr value"}), @Rule(head = "Var", body = {"Token name", "Expr initializer"}), @Rule(head = "While", body = {"Expr condition", "Stmt body"}) })