diff --git a/src/DiagnosticsProvider.php b/src/DiagnosticsProvider.php index e918fe69..735c1812 100644 --- a/src/DiagnosticsProvider.php +++ b/src/DiagnosticsProvider.php @@ -84,6 +84,52 @@ public static function checkDiagnostics($node) { } } } + else if ($node instanceof Node\Statement\BreakOrContinueStatement) { + if ($node->breakoutLevel === null) { + return null; + } + + $breakoutLevel = $node->breakoutLevel; + while ($breakoutLevel instanceof Node\Expression\ParenthesizedExpression) { + $breakoutLevel = $breakoutLevel->expression; + } + + if ($breakoutLevel instanceof Node\Expression\NumericLiteral) { + $literalString = $breakoutLevel->getText(); + if ( + $breakoutLevel->children->kind === TokenKind::BinaryLiteralToken + && \bindec(\substr($literalString, 2)) > 0 + ) { + return null; + } + else if ( + \in_array($breakoutLevel->children->kind, [ + TokenKind::DecimalLiteralToken, + TokenKind::HexadecimalLiteralToken, + TokenKind::OctalLiteralToken, + TokenKind::IntegerLiteralToken + ]) + && \intval($literalString, 0) > 0 + ) { + return null; + } + } + + if ($breakoutLevel instanceof Token) { + $start = $breakoutLevel->getStartPosition(); + } + else { + $start = $breakoutLevel->getStart(); + } + $end = $breakoutLevel->getEndPosition(); + + return new Diagnostic( + DiagnosticKind::Error, + "Expected positive integer literal.", + $start, + $end - $start + ); + } } return null; } diff --git a/src/Node/Statement/BreakOrContinueStatement.php b/src/Node/Statement/BreakOrContinueStatement.php index bc4a65af..aa283de2 100644 --- a/src/Node/Statement/BreakOrContinueStatement.php +++ b/src/Node/Statement/BreakOrContinueStatement.php @@ -12,7 +12,7 @@ class BreakOrContinueStatement extends StatementNode { /** @var Token */ public $breakOrContinueKeyword; - /** @var Token|null */ + /** @var Expression|null */ public $breakoutLevel; /** @var Token */ public $semicolon; diff --git a/src/Parser.php b/src/Parser.php index 6e4782af..87a7a13f 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -1865,20 +1865,10 @@ private function parseBreakOrContinueStatement($parentNode) { $continueStatement->parent = $parentNode; $continueStatement->breakOrContinueKeyword = $this->eat(TokenKind::ContinueKeyword, TokenKind::BreakKeyword); - // TODO this level of granularity is unnecessary - integer-literal should be sufficient - $continueStatement->breakoutLevel = - $this->eatOptional( - TokenKind::BinaryLiteralToken, - TokenKind::DecimalLiteralToken, - TokenKind::InvalidHexadecimalLiteral, - TokenKind::InvalidBinaryLiteral, - TokenKind::FloatingLiteralToken, - TokenKind::HexadecimalLiteralToken, - TokenKind::OctalLiteralToken, - TokenKind::InvalidOctalLiteralToken, - // TODO the parser should be permissive of floating literals, but rule validation should produce error - TokenKind::IntegerLiteralToken - ); + if ($this->isExpressionStart($this->getCurrentToken())) { + $continueStatement->breakoutLevel = $this->parseExpression($continueStatement); + } + $continueStatement->semicolon = $this->eatSemicolonOrAbortStatement(); return $continueStatement; diff --git a/tests/cases/parser/breakStatement10.php b/tests/cases/parser/breakStatement10.php new file mode 100644 index 00000000..abb34ee6 --- /dev/null +++ b/tests/cases/parser/breakStatement10.php @@ -0,0 +1,5 @@ +