[jlox] Add function declaration
This commit is contained in:
@@ -3,7 +3,29 @@ package xyz.ctsk.lox;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
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) {
|
void interpret(List<Stmt> statements) {
|
||||||
try {
|
try {
|
||||||
@@ -17,7 +39,7 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
|||||||
stmt.accept(this);
|
stmt.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeBlock(List<Stmt> statements, Environment environment) {
|
void executeBlock(List<Stmt> statements, Environment environment) {
|
||||||
Environment previous = this.environment;
|
Environment previous = this.environment;
|
||||||
try {
|
try {
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
@@ -78,6 +100,12 @@ public class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitReturnStmt(Stmt.Return stmt) {
|
||||||
|
var value = stmt.value() == null ? null : evaluate(stmt.value());
|
||||||
|
throw new Return(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visitVarStmt(Stmt.Var stmt) {
|
public Void visitVarStmt(Stmt.Var stmt) {
|
||||||
Object value = stmt.initializer() == null ? null : evaluate(stmt.initializer());
|
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.");
|
throw new RuntimeError(expr.paren(), "Can only call functions and classes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return function.call(this, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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;
|
package xyz.ctsk.lox;
|
||||||
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -11,13 +10,18 @@ import static xyz.ctsk.lox.TokenType.*;
|
|||||||
* A recursive descent parser for the following grammar:
|
* A recursive descent parser for the following grammar:
|
||||||
* <p>
|
* <p>
|
||||||
* program → declaration* EOF ;
|
* program → declaration* EOF ;
|
||||||
* declaration → varDecl
|
* declaration → funDecl
|
||||||
|
* | varDecl
|
||||||
* | statement ;
|
* | statement ;
|
||||||
|
* funDecl → "fun" function ;
|
||||||
|
* function → IDENTIFIER "(" parameters? ")" block ;
|
||||||
|
* parameters → IDENTIFIER ( "," IDENTIFIER )* ;
|
||||||
* varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
|
* varDecl → "var" IDENTIFIER ( "=" expression )? ";" ;
|
||||||
* statement → exprStmt
|
* statement → exprStmt
|
||||||
* | forStmt
|
* | forStmt
|
||||||
* | ifStmt
|
* | ifStmt
|
||||||
* | printStmt
|
* | printStmt
|
||||||
|
* | returnStmt
|
||||||
* | whileStmt
|
* | whileStmt
|
||||||
* | block ;
|
* | block ;
|
||||||
* block → "{" declaration* "}" ;
|
* block → "{" declaration* "}" ;
|
||||||
@@ -27,6 +31,7 @@ import static xyz.ctsk.lox.TokenType.*;
|
|||||||
* ifStmt → "if" "(" expression ")" statement ( "else" statement )? ;
|
* ifStmt → "if" "(" expression ")" statement ( "else" statement )? ;
|
||||||
* whileStmt → "while" "(" expression ")" statement ;
|
* whileStmt → "while" "(" expression ")" statement ;
|
||||||
* printStmt → "print" expression ";" ;
|
* printStmt → "print" expression ";" ;
|
||||||
|
* returnStmt → "return" expression? ";" ;
|
||||||
* exprStmt → expression ";" ;
|
* exprStmt → expression ";" ;
|
||||||
* expression → equality ;
|
* expression → equality ;
|
||||||
* expression → assignment ;
|
* expression → assignment ;
|
||||||
@@ -67,6 +72,8 @@ public class Parser {
|
|||||||
|
|
||||||
private Stmt declaration() {
|
private Stmt declaration() {
|
||||||
try {
|
try {
|
||||||
|
if (match(FUN)) return function(FunctionType.FUNCTION);
|
||||||
|
if (match(VAR)) return varDeclaration();
|
||||||
return match(VAR) ? varDeclaration() : statement();
|
return match(VAR) ? varDeclaration() : statement();
|
||||||
} catch (ParseError error) {
|
} catch (ParseError error) {
|
||||||
synchronize();
|
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() {
|
private Stmt varDeclaration() {
|
||||||
Token name = consume(IDENTIFIER, "Expect variable name.");
|
Token name = consume(IDENTIFIER, "Expect variable name.");
|
||||||
|
|
||||||
@@ -91,11 +120,13 @@ public class Parser {
|
|||||||
if (match(FOR)) return forStatement();
|
if (match(FOR)) return forStatement();
|
||||||
if (match(IF)) return ifStatement();
|
if (match(IF)) return ifStatement();
|
||||||
if (match(PRINT)) return printStatement();
|
if (match(PRINT)) return printStatement();
|
||||||
|
if (match(RETURN)) return returnStatement();
|
||||||
if (match(WHILE)) return whileStatement();
|
if (match(WHILE)) return whileStatement();
|
||||||
if (match(LEFT_BRACE)) return new Stmt.Block(blockStatement());
|
if (match(LEFT_BRACE)) return new Stmt.Block(blockStatement());
|
||||||
return expressionStatement();
|
return expressionStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Stmt forStatement() {
|
private Stmt forStatement() {
|
||||||
consume(LEFT_PAREN, "Expect '(' after 'for'.");
|
consume(LEFT_PAREN, "Expect '(' after 'for'.");
|
||||||
|
|
||||||
@@ -150,6 +181,18 @@ public class Parser {
|
|||||||
return new Stmt.Print(value);
|
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() {
|
private Stmt whileStatement() {
|
||||||
consume(LEFT_PAREN, "Expect '(' after 'while'.");
|
consume(LEFT_PAREN, "Expect '(' after 'while'.");
|
||||||
Expr condition = expression();
|
Expr condition = expression();
|
||||||
@@ -387,4 +430,5 @@ public class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class ParseError extends RuntimeException {}
|
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 = {
|
rules = {
|
||||||
@Rule(head = "Block", body = {"List<Stmt> statements"}),
|
@Rule(head = "Block", body = {"List<Stmt> statements"}),
|
||||||
@Rule(head = "Expression", body = {"Expr expression"}),
|
@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 = "If", body = {"Expr condition", "Stmt thenBranch", "Stmt elseBranch"}),
|
||||||
@Rule(head = "Print", body = {"Expr expression"}),
|
@Rule(head = "Print", body = {"Expr expression"}),
|
||||||
|
@Rule(head = "Return", body = {"Token keyword", "Expr value"}),
|
||||||
@Rule(head = "Var", body = {"Token name", "Expr initializer"}),
|
@Rule(head = "Var", body = {"Token name", "Expr initializer"}),
|
||||||
@Rule(head = "While", body = {"Expr condition", "Stmt body"})
|
@Rule(head = "While", body = {"Expr condition", "Stmt body"})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user