From 43badb2e0c010fe12939dfbb723eb8591d90e847 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Sun, 28 Jan 2024 10:45:56 +0100 Subject: [PATCH] fix Continues Statements in for loops --- for.lox | 4 + plox.php | 9 ++ src/AST/AstNode.php | 11 ++ src/AST/AstPrinter.php | 175 +++++++++++++++++++-- src/AST/Expressions/Assign.php | 5 +- src/AST/Expressions/Expression.php | 3 +- src/AST/Statements/BlockStatement.php | 2 +- src/AST/Statements/CompletionStatement.php | 2 +- src/AST/Statements/ExpressionStatement.php | 2 +- src/AST/Statements/IfStatement.php | 2 +- src/AST/Statements/ReturnStatement.php | 2 +- src/AST/Statements/Statement.php | 3 +- src/AST/Statements/VarStatement.php | 2 +- src/AST/Statements/WhileStatement.php | 2 +- src/Lox.php | 28 ++++ src/Parser/Parser.php | 13 +- src/Scaner/TokenType.php | 90 +++++------ test | 3 + test.lox | 47 ++++-- tests/Feature/LoopTest.php | 4 +- 20 files changed, 319 insertions(+), 90 deletions(-) create mode 100644 for.lox create mode 100644 src/AST/AstNode.php create mode 100755 test diff --git a/for.lox b/for.lox new file mode 100644 index 0000000..4d7920f --- /dev/null +++ b/for.lox @@ -0,0 +1,4 @@ +for(var inc = 0; inc < 10; inc=inc+1){ + print(inc) + break +} diff --git a/plox.php b/plox.php index 4b2e7a8..676775c 100755 --- a/plox.php +++ b/plox.php @@ -12,6 +12,15 @@ /** @var Lox $lox */ $lox = dependency(Lox::class); if (isset($argv[1])) { + if (isset($argv[2]) && $argv[2] == "--print") { + $result = $lox->reverseAST($argv[1]); + echo $result; + return; + } else if (isset($argv[2]) && $argv[2] == "--json") { + $result = $lox->jsonAST($argv[1]); + echo $result; + return; + } $lox->runFile($argv[1]); } else { $lox->runCli(); diff --git a/src/AST/AstNode.php b/src/AST/AstNode.php new file mode 100644 index 0000000..ffa984f --- /dev/null +++ b/src/AST/AstNode.php @@ -0,0 +1,11 @@ +accept($this); - } + public $indent = " "; + private $currentIndent = ""; #[\Override] public function visitBinaryExpr(Binary $expression) { - return $this->parenthesize($expression->operator->lexeme, $expression->left, $expression->right); + $left = $expression->left->accept($this); + $right = $expression->right->accept($this); + + return "$left {$expression->operator->lexeme} $right"; } #[\Override] public function visitGroupingExpr(Grouping $expression) { - return $this->parenthesize("group", $expression->expression); + return $this->indent("(".$expression->accept($this).")\n"); } #[\Override] public function visitLiteralExpr(Literal $expression) { if ($expression->value == null) return "nil"; - return strval($expression->value); + return strval($expression->value->value); } #[\Override] public function visitUnaryExpr(Unary $expression) { - return $this->parenthesize($expression->operator->lexeme, $expression->right); + $right = $expression->right->accept($this); + return "{$expression->operator->lexeme}$right"; + } + + #[\Override] public function visitTernaryExpr(Ternary $expression) + { + $condition = $expression->condition->accept($this); + $then = $expression->then->accept($this); + $else = $expression->else->accept($this); + + return "$condition ? $then : $else\n"; + } + + #[\Override] public function visitVariableExpr(Variable $expression) + { + return $expression->name->lexeme; + } + + #[\Override] public function visitAssignExpr(Assign $expression) + { + return $this->indent("{$expression->name->lexeme} = ".$expression->value->accept($this)."\n"); + } + + #[\Override] public function visitLogicalExpr(Logical $expression) + { + $left = $expression->left->accept($this); + $right = $expression->right->accept($this); + + return "$left {$expression->operator->lexeme} $right"; + } + + #[\Override] public function visitCallExpr(Call $call) + { + $arguments = []; + foreach ($call->arguments as $argument) { + $arguments[] = $argument->accept($this); + } + + return $this->indent($call->callee->accept($this)."(".implode(', ', $arguments).")\n"); + } + + #[\Override] public function visitFunctionExpr(FunctionExpression $expression) + { + $parameters = []; + foreach ($expression->parameters as $parameter) { + $parameters[] = $parameter->lexeme; + } + $result = $this->indent("{$expression->name->lexeme}(".implode(', ', $parameters).") {"); + + $this->addIndent(); + foreach ($expression->body as $item) { + $result .= $this->indent($item->accept($this)."\n"); + } + $this->removeIndent(); + + $result .= $this->indent("}\n"); + return $result; + } + + #[\Override] public function visitExpressionStmt(ExpressionStatement $statement) + { + return $statement->expression->accept($this); } - protected function parenthesize(string $name, Expression ...$expressions) + #[\Override] public function visitVarStmt(VarStatement $statement) { - $str = "($name"; - foreach ($expressions as $expression) { - $str .= " ".$expression->accept($this); + if ($statement->initializer == null) { + return $this->indent("var {$statement->name->lexeme}"); + } else { + $initializer = $statement->initializer->accept($this); + return $this->indent("var {$statement->name->lexeme} = $initializer\n"); } - return "$str)"; + } + + #[\Override] public function visitBlockStmt(BlockStatement $statement) + { + $result = $this->indent("{\n"); + $this->addIndent(); + foreach ($statement->statements as $statement) { + $result .= $statement->accept($this)."\n"; + } + $this->removeIndent(); + $result .= $this->indent("}\n"); + + return $result; + } + + #[\Override] public function visitIfStmt(IfStatement $statement) + { + $result = $this->indent("if(".$statement->condition->accept($this).") {\n"); + + $this->addIndent(); + $result .= $this->indent($statement->thenBranch->accept($this)."\n"); + $this->removeIndent(); + $result .= $this->indent("}"); + if ($statement->elseBranch != null) { + $result .= " else {\n"; + + $this->addIndent(); + $result .= $this->indent($statement->elseBranch->accept($this)."\n"); + $this->removeIndent(); + $result .= $this->indent("}\n"); + return $result; + } + return $result."\n"; + } + + #[\Override] public function visitWhileStmt(WhileStatement $statement) + { + $result = $this->indent("while(".$statement->condition->accept($this).") \n"); + $result .= $statement->body->accept($this)."\n"; + + return $result; + } + + #[\Override] public function visitCompletionStmt(CompletionStatement $statement) + { + return $this->indent("{$statement->operator->lexeme}\n"); + } + + #[\Override] public function visitReturnStmt(ReturnStatement $statement) + { + if ($statement->value == null) { + return $this->indent("return\n"); + } else { + return $this->indent("return ".$statement->value->accept($this)."\n"); + } + } + + private function indent($str) + { + return $this->currentIndent.$str; + } + + private function addIndent() + { + $this->currentIndent .= $this->indent; + } + + private function removeIndent() + { + $this->currentIndent = substr($this->currentIndent, strlen($this->indent)); } } \ No newline at end of file diff --git a/src/AST/Expressions/Assign.php b/src/AST/Expressions/Assign.php index 9224189..09a257e 100644 --- a/src/AST/Expressions/Assign.php +++ b/src/AST/Expressions/Assign.php @@ -23,6 +23,9 @@ public function __construct( #[\Override] public function jsonSerialize(): mixed { - // TODO: Implement jsonSerialize() method. + return [ + "name" => $this->name, + "value" => $this->value + ]; } } \ No newline at end of file diff --git a/src/AST/Expressions/Expression.php b/src/AST/Expressions/Expression.php index 31da01e..af9d76b 100644 --- a/src/AST/Expressions/Expression.php +++ b/src/AST/Expressions/Expression.php @@ -2,10 +2,11 @@ namespace src\AST\Expressions; +use src\AST\AstNode; use src\AST\ExpressionVisitor; use src\Scaner\Token; -abstract class Expression implements \JsonSerializable +abstract class Expression extends AstNode implements \JsonSerializable { public function __construct( diff --git a/src/AST/Statements/BlockStatement.php b/src/AST/Statements/BlockStatement.php index deb69b3..4b63e91 100644 --- a/src/AST/Statements/BlockStatement.php +++ b/src/AST/Statements/BlockStatement.php @@ -20,7 +20,7 @@ public function __construct( #[\Override] function accept(StatementVisitor $visitor) { - $visitor->visitBlockStmt($this); + return $visitor->visitBlockStmt($this); } #[\Override] public function jsonSerialize(): mixed diff --git a/src/AST/Statements/CompletionStatement.php b/src/AST/Statements/CompletionStatement.php index 8dcbe3e..05df8f0 100644 --- a/src/AST/Statements/CompletionStatement.php +++ b/src/AST/Statements/CompletionStatement.php @@ -17,7 +17,7 @@ public function __construct( #[\Override] function accept(StatementVisitor $visitor) { - $visitor->visitCompletionStmt($this); + return $visitor->visitCompletionStmt($this); } #[\Override] public function jsonSerialize(): mixed diff --git a/src/AST/Statements/ExpressionStatement.php b/src/AST/Statements/ExpressionStatement.php index c88378f..b271911 100644 --- a/src/AST/Statements/ExpressionStatement.php +++ b/src/AST/Statements/ExpressionStatement.php @@ -17,7 +17,7 @@ public function __construct( #[\Override] function accept(StatementVisitor $visitor) { - $visitor->visitExpressionStmt($this); + return $visitor->visitExpressionStmt($this); } #[\Override] public function jsonSerialize(): mixed diff --git a/src/AST/Statements/IfStatement.php b/src/AST/Statements/IfStatement.php index 9e801b9..47dd2f4 100644 --- a/src/AST/Statements/IfStatement.php +++ b/src/AST/Statements/IfStatement.php @@ -22,7 +22,7 @@ public function __construct( #[\Override] function accept(StatementVisitor $visitor) { - $visitor->visitIfStmt($this); + return $visitor->visitIfStmt($this); } #[\Override] public function jsonSerialize(): mixed diff --git a/src/AST/Statements/ReturnStatement.php b/src/AST/Statements/ReturnStatement.php index ff6d207..d87575d 100644 --- a/src/AST/Statements/ReturnStatement.php +++ b/src/AST/Statements/ReturnStatement.php @@ -17,7 +17,7 @@ public function __construct( #[\Override] function accept(StatementVisitor $visitor) { - $visitor->visitReturnStmt($this); + return $visitor->visitReturnStmt($this); } #[\Override] public function jsonSerialize(): mixed diff --git a/src/AST/Statements/Statement.php b/src/AST/Statements/Statement.php index 78dd84c..445181f 100644 --- a/src/AST/Statements/Statement.php +++ b/src/AST/Statements/Statement.php @@ -2,10 +2,11 @@ namespace src\AST\Statements; +use src\AST\AstNode; use src\AST\StatementVisitor; use src\Scaner\Token; -abstract class Statement implements \JsonSerializable +abstract class Statement extends AstNode implements \JsonSerializable { public function __construct( public readonly Token $tokenStart, diff --git a/src/AST/Statements/VarStatement.php b/src/AST/Statements/VarStatement.php index e4c70a6..f81f4cd 100644 --- a/src/AST/Statements/VarStatement.php +++ b/src/AST/Statements/VarStatement.php @@ -21,7 +21,7 @@ public function __construct( #[\Override] function accept(StatementVisitor $visitor) { - $visitor->visitVarStmt($this); + return $visitor->visitVarStmt($this); } #[\Override] public function jsonSerialize(): mixed diff --git a/src/AST/Statements/WhileStatement.php b/src/AST/Statements/WhileStatement.php index 1e9cf51..6994ec9 100644 --- a/src/AST/Statements/WhileStatement.php +++ b/src/AST/Statements/WhileStatement.php @@ -20,7 +20,7 @@ public function __construct( #[\Override] function accept(StatementVisitor $visitor) { - $visitor->visitWhileStmt($this); + return $visitor->visitWhileStmt($this); } #[\Override] public function jsonSerialize(): mixed diff --git a/src/Lox.php b/src/Lox.php index 6438ed6..26ab2a6 100644 --- a/src/Lox.php +++ b/src/Lox.php @@ -3,6 +3,7 @@ namespace src; +use src\AST\AstPrinter; use src\Interpreter\Interpreter; use src\Interpreter\Runtime\LoxType; use src\Parser\Parser; @@ -28,6 +29,33 @@ public function __construct( { } + public function reverseAST(string $file) { + $source = file_get_contents($file); + + $tokens = $this->scanner->scanTokens($source); + $statements = $this->parser->parse($tokens); + $this->resolver->resolveAll($statements); + + $astPrinter = new AstPrinter(); + $serialized = ""; + + foreach ($statements as $statement) { + $serialized .= $statement->accept($astPrinter); + } + + return $serialized; + } + + public function jsonAST(string $file) { + $source = file_get_contents($file); + + $tokens = $this->scanner->scanTokens($source); + $statements = $this->parser->parse($tokens); + $this->resolver->resolveAll($statements); + + return json_encode($statements, JSON_PRETTY_PRINT); + } + public function runString(string $source) { [$result, $expression] = $this->run($source); diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index dc134bd..ad84574 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -148,9 +148,9 @@ private function forStmt(ParserContext $context) $this->consume(TokenType::LEFT_PAREN, "Expect '(' after 'for'."); - $tokenInitializer = null; if ($this->match(TokenType::SEMICOLON)) { - $initializer = null; + $tokenInitializer = null; + $initializer = null; } else if ($this->match(TokenType::VAR)) { $tokenInitializer = $this->previous(); $initializer = $this->varDeclaration($context); @@ -249,11 +249,6 @@ private function returnStmt(ParserContext $context): Statement $keyword = $this->previous(); $value = new Literal(dependency(NilValue::class), $keyword); - - $strictLB = $this->peek()->type == TokenType::LINE_BREAK; - $strictSC = $this->peek()->type == TokenType::SEMICOLON; - - if (!$this->checkStrict(TokenType::LINE_BREAK) && !$this->checkStrict(TokenType::SEMICOLON)) { $value = $this->expression($context); } @@ -275,7 +270,7 @@ private function completionStmt(ParserContext $context): Statement return new BlockStatement( $completionToken, [ - new ExpressionStatement($increment), + new ExpressionStatement($increment->copy()), $completion ], $completionToken); @@ -506,7 +501,7 @@ private function function (string $kind, ParserContext $context) $tokenStart = $this->previous(); $name = null; - if($this->match(TokenType::IDENTIFIER)) { + if ($this->match(TokenType::IDENTIFIER)) { $name = $this->previous(); } diff --git a/src/Scaner/TokenType.php b/src/Scaner/TokenType.php index 5a9097d..b515669 100644 --- a/src/Scaner/TokenType.php +++ b/src/Scaner/TokenType.php @@ -2,58 +2,58 @@ namespace src\Scaner; -enum TokenType +enum TokenType: string { // Single-character tokens. - case LEFT_PAREN; - case RIGHT_PAREN; - case LEFT_BRACE; - case RIGHT_BRACE; - case COMMA; - case DOT; - case COLON; - case QUESTION_MARK; - case MINUS; - case PLUS; - case SEMICOLON; - case SLASH; - case STAR; + case LEFT_PAREN = "LEFT_PAREN"; + case RIGHT_PAREN = "RIGHT_PAREN"; + case LEFT_BRACE = "LEFT_BRACE"; + case RIGHT_BRACE = "RIGHT_BRACE"; + case COMMA = "COMMA"; + case DOT = "DOT"; + case COLON = "COLON"; + case QUESTION_MARK = "QUESTION_MARK"; + case MINUS = "MINUS"; + case PLUS = "PLUS"; + case SEMICOLON = "SEMICOLON"; + case SLASH = "SLASH"; + case STAR = "STAR"; // One or two character tokens. - case BANG; - case BANG_EQUAL; - case EQUAL; - case EQUAL_EQUAL; - case GREATER; - case GREATER_EQUAL; - case LESS; - case LESS_EQUAL; + case BANG = "BANG"; + case BANG_EQUAL = "BANG_EQUAL"; + case EQUAL = "EQUAL"; + case EQUAL_EQUAL = "EQUAL_EQUAL"; + case GREATER = "GREATER"; + case GREATER_EQUAL = "GREATER_EQUAL"; + case LESS = "LESS"; + case LESS_EQUAL = "LESS_EQUAL"; // Literals. - case IDENTIFIER; - case STRING; - case NUMBER; + case IDENTIFIER = "IDENTIFIER"; + case STRING = "STRING"; + case NUMBER = "NUMBER"; // Keywords. - case AND; - case CLS; - case ELSE; - case FALSE; - case FUNCTION; - case FOR; - case IF; - case NIL; - case OR; - case RETURN; - case SUPER; - case THIS; - case TRUE; - case VAR; - case WHILE; - case BREAK; - case CONTINUE; + case AND = "AND"; + case CLS = "CLS"; + case ELSE = "ELSE"; + case FALSE = "FALSE"; + case FUNCTION = "FUNCTION"; + case FOR = "FOR"; + case IF = "IF"; + case NIL = "NIL"; + case OR = "OR"; + case RETURN = "RETURN"; + case SUPER = "SUPER"; + case THIS = "THIS"; + case TRUE = "TRUE"; + case VAR = "VAR"; + case WHILE = "WHILE"; + case BREAK = "BREAK"; + case CONTINUE = "CONTINUE"; - case LINE_BREAK; - case EOF; - case ERROR; + case LINE_BREAK = "LINE_BREAK"; + case EOF = "EOF"; + case ERROR = "ERROR"; } diff --git a/test b/test new file mode 100755 index 0000000..446346d --- /dev/null +++ b/test @@ -0,0 +1,3 @@ +#!/bin/bash + +docker run -it --rm -e PHP_IDE_CONFIG="serverName=PloxHost" -e XDEBUG_SESSION=1 -v $PWD:/app -w /app -u $(id -u):$(id -g) --add-host=host.docker.internal:host-gateway phpdev bash -c "php vendor/bin/pest $@" diff --git a/test.lox b/test.lox index 8925e8f..a8d5ba4 100644 --- a/test.lox +++ b/test.lox @@ -1,9 +1,38 @@ -var a = nil - function test() { - return - - a = 42 - } - - test() - print(a) +{ + var inc = 0 + + while(inc < 10) + { + { + print(inc) + + { + inc = inc + 1 + + break + + } + + } + + inc = inc + 1 + + } + + +} +/* +var y=0 +var z=0 +while(y<10) { + for(var i=0; i<=100; i=i+1) { + if(i > 10) { + y = y+1 + break + } + } + z = y +} +print(z) + +*/ diff --git a/tests/Feature/LoopTest.php b/tests/Feature/LoopTest.php index acf736c..dc58d3c 100644 --- a/tests/Feature/LoopTest.php +++ b/tests/Feature/LoopTest.php @@ -86,9 +86,9 @@ } z = y } - print z + print(z) '); expect($this->environment) ->toHave('z', new NumberValue(10)); -}); \ No newline at end of file +})->only(); \ No newline at end of file