[jlox] Add function declaration

This commit is contained in:
ctsk
2022-09-10 21:09:53 +02:00
parent b83e6735f7
commit 12b0d6d0db
5 changed files with 126 additions and 5 deletions

View File

@@ -3,7 +3,29 @@ package xyz.ctsk.lox;
import java.util.List;
public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
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<Object> arguments) {
return (double) System.currentTimeMillis() / 1000.0;
}
@Override
public String toString() {
return "<native fn>";
}
});
}
void interpret(List<Stmt> statements) {
try {
@@ -17,7 +39,7 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
stmt.accept(this);
}
private void executeBlock(List<Stmt> statements, Environment environment) {
void executeBlock(List<Stmt> statements, Environment environment) {
Environment previous = this.environment;
try {
this.environment = environment;
@@ -78,6 +100,12 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
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<Object>, Stmt.Visitor<Void> {
throw new RuntimeError(expr.paren(), "Can only call functions and classes.");
}
return null;
return function.call(this, arguments);
}
@Override

View File

@@ -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<Object> 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 "<fn " + declaration.name().lexeme() + " >";
}
}

View File

@@ -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:
* <p>
* 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<Token> 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<Stmt> 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 }
}

View File

@@ -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;
}
}

View File

@@ -14,8 +14,10 @@
rules = {
@Rule(head = "Block", body = {"List<Stmt> statements"}),
@Rule(head = "Expression", body = {"Expr expression"}),
@Rule(head = "Function", body = {"Token name", "List<Token> params", "List<Stmt> 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"})
})