Compare commits

..

16 Commits

Author SHA1 Message Date
Nikita Popov
97e59c7a16 Release PHP-Parser 4.2.4 2019-09-01 09:51:21 +02:00
Tomas Votruba
006acba066 add getTraitUses() method to ClassLike 2019-08-31 09:26:51 +02:00
Tomas Votruba
005bb1dba7 add getProperties() and getConstants() to ClassLike 2019-08-30 20:47:14 +02:00
Tomas Votruba
40e7b67d69 gitignore: add phpunit test cache 2019-08-30 20:47:14 +02:00
Nikita Popov
5644a916bc Sync flexible heredoc emulation with label fix
This was recently fixed in PHP via
310708845f
so we should fix it here as well.

I'm not adding a dedicated test as it will fail until new PHP versions
are released. This is indirectly tested through the php-src tester.
2019-08-30 20:21:28 +02:00
Nikita Popov
e612609022 Release PHP-Parser 4.2.3 2019-08-12 22:17:41 +02:00
Nikita Popov
4fd36b9946 Fix compatibility with T_BAD_CHARACTER in PHP 7.4 2019-08-12 22:10:02 +02:00
Tyson Andre
a1f72690ef Fix harmless typos in documentation 2019-07-23 12:32:37 +02:00
Tyson Andre
2e2954ccdf Avoid notices in php 7.4 with hexdec/base_convert (#619)
This is made to avoid notices caused by
https://wiki.php.net/rfc/base_convert_improvements

(seen with `php -d error_reporting=E_ALL vendor/bin/phpunit`)
2019-07-14 10:56:13 +02:00
Tomáš Votruba
3f718ee2c3 [PHP 7.4] Add support for numeric literal separators (#615)
Implements RFC https://wiki.php.net/rfc/numeric_literal_separator.

Closes #614.
2019-06-30 12:13:28 +02:00
Nikita Popov
b9b45dd2bc Insert T_BAD_CHARACTER tokens for missing characters
The token stream should cover all characters in the original code,
insert a dummy token for missing illegal characters. We should
really be doing this in token_get_all() as well.
2019-06-30 11:43:48 +02:00
Chris Hewitt
a4b43edb03 Fix one-character inconsistency 2019-06-30 09:25:26 +02:00
Nikita Popov
3cf61fdd26 Only ignore-platform-reqs on nightly 2019-06-23 15:11:05 +02:00
Nikita Popov
9484baf8f8 Make compatible with PhpUnit 8 2019-06-23 15:03:40 +02:00
Nikita Popov
aad0e2896f Remove token registration from TokenEmulator interface 2019-06-23 14:50:14 +02:00
hoels
624f71fa6f Resolve return type of arrow functions (#613) 2019-06-04 16:25:12 +02:00
34 changed files with 671 additions and 126 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ vendor/
composer.lock composer.lock
grammar/kmyacc.exe grammar/kmyacc.exe
grammar/y.output grammar/y.output
.phpunit.result.cache

View File

@@ -16,7 +16,12 @@ php:
install: install:
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then composer require satooshi/php-coveralls '~1.0'; fi - if [ $TRAVIS_PHP_VERSION = '7.0' ]; then composer require satooshi/php-coveralls '~1.0'; fi
- composer install --prefer-dist --ignore-platform-reqs - |
if [ $TRAVIS_PHP_VERSION = 'nightly' ]; then
composer install --prefer-dist --ignore-platform-reqs;
else
composer install --prefer-dist;
fi
matrix: matrix:
allow_failures: allow_failures:

View File

@@ -1,8 +1,32 @@
Version 4.2.3-dev Version 4.2.5-dev
----------------- -----------------
Nothing yet. Nothing yet.
Version 4.2.4 (2019-09-01)
--------------------------
### Added
* Added getProperties(), getConstants() and getTraitUses() to ClassLike. (#629, #630)
### Fixed
* Fixed flexible heredoc emulation to check for digits after the end label. This synchronizes
behavior with the upcoming PHP 7.3.10 release.
Version 4.2.3 (2019-08-12)
--------------------------
### Added
* [PHP 7.4] Add support for numeric literal separators. (#615)
### Fixed
* Fixed resolution of return types for arrow functions. (#613)
* Fixed compatibility with PHP 7.4.
Version 4.2.2 (2019-05-25) Version 4.2.2 (2019-05-25)
-------------------------- --------------------------

View File

@@ -3,10 +3,10 @@ PHP Parser
[![Build Status](https://travis-ci.org/nikic/PHP-Parser.svg?branch=master)](https://travis-ci.org/nikic/PHP-Parser) [![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master) [![Build Status](https://travis-ci.org/nikic/PHP-Parser.svg?branch=master)](https://travis-ci.org/nikic/PHP-Parser) [![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
This is a PHP 5.2 to PHP 7.3 parser written in PHP. Its purpose is to simplify static code analysis and This is a PHP 5.2 to PHP 7.4 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation. manipulation.
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.3). [**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.4).
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2). [Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).

View File

@@ -17,7 +17,7 @@
"ext-tokenizer": "*" "ext-tokenizer": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^6.5 || ^7.0" "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0"
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {

View File

@@ -1,7 +1,7 @@
Introduction Introduction
============ ============
This project is a PHP 5.2 to PHP 7.3 parser **written in PHP itself**. This project is a PHP 5.2 to PHP 7.4 parser **written in PHP itself**.
What is this for? What is this for?
----------------- -----------------
@@ -26,11 +26,11 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
What can it parse? What can it parse?
------------------ ------------------
The parser supports parsing PHP 5.2-7.3. The parser supports parsing PHP 5.2-7.4.
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided. version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
This allows to parse PHP 7.3 source code running on PHP 7.0, for example. This emulation is somewhat This allows to parse PHP 7.4 source code running on PHP 7.0, for example. This emulation is somewhat
hacky and not perfect, but it should work well on any sane code. hacky and not perfect, but it should work well on any sane code.
What output does it produce? What output does it produce?
@@ -56,7 +56,7 @@ array(
``` ```
This matches the structure of the code: An echo statement, which takes two strings as expressions, This matches the structure of the code: An echo statement, which takes two strings as expressions,
with the values `Hi` and `World!`. with the values `Hi` and `World`.
You can also see that the AST does not contain any whitespace information (but most comments are saved). You can also see that the AST does not contain any whitespace information (but most comments are saved).
So using it for formatting analysis is not possible. So using it for formatting analysis is not possible.

View File

@@ -77,7 +77,7 @@ A parser instance can be reused to parse multiple files.
Node dumping Node dumping
------------ ------------
To dump the abstact syntax tree in human readable form, a `NodeDumper` can be used: To dump the abstract syntax tree in human readable form, a `NodeDumper` can be used:
```php ```php
<?php <?php

View File

@@ -312,7 +312,7 @@ $extendingClasses = $nodeFinder->find($stmts, function(Node $node) {
&& $node->extends !== null; && $node->extends !== null;
}); });
// Find first class occuring in the AST. Returns null if no class exists. // Find first class occurring in the AST. Returns null if no class exists.
$class = $nodeFinder->findFirstInstanceOf($stmts, Node\Stmt\Class_::class); $class = $nodeFinder->findFirstInstanceOf($stmts, Node\Stmt\Class_::class);
// Find first class that has name $name // Find first class that has name $name

View File

@@ -37,10 +37,15 @@ class Lexer
// map from internal tokens to PhpParser tokens // map from internal tokens to PhpParser tokens
$this->tokenMap = $this->createTokenMap(); $this->tokenMap = $this->createTokenMap();
// Compatibility define for PHP < 7.4
if (!defined('T_BAD_CHARACTER')) {
\define('T_BAD_CHARACTER', -1);
}
// map of tokens to drop while lexing (the map is only used for isset lookup, // map of tokens to drop while lexing (the map is only used for isset lookup,
// that's why the value is simply set to 1; the value is never actually used.) // that's why the value is simply set to 1; the value is never actually used.)
$this->dropTokens = array_fill_keys( $this->dropTokens = array_fill_keys(
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT], 1 [\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1
); );
$defaultAttributes = ['comments', 'startLine', 'endLine']; $defaultAttributes = ['comments', 'startLine', 'endLine'];
@@ -92,13 +97,9 @@ class Lexer
} }
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) { private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
$tokens = [];
for ($i = $start; $i < $end; $i++) { for ($i = $start; $i < $end; $i++) {
$chr = $this->code[$i]; $chr = $this->code[$i];
if ($chr === 'b' || $chr === 'B') {
// HHVM does not treat b" tokens correctly, so ignore these
continue;
}
if ($chr === "\0") { if ($chr === "\0") {
// PHP cuts error message after null byte, so need special case // PHP cuts error message after null byte, so need special case
$errorMsg = 'Unexpected null byte'; $errorMsg = 'Unexpected null byte';
@@ -108,6 +109,7 @@ class Lexer
); );
} }
$tokens[] = [\T_BAD_CHARACTER, $chr, $line];
$errorHandler->handleError(new Error($errorMsg, [ $errorHandler->handleError(new Error($errorMsg, [
'startLine' => $line, 'startLine' => $line,
'endLine' => $line, 'endLine' => $line,
@@ -115,6 +117,7 @@ class Lexer
'endFilePos' => $i, 'endFilePos' => $i,
])); ]));
} }
return $tokens;
} }
/** /**
@@ -155,16 +158,29 @@ class Lexer
$filePos = 0; $filePos = 0;
$line = 1; $line = 1;
foreach ($this->tokens as $token) { $numTokens = \count($this->tokens);
for ($i = 0; $i < $numTokens; $i++) {
$token = $this->tokens[$i];
// Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token.
// In this case we only need to emit an error.
if ($token[0] === \T_BAD_CHARACTER) {
$this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler);
}
$tokenValue = \is_string($token) ? $token : $token[1]; $tokenValue = \is_string($token) ? $token : $token[1];
$tokenLen = \strlen($tokenValue); $tokenLen = \strlen($tokenValue);
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) { if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
// Something is missing, must be an invalid character // Something is missing, must be an invalid character
$nextFilePos = strpos($this->code, $tokenValue, $filePos); $nextFilePos = strpos($this->code, $tokenValue, $filePos);
$this->handleInvalidCharacterRange( $badCharTokens = $this->handleInvalidCharacterRange(
$filePos, $nextFilePos, $line, $errorHandler); $filePos, $nextFilePos, $line, $errorHandler);
$filePos = (int) $nextFilePos; $filePos = (int) $nextFilePos;
array_splice($this->tokens, $i, 0, $badCharTokens);
$numTokens += \count($badCharTokens);
$i += \count($badCharTokens);
} }
$filePos += $tokenLen; $filePos += $tokenLen;
@@ -187,8 +203,9 @@ class Lexer
$this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line]; $this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line];
} else { } else {
// Invalid characters at the end of the input // Invalid characters at the end of the input
$this->handleInvalidCharacterRange( $badCharTokens = $this->handleInvalidCharacterRange(
$filePos, \strlen($this->code), $line, $errorHandler); $filePos, \strlen($this->code), $line, $errorHandler);
$this->tokens = array_merge($this->tokens, $badCharTokens);
} }
return; return;
} }

View File

@@ -7,17 +7,22 @@ use PhpParser\ErrorHandler;
use PhpParser\Lexer; use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator; use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator; use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface; use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface;
use PhpParser\Parser\Tokens;
class Emulative extends Lexer class Emulative extends Lexer
{ {
const PHP_7_3 = '7.3.0dev'; const PHP_7_3 = '7.3.0dev';
const PHP_7_4 = '7.4.0dev'; const PHP_7_4 = '7.4.0dev';
const T_COALESCE_EQUAL = 1007;
const T_FN = 1008;
const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX' const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n /<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*? (?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x (?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX; REGEX;
/** @var mixed[] Patches used to reverse changes introduced in the code */ /** @var mixed[] Patches used to reverse changes introduced in the code */
@@ -33,14 +38,12 @@ REGEX;
{ {
parent::__construct($options); parent::__construct($options);
// prepare token emulators
$this->tokenEmulators[] = new FnTokenEmulator(); $this->tokenEmulators[] = new FnTokenEmulator();
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator(); $this->tokenEmulators[] = new CoaleseEqualTokenEmulator();
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator();
// add emulated tokens here $this->tokenMap[self::T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL;
foreach ($this->tokenEmulators as $emulativeToken) { $this->tokenMap[self::T_FN] = Tokens::T_FN;
$this->tokenMap[$emulativeToken->getTokenId()] = $emulativeToken->getParserTokenId();
}
} }
public function startLexing(string $code, ErrorHandler $errorHandler = null) { public function startLexing(string $code, ErrorHandler $errorHandler = null) {
@@ -57,14 +60,6 @@ REGEX;
// 1. emulation of heredoc and nowdoc new syntax // 1. emulation of heredoc and nowdoc new syntax
$preparedCode = $this->processHeredocNowdoc($code); $preparedCode = $this->processHeredocNowdoc($code);
parent::startLexing($preparedCode, $collector); parent::startLexing($preparedCode, $collector);
// add token emulation
foreach ($this->tokenEmulators as $emulativeToken) {
if ($emulativeToken->isEmulationNeeded($code)) {
$this->tokens = $emulativeToken->emulate($code, $this->tokens);
}
}
$this->fixupTokens(); $this->fixupTokens();
$errors = $collector->getErrors(); $errors = $collector->getErrors();
@@ -74,6 +69,13 @@ REGEX;
$errorHandler->handleError($error); $errorHandler->handleError($error);
} }
} }
// add token emulation
foreach ($this->tokenEmulators as $emulativeToken) {
if ($emulativeToken->isEmulationNeeded($code)) {
$this->tokens = $emulativeToken->emulate($code, $this->tokens);
}
}
} }
private function isHeredocNowdocEmulationNeeded(string $code): bool private function isHeredocNowdocEmulationNeeded(string $code): bool

View File

@@ -3,22 +3,9 @@
namespace PhpParser\Lexer\TokenEmulator; namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative; use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Tokens;
final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
{ {
const T_COALESCE_EQUAL = 1007;
public function getTokenId(): int
{
return self::T_COALESCE_EQUAL;
}
public function getParserTokenId(): int
{
return Tokens::T_COALESCE_EQUAL;
}
public function isEmulationNeeded(string $code) : bool public function isEmulationNeeded(string $code) : bool
{ {
// skip version where this is supported // skip version where this is supported
@@ -38,7 +25,7 @@ final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
if (isset($tokens[$i + 1])) { if (isset($tokens[$i + 1])) {
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') { if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
array_splice($tokens, $i, 2, [ array_splice($tokens, $i, 2, [
[self::T_COALESCE_EQUAL, '??=', $line] [Emulative::T_COALESCE_EQUAL, '??=', $line]
]); ]);
$c--; $c--;
continue; continue;

View File

@@ -3,22 +3,9 @@
namespace PhpParser\Lexer\TokenEmulator; namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative; use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Tokens;
final class FnTokenEmulator implements TokenEmulatorInterface final class FnTokenEmulator implements TokenEmulatorInterface
{ {
const T_FN = 1008;
public function getTokenId(): int
{
return self::T_FN;
}
public function getParserTokenId(): int
{
return Tokens::T_FN;
}
public function isEmulationNeeded(string $code) : bool public function isEmulationNeeded(string $code) : bool
{ {
// skip version where this is supported // skip version where this is supported
@@ -40,7 +27,7 @@ final class FnTokenEmulator implements TokenEmulatorInterface
continue; continue;
} }
$tokens[$i][0] = self::T_FN; $tokens[$i][0] = Emulative::T_FN;
} }
} }

View File

@@ -0,0 +1,98 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class NumericLiteralSeparatorEmulator implements TokenEmulatorInterface
{
const BIN = '(?:0b[01]+(?:_[01]+)*)';
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
const EXP = '(?:e[+-]?' . self::DEC . ')';
const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
public function isEmulationNeeded(string $code) : bool
{
// skip version where this is supported
if (version_compare(\PHP_VERSION, Emulative::PHP_7_4, '>=')) {
return false;
}
return preg_match('~[0-9a-f]_[0-9a-f]~i', $code) !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$codeOffset = 0;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
$tokenLen = \strlen(\is_array($token) ? $token[1] : $token);
if ($token[0] !== T_LNUMBER && $token[0] !== T_DNUMBER) {
$codeOffset += $tokenLen;
continue;
}
$res = preg_match(self::NUMBER, $code, $matches, 0, $codeOffset);
assert($res, "No number at number token position");
$match = $matches[0];
$matchLen = \strlen($match);
if ($matchLen === $tokenLen) {
// Original token already holds the full number.
$codeOffset += $tokenLen;
continue;
}
$tokenKind = $this->resolveIntegerOrFloatToken($match);
$newTokens = [[$tokenKind, $match, $token[2]]];
$numTokens = 1;
$len = $tokenLen;
while ($matchLen > $len) {
$nextToken = $tokens[$i + $numTokens];
$nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken;
$nextTokenLen = \strlen($nextTokenText);
$numTokens++;
if ($matchLen < $len + $nextTokenLen) {
// Split trailing characters into a partial token.
assert(is_array($nextToken), "Partial token should be an array token");
$partialText = substr($nextTokenText, $matchLen - $len);
$newTokens[] = [$nextToken[0], $partialText, $nextToken[2]];
break;
}
$len += $nextTokenLen;
}
array_splice($tokens, $i, $numTokens, $newTokens);
$c -= $numTokens - \count($newTokens);
$codeOffset += $matchLen;
}
return $tokens;
}
private function resolveIntegerOrFloatToken(string $str): int
{
$str = str_replace('_', '', $str);
if (stripos($str, '0b') === 0) {
$num = bindec($str);
} elseif (stripos($str, '0x') === 0) {
$num = hexdec($str);
} elseif (stripos($str, '0') === 0 && ctype_digit($str)) {
$num = octdec($str);
} else {
$num = +$str;
}
return is_float($num) ? T_DNUMBER : T_LNUMBER;
}
}

View File

@@ -2,12 +2,9 @@
namespace PhpParser\Lexer\TokenEmulator; namespace PhpParser\Lexer\TokenEmulator;
/** @internal */
interface TokenEmulatorInterface interface TokenEmulatorInterface
{ {
public function getTokenId(): int;
public function getParserTokenId(): int;
public function isEmulationNeeded(string $code): bool; public function isEmulationNeeded(string $code): bool;
/** /**

View File

@@ -34,6 +34,8 @@ class DNumber extends Scalar
* @return float The parsed number * @return float The parsed number
*/ */
public static function parse(string $str) : float { public static function parse(string $str) : float {
$str = str_replace('_', '', $str);
// if string contains any of .eE just cast it to float // if string contains any of .eE just cast it to float
if (false !== strpbrk($str, '.eE')) { if (false !== strpbrk($str, '.eE')) {
return (float) $str; return (float) $str;

View File

@@ -41,6 +41,8 @@ class LNumber extends Scalar
* @return LNumber The constructed LNumber, including kind attribute * @return LNumber The constructed LNumber, including kind attribute
*/ */
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber { public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
$str = str_replace('_', '', $str);
if ('0' !== $str[0] || '0' === $str) { if ('0' !== $str[0] || '0' === $str) {
$attributes['kind'] = LNumber::KIND_DEC; $attributes['kind'] = LNumber::KIND_DEC;
return new LNumber((int) $str, $attributes); return new LNumber((int) $str, $attributes);

View File

@@ -100,7 +100,7 @@ class String_ extends Scalar
if (isset(self::$replacements[$str])) { if (isset(self::$replacements[$str])) {
return self::$replacements[$str]; return self::$replacements[$str];
} elseif ('x' === $str[0] || 'X' === $str[0]) { } elseif ('x' === $str[0] || 'X' === $str[0]) {
return chr(hexdec($str)); return chr(hexdec(substr($str, 1)));
} elseif ('u' === $str[0]) { } elseif ('u' === $str[0]) {
return self::codePointToUtf8(hexdec($matches[2])); return self::codePointToUtf8(hexdec($matches[2]));
} else { } else {
@@ -134,7 +134,7 @@ class String_ extends Scalar
} }
throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large'); throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
} }
public function getType() : string { public function getType() : string {
return 'Scalar_String'; return 'Scalar_String';
} }

View File

@@ -14,6 +14,45 @@ abstract class ClassLike extends Node\Stmt
/** @var Node\Stmt[] Statements */ /** @var Node\Stmt[] Statements */
public $stmts; public $stmts;
/**
* @return TraitUse[]
*/
public function getTraitUses() : array {
$traitUses = [];
foreach ($this->stmts as $stmt) {
if ($stmt instanceof TraitUse) {
$traitUses[] = $stmt;
}
}
return $traitUses;
}
/**
* @return ClassConst[]
*/
public function getConstants() : array {
$constants = [];
foreach ($this->stmts as $stmt) {
if ($stmt instanceof ClassConst) {
$constants[] = $stmt;
}
}
return $constants;
}
/**
* @return Property[]
*/
public function getProperties() : array {
$properties = [];
foreach ($this->stmts as $stmt) {
if ($stmt instanceof Property) {
$properties[] = $stmt;
}
}
return $properties;
}
/** /**
* Gets all methods defined directly in this class/interface/trait * Gets all methods defined directly in this class/interface/trait
* *

View File

@@ -98,7 +98,7 @@ class Class_ extends ClassLike
throw new Error('Cannot use the final modifier on an abstract class member'); throw new Error('Cannot use the final modifier on an abstract class member');
} }
} }
public function getType() : string { public function getType() : string {
return 'Stmt_Class'; return 'Stmt_Class';
} }

View File

@@ -28,7 +28,7 @@ class Interface_ extends ClassLike
public function getSubNodeNames() : array { public function getSubNodeNames() : array {
return ['name', 'extends', 'stmts']; return ['name', 'extends', 'stmts'];
} }
public function getType() : string { public function getType() : string {
return 'Stmt_Interface'; return 'Stmt_Interface';
} }

View File

@@ -23,7 +23,7 @@ class Trait_ extends ClassLike
public function getSubNodeNames() : array { public function getSubNodeNames() : array {
return ['name', 'stmts']; return ['name', 'stmts'];
} }
public function getType() : string { public function getType() : string {
return 'Stmt_Trait'; return 'Stmt_Trait';
} }

View File

@@ -18,7 +18,7 @@ class TryCatch extends Node\Stmt
* *
* @param Node\Stmt[] $stmts Statements * @param Node\Stmt[] $stmts Statements
* @param Catch_[] $catches Catches * @param Catch_[] $catches Catches
* @param null|Finally_ $finally Optionaly finally node * @param null|Finally_ $finally Optional finally node
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $stmts, array $catches, Finally_ $finally = null, array $attributes = []) { public function __construct(array $stmts, array $catches, Finally_ $finally = null, array $attributes = []) {
@@ -31,7 +31,7 @@ class TryCatch extends Node\Stmt
public function getSubNodeNames() : array { public function getSubNodeNames() : array {
return ['stmts', 'catches', 'finally']; return ['stmts', 'catches', 'finally'];
} }
public function getType() : string { public function getType() : string {
return 'Stmt_TryCatch'; return 'Stmt_TryCatch';
} }

View File

@@ -91,6 +91,7 @@ class NameResolver extends NodeVisitorAbstract
$this->resolveSignature($node); $this->resolveSignature($node);
} elseif ($node instanceof Stmt\ClassMethod } elseif ($node instanceof Stmt\ClassMethod
|| $node instanceof Expr\Closure || $node instanceof Expr\Closure
|| $node instanceof Expr\ArrowFunction
) { ) {
$this->resolveSignature($node); $this->resolveSignature($node);
} elseif ($node instanceof Stmt\Property) { } elseif ($node instanceof Stmt\Property) {

View File

@@ -159,8 +159,13 @@ class Standard extends PrettyPrinterAbstract
return (string) $node->value; return (string) $node->value;
} }
$sign = $node->value < 0 ? '-' : ''; if ($node->value < 0) {
$str = (string) $node->value; $sign = '-';
$str = (string) -$node->value;
} else {
$sign = '';
$str = (string) $node->value;
}
switch ($kind) { switch ($kind) {
case Scalar\LNumber::KIND_BIN: case Scalar\LNumber::KIND_BIN:
return $sign . '0b' . base_convert($str, 10, 2); return $sign . '0b' . base_convert($str, 10, 2);

View File

@@ -9,11 +9,8 @@ use PhpParser\Node\Stmt;
class InterfaceTest extends \PHPUnit\Framework\TestCase class InterfaceTest extends \PHPUnit\Framework\TestCase
{ {
/** @var Interface_ */ protected function createInterfaceBuilder() {
protected $builder; return new Interface_('Contract');
protected function setUp() {
$this->builder = new Interface_('Contract');
} }
private function dump($node) { private function dump($node) {
@@ -22,13 +19,14 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
} }
public function testEmpty() { public function testEmpty() {
$contract = $this->builder->getNode(); $contract = $this->createInterfaceBuilder()->getNode();
$this->assertInstanceOf(Stmt\Interface_::class, $contract); $this->assertInstanceOf(Stmt\Interface_::class, $contract);
$this->assertEquals(new Node\Identifier('Contract'), $contract->name); $this->assertEquals(new Node\Identifier('Contract'), $contract->name);
} }
public function testExtending() { public function testExtending() {
$contract = $this->builder->extend('Space\Root1', 'Root2')->getNode(); $contract = $this->createInterfaceBuilder()
->extend('Space\Root1', 'Root2')->getNode();
$this->assertEquals( $this->assertEquals(
new Stmt\Interface_('Contract', [ new Stmt\Interface_('Contract', [
'extends' => [ 'extends' => [
@@ -41,7 +39,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
public function testAddMethod() { public function testAddMethod() {
$method = new Stmt\ClassMethod('doSomething'); $method = new Stmt\ClassMethod('doSomething');
$contract = $this->builder->addStmt($method)->getNode(); $contract = $this->createInterfaceBuilder()->addStmt($method)->getNode();
$this->assertSame([$method], $contract->stmts); $this->assertSame([$method], $contract->stmts);
} }
@@ -49,7 +47,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
$const = new Stmt\ClassConst([ $const = new Stmt\ClassConst([
new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458.0)) new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458.0))
]); ]);
$contract = $this->builder->addStmt($const)->getNode(); $contract = $this->createInterfaceBuilder()->addStmt($const)->getNode();
$this->assertSame(299792458.0, $contract->stmts[0]->consts[0]->value->value); $this->assertSame(299792458.0, $contract->stmts[0]->consts[0]->value->value);
} }
@@ -58,7 +56,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458)) new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458))
]); ]);
$method = new Stmt\ClassMethod('doSomething'); $method = new Stmt\ClassMethod('doSomething');
$contract = $this->builder $contract = $this->createInterfaceBuilder()
->addStmt($method) ->addStmt($method)
->addStmt($const) ->addStmt($const)
->getNode() ->getNode()
@@ -69,7 +67,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
} }
public function testDocComment() { public function testDocComment() {
$node = $this->builder $node = $this->createInterfaceBuilder()
->setDocComment('/** Test */') ->setDocComment('/** Test */')
->getNode(); ->getNode();
@@ -81,7 +79,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
public function testInvalidStmtError() { public function testInvalidStmtError() {
$this->expectException(\LogicException::class); $this->expectException(\LogicException::class);
$this->expectExceptionMessage('Unexpected node of type "Stmt_PropertyProperty"'); $this->expectExceptionMessage('Unexpected node of type "Stmt_PropertyProperty"');
$this->builder->addStmt(new Stmt\PropertyProperty('invalid')); $this->createInterfaceBuilder()->addStmt(new Stmt\PropertyProperty('invalid'));
} }
public function testFullFunctional() { public function testFullFunctional() {
@@ -89,7 +87,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458)) new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458))
]); ]);
$method = new Stmt\ClassMethod('doSomething'); $method = new Stmt\ClassMethod('doSomething');
$contract = $this->builder $contract = $this->createInterfaceBuilder()
->addStmt($method) ->addStmt($method)
->addStmt($const) ->addStmt($const)
->getNode() ->getNode()

View File

@@ -5,6 +5,12 @@ namespace PhpParser\Builder;
use PhpParser\Comment; use PhpParser\Comment;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\Stmt\TraitUse;
class TraitTest extends \PHPUnit\Framework\TestCase class TraitTest extends \PHPUnit\Framework\TestCase
{ {
@@ -43,4 +49,43 @@ class TraitTest extends \PHPUnit\Framework\TestCase
->addStmt(new Stmt\Echo_([])) ->addStmt(new Stmt\Echo_([]))
; ;
} }
public function testGetMethods() {
$methods = [
new ClassMethod('foo'),
new ClassMethod('bar'),
new ClassMethod('fooBar'),
];
$trait = new Stmt\Trait_('Foo', [
'stmts' => [
new TraitUse([]),
$methods[0],
new ClassConst([]),
$methods[1],
new Property(0, []),
$methods[2],
]
]);
$this->assertSame($methods, $trait->getMethods());
}
public function testGetProperties()
{
$properties = [
new Property(Class_::MODIFIER_PUBLIC, [new PropertyProperty('foo')]),
new Property(Class_::MODIFIER_PUBLIC, [new PropertyProperty('bar')]),
];
$trait = new Stmt\Trait_('Foo', [
'stmts' => [
new TraitUse([]),
$properties[0],
new ClassConst([]),
$properties[1],
new ClassMethod('fooBar'),
]
]);
$this->assertSame($properties, $trait->getProperties());
}
} }

View File

@@ -123,10 +123,6 @@ class EmulativeTest extends LexerTest
public function provideTestLexNewFeatures() { public function provideTestLexNewFeatures() {
return [ return [
// PHP 7.4
['??=', [
[Tokens::T_COALESCE_EQUAL, '??='],
]],
['yield from', [ ['yield from', [
[Tokens::T_YIELD_FROM, 'yield from'], [Tokens::T_YIELD_FROM, 'yield from'],
]], ]],
@@ -169,7 +165,7 @@ class EmulativeTest extends LexerTest
[ord(';'), ';'], [ord(';'), ';'],
]], ]],
// Flexible heredoc/nowdoc // PHP 7.3: Flexible heredoc/nowdoc
["<<<LABEL\nLABEL,", [ ["<<<LABEL\nLABEL,", [
[Tokens::T_START_HEREDOC, "<<<LABEL\n"], [Tokens::T_START_HEREDOC, "<<<LABEL\n"],
[Tokens::T_END_HEREDOC, "LABEL"], [Tokens::T_END_HEREDOC, "LABEL"],
@@ -205,6 +201,58 @@ class EmulativeTest extends LexerTest
[Tokens::T_END_HEREDOC, " LABEL"], [Tokens::T_END_HEREDOC, " LABEL"],
[Tokens::T_STRING, "LABEL"], [Tokens::T_STRING, "LABEL"],
]], ]],
// PHP 7.4: Null coalesce equal
['??=', [
[Tokens::T_COALESCE_EQUAL, '??='],
]],
// PHP 7.4: Number literal separator
['1_000', [
[Tokens::T_LNUMBER, '1_000'],
]],
['0xCAFE_F00D', [
[Tokens::T_LNUMBER, '0xCAFE_F00D'],
]],
['0b0101_1111', [
[Tokens::T_LNUMBER, '0b0101_1111'],
]],
['0137_041', [
[Tokens::T_LNUMBER, '0137_041'],
]],
['1_000.0', [
[Tokens::T_DNUMBER, '1_000.0'],
]],
['1_0.0', [
[Tokens::T_DNUMBER, '1_0.0']
]],
['1_000_000_000.0', [
[Tokens::T_DNUMBER, '1_000_000_000.0']
]],
['0e1_0', [
[Tokens::T_DNUMBER, '0e1_0']
]],
['1_0e+10', [
[Tokens::T_DNUMBER, '1_0e+10']
]],
['1_0e-10', [
[Tokens::T_DNUMBER, '1_0e-10']
]],
['0b1011010101001010_110101010010_10101101010101_0101101011001_110111100', [
[Tokens::T_DNUMBER, '0b1011010101001010_110101010010_10101101010101_0101101011001_110111100'],
]],
['0xFFFF_FFFF_FFFF_FFFF', [
[Tokens::T_DNUMBER, '0xFFFF_FFFF_FFFF_FFFF'],
]],
['1_000+1', [
[Tokens::T_LNUMBER, '1_000'],
[ord('+'), '+'],
[Tokens::T_LNUMBER, '1'],
]],
['1_0abc', [
[Tokens::T_LNUMBER, '1_0'],
[Tokens::T_STRING, 'abc'],
]],
]; ];
} }
} }

View File

@@ -2,6 +2,8 @@
namespace PhpParser\Node\Stmt; namespace PhpParser\Node\Stmt;
use PhpParser\Node\Scalar\String_;
class ClassTest extends \PHPUnit\Framework\TestCase class ClassTest extends \PHPUnit\Framework\TestCase
{ {
public function testIsAbstract() { public function testIsAbstract() {
@@ -20,6 +22,22 @@ class ClassTest extends \PHPUnit\Framework\TestCase
$this->assertFalse($class->isFinal()); $this->assertFalse($class->isFinal());
} }
public function testGetTraitUses() {
$traitUses = [
new TraitUse([new Trait_('foo')]),
new TraitUse([new Trait_('bar')]),
];
$class = new Class_('Foo', [
'stmts' => [
$traitUses[0],
new ClassMethod('fooBar'),
$traitUses[1],
]
]);
$this->assertSame($traitUses, $class->getTraitUses());
}
public function testGetMethods() { public function testGetMethods() {
$methods = [ $methods = [
new ClassMethod('foo'), new ClassMethod('foo'),
@@ -40,6 +58,42 @@ class ClassTest extends \PHPUnit\Framework\TestCase
$this->assertSame($methods, $class->getMethods()); $this->assertSame($methods, $class->getMethods());
} }
public function testGetConstants() {
$constants = [
new ClassConst([new \PhpParser\Node\Const_('foo', new String_('foo_value'))]),
new ClassConst([new \PhpParser\Node\Const_('bar', new String_('bar_value'))]),
];
$class = new Class_('Foo', [
'stmts' => [
new TraitUse([]),
$constants[0],
new ClassMethod('fooBar'),
$constants[1],
]
]);
$this->assertSame($constants, $class->getConstants());
}
public function testGetProperties()
{
$properties = [
new Property(Class_::MODIFIER_PUBLIC, [new PropertyProperty('foo')]),
new Property(Class_::MODIFIER_PUBLIC, [new PropertyProperty('bar')]),
];
$class = new Class_('Foo', [
'stmts' => [
new TraitUse([]),
$properties[0],
new ClassConst([]),
$properties[1],
new ClassMethod('fooBar'),
]
]);
$this->assertSame($properties, $class->getProperties());
}
public function testGetMethod() { public function testGetMethod() {
$methodConstruct = new ClassMethod('__CONSTRUCT'); $methodConstruct = new ClassMethod('__CONSTRUCT');
$methodTest = new ClassMethod('test'); $methodTest = new ClassMethod('test');

View File

@@ -3,6 +3,7 @@
namespace PhpParser\Node\Stmt; namespace PhpParser\Node\Stmt;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Scalar\String_;
class InterfaceTest extends \PHPUnit\Framework\TestCase class InterfaceTest extends \PHPUnit\Framework\TestCase
{ {
@@ -11,7 +12,7 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
new ClassMethod('foo'), new ClassMethod('foo'),
new ClassMethod('bar'), new ClassMethod('bar'),
]; ];
$interface = new Class_('Foo', [ $interface = new Interface_('Foo', [
'stmts' => [ 'stmts' => [
new Node\Stmt\ClassConst([new Node\Const_('C1', new Node\Scalar\String_('C1'))]), new Node\Stmt\ClassConst([new Node\Const_('C1', new Node\Scalar\String_('C1'))]),
$methods[0], $methods[0],
@@ -23,4 +24,21 @@ class InterfaceTest extends \PHPUnit\Framework\TestCase
$this->assertSame($methods, $interface->getMethods()); $this->assertSame($methods, $interface->getMethods());
} }
public function testGetConstants() {
$constants = [
new ClassConst([new \PhpParser\Node\Const_('foo', new String_('foo_value'))]),
new ClassConst([new \PhpParser\Node\Const_('bar', new String_('bar_value'))]),
];
$class = new Interface_('Foo', [
'stmts' => [
new TraitUse([]),
$constants[0],
new ClassMethod('fooBar'),
$constants[1],
]
]);
$this->assertSame($constants, $class->getConstants());
}
} }

View File

@@ -266,13 +266,17 @@ class NodeTraverserTest extends \PHPUnit\Framework\TestCase
$traverser->addVisitor($visitor2); $traverser->addVisitor($visitor2);
$traverser->addVisitor($visitor3); $traverser->addVisitor($visitor3);
$getVisitors = (function () {
return $this->visitors;
})->bindTo($traverser, NodeTraverser::class);
$preExpected = [$visitor1, $visitor2, $visitor3]; $preExpected = [$visitor1, $visitor2, $visitor3];
$this->assertAttributeSame($preExpected, 'visitors', $traverser, 'The appropriate visitors have not been added'); $this->assertSame($preExpected, $getVisitors());
$traverser->removeVisitor($visitor2); $traverser->removeVisitor($visitor2);
$postExpected = [0 => $visitor1, 2 => $visitor3]; $postExpected = [0 => $visitor1, 2 => $visitor3];
$this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal'); $this->assertSame($postExpected, $getVisitors());
} }
public function testNoCloneNodes() { public function testNoCloneNodes() {

View File

@@ -219,6 +219,10 @@ function(A $a) : A {};
function fn3(?A $a) : ?A {} function fn3(?A $a) : ?A {}
function fn4(?array $a) : ?array {} function fn4(?array $a) : ?array {}
fn(array $a): array => $a;
fn(A $a): A => $a;
fn(?A $a): ?A => $a;
A::b(); A::b();
A::$b; A::$b;
A::B; A::B;
@@ -263,6 +267,9 @@ function fn3(?\NS\A $a) : ?\NS\A
function fn4(?array $a) : ?array function fn4(?array $a) : ?array
{ {
} }
fn(array $a): array => $a;
fn(\NS\A $a): \NS\A => $a;
fn(?\NS\A $a): ?\NS\A => $a;
\NS\A::b(); \NS\A::b();
\NS\A::$b; \NS\A::$b;
\NS\A::B; \NS\A::B;

View File

@@ -32,24 +32,25 @@ $a = 42;
@@{ "\1" }@@ @@{ "\1" }@@
$b = 24; $b = 24;
----- -----
!!positions
Unexpected character "" (ASCII 1) from 4:1 to 4:1 Unexpected character "" (ASCII 1) from 4:1 to 4:1
array( array(
0: Stmt_Expression( 0: Stmt_Expression[3:1 - 3:8](
expr: Expr_Assign( expr: Expr_Assign[3:1 - 3:7](
var: Expr_Variable( var: Expr_Variable[3:1 - 3:2](
name: a name: a
) )
expr: Scalar_LNumber( expr: Scalar_LNumber[3:6 - 3:7](
value: 42 value: 42
) )
) )
) )
1: Stmt_Expression( 1: Stmt_Expression[5:1 - 5:8](
expr: Expr_Assign( expr: Expr_Assign[5:1 - 5:7](
var: Expr_Variable( var: Expr_Variable[5:1 - 5:2](
name: b name: b
) )
expr: Scalar_LNumber( expr: Scalar_LNumber[5:6 - 5:7](
value: 24 value: 24
) )
) )
@@ -62,24 +63,25 @@ $a = 42;
@@{ "\0" }@@ @@{ "\0" }@@
$b = 24; $b = 24;
----- -----
!!positions
Unexpected null byte from 4:1 to 4:1 Unexpected null byte from 4:1 to 4:1
array( array(
0: Stmt_Expression( 0: Stmt_Expression[3:1 - 3:8](
expr: Expr_Assign( expr: Expr_Assign[3:1 - 3:7](
var: Expr_Variable( var: Expr_Variable[3:1 - 3:2](
name: a name: a
) )
expr: Scalar_LNumber( expr: Scalar_LNumber[3:6 - 3:7](
value: 42 value: 42
) )
) )
) )
1: Stmt_Expression( 1: Stmt_Expression[5:1 - 5:8](
expr: Expr_Assign( expr: Expr_Assign[5:1 - 5:7](
var: Expr_Variable( var: Expr_Variable[5:1 - 5:2](
name: b name: b
) )
expr: Scalar_LNumber( expr: Scalar_LNumber[5:6 - 5:7](
value: 24 value: 24
) )
) )
@@ -94,35 +96,36 @@ $b = 2;
@@{ "\2" }@@ @@{ "\2" }@@
$c = 3; $c = 3;
----- -----
Unexpected character "@@{ "\1" }@@" (ASCII 1) from 4:1 to 4:1 !!positions
Unexpected character "@@{ "\2" }@@" (ASCII 2) from 6:1 to 6:1 Unexpected character "" (ASCII 1) from 4:1 to 4:1
Unexpected character "" (ASCII 2) from 6:1 to 6:1
array( array(
0: Stmt_Expression( 0: Stmt_Expression[3:1 - 3:7](
expr: Expr_Assign( expr: Expr_Assign[3:1 - 3:6](
var: Expr_Variable( var: Expr_Variable[3:1 - 3:2](
name: a name: a
) )
expr: Scalar_LNumber( expr: Scalar_LNumber[3:6 - 3:6](
value: 1 value: 1
) )
) )
) )
1: Stmt_Expression( 1: Stmt_Expression[5:1 - 5:7](
expr: Expr_Assign( expr: Expr_Assign[5:1 - 5:6](
var: Expr_Variable( var: Expr_Variable[5:1 - 5:2](
name: b name: b
) )
expr: Scalar_LNumber( expr: Scalar_LNumber[5:6 - 5:6](
value: 2 value: 2
) )
) )
) )
2: Stmt_Expression( 2: Stmt_Expression[7:1 - 7:7](
expr: Expr_Assign( expr: Expr_Assign[7:1 - 7:6](
var: Expr_Variable( var: Expr_Variable[7:1 - 7:2](
name: c name: c
) )
expr: Scalar_LNumber( expr: Scalar_LNumber[7:6 - 7:6](
value: 3 value: 3
) )
) )

View File

@@ -0,0 +1,199 @@
Different integer syntaxes
-----
<?php
6.674_083e-11;
299_792_458;
0xCAFE_F00D;
0b0101_1111;
0137_041;
// already a valid constant name
_100;
// syntax errors
100_;
1__1;
1_.0;
1._0;
0x_123;
0b_101;
1_e2;
1e_2;
-----
Syntax error, unexpected T_STRING from 13:4 to 13:4
Syntax error, unexpected T_STRING from 14:2 to 14:4
Syntax error, unexpected T_STRING from 15:2 to 15:2
Syntax error, unexpected T_STRING from 16:3 to 16:4
Syntax error, unexpected T_STRING from 17:2 to 17:6
Syntax error, unexpected T_STRING from 18:2 to 18:6
Syntax error, unexpected T_STRING from 19:2 to 19:4
Syntax error, unexpected T_STRING from 20:2 to 20:4
array(
0: Stmt_Expression(
expr: Scalar_DNumber(
value: 6.674083E-11
)
)
1: Stmt_Expression(
expr: Scalar_LNumber(
value: 299792458
)
)
2: Stmt_Expression(
expr: Scalar_LNumber(
value: 3405705229
)
)
3: Stmt_Expression(
expr: Scalar_LNumber(
value: 95
)
)
4: Stmt_Expression(
expr: Scalar_LNumber(
value: 48673
)
)
5: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: _100
)
comments: array(
0: // already a valid constant name
)
)
comments: array(
0: // already a valid constant name
)
)
comments: array(
0: // already a valid constant name
)
)
6: Stmt_Expression(
expr: Scalar_LNumber(
value: 100
comments: array(
0: // syntax errors
)
)
comments: array(
0: // syntax errors
)
)
7: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: _
)
)
)
)
8: Stmt_Expression(
expr: Scalar_LNumber(
value: 1
)
)
9: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: __1
)
)
)
)
10: Stmt_Expression(
expr: Scalar_LNumber(
value: 1
)
)
11: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: _
)
)
)
)
12: Stmt_Expression(
expr: Scalar_DNumber(
value: 0
)
)
13: Stmt_Expression(
expr: Scalar_DNumber(
value: 1
)
)
14: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: _0
)
)
)
)
15: Stmt_Expression(
expr: Scalar_LNumber(
value: 0
)
)
16: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: x_123
)
)
)
)
17: Stmt_Expression(
expr: Scalar_LNumber(
value: 0
)
)
18: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: b_101
)
)
)
)
19: Stmt_Expression(
expr: Scalar_LNumber(
value: 1
)
)
20: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: _e2
)
)
)
)
21: Stmt_Expression(
expr: Scalar_LNumber(
value: 1
)
)
22: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: e_2
)
)
)
)
)

View File

@@ -231,6 +231,8 @@ foreach (new RecursiveIteratorIterator(
echo $file, ":\n Parse failed with message: {$e->getMessage()}\n"; echo $file, ":\n Parse failed with message: {$e->getMessage()}\n";
++$parseFail; ++$parseFail;
} catch (Throwable $e) {
echo $file, ":\n Unknown error occurred: $e\n";
} }
} }