[jlox] Add function declaration
This commit is contained in:
@@ -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
|
||||
|
||||
37
jlox/lox/src/main/java/xyz/ctsk/lox/LoxFunction.java
Normal file
37
jlox/lox/src/main/java/xyz/ctsk/lox/LoxFunction.java
Normal 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() + " >";
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
10
jlox/lox/src/main/java/xyz/ctsk/lox/Return.java
Normal file
10
jlox/lox/src/main/java/xyz/ctsk/lox/Return.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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"})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user