diff --git a/src/Interpreter.php b/src/Interpreter.php index acbaeac..3df34e8 100644 --- a/src/Interpreter.php +++ b/src/Interpreter.php @@ -12,6 +12,7 @@ use Lox\Expr\Visitor as VisitorExpr; use Lox\Stmt\BlockStmt; use Lox\Stmt\ExpressionStmt; +use Lox\Stmt\IfStmt; use Lox\Stmt\PrintStmt; use Lox\Stmt\Stmt; use Lox\Stmt\VarStmt; @@ -146,6 +147,16 @@ public function visitExpressionStmt(ExpressionStmt $stmt): void $this->evaluate($stmt->expression()); } + #[\Override] + public function visitIfStmt(IfStmt $stmt): void + { + if ($this->isTruthy($this->evaluate($stmt->condition()))) { + $this->execute($stmt->thenBranch()); + } elseif (null !== $stmt->elseBranch()) { + $this->execute($stmt->elseBranch()); + } + } + #[\Override] public function visitPrintStmt(PrintStmt $stmt): void { diff --git a/src/Parser.php b/src/Parser.php index 6a7251c..0bcbec1 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -11,6 +11,7 @@ use Lox\Expr\Variable; use Lox\Stmt\BlockStmt; use Lox\Stmt\ExpressionStmt; +use Lox\Stmt\IfStmt; use Lox\Stmt\PrintStmt; use Lox\Stmt\Stmt; use Lox\Stmt\VarStmt; @@ -66,6 +67,10 @@ private function declaration(): ?Stmt private function statement(): Stmt { + if ($this->match(TokenType::IF)) { + return $this->ifStatement(); + } + if ($this->match(TokenType::PRINT)) { return $this->printStatement(); } @@ -77,6 +82,21 @@ private function statement(): Stmt return $this->expressionStatement(); } + private function ifStatement(): Stmt + { + $this->consume(TokenType::LEFT_PAREN, 'Expect "(" after "if".'); + $condition = $this->expression(); + $this->consume(TokenType::RIGHT_PAREN, 'Expect ")" after if condition.'); + + $thenBranch = $this->statement(); + $elseBranch = null; + if ($this->match(TokenType::ELSE)) { + $elseBranch = $this->statement(); + } + + return new IfStmt($condition, $thenBranch, $elseBranch); + } + private function printStatement(): Stmt { $value = $this->expression(); diff --git a/src/Stmt/IfStmt.php b/src/Stmt/IfStmt.php new file mode 100644 index 0000000..18ac3bc --- /dev/null +++ b/src/Stmt/IfStmt.php @@ -0,0 +1,46 @@ +condition = $condition; + $this->thenBranch = $thenBranch; + $this->elseBranch = $elseBranch; + } + + public function condition(): Expr + { + return $this->condition; + } + + public function thenBranch(): Stmt + { + return $this->thenBranch; + } + + public function elseBranch(): ?Stmt + { + return $this->elseBranch; + } + + #[\Override] + public function accept(Visitor $visitor) + { + return $visitor->visitIfStmt($this); + } +} diff --git a/src/Stmt/Visitor.php b/src/Stmt/Visitor.php index 20f3056..f3d2894 100644 --- a/src/Stmt/Visitor.php +++ b/src/Stmt/Visitor.php @@ -14,6 +14,8 @@ public function visitBlockStmt(BlockStmt $stmt): void; public function visitExpressionStmt(ExpressionStmt $stmt): void; + public function visitIfStmt(IfStmt $stmt): void; + public function visitPrintStmt(PrintStmt $stmt): void; public function visitVarStmt(VarStmt $stmt): void; diff --git a/tests/lox/block/empty.lox b/tests/lox/block/empty.lox index aa00db0..982139a 100644 --- a/tests/lox/block/empty.lox +++ b/tests/lox/block/empty.lox @@ -1,3 +1,7 @@ {} // By itself. +// In a statement. +if (true) {} +if (false) {} else {} + print "ok"; // expect: ok diff --git a/tests/lox/if/dangling_else.lox b/tests/lox/if/dangling_else.lox new file mode 100644 index 0000000..05e99d6 --- /dev/null +++ b/tests/lox/if/dangling_else.lox @@ -0,0 +1,3 @@ +// A dangling else binds to the right-most if. +if (true) if (false) print "bad"; else print "good"; // expect: good +if (false) if (true) print "bad"; else print "bad"; diff --git a/tests/lox/if/else.lox b/tests/lox/if/else.lox new file mode 100644 index 0000000..326e1c5 --- /dev/null +++ b/tests/lox/if/else.lox @@ -0,0 +1,6 @@ +// Evaluate the 'else' expression if the condition is false. +if (true) print "good"; else print "bad"; // expect: good +if (false) print "bad"; else print "good"; // expect: good + +// Allow block body. +if (false) nil; else { print "block"; } // expect: block diff --git a/tests/lox/if/if.lox b/tests/lox/if/if.lox new file mode 100644 index 0000000..7f46c98 --- /dev/null +++ b/tests/lox/if/if.lox @@ -0,0 +1,10 @@ +// Evaluate the 'then' expression if the condition is true. +if (true) print "good"; // expect: good +if (false) print "bad"; + +// Allow block body. +if (true) { print "block"; } // expect: block + +// Assignment in if condition. +var a = false; +if (a = true) print a; // expect: true diff --git a/tests/lox/if/truth.lox b/tests/lox/if/truth.lox new file mode 100644 index 0000000..97d302a --- /dev/null +++ b/tests/lox/if/truth.lox @@ -0,0 +1,8 @@ +// False and nil are false. +if (false) print "bad"; else print "false"; // expect: false +if (nil) print "bad"; else print "nil"; // expect: nil + +// Everything else is true. +if (true) print true; // expect: true +if (0) print 0; // expect: 0 +if ("") print "empty"; // expect: empty diff --git a/tests/lox/if/var_in_else.lox b/tests/lox/if/var_in_else.lox new file mode 100644 index 0000000..1354729 --- /dev/null +++ b/tests/lox/if/var_in_else.lox @@ -0,0 +1,2 @@ +// [line 2] Error at "var": Expect expression. +if (true) "ok"; else var foo; diff --git a/tests/lox/if/var_in_then.lox b/tests/lox/if/var_in_then.lox new file mode 100644 index 0000000..3f7b6cb --- /dev/null +++ b/tests/lox/if/var_in_then.lox @@ -0,0 +1,2 @@ +// [line 2] Error at "var": Expect expression. +if (true) var foo; diff --git a/tests/lox/variable/unreached_undefined.lox b/tests/lox/variable/unreached_undefined.lox new file mode 100644 index 0000000..e7bf633 --- /dev/null +++ b/tests/lox/variable/unreached_undefined.lox @@ -0,0 +1,5 @@ +if (false) { + print notDefined; +} + +print "ok"; // expect: ok diff --git a/tools/generate-ast b/tools/generate-ast index 15a14da..d5cc352 100755 --- a/tools/generate-ast +++ b/tools/generate-ast @@ -22,8 +22,9 @@ defineAst(EXPR_DIR, 'Expr', [ // Stmt classes must have Stmt suffix to be valid defineAst(STMT_DIR, 'Stmt', [ - 'BlockStmt : List statements', + 'BlockStmt : list statements', 'ExpressionStmt : Lox\Expr\Expr expression', + 'IfStmt : Lox\Expr\Expr condition, Lox\Stmt\Stmt thenBranch, ?Lox\Stmt\Stmt elseBranch', 'PrintStmt : Lox\Expr\Expr expression', 'VarStmt : Lox\Token name, ?Lox\Expr\Expr initializer', ]); @@ -107,10 +108,10 @@ function generateType(string $baseName, string $className, array $properties = [ $type = $parts[0]; $propertyName = $parts[1]; - if (preg_match('/^List<.*>$/', $type)) { - $propertyComment = sprintf('@var %s', lcfirst($type)); - $constructorComment = sprintf('@param %s $%s', lcfirst($type), $propertyName); - $returnComment = sprintf('@return %s', lcfirst($type)); + if (preg_match('/^list<.*>$/', $type)) { + $propertyComment = sprintf('@var %s', $type); + $constructorComment = sprintf('@param %s $%s', $type, $propertyName); + $returnComment = sprintf('@return %s', $type); $type = 'array'; } } else {