diff --git a/src/jlox/expr.d b/src/jlox/expr.d index 79ed79f..ddf4df5 100644 --- a/src/jlox/expr.d +++ b/src/jlox/expr.d @@ -16,6 +16,7 @@ abstract class Expr{ R visit(Binary expr); R visit(Grouping expr); R visit(Literal expr); + R visit(Logical expr); R visit(Unary expr); R visit(Variable expr); } @@ -47,6 +48,12 @@ abstract class Expr{ TValue value; mixin defCtorAndAccept; } + static class Logical : typeof(this){ + Expr left; + Token operator; + Expr right; + mixin defCtorAndAccept; + } static class Unary : typeof(this){ Token operator; Expr right; diff --git a/src/jlox/interpreter.d b/src/jlox/interpreter.d index a207492..1fde9e2 100644 --- a/src/jlox/interpreter.d +++ b/src/jlox/interpreter.d @@ -75,6 +75,12 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue { void visit(Stmt.Expression stmt){ evaluate(stmt.expression); } + void visit(Stmt.If stmt){ + if(isTruthy(evaluate(stmt.condition))) + execute(stmt.thenBranch); + else if(stmt.elseBranch) + execute(stmt.elseBranch); + } void visit(Stmt.Print stmt){ TValue value = evaluate(stmt.expression); writeln(tvalueToString(value)); @@ -82,6 +88,10 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue { void visit(Stmt.Var stmt){ environment.define(stmt.name.lexeme, stmt.initialiser is null ? TValue.nil(0) : evaluate(stmt.initialiser)); } + void visit(Stmt.While stmt){ + while(isTruthy(evaluate(stmt.condition))) + execute(stmt.body); + } TValue visit(Expr.Literal expr){ return expr.value; @@ -101,6 +111,17 @@ class Interpreter : Stmt.Visitor!void, Expr.Visitor!TValue { assert(0); } } + TValue visit(Expr.Logical expr){ + TValue left = evaluate(expr.left); + if(expr.operator.type == TokenType.OR){ + if(isTruthy(left)) + return left; + } else { + if(!isTruthy(left)) + return left; + } + return evaluate(expr.right); + } TValue visit(Expr.Binary expr){ TValue left = evaluate(expr.left); TValue right = evaluate(expr.right); diff --git a/src/jlox/main.d b/src/jlox/main.d index 3bf99fe..92bb2cc 100644 --- a/src/jlox/main.d +++ b/src/jlox/main.d @@ -15,6 +15,12 @@ import jlox.interpreter; import jlox.expr; import jlox.stmt; +private class MainException : Exception{ + this(){ + super(null); + } +} + static class Lox{ private static Interpreter interpreter; static this(){ @@ -56,7 +62,7 @@ static class Lox{ static void runFile(string path){ string bytes = readText(path); run(bytes); - enforce(!hadError && !hadRuntimeError); + enforce(!hadError && !hadRuntimeError, new MainException); } static void runPrompt(){ while(true){ @@ -75,10 +81,14 @@ int main(string[] argv){ auto args = new Program("lox") .add(new Argument("path").optional.acceptsFiles) .parse(argv); - if(args.arg("path")) - Lox.runFile(args.arg("path")); - else - Lox.runPrompt(); + try{ + if(args.arg("path")) + Lox.runFile(args.arg("path")); + else + Lox.runPrompt(); + } catch(MainException rte){ + return Lox.hadError ? 1 : 2; + } return 0; } diff --git a/src/jlox/parser.d b/src/jlox/parser.d index ce52c04..5cc5972 100644 --- a/src/jlox/parser.d +++ b/src/jlox/parser.d @@ -57,7 +57,7 @@ class Parser{ Lox.error(token, message); return new ParseError("hello"); } - private void synchronize(){ + private void synchronise(){ advance(); with(TokenType) while(!isAtEnd){ if(previous().type == SEMICOLON) @@ -89,11 +89,61 @@ class Parser{ consume(TokenType.SEMICOLON, "Expect ';' after expression."); return new Stmt.Expression(expr); } + private Stmt ifStatement(){ + consume(TokenType.LEFT_PAREN, "Expect '(' after 'if'."); + Expr condition = expression(); + consume(TokenType.RIGHT_PAREN, "Expect ')' after if condition."); + Stmt thenBranch = statement(); + Stmt elseBranch = null; + if(match(TokenType.ELSE)) + elseBranch = statement(); + return new Stmt.If(condition, thenBranch, elseBranch); + } + private Stmt forStatement(){ + consume(TokenType.LEFT_PAREN, "Expect '(' after 'for'."); + + Stmt initialiser; + if(match(TokenType.SEMICOLON)) + initialiser = null; + else if(match(TokenType.VAR)) + initialiser = varDeclaration(); + else + initialiser = expressionStatement(); + + Expr condition = check(TokenType.SEMICOLON) ? new Expr.Literal(TValue.bln(true)) : expression(); + consume(TokenType.SEMICOLON, "Expect ';' after loop condition."); + + Expr increment; + if(!check(TokenType.RIGHT_PAREN)) + increment = expression(); + consume(TokenType.RIGHT_PAREN, "Expect ')' after for clauses."); + + Stmt body = statement(); + if(increment !is null) + body = new Stmt.Block([ body, new Stmt.Expression(increment) ]); + body = new Stmt.While(condition, body); + if(initialiser !is null) + body = new Stmt.Block([ initialiser, body ]); + return body; + } + private Stmt whileStatement(){ + consume(TokenType.LEFT_PAREN, "Expect '(' after 'while'."); + Expr condition = expression(); + consume(TokenType.RIGHT_PAREN, "Expect ')' after condition."); + Stmt body = statement(); + return new Stmt.While(condition, body); + } private Stmt statement(){ - if(match(TokenType.LEFT_BRACE)) - return new Stmt.Block(block()); + if(match(TokenType.IF)) + return ifStatement(); + if(match(TokenType.FOR)) + return forStatement(); + if(match(TokenType.WHILE)) + return whileStatement(); if(match(TokenType.PRINT)) return printStatement(); + if(match(TokenType.LEFT_BRACE)) + return new Stmt.Block(block()); return expressionStatement(); } private Stmt[] block(){ @@ -107,7 +157,7 @@ class Parser{ return assignment(); } private Expr assignment(){ - Expr expr = equality(); + Expr expr = or(); if(match(TokenType.EQUAL)){ Token equals = previous(); Expr value = assignment(); @@ -117,6 +167,24 @@ class Parser{ } return expr; } + private Expr or(){ + Expr expr = and(); + while(match(TokenType.OR)){ + Token operator = previous(); + Expr right = and(); + expr = new Expr.Logical(expr, operator, right); + } + return expr; + } + private Expr and(){ + Expr expr = equality(); + while(match(TokenType.AND)){ + Token operator = previous(); + Expr right = equality(); + expr = new Expr.Logical(expr, operator, right); + } + return expr; + } private Expr equality(){ Expr expr = comparison(); while(match(TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL)){ @@ -181,11 +249,11 @@ class Parser{ } private Stmt varDeclaration(){ Token name = consume(TokenType.IDENTIFIER, "Expect variable name."); - Expr initializer = null; + Expr initialiser = null; if(match(TokenType.EQUAL)) - initializer = expression(); + initialiser = expression(); consume(TokenType.SEMICOLON, "Expect ';' after variable declaration."); - return new Stmt.Var(name, initializer); + return new Stmt.Var(name, initialiser); } private Stmt declaration(){ try { @@ -193,7 +261,7 @@ class Parser{ return varDeclaration(); return statement(); } catch(ParseError error){ - synchronize(); + synchronise(); return null; } } diff --git a/src/jlox/scanner.d b/src/jlox/scanner.d index 0cb45e7..bcb7740 100644 --- a/src/jlox/scanner.d +++ b/src/jlox/scanner.d @@ -72,7 +72,7 @@ class Scanner { case "class": return CLASS; case "else": return ELSE; case "false": return FALSE; - case ":or": return FOR; + case "for": return FOR; case "fun": return FUN; case "if": return IF; case "nil": return NIL; diff --git a/src/jlox/stmt.d b/src/jlox/stmt.d index ecd7c9c..f088a41 100644 --- a/src/jlox/stmt.d +++ b/src/jlox/stmt.d @@ -13,8 +13,10 @@ abstract class Stmt{ interface Visitor(R){ R visit(Block expr); R visit(Expression expr); + R visit(If expr); R visit(Print expr); R visit(Var expr); + R visit(While expr); } private alias rTypes = AliasSeq!(void); static foreach(T; rTypes) @@ -33,6 +35,12 @@ abstract class Stmt{ Expr expression; mixin defCtorAndAccept; } + static class If : typeof(this){ + Expr condition; + Stmt thenBranch; + Stmt elseBranch; + mixin defCtorAndAccept; + } static class Print : typeof(this){ Expr expression; mixin defCtorAndAccept; @@ -42,5 +50,10 @@ abstract class Stmt{ Expr initialiser; mixin defCtorAndAccept; } + static class While : typeof(this){ + Expr condition; + Stmt body; + mixin defCtorAndAccept; + } }