diff --git a/src/com/lox/Interpreter.java b/src/com/lox/Interpreter.java index 42f2e04..d47c3ef 100644 --- a/src/com/lox/Interpreter.java +++ b/src/com/lox/Interpreter.java @@ -1,9 +1,26 @@ package com.lox; - +import java.util.ArrayList; import java.util.List; 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 ""; } + }); + } @Override public Object visitLiteralExpr(Expr.Literal expr){ return expr.value; @@ -99,11 +116,33 @@ 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 Void visitExpressionStmt(Stmt.Expression stmt) { evaluate(stmt.expression); 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))){ execute(stmt.thenBranch); @@ -120,6 +159,13 @@ 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; if (stmt.initializer != null) { diff --git a/src/com/lox/LoxCallable.java b/src/com/lox/LoxCallable.java new file mode 100644 index 0000000..3e5d433 --- /dev/null +++ b/src/com/lox/LoxCallable.java @@ -0,0 +1,8 @@ +package com.lox; + +import java.util.List; + +interface LoxCallable { + int arity(); + Object call(Interpreter interpreter, List arguments); +} \ No newline at end of file diff --git a/src/com/lox/LoxFunction.java b/src/com/lox/LoxFunction.java new file mode 100644 index 0000000..832c991 --- /dev/null +++ b/src/com/lox/LoxFunction.java @@ -0,0 +1,35 @@ +package com.lox; + +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; + } +} \ No newline at end of file diff --git a/src/com/lox/Parser.java b/src/com/lox/Parser.java index c61699c..a79f54e 100644 --- a/src/com/lox/Parser.java +++ b/src/com/lox/Parser.java @@ -23,6 +23,7 @@ class Parser { } private Stmt declaration() { try { + if (match(FUN)) return function("function"); if (match(VAR)) return varDeclaration(); return statement(); } catch (ParseError error) { @@ -38,6 +39,7 @@ 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(block()); return expressionStatement(); @@ -98,6 +100,16 @@ class Parser { consume(SEMICOLON, "Expect ';' after 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 varDeclaration() { Token name = consume(IDENTIFIER, "Expect variable name."); @@ -121,6 +133,26 @@ class Parser { consume(SEMICOLON, "Expect ';' after expression."); 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<>(); @@ -222,9 +254,34 @@ class Parser { return new Expr.Unary(operator, right); } - return primary(); + return call(); } - + 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(calle,paren,arguments); + } + private Expr call(){ + Expr expr = primary(); + while(true){ + if(match(LEFT_PAREN)){ + expr = finishCall(expr); + } + else { + break; + } + } + return expr; + } private Expr primary() { if (match(FALSE)) return new Expr.Literal(false); if (match(TRUE)) return new Expr.Literal(true); diff --git a/src/com/lox/Return.java b/src/com/lox/Return.java new file mode 100644 index 0000000..e011406 --- /dev/null +++ b/src/com/lox/Return.java @@ -0,0 +1,10 @@ +package com.lox; + +class Return extends RuntimeException { + final Object value; + + Return(Object value) { + super(null, null, false, false); + this.value = value; + } +} \ No newline at end of file diff --git a/src/com/tool/GenerateAst.java b/src/com/tool/GenerateAst.java index 85298e4..12c62a0 100644 --- a/src/com/tool/GenerateAst.java +++ b/src/com/tool/GenerateAst.java @@ -15,6 +15,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", @@ -26,7 +27,10 @@ public class GenerateAst { "If : Expr condition, Stmt thenBranch," + " Stmt elseBranch", "Expression : Expr expression", + "Function : Token name, List params," + + " List body", "Print : Expr expression", + "Return : Token keyword, Expr value", "Var : Token name, Expr initializer", "While : Expr condition, Stmt body" )); @@ -37,7 +41,7 @@ public class GenerateAst { String path = outputDir + "/" + baseName + ".java"; PrintWriter writer = new PrintWriter(path, "UTF-8"); - writer.println("package com.craftinginterpreters.lox;"); + writer.println("package com.lox;"); writer.println(); writer.println("import java.util.List;"); writer.println();