Compare commits

...

51 Commits

Author SHA1 Message Date
Nikita Popov
1626c19d98 Update lexer implementations and tests 2019-06-30 23:07:22 +02:00
Nikita Popov
f52785a2b8 Start on lexer refactoring 2019-06-30 19:31:22 +02:00
Nikita Popov
a21a614737 WIP 2019-06-30 17:21:55 +02:00
Nikita Popov
6f74784e16 Switch to a normalized token representation
Each token is now represented by a Token object.
2019-06-30 14:14:24 +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
Nikita Popov
1bd73cc04c Release PHP-Parser 4.2.2 2019-05-25 22:07:01 +02:00
Nikita Popov
94d93f27a5 Revert "Recover from error inside alternative array deref syntax"
This reverts commit 9d44edf85d.
2019-05-24 22:58:13 +02:00
Nikita Popov
a167aa2061 Optimize attribue checks in the lexer 2019-05-12 15:26:26 +02:00
Nikita Popov
993f29906b Avoid parent constructor call during node construction
Instead explicitly assign the attributes. This is a minor
performance improvement.
2019-05-12 14:55:21 +02:00
Nikita Popov
9d44edf85d Recover from error inside alternative array deref syntax
This is to improve error recovery for cases like #545.
2019-05-12 11:38:15 +02:00
Nikita Popov
aa97a9bb69 Add changelog entries 2019-05-11 23:08:31 +02:00
Nikita Popov
aa72c5d674 FPPP: Support inserting into empty lists 2019-05-11 22:49:32 +02:00
Nikita Popov
60d025a914 Fix attributes for zero-length nop nodes
Previously zero-length nop nodes used the lookahead start attributes
and current end attributes. This choice ends up being somewhat weird,
because the end attributes will be the at the last non-whitespace,
non-comment token, which might be quite far back. More problematically,
we may not have encountered any non-discarded token if we're at the
start of the file, in which case we will have no end attributes to
assign.

Change things to use a canonical "zero-length" node representation,
where the end position (token & file) will be exactly one before the
start position.

Fixes #589.
2019-05-11 20:01:25 +02:00
Nikita Popov
b2cecec6bc Remove bogus exprStmt mode test
We're always generating expression statements nowadays, this flag
hasn't existed for a long while now...
2019-05-11 18:51:37 +02:00
Tomas Votruba
8012faea54 [PHP 7.4] Add array spread 2019-05-09 19:15:35 +02:00
Tomáš Votruba
f3b19c19ef [PHP 7.4] Add support for arrow functions (#602)
Per RFC https://wiki.php.net/rfc/arrow_functions_v2.
2019-05-09 14:17:28 +02:00
Andrea Cardaci
78d9985d11 Print messages to stderr in bin/php-parse and fix exit status
Close #605.
2019-04-28 22:06:06 +02:00
Nikita Popov
57b8673ea7 Use --ignore-platform-reqs on Travis
Allows testing on nightly, which is PHP 8.
2019-02-16 21:58:22 +01:00
Nikita Popov
5221f49a60 Release PHP-Parser 4.2.1 2019-02-16 21:54:15 +01:00
Nikita Popov
ae4e90d558 Add PHP 7.4 to Travis matrix 2019-02-16 21:52:44 +01:00
Tomas Votruba
9de96821f7 Add support for ??= operator
Introduced in PHP 5.4, represented using an AssignOp\Coalesce node.
2019-02-09 11:16:26 +01:00
Lars Moelleken
b7e6361536 updates via "rectorphp/rector" (#573)
- "global" -> remove unused "use" statements
- "phpunit" -> fix "@covers" comments
- "phpunit" -> replace "->will($this->returnValue()" with "->willReturn()"
- "UseTest.php" -> add missing namespace
- "composer.json" -> use "autoload-dev"
- remove -> "require_once" usage in the tests (use autoload-dev via composer.json)

-> most of the changes are done automatically by "https://github.com/rectorphp/rector"
2019-01-19 11:18:00 +01:00
Nikita Popov
594bcae1fc Release PHP-Parser 4.2.0 2019-01-12 17:31:37 +01:00
Nikita Popov
d5180f0d95 Change test to use correct node type
Class name is an Identifier, not a Name.
2019-01-12 17:31:37 +01:00
Nikita Popov
1f95f9215c Tweak type annotation to include null 2019-01-12 17:31:37 +01:00
Nikita Popov
6b9dd7afe7 Avoid redundant argument 2019-01-12 17:31:37 +01:00
Andreas Möller
ba092652fe Enhancement: Reference phpunit.xsd as installed with composer 2019-01-09 21:50:03 +01:00
Andreas Möller
ec0d834c5f Enhancement: Normalize composer.json 2019-01-09 21:49:38 +01:00
Nikita Popov
ea3657fc5f Bump version to 4.2.0-dev 2019-01-05 21:34:53 +01:00
Tomas Votruba
af8c729603 Add PHP 7.4 typed properties support 2019-01-05 21:34:25 +01:00
Tomas Votruba
0ef61b49bb add float/double/real KIND support to Cast\Double node 2019-01-05 19:23:00 +01:00
Nikita Popov
90ee36a7fc Actually remove assertion... 2018-12-26 14:19:18 +01:00
Nikita Popov
8aae5b59b8 Release PHP-Parser 4.1.1 2018-12-26 12:32:39 +01:00
Nikita Popov
49d73e829f Remove anon class null name assertion
Fixes #554.
2018-12-26 12:23:42 +01:00
Gabriel Caruso
80ead71da2 Use Ubuntu 16.04 2018-12-26 11:37:14 +01:00
Niko
294b93fbca Another Typo 2018-12-24 17:28:52 +01:00
Niko
bc3ac5e5ea Fixed typo 2018-12-24 16:23:47 +01:00
Andrey Bolonin
382ca0128f add php 7.3 to travis 2018-12-08 16:37:28 +01:00
Nikita Popov
f42bbc2403 Avoid default action for error production
error is not necessarily going to have a semantic value, make sure
that the default action $$=$1 is not used.

Fixes #551.
2018-11-30 11:00:48 +01:00
Cosmin Radoi
ebf6b1c33b Fix NodeDecoder => JsonDecoder in docs (#552) 2018-11-29 23:12:33 +01:00
Tomas Votruba
a74b54ce8b [cs] remove unused @var name 2018-11-20 20:31:31 +01:00
Romain Canon
8e4f972036 Fix wrong method call in doc 2018-11-07 17:34:08 +01:00
Nikita Popov
3d0f7843d4 Support error recovery for missing return type
The return type is set to "null" in this case.

Fixes #544.
2018-10-25 16:56:50 +02:00
Phil Davis
69068722b7 Fix typo of DONT_TRAVERSE_CURRENT_AND_CHILDREN 2018-10-11 10:16:31 +02:00
Maks Rafalko
fb3d89e463 Document new DONT_TRAVERSER_CURRENT_AND_CHILDREN constant (#540)
* Document new `DONT_TRAVERSER_CURRENT_AND_CHILDREN` constant

* Rewording sentences, added 1 space indentation to make bullet list correct
2018-10-10 21:56:48 +02:00
225 changed files with 5671 additions and 3991 deletions

View File

@@ -1,5 +1,5 @@
language: php language: php
dist: trusty dist: xenial
sudo: false sudo: false
cache: cache:
@@ -10,11 +10,18 @@ php:
- 7.0 - 7.0
- 7.1 - 7.1
- 7.2 - 7.2
- 7.3
- 7.4snapshot
- nightly - nightly
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 - |
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,59 @@
Version 4.1.1-dev Version 4.2.3-dev
----------------- -----------------
Nothing yet. Nothing yet.
Version 4.2.2 (2019-05-25)
--------------------------
### Added
* [PHP 7.4] Add support for arrow functions using a new `Expr\ArrowFunction` node. (#602)
* [PHP 7.4] Add support for array spreads, using a new `unpack` subnode on `ArrayItem`. (#609)
* Added support for inserting into empty list nodes in the formatting preserving pretty printer.
### Changed
* `php-parse` will now print messages to stderr, so that stdout only contains the actual result of
the operation (such as a JSON dump). (#605)
### Fixed
* Fixed attribute assignment for zero-length nop statements, and a related assertion failure in
the formatting-preserving pretty printer. (#589)
Version 4.2.1 (2019-02-16)
--------------------------
### Added
* [PHP 7.4] Add support for `??=` operator through a new `AssignOp\Coalesce` node. (#575)
Version 4.2.0 (2019-01-12)
--------------------------
### Added
* [PHP 7.4] Add support for typed properties through a new `type` subnode of `Stmt\Property`.
Additionally `Builder\Property` now has a `setType()` method. (#567)
* Add `kind` attribute to `Cast\Double_`, which allows to distinguish between `(float)`,
`(double)` and `(real)`. The form of the cast will be preserved by the pretty printer. (#565)
### Fixed
* Remove assertion when pretty printing anonymous class with a name (#554).
Version 4.1.1 (2018-12-26)
--------------------------
### Fixed
* Fix "undefined offset" notice when parsing specific malformed code (#551).
### Added
* Support error recovery for missing return type (`function foo() : {}`) (#544).
Version 4.1.0 (2018-10-10) Version 4.1.0 (2018-10-10)
-------------------------- --------------------------
@@ -17,7 +68,7 @@ Version 4.1.0 (2018-10-10)
and some cases which we do not expect to occur in practice (such as flexible doc strings being and some cases which we do not expect to occur in practice (such as flexible doc strings being
nested within each other through abuse of variable-variable interpolation syntax) may not be nested within each other through abuse of variable-variable interpolation syntax) may not be
recognized correctly. recognized correctly.
* Added `DONT_TRAVERSER_CURRENT_AND_CHILDREN` to `NodeTraverser` to skip both traversal of child * Added `DONT_TRAVERSE_CURRENT_AND_CHILDREN` to `NodeTraverser` to skip both traversal of child
nodes, and prevent subsequent visitors from visiting the current node. nodes, and prevent subsequent visitors from visiting the current node.
Version 4.0.4 (2018-09-18) Version 4.0.4 (2018-09-18)

View File

@@ -3,7 +3,7 @@ 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.2 parser written in PHP. Its purpose is to simplify static code analysis and This is a PHP 5.2 to PHP 7.3 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.3).

View File

@@ -45,14 +45,15 @@ $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
foreach ($files as $file) { foreach ($files as $file) {
if (strpos($file, '<?php') === 0) { if (strpos($file, '<?php') === 0) {
$code = $file; $code = $file;
echo "====> Code $code\n"; fwrite(STDERR, "====> Code $code\n");
} else { } else {
if (!file_exists($file)) { if (!file_exists($file)) {
die("File $file does not exist.\n"); fwrite(STDERR, "File $file does not exist.\n");
exit(1);
} }
$code = file_get_contents($file); $code = file_get_contents($file);
echo "====> File $file:\n"; fwrite(STDERR, "====> File $file:\n");
} }
if ($attributes['with-recovery']) { if ($attributes['with-recovery']) {
@@ -60,7 +61,7 @@ foreach ($files as $file) {
$stmts = $parser->parse($code, $errorHandler); $stmts = $parser->parse($code, $errorHandler);
foreach ($errorHandler->getErrors() as $error) { foreach ($errorHandler->getErrors() as $error) {
$message = formatErrorMessage($error, $code, $attributes['with-column-info']); $message = formatErrorMessage($error, $code, $attributes['with-column-info']);
echo $message . "\n"; fwrite(STDERR, $message . "\n");
} }
if (null === $stmts) { if (null === $stmts) {
continue; continue;
@@ -70,25 +71,26 @@ foreach ($files as $file) {
$stmts = $parser->parse($code); $stmts = $parser->parse($code);
} catch (PhpParser\Error $error) { } catch (PhpParser\Error $error) {
$message = formatErrorMessage($error, $code, $attributes['with-column-info']); $message = formatErrorMessage($error, $code, $attributes['with-column-info']);
die($message . "\n"); fwrite(STDERR, $message . "\n");
exit(1);
} }
} }
foreach ($operations as $operation) { foreach ($operations as $operation) {
if ('dump' === $operation) { if ('dump' === $operation) {
echo "==> Node dump:\n"; fwrite(STDERR, "==> Node dump:\n");
echo $dumper->dump($stmts, $code), "\n"; echo $dumper->dump($stmts, $code), "\n";
} elseif ('pretty-print' === $operation) { } elseif ('pretty-print' === $operation) {
echo "==> Pretty print:\n"; fwrite(STDERR, "==> Pretty print:\n");
echo $prettyPrinter->prettyPrintFile($stmts), "\n"; echo $prettyPrinter->prettyPrintFile($stmts), "\n";
} elseif ('json-dump' === $operation) { } elseif ('json-dump' === $operation) {
echo "==> JSON dump:\n"; fwrite(STDERR, "==> JSON dump:\n");
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n"; echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
} elseif ('var-dump' === $operation) { } elseif ('var-dump' === $operation) {
echo "==> var_dump():\n"; fwrite(STDERR, "==> var_dump():\n");
var_dump($stmts); var_dump($stmts);
} elseif ('resolve-names' === $operation) { } elseif ('resolve-names' === $operation) {
echo "==> Resolved names.\n"; fwrite(STDERR, "==> Resolved names.\n");
$stmts = $traverser->traverse($stmts); $stmts = $traverser->traverse($stmts);
} }
} }
@@ -104,9 +106,9 @@ function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) {
function showHelp($error = '') { function showHelp($error = '') {
if ($error) { if ($error) {
echo $error . "\n\n"; fwrite(STDERR, $error . "\n\n");
} }
die(<<<OUTPUT fwrite($error ? STDERR : STDOUT, <<<OUTPUT
Usage: php-parse [operations] file1.php [file2.php ...] Usage: php-parse [operations] file1.php [file2.php ...]
or: php-parse [operations] "<?php code" or: php-parse [operations] "<?php code"
Turn PHP source code into an abstract syntax tree. Turn PHP source code into an abstract syntax tree.
@@ -131,6 +133,7 @@ Example:
OUTPUT OUTPUT
); );
exit($error ? 1 : 0);
} }
function parseArgs($args) { function parseArgs($args) {

View File

@@ -1,8 +1,11 @@
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"description": "A PHP parser written in PHP",
"keywords": ["php", "parser"],
"type": "library", "type": "library",
"description": "A PHP parser written in PHP",
"keywords": [
"php",
"parser"
],
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"authors": [ "authors": [
{ {
@@ -11,20 +14,28 @@
], ],
"require": { "require": {
"php": ">=7.0", "php": ">=7.0",
"ext-json": "*",
"ext-tokenizer": "*" "ext-tokenizer": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^6.5 || ^7.0" "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0"
},
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
}
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"PhpParser\\": "lib/PhpParser" "PhpParser\\": "lib/PhpParser"
} }
}, },
"bin": ["bin/php-parse"], "autoload-dev": {
"extra": { "psr-4": {
"branch-alias": { "PhpParser\\": "test/PhpParser/"
"dev-master": "4.1-dev"
} }
} },
"bin": [
"bin/php-parse"
]
} }

View File

@@ -1,7 +1,7 @@
Introduction Introduction
============ ============
This project is a PHP 5.2 to PHP 7.2 parser **written in PHP itself**. This project is a PHP 5.2 to PHP 7.3 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.2. The parser supports parsing PHP 5.2-7.3.
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.2 source code running on PHP 5.5, for example. This emulation is somewhat This allows to parse PHP 7.3 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

@@ -339,7 +339,8 @@ All four methods can either return the changed node or not return at all (i.e. `
case the current node is not changed. case the current node is not changed.
The `enterNode()` method can additionally return the value `NodeTraverser::DONT_TRAVERSE_CHILDREN`, The `enterNode()` method can additionally return the value `NodeTraverser::DONT_TRAVERSE_CHILDREN`,
which instructs the traverser to skip all children of the current node. which instructs the traverser to skip all children of the current node. To furthermore prevent subsequent
visitors from visiting the current node, `NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN` can be used instead.
The `leaveNode()` method can additionally return the value `NodeTraverser::REMOVE_NODE`, in which The `leaveNode()` method can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
case the current node will be removed from the parent array. Furthermore it is possible to return case the current node will be removed from the parent array. Furthermore it is possible to return

View File

@@ -48,7 +48,7 @@ $node = $factory->namespace('Name\Space')
->makePublic() ->makePublic()
->makeAbstract() // ->makeFinal() ->makeAbstract() // ->makeFinal()
->setReturnType('bool') // ->makeReturnByRef() ->setReturnType('bool') // ->makeReturnByRef()
->addParam($factory->param('someParam')->setTypeHint('SomeClass')) ->addParam($factory->param('someParam')->setType('SomeClass'))
->setDocComment('/** ->setDocComment('/**
* This method does something. * This method does something.
* *

View File

@@ -116,8 +116,8 @@ The JSON representation may be converted back into an AST using the `JsonDecoder
```php ```php
<?php <?php
$nodeDecoder = new PhpParser\NodeDecoder(); $jsonDecoder = new PhpParser\JsonDecoder();
$ast = $nodeDecoder->decode($json); $ast = $jsonDecoder->decode($json);
``` ```
Note that not all ASTs can be represented using JSON. In particular: Note that not all ASTs can be represented using JSON. In particular:
@@ -128,4 +128,4 @@ Note that not all ASTs can be represented using JSON. In particular:
If the node tree is not representable in JSON, the initial `json_encode()` call will fail. If the node tree is not representable in JSON, the initial `json_encode()` call will fail.
From the command line, a JSON dump can be obtained using `vendor/bin/php-parse -j file.php`. From the command line, a JSON dump can be obtained using `vendor/bin/php-parse -j file.php`.

View File

@@ -215,7 +215,7 @@ once you found it:
private $class = null; private $class = null;
public function enterNode(Node $node) { public function enterNode(Node $node) {
if ($node instanceof Node\Stmt\Class_ && if ($node instanceof Node\Stmt\Class_ &&
$node->namespaceName->toString() === 'Foo\Bar\Baz' $node->namespacedName->toString() === 'Foo\Bar\Baz'
) { ) {
$this->class = $node; $this->class = $node;
return NodeTraverser::STOP_TRAVERSAL; return NodeTraverser::STOP_TRAVERSAL;
@@ -235,7 +235,7 @@ A single traverser can be used with multiple visitors:
$traverser = new NodeTraverser; $traverser = new NodeTraverser;
$traverser->addVisitor($visitorA); $traverser->addVisitor($visitorA);
$traverser->addVisitor($visitorB); $traverser->addVisitor($visitorB);
$stmts = $traverser->traverser($stmts); $stmts = $traverser->traverse($stmts);
``` ```
It is important to understand that if a traverser is run with multiple visitors, the visitors will It is important to understand that if a traverser is run with multiple visitors, the visitors will
@@ -281,6 +281,8 @@ special enterNode/leaveNode return values:
* If *any* visitor returns `DONT_TRAVERSE_CHILDREN`, the children will be skipped for *all* * If *any* visitor returns `DONT_TRAVERSE_CHILDREN`, the children will be skipped for *all*
visitors. visitors.
* If *any* visitor returns `DONT_TRAVERSE_CURRENT_AND_CHILDREN`, the children will be skipped for *all*
visitors, and all *subsequent* visitors will not visit the current node.
* If *any* visitor returns `STOP_TRAVERSAL`, traversal is stopped for *all* visitors. * If *any* visitor returns `STOP_TRAVERSAL`, traversal is stopped for *all* visitors.
* If a visitor returns a replacement node, subsequent visitors will be passed the replacement node, * If a visitor returns a replacement node, subsequent visitors will be passed the replacement node,
not the original one. not the original one.
@@ -305,7 +307,7 @@ $nodeFinder = new NodeFinder;
$classes = $nodeFinder->findInstanceOf($stmts, Node\Stmt\Class_::class); $classes = $nodeFinder->findInstanceOf($stmts, Node\Stmt\Class_::class);
// Find all classes that extend another class // Find all classes that extend another class
$extendingClasses = $nodeFinder->findInstanceOf($stmts, function(Node $node) { $extendingClasses = $nodeFinder->find($stmts, function(Node $node) {
return $node instanceof Node\Stmt\Class_ return $node instanceof Node\Stmt\Class_
&& $node->extends !== null; && $node->extends !== null;
}); });
@@ -332,4 +334,4 @@ reverse direction: When working on a node, you might want to check if the parent
certain property. certain property.
PHP-Parser does not add parent (or sibling) references to nodes by itself, but you can easily PHP-Parser does not add parent (or sibling) references to nodes by itself, but you can easily
emulate this with a visitor. See the [FAQ](FAQ.markdown) for more information. emulate this with a visitor. See the [FAQ](FAQ.markdown) for more information.

View File

@@ -16,7 +16,7 @@ top_statement_list_ex:
top_statement_list: top_statement_list:
top_statement_list_ex top_statement_list_ex
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes); { makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; } if ($nop !== null) { $1[] = $nop; } $$ = $1; }
; ;
@@ -27,7 +27,7 @@ reserved_non_modifiers:
| T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
; ;
semi_reserved: semi_reserved:
@@ -160,7 +160,7 @@ inner_statement_list_ex:
inner_statement_list: inner_statement_list:
inner_statement_list_ex inner_statement_list_ex
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes); { makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; } if ($nop !== null) { $1[] = $nop; } $$ = $1; }
; ;
@@ -461,7 +461,7 @@ class_statement_list_ex:
class_statement_list: class_statement_list:
class_statement_list_ex class_statement_list_ex
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes); { makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; } if ($nop !== null) { $1[] = $nop; } $$ = $1; }
; ;
@@ -579,6 +579,7 @@ expr:
| variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; } | variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
| variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; } | variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; }
| variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; } | variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; }
| variable T_COALESCE_EQUAL expr { $$ = Expr\AssignOp\Coalesce [$1, $3]; }
| variable T_INC { $$ = Expr\PostInc[$1]; } | variable T_INC { $$ = Expr\PostInc[$1]; }
| T_INC variable { $$ = Expr\PreInc [$2]; } | T_INC variable { $$ = Expr\PreInc [$2]; }
| variable T_DEC { $$ = Expr\PostDec[$1]; } | variable T_DEC { $$ = Expr\PostDec[$1]; }
@@ -628,7 +629,10 @@ expr:
| T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; } | T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
| T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; } | T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
| T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; } | T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; }
| T_DOUBLE_CAST expr { $$ = Expr\Cast\Double [$2]; } | T_DOUBLE_CAST expr
{ $attrs = attributes();
$attrs['kind'] = $this->getFloatCastKind($1);
$$ = new Expr\Cast\Double($2, $attrs); }
| T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; } | T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; }
| T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; } | T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; }
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; } | T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
@@ -984,6 +988,7 @@ array_pair:
| expr { $$ = Expr\ArrayItem[$1, null, false]; } | expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; } | expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; } | '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
; ;
encaps_list: encaps_list:

View File

@@ -16,7 +16,7 @@ top_statement_list_ex:
top_statement_list: top_statement_list:
top_statement_list_ex top_statement_list_ex
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes); { makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; } if ($nop !== null) { $1[] = $nop; } $$ = $1; }
; ;
@@ -27,7 +27,7 @@ reserved_non_modifiers:
| T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
; ;
semi_reserved: semi_reserved:
@@ -196,7 +196,7 @@ inner_statement_list_ex:
inner_statement_list: inner_statement_list:
inner_statement_list_ex inner_statement_list_ex
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes); { makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; } if ($nop !== null) { $1[] = $nop; } $$ = $1; }
; ;
@@ -450,11 +450,11 @@ non_empty_parameter_list:
; ;
parameter: parameter:
optional_param_type optional_ref optional_ellipsis plain_variable optional_type optional_ref optional_ellipsis plain_variable
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); } { $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
| optional_param_type optional_ref optional_ellipsis plain_variable '=' expr | optional_type optional_ref optional_ellipsis plain_variable '=' expr
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); } { $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
| optional_param_type optional_ref optional_ellipsis error | optional_type optional_ref optional_ellipsis error
{ $$ = Node\Param[Expr\Error[], null, $1, $2, $3]; } { $$ = Node\Param[Expr\Error[], null, $1, $2, $3]; }
; ;
@@ -469,7 +469,7 @@ type:
| T_CALLABLE { $$ = Node\Identifier['callable']; } | T_CALLABLE { $$ = Node\Identifier['callable']; }
; ;
optional_param_type: optional_type:
/* empty */ { $$ = null; } /* empty */ { $$ = null; }
| type_expr { $$ = $1; } | type_expr { $$ = $1; }
; ;
@@ -477,6 +477,7 @@ optional_param_type:
optional_return_type: optional_return_type:
/* empty */ { $$ = null; } /* empty */ { $$ = null; }
| ':' type_expr { $$ = $2; } | ':' type_expr { $$ = $2; }
| ':' error { $$ = null; }
; ;
argument_list: argument_list:
@@ -529,13 +530,14 @@ class_statement_list_ex:
class_statement_list: class_statement_list:
class_statement_list_ex class_statement_list_ex
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes); { makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; } if ($nop !== null) { $1[] = $nop; } $$ = $1; }
; ;
class_statement: class_statement:
variable_modifiers property_declaration_list ';' variable_modifiers optional_type property_declaration_list ';'
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); } { $attrs = attributes();
$$ = new Stmt\Property($1, $3, $attrs, $2); $this->checkProperty($$, #1); }
| method_modifiers T_CONST class_const_list ';' | method_modifiers T_CONST class_const_list ';'
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); } { $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body | method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
@@ -658,6 +660,7 @@ expr:
| variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; } | variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
| variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; } | variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; }
| variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; } | variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; }
| variable T_COALESCE_EQUAL expr { $$ = Expr\AssignOp\Coalesce [$1, $3]; }
| variable T_INC { $$ = Expr\PostInc[$1]; } | variable T_INC { $$ = Expr\PostInc[$1]; }
| T_INC variable { $$ = Expr\PreInc [$2]; } | T_INC variable { $$ = Expr\PreInc [$2]; }
| variable T_DEC { $$ = Expr\PostDec[$1]; } | variable T_DEC { $$ = Expr\PostDec[$1]; }
@@ -705,7 +708,10 @@ expr:
| T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; } | T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
| T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; } | T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
| T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; } | T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; }
| T_DOUBLE_CAST expr { $$ = Expr\Cast\Double [$2]; } | T_DOUBLE_CAST expr
{ $attrs = attributes();
$attrs['kind'] = $this->getFloatCastKind($1);
$$ = new Expr\Cast\Double($2, $attrs); }
| T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; } | T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; }
| T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; } | T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; }
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; } | T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
@@ -723,6 +729,12 @@ expr:
| T_YIELD expr { $$ = Expr\Yield_[$2, null]; } | T_YIELD expr { $$ = Expr\Yield_[$2, null]; }
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; } | T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; }
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; } | T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8]]; }
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9]]; }
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
block_or_error block_or_error
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; } { $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; }
@@ -958,6 +970,7 @@ array_pair_list:
comma_or_error: comma_or_error:
',' ','
| error | error
{ /* do nothing -- prevent default action of $$=$1. See #551. */ }
; ;
inner_array_pair_list: inner_array_pair_list:
@@ -970,6 +983,7 @@ array_pair:
| expr { $$ = Expr\ArrayItem[$1, null, false]; } | expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; } | expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; } | '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
| /* empty */ { $$ = null; } | /* empty */ { $$ = null; }
; ;

View File

@@ -13,9 +13,12 @@ $tmpResultFile = __DIR__ . '/tmp_parser.php';
$resultDir = __DIR__ . '/../lib/PhpParser/Parser'; $resultDir = __DIR__ . '/../lib/PhpParser/Parser';
$tokensResultsFile = $resultDir . '/Tokens.php'; $tokensResultsFile = $resultDir . '/Tokens.php';
// check for kmyacc.exe binary in this directory, otherwise fall back to global name // check for kmyacc binary in this directory, otherwise fall back to global name
$kmyacc = __DIR__ . '/kmyacc.exe'; if (file_exists(__DIR__ . '/kmyacc.exe')) {
if (!file_exists($kmyacc)) { $kmyacc = __DIR__ . '/kmyacc.exe';
} else if (file_exists(__DIR__ . '/kmyacc')) {
$kmyacc = __DIR__ . '/kmyacc';
} else {
$kmyacc = 'kmyacc'; $kmyacc = 'kmyacc';
} }
@@ -175,6 +178,15 @@ function resolveMacros($code) {
. ' else { ' . $args[0] . ' = null; }'; . ' else { ' . $args[0] . ' = null; }';
} }
if ('makeZeroLengthNop' == $name) {
assertArgs(2, $args, $name);
return '$startAttributes = ' . $args[1] . ';'
. ' if (isset($startAttributes[\'comments\']))'
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createZeroLengthAttributes($startAttributes)); }'
. ' else { ' . $args[0] . ' = null; }';
}
if ('strKind' == $name) { if ('strKind' == $name) {
assertArgs(1, $args, $name); assertArgs(1, $args, $name);

View File

@@ -10,7 +10,7 @@
%right T_YIELD %right T_YIELD
%right T_DOUBLE_ARROW %right T_DOUBLE_ARROW
%right T_YIELD_FROM %right T_YIELD_FROM
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL %left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL T_COALESCE_EQUAL
%left '?' ':' %left '?' ':'
%right T_COALESCE %right T_COALESCE
%left T_BOOLEAN_OR %left T_BOOLEAN_OR
@@ -64,6 +64,7 @@
%token T_CONTINUE %token T_CONTINUE
%token T_GOTO %token T_GOTO
%token T_FUNCTION %token T_FUNCTION
%token T_FN
%token T_CONST %token T_CONST
%token T_RETURN %token T_RETURN
%token T_TRY %token T_TRY

View File

@@ -4,6 +4,9 @@ namespace PhpParser\Builder;
use PhpParser; use PhpParser;
use PhpParser\BuilderHelpers; use PhpParser\BuilderHelpers;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
class Property implements PhpParser\Builder class Property implements PhpParser\Builder
@@ -14,6 +17,9 @@ class Property implements PhpParser\Builder
protected $default = null; protected $default = null;
protected $attributes = []; protected $attributes = [];
/** @var null|Identifier|Name|NullableType */
protected $type;
/** /**
* Creates a property builder. * Creates a property builder.
* *
@@ -95,6 +101,19 @@ class Property implements PhpParser\Builder
return $this; return $this;
} }
/**
* Sets the property type for PHP 7.4+.
*
* @param string|Name|NullableType|Identifier $type
*
* @return $this
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
return $this;
}
/** /**
* Returns the built class node. * Returns the built class node.
* *
@@ -106,7 +125,8 @@ class Property implements PhpParser\Builder
[ [
new Stmt\PropertyProperty($this->name, $this->default) new Stmt\PropertyProperty($this->name, $this->default)
], ],
$this->attributes $this->attributes,
$this->type
); );
} }
} }

View File

@@ -8,7 +8,6 @@ use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Identifier; use PhpParser\Node\Identifier;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_; use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Use_; use PhpParser\Node\Stmt\Use_;
class BuilderFactory class BuilderFactory
@@ -77,7 +76,7 @@ class BuilderFactory
* @return Builder\TraitUseAdaptation The create trait use adaptation builder * @return Builder\TraitUseAdaptation The create trait use adaptation builder
*/ */
public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation { public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation {
if (is_null($method)) { if ($method === null) {
$method = $trait; $method = $trait;
$trait = null; $trait = null;
} }

View File

@@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace PhpParser;
class FileContext {
/** @var Token[] */
public $tokens;
}

View File

@@ -39,7 +39,8 @@ class PrintableNewAnonClassNode extends Expr
public static function fromNewNode(Expr\New_ $newNode) { public static function fromNewNode(Expr\New_ $newNode) {
$class = $newNode->class; $class = $newNode->class;
assert($class instanceof Node\Stmt\Class_); assert($class instanceof Node\Stmt\Class_);
assert($class->name === null); // We don't assert that $class->name is null here, to allow consumers to assign unique names
// to anonymous classes for their own purposes. We simplify ignore the name here.
return new self( return new self(
$newNode->args, $class->extends, $class->implements, $newNode->args, $class->extends, $class->implements,
$class->stmts, $newNode->getAttributes() $class->stmts, $newNode->getAttributes()

View File

@@ -2,6 +2,8 @@
namespace PhpParser\Internal; namespace PhpParser\Internal;
use PhpParser\Token;
/** /**
* Provides operations on token streams, for use by pretty printer. * Provides operations on token streams, for use by pretty printer.
* *
@@ -9,7 +11,7 @@ namespace PhpParser\Internal;
*/ */
class TokenStream class TokenStream
{ {
/** @var array Tokens (in token_get_all format) */ /** @var Token[] */
private $tokens; private $tokens;
/** @var int[] Map from position to indentation */ /** @var int[] Map from position to indentation */
private $indentMap; private $indentMap;
@@ -17,7 +19,7 @@ class TokenStream
/** /**
* Create token stream instance. * Create token stream instance.
* *
* @param array $tokens Tokens in token_get_all() format * @param Token[] $tokens Tokens
*/ */
public function __construct(array $tokens) { public function __construct(array $tokens) {
$this->tokens = $tokens; $this->tokens = $tokens;
@@ -33,8 +35,8 @@ class TokenStream
* @return bool * @return bool
*/ */
public function haveParens(int $startPos, int $endPos) : bool { public function haveParens(int $startPos, int $endPos) : bool {
return $this->haveTokenImmediativelyBefore($startPos, '(') return $this->haveTokenImmediativelyBefore($startPos, \ord('('))
&& $this->haveTokenImmediatelyAfter($endPos, ')'); && $this->haveTokenImmediatelyAfter($endPos, \ord(')'));
} }
/** /**
@@ -46,8 +48,8 @@ class TokenStream
* @return bool * @return bool
*/ */
public function haveBraces(int $startPos, int $endPos) : bool { public function haveBraces(int $startPos, int $endPos) : bool {
return $this->haveTokenImmediativelyBefore($startPos, '{') return $this->haveTokenImmediativelyBefore($startPos, \ord('{'))
&& $this->haveTokenImmediatelyAfter($endPos, '}'); && $this->haveTokenImmediatelyAfter($endPos, \ord('}'));
} }
/** /**
@@ -64,7 +66,7 @@ class TokenStream
$tokens = $this->tokens; $tokens = $this->tokens;
$pos--; $pos--;
for (; $pos >= 0; $pos--) { for (; $pos >= 0; $pos--) {
$tokenType = $tokens[$pos][0]; $tokenType = $tokens[$pos]->id;
if ($tokenType === $expectedTokenType) { if ($tokenType === $expectedTokenType) {
return true; return true;
} }
@@ -90,7 +92,7 @@ class TokenStream
$tokens = $this->tokens; $tokens = $this->tokens;
$pos++; $pos++;
for (; $pos < \count($tokens); $pos++) { for (; $pos < \count($tokens); $pos++) {
$tokenType = $tokens[$pos][0]; $tokenType = $tokens[$pos]->id;
if ($tokenType === $expectedTokenType) { if ($tokenType === $expectedTokenType) {
return true; return true;
} }
@@ -110,7 +112,7 @@ class TokenStream
return $pos; return $pos;
} }
if ($tokens[$pos][0] !== $skipTokenType) { if ($tokens[$pos]->id !== $skipTokenType) {
// Shouldn't happen. The skip token MUST be there // Shouldn't happen. The skip token MUST be there
throw new \Exception('Encountered unexpected token'); throw new \Exception('Encountered unexpected token');
} }
@@ -127,7 +129,7 @@ class TokenStream
return $pos; return $pos;
} }
if ($tokens[$pos][0] !== $skipTokenType) { if ($tokens[$pos]->id !== $skipTokenType) {
// Shouldn't happen. The skip token MUST be there // Shouldn't happen. The skip token MUST be there
throw new \Exception('Encountered unexpected token'); throw new \Exception('Encountered unexpected token');
} }
@@ -145,7 +147,7 @@ class TokenStream
public function skipLeftWhitespace(int $pos) { public function skipLeftWhitespace(int $pos) {
$tokens = $this->tokens; $tokens = $this->tokens;
for (; $pos >= 0; $pos--) { for (; $pos >= 0; $pos--) {
$type = $tokens[$pos][0]; $type = $tokens[$pos]->id;
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
break; break;
} }
@@ -162,7 +164,7 @@ class TokenStream
public function skipRightWhitespace(int $pos) { public function skipRightWhitespace(int $pos) {
$tokens = $this->tokens; $tokens = $this->tokens;
for ($count = \count($tokens); $pos < $count; $pos++) { for ($count = \count($tokens); $pos < $count; $pos++) {
$type = $tokens[$pos][0]; $type = $tokens[$pos]->id;
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) { if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
break; break;
} }
@@ -173,7 +175,7 @@ class TokenStream
public function findRight($pos, $findTokenType) { public function findRight($pos, $findTokenType) {
$tokens = $this->tokens; $tokens = $this->tokens;
for ($count = \count($tokens); $pos < $count; $pos++) { for ($count = \count($tokens); $pos < $count; $pos++) {
$type = $tokens[$pos][0]; $type = $tokens[$pos]->id;
if ($type === $findTokenType) { if ($type === $findTokenType) {
return $pos; return $pos;
} }
@@ -206,23 +208,19 @@ class TokenStream
$result = ''; $result = '';
for ($pos = $from; $pos < $to; $pos++) { for ($pos = $from; $pos < $to; $pos++) {
$token = $tokens[$pos]; $token = $tokens[$pos];
if (\is_array($token)) { $type = $token->id;
$type = $token[0]; $content = $token->value;
$content = $token[1]; if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) {
if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) { $result .= $content;
$result .= $content;
} else {
// TODO Handle non-space indentation
if ($indent < 0) {
$result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $content);
} elseif ($indent > 0) {
$result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $content);
} else {
$result .= $content;
}
}
} else { } else {
$result .= $token; // TODO Handle non-space indentation
if ($indent < 0) {
$result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $content);
} elseif ($indent > 0) {
$result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $content);
} else {
$result .= $content;
}
} }
} }
return $result; return $result;
@@ -239,8 +237,8 @@ class TokenStream
foreach ($this->tokens as $token) { foreach ($this->tokens as $token) {
$indentMap[] = $indent; $indentMap[] = $indent;
if ($token[0] === \T_WHITESPACE) { if ($token->id === \T_WHITESPACE) {
$content = $token[1]; $content = $token->value;
$newlinePos = \strrpos($content, "\n"); $newlinePos = \strrpos($content, "\n");
if (false !== $newlinePos) { if (false !== $newlinePos) {
$indent = \strlen($content) - $newlinePos - 1; $indent = \strlen($content) - $newlinePos - 1;

View File

@@ -6,88 +6,132 @@ use PhpParser\Parser\Tokens;
class Lexer class Lexer
{ {
protected $code; /** @var array Map from PHP tokens to PhpParser tokens. */
protected $tokens;
protected $pos;
protected $line;
protected $filePos;
protected $prevCloseTagHasNewline;
protected $tokenMap; protected $tokenMap;
protected $dropTokens;
protected $usedAttributes;
/** /**
* Creates a Lexer. * Creates a Lexer.
* *
* @param array $options Options array. Currently only the 'usedAttributes' option is supported, * @param array $options Options array. Currently unused.
* which is an array of attributes to add to the AST nodes. Possible
* attributes are: 'comments', 'startLine', 'endLine', 'startTokenPos',
* 'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the
* first three. For more info see getNextToken() docs.
*/ */
public function __construct(array $options = []) { public function __construct(array $options = []) {
// map from internal tokens to PhpParser tokens
$this->tokenMap = $this->createTokenMap(); $this->tokenMap = $this->createTokenMap();
// 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.)
$this->dropTokens = array_fill_keys(
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT], 1
);
// the usedAttributes member is a map of the used attribute names to a dummy
// value (here "true")
$options += [
'usedAttributes' => ['comments', 'startLine', 'endLine'],
];
$this->usedAttributes = array_fill_keys($options['usedAttributes'], true);
} }
/** /**
* Initializes the lexer for lexing the provided source code. * Get tokens IDs that should be ignored by the parser.
*
* @return array
*/
public function getIgnorableTokens(): array {
return [
Tokens::T_WHITESPACE,
Tokens::T_COMMENT,
Tokens::T_DOC_COMMENT,
Tokens::T_OPEN_TAG,
Tokens::T_BAD_CHARACTER,
];
}
/**
* Get map for token canonicalization.
*
* @return array
*/
public function getCanonicalizationMap(): array {
return [
Tokens::T_OPEN_TAG_WITH_ECHO => Tokens::T_ECHO,
Tokens::T_CLOSE_TAG => \ord(';'),
];
}
/**
* Tokenizes the given PHP code into an array of Tokens.
* *
* This function does not throw if lexing errors occur. Instead, errors may be retrieved using * This function does not throw if lexing errors occur. Instead, errors may be retrieved using
* the getErrors() method. * the getErrors() method.
* *
* @param string $code The source code to lex * @param string $code The source code to tokenize
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to * @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
* ErrorHandler\Throwing * ErrorHandler\Throwing
*
* @return Token[] Sequence of tokens
*/ */
public function startLexing(string $code, ErrorHandler $errorHandler = null) { public function tokenize(string $code, ErrorHandler $errorHandler = null) {
if (null === $errorHandler) { if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing(); $errorHandler = new ErrorHandler\Throwing();
} }
$this->code = $code; // keep the code around for __halt_compiler() handling
$this->pos = -1;
$this->line = 1;
$this->filePos = 0;
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
// This ensures proper composability, because having a newline is the "safe" assumption.
$this->prevCloseTagHasNewline = true;
$scream = ini_set('xdebug.scream', '0'); $scream = ini_set('xdebug.scream', '0');
error_clear_last(); error_clear_last();
$this->tokens = @token_get_all($code); $rawTokens = @token_get_all($code);
$this->handleErrors($errorHandler); $checkForMissingTokens = null !== error_get_last();
if (false !== $scream) { if (false !== $scream) {
ini_set('xdebug.scream', $scream); ini_set('xdebug.scream', $scream);
} }
}
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) { $tokens = [];
for ($i = $start; $i < $end; $i++) { $filePos = 0;
$chr = $this->code[$i]; $line = 1;
if ($chr === 'b' || $chr === 'B') { foreach ($rawTokens as $rawToken) {
// HHVM does not treat b" tokens correctly, so ignore these if (\is_array($rawToken)) {
continue; $token = new Token($this->tokenMap[$rawToken[0]], $rawToken[1], $line, $filePos);
} elseif (\strlen($rawToken) == 2) {
// Bug in token_get_all() when lexing b".
$token = new Token(\ord('"'), $rawToken, $line, $filePos);
} else {
$token = new Token(\ord($rawToken), $rawToken, $line, $filePos);
} }
$value = $token->value;
$tokenLen = \strlen($value);
if ($checkForMissingTokens && substr($code, $filePos, $tokenLen) !== $value) {
// Something is missing, must be an invalid character
$nextFilePos = strpos($code, $value, $filePos);
$badCharTokens = $this->handleInvalidCharacterRange(
$code, $filePos, $nextFilePos, $line, $errorHandler);
$tokens = array_merge($tokens, $badCharTokens);
$filePos = (int) $nextFilePos;
}
$tokens[] = $token;
$filePos += $tokenLen;
$line += substr_count($value, "\n");
}
if ($filePos !== \strlen($code)) {
// Invalid characters at the end of the input
$badCharTokens = $this->handleInvalidCharacterRange(
$code, $filePos, \strlen($code), $line, $errorHandler);
$tokens = array_merge($tokens, $badCharTokens);
}
if (\count($tokens) > 0) {
// Check for unterminated comment
$lastToken = $tokens[\count($tokens) - 1];
if ($this->isUnterminatedComment($lastToken)) {
$errorHandler->handleError(new Error('Unterminated comment', [
'startLine' => $line - substr_count($lastToken->value, "\n"),
'endLine' => $line,
'startFilePos' => $filePos - \strlen($lastToken->value),
'endFilePos' => $filePos,
]));
}
}
// Add an EOF sentinel token
// TODO: Should the value be an empty string instead?
$tokens[] = new Token(0, "\0", $line, \strlen($code));
return $tokens;
}
private function handleInvalidCharacterRange(
string $code, int $start, int $end, int $line, ErrorHandler $errorHandler
) {
$tokens = [];
for ($i = $start; $i < $end; $i++) {
$chr = $code[$i];
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';
@@ -97,6 +141,7 @@ class Lexer
); );
} }
$tokens[] = new Token(Tokens::T_BAD_CHARACTER, $chr, $line, $i);
$errorHandler->handleError(new Error($errorMsg, [ $errorHandler->handleError(new Error($errorMsg, [
'startLine' => $line, 'startLine' => $line,
'endLine' => $line, 'endLine' => $line,
@@ -104,273 +149,27 @@ class Lexer
'endFilePos' => $i, 'endFilePos' => $i,
])); ]));
} }
return $tokens;
} }
/** private function isUnterminatedComment(Token $token): bool {
* Check whether comment token is unterminated. return ($token->id === \T_COMMENT || $token->id === \T_DOC_COMMENT)
* && substr($token->value, 0, 2) === '/*'
* @return bool && substr($token->value, -2) !== '*/';
*/
private function isUnterminatedComment($token) : bool {
return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT)
&& substr($token[1], 0, 2) === '/*'
&& substr($token[1], -2) !== '*/';
} }
/** private function createTokenMap(): array {
* Check whether an error *may* have occurred during tokenization.
*
* @return bool
*/
private function errorMayHaveOccurred() : bool {
if (defined('HHVM_VERSION')) {
// In HHVM token_get_all() does not throw warnings, so we need to conservatively
// assume that an error occurred
return true;
}
return null !== error_get_last();
}
protected function handleErrors(ErrorHandler $errorHandler) {
if (!$this->errorMayHaveOccurred()) {
return;
}
// PHP's error handling for token_get_all() is rather bad, so if we want detailed
// error information we need to compute it ourselves. Invalid character errors are
// detected by finding "gaps" in the token array. Unterminated comments are detected
// by checking if a trailing comment has a "*/" at the end.
$filePos = 0;
$line = 1;
foreach ($this->tokens as $token) {
$tokenValue = \is_string($token) ? $token : $token[1];
$tokenLen = \strlen($tokenValue);
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
// Something is missing, must be an invalid character
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
$this->handleInvalidCharacterRange(
$filePos, $nextFilePos, $line, $errorHandler);
$filePos = (int) $nextFilePos;
}
$filePos += $tokenLen;
$line += substr_count($tokenValue, "\n");
}
if ($filePos !== \strlen($this->code)) {
if (substr($this->code, $filePos, 2) === '/*') {
// Unlike PHP, HHVM will drop unterminated comments entirely
$comment = substr($this->code, $filePos);
$errorHandler->handleError(new Error('Unterminated comment', [
'startLine' => $line,
'endLine' => $line + substr_count($comment, "\n"),
'startFilePos' => $filePos,
'endFilePos' => $filePos + \strlen($comment),
]));
// Emulate the PHP behavior
$isDocComment = isset($comment[3]) && $comment[3] === '*';
$this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line];
} else {
// Invalid characters at the end of the input
$this->handleInvalidCharacterRange(
$filePos, \strlen($this->code), $line, $errorHandler);
}
return;
}
if (count($this->tokens) > 0) {
// Check for unterminated comment
$lastToken = $this->tokens[count($this->tokens) - 1];
if ($this->isUnterminatedComment($lastToken)) {
$errorHandler->handleError(new Error('Unterminated comment', [
'startLine' => $line - substr_count($lastToken[1], "\n"),
'endLine' => $line,
'startFilePos' => $filePos - \strlen($lastToken[1]),
'endFilePos' => $filePos,
]));
}
}
}
/**
* Fetches the next token.
*
* The available attributes are determined by the 'usedAttributes' option, which can
* be specified in the constructor. The following attributes are supported:
*
* * 'comments' => Array of PhpParser\Comment or PhpParser\Comment\Doc instances,
* representing all comments that occurred between the previous
* non-discarded token and the current one.
* * 'startLine' => Line in which the node starts.
* * 'endLine' => Line in which the node ends.
* * 'startTokenPos' => Offset into the token array of the first token in the node.
* * 'endTokenPos' => Offset into the token array of the last token in the node.
* * 'startFilePos' => Offset into the code string of the first character that is part of the node.
* * 'endFilePos' => Offset into the code string of the last character that is part of the node.
*
* @param mixed $value Variable to store token content in
* @param mixed $startAttributes Variable to store start attributes in
* @param mixed $endAttributes Variable to store end attributes in
*
* @return int Token id
*/
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {
$startAttributes = [];
$endAttributes = [];
while (1) {
if (isset($this->tokens[++$this->pos])) {
$token = $this->tokens[$this->pos];
} else {
// EOF token with ID 0
$token = "\0";
}
if (isset($this->usedAttributes['startLine'])) {
$startAttributes['startLine'] = $this->line;
}
if (isset($this->usedAttributes['startTokenPos'])) {
$startAttributes['startTokenPos'] = $this->pos;
}
if (isset($this->usedAttributes['startFilePos'])) {
$startAttributes['startFilePos'] = $this->filePos;
}
if (\is_string($token)) {
$value = $token;
if (isset($token[1])) {
// bug in token_get_all
$this->filePos += 2;
$id = ord('"');
} else {
$this->filePos += 1;
$id = ord($token);
}
} elseif (!isset($this->dropTokens[$token[0]])) {
$value = $token[1];
$id = $this->tokenMap[$token[0]];
if (\T_CLOSE_TAG === $token[0]) {
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n");
} elseif (\T_INLINE_HTML === $token[0]) {
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
}
$this->line += substr_count($value, "\n");
$this->filePos += \strlen($value);
} else {
if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) {
if (isset($this->usedAttributes['comments'])) {
$comment = \T_DOC_COMMENT === $token[0]
? new Comment\Doc($token[1], $this->line, $this->filePos, $this->pos)
: new Comment($token[1], $this->line, $this->filePos, $this->pos);
$startAttributes['comments'][] = $comment;
}
}
$this->line += substr_count($token[1], "\n");
$this->filePos += \strlen($token[1]);
continue;
}
if (isset($this->usedAttributes['endLine'])) {
$endAttributes['endLine'] = $this->line;
}
if (isset($this->usedAttributes['endTokenPos'])) {
$endAttributes['endTokenPos'] = $this->pos;
}
if (isset($this->usedAttributes['endFilePos'])) {
$endAttributes['endFilePos'] = $this->filePos - 1;
}
return $id;
}
throw new \RuntimeException('Reached end of lexer loop');
}
/**
* Returns the token array for current code.
*
* The token array is in the same format as provided by the
* token_get_all() function and does not discard tokens (i.e.
* whitespace and comments are included). The token position
* attributes are against this token array.
*
* @return array Array of tokens in token_get_all() format
*/
public function getTokens() : array {
return $this->tokens;
}
/**
* Handles __halt_compiler() by returning the text after it.
*
* @return string Remaining text
*/
public function handleHaltCompiler() : string {
// text after T_HALT_COMPILER, still including ();
$textAfter = substr($this->code, $this->filePos);
// ensure that it is followed by ();
// this simplifies the situation, by not allowing any comments
// in between of the tokens.
if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
throw new Error('__HALT_COMPILER must be followed by "();"');
}
// prevent the lexer from returning any further tokens
$this->pos = count($this->tokens);
// return with (); removed
return substr($textAfter, strlen($matches[0]));
}
/**
* Creates the token map.
*
* The token map maps the PHP internal token identifiers
* to the identifiers used by the Parser. Additionally it
* maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'.
*
* @return array The token map
*/
protected function createTokenMap() : array {
$tokenMap = []; $tokenMap = [];
// 256 is the minimum possible token number, as everything below
// it is an ASCII value
for ($i = 256; $i < 1000; ++$i) { for ($i = 256; $i < 1000; ++$i) {
if (\T_DOUBLE_COLON === $i) { $name = token_name($i);
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM if ('UNKNOWN' === $name) {
$tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM; continue;
} elseif(\T_OPEN_TAG_WITH_ECHO === $i) { }
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO $constName = Tokens::class . '::' . $name;
$tokenMap[$i] = Tokens::T_ECHO; if (defined($constName)) {
} elseif(\T_CLOSE_TAG === $i) { $tokenMap[$i] = constant($constName);
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[$i] = ord(';');
} elseif ('UNKNOWN' !== $name = token_name($i)) {
if ('T_HASHBANG' === $name) {
// HHVM uses a special token for #! hashbang lines
$tokenMap[$i] = Tokens::T_INLINE_HTML;
} elseif (defined($name = Tokens::class . '::' . $name)) {
// Other tokens can be mapped directly
$tokenMap[$i] = constant($name);
}
} }
}
// HHVM uses a special token for numbers that overflow to double
if (defined('T_ONUMBER')) {
$tokenMap[\T_ONUMBER] = Tokens::T_DNUMBER;
}
// HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
if (defined('T_COMPILER_HALT_OFFSET')) {
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
} }
return $tokenMap; return $tokenMap;

View File

@@ -4,28 +4,56 @@ namespace PhpParser\Lexer;
use PhpParser\Error; use PhpParser\Error;
use PhpParser\ErrorHandler; use PhpParser\ErrorHandler;
use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface;
use PhpParser\Parser\Tokens;
class Emulative extends \PhpParser\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 FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
/** @var mixed[] Patches used to reverse changes introduced in the code */
private $patches = [];
/** @var TokenEmulatorInterface[] */
private $tokenEmulators = [];
/** /**
* @var array Patches used to reverse changes introduced in the code * @param mixed[] $options
*/ */
private $patches; public function __construct(array $options = [])
{
parent::__construct($options);
public function startLexing(string $code, ErrorHandler $errorHandler = null) { $this->tokenEmulators[] = new FnTokenEmulator();
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator();
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator();
}
public function tokenize(string $code, ErrorHandler $errorHandler = null) {
$this->patches = []; $this->patches = [];
$preparedCode = $this->prepareCode($code);
if (null === $preparedCode) { if ($this->isEmulationNeeded($code) === false) {
// Nothing to emulate, yay // Nothing to emulate, yay
parent::startLexing($code, $errorHandler); return parent::tokenize($code, $errorHandler);
return;
} }
$collector = new ErrorHandler\Collecting(); $collector = new ErrorHandler\Collecting();
parent::startLexing($preparedCode, $collector);
$this->fixupTokens(); // 1. emulation of heredoc and nowdoc new syntax
$preparedCode = $this->processHeredocNowdoc($code);
$tokens = parent::tokenize($preparedCode, $collector);
$tokens = $this->fixupTokens($tokens);
$errors = $collector->getErrors(); $errors = $collector->getErrors();
if (!empty($errors)) { if (!empty($errors)) {
@@ -34,32 +62,36 @@ class Emulative extends \PhpParser\Lexer
$errorHandler->handleError($error); $errorHandler->handleError($error);
} }
} }
// add token emulation
foreach ($this->tokenEmulators as $emulativeToken) {
if ($emulativeToken->isEmulationNeeded($code)) {
$tokens = $emulativeToken->emulate($code, $tokens);
}
}
return $tokens;
} }
/** private function isHeredocNowdocEmulationNeeded(string $code): bool
* Prepares code for emulation. If nothing has to be emulated null is returned. {
* // skip version where this works without emulation
* @param string $code
* @return null|string
*/
private function prepareCode(string $code) {
if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) { if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) {
return null; return false;
} }
if (strpos($code, '<<<') === false) { return strpos($code, '<<<') !== false;
// Definitely doesn't contain heredoc/nowdoc }
return null;
private function processHeredocNowdoc(string $code): string
{
if ($this->isHeredocNowdocEmulationNeeded($code) === false) {
return $code;
} }
$flexibleDocStringRegex = <<<'REGEX' if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
if (!preg_match_all($flexibleDocStringRegex, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
// No heredoc/nowdoc found // No heredoc/nowdoc found
return null; return $code;
} }
// Keep track of how much we need to adjust string offsets due to the modifications we // Keep track of how much we need to adjust string offsets due to the modifications we
@@ -93,52 +125,56 @@ REGEX;
} }
} }
if (empty($this->patches)) {
// We did not end up emulating anything
return null;
}
return $code; return $code;
} }
private function fixupTokens() { private function isEmulationNeeded(string $code): bool
assert(count($this->patches) > 0); {
foreach ($this->tokenEmulators as $emulativeToken) {
if ($emulativeToken->isEmulationNeeded($code)) {
return true;
}
}
return $this->isHeredocNowdocEmulationNeeded($code);
}
private function fixupTokens(array $tokens): array
{
if (\count($this->patches) === 0) {
return $tokens;
}
// Load first patch // Load first patch
$patchIdx = 0; $patchIdx = 0;
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// We use a manual loop over the tokens, because we modify the array on the fly // We use a manual loop over the tokens, because we modify the array on the fly
$pos = 0; $pos = 0;
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) { for ($i = 0, $c = \count($tokens); $i < $c; $i++) {
$token = $this->tokens[$i]; $token = $tokens[$i];
if (\is_string($token)) { $len = \strlen($token->value);
// We assume that patches don't apply to string tokens
$pos += \strlen($token);
continue;
}
$len = \strlen($token[1]);
$posDelta = 0; $posDelta = 0;
while ($patchPos >= $pos && $patchPos < $pos + $len) { while ($patchPos >= $pos && $patchPos < $pos + $len) {
$patchTextLen = \strlen($patchText); $patchTextLen = \strlen($patchText);
if ($patchType === 'remove') { if ($patchType === 'remove') {
if ($patchPos === $pos && $patchTextLen === $len) { if ($patchPos === $pos && $patchTextLen === $len) {
// Remove token entirely // Remove token entirely
array_splice($this->tokens, $i, 1, []); array_splice($tokens, $i, 1, []);
$i--; $i--;
$c--; $c--;
} else { } else {
// Remove from token string // Remove from token string
$this->tokens[$i][1] = substr_replace( $tokens[$i]->value = substr_replace(
$token[1], '', $patchPos - $pos + $posDelta, $patchTextLen $token->value, '', $patchPos - $pos + $posDelta, $patchTextLen
); );
$posDelta -= $patchTextLen; $posDelta -= $patchTextLen;
} }
} elseif ($patchType === 'add') { } elseif ($patchType === 'add') {
// Insert into the token string // Insert into the token string
$this->tokens[$i][1] = substr_replace( $tokens[$i]->value = substr_replace(
$token[1], $patchText, $patchPos - $pos + $posDelta, 0 $token->value, $patchText, $patchPos - $pos + $posDelta, 0
); );
$posDelta += $patchTextLen; $posDelta += $patchTextLen;
} else { } else {
@@ -149,21 +185,30 @@ REGEX;
$patchIdx++; $patchIdx++;
if ($patchIdx >= \count($this->patches)) { if ($patchIdx >= \count($this->patches)) {
// No more patches, we're done // No more patches, we're done
return; break 2;
} }
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx]; list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// Multiple patches may apply to the same token. Reload the current one to check // Multiple patches may apply to the same token. Reload the current one to check
// If the new patch applies // If the new patch applies
$token = $this->tokens[$i]; $token = $tokens[$i];
} }
$pos += $len; $pos += $len;
} }
// A patch did not apply // To retain a minimum amount of sanity, recompute lines and offsets in a separate loop.
assert(false); $pos = 0;
$line = 1;
foreach ($tokens as $token) {
$token->filePos = $pos;
$token->line = $line;
$pos += \strlen($token->value);
$line += \substr_count($token->value, "\n");
}
return $tokens;
} }
/** /**
@@ -200,4 +245,4 @@ REGEX;
$error->setAttributes($attrs); $error->setAttributes($attrs);
} }
} }
} }

View File

@@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Tokens;
use PhpParser\Token;
final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
{
public function isEmulationNeeded(string $code) : bool
{
// skip version where this is supported
if (version_compare(\PHP_VERSION, Emulative::PHP_7_4, '>=')) {
return false;
}
return strpos($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
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if (isset($tokens[$i + 1])) {
$token = $tokens[$i];
if ($token->id === Tokens::T_COALESCE && $tokens[$i + 1]->value === '=') {
array_splice($tokens, $i, 2, [
new Token(Tokens::T_COALESCE_EQUAL, '??=', $token->line, $token->filePos),
]);
$c--;
continue;
}
}
}
return $tokens;
}
}

View File

@@ -0,0 +1,56 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Tokens;
use PhpParser\Token;
final class FnTokenEmulator implements TokenEmulatorInterface
{
public function isEmulationNeeded(string $code) : bool
{
// skip version where this is supported
if (version_compare(\PHP_VERSION, Emulative::PHP_7_4, '>=')) {
return false;
}
return strpos($code, 'fn') !== 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
foreach ($tokens as $i => $token) {
if ($token->id === Tokens::T_STRING && $token->value === 'fn') {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i);
if ($previousNonSpaceToken !== null
&& $previousNonSpaceToken->id === Tokens::T_OBJECT_OPERATOR) {
continue;
}
$token->id = Tokens::T_FN;
}
}
return $tokens;
}
/**
* @param Token[] $tokens
* @return Token|null
*/
private function getPreviousNonSpaceToken(array $tokens, int $start)
{
for ($i = $start - 1; $i >= 0; --$i) {
if ($tokens[$i]->id === Tokens::T_WHITESPACE) {
continue;
}
return $tokens[$i];
}
return null;
}
}

View File

@@ -0,0 +1,100 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Tokens;
use PhpParser\Token;
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;
}
/**
* @param Token[] $tokens
* @return Token[]
*/
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
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
$tokenLen = \strlen($token->value);
if ($token->id !== Tokens::T_LNUMBER && $token->id !== Tokens::T_DNUMBER) {
continue;
}
$res = preg_match(self::NUMBER, $code, $matches, 0, $token->filePos);
assert($res, "No number at number token position");
$match = $matches[0];
$matchLen = \strlen($match);
if ($matchLen === $tokenLen) {
// Original token already holds the full number.
continue;
}
$tokenKind = $this->resolveIntegerOrFloatToken($match);
$newTokens = [new Token($tokenKind, $match, $token->line, $token->filePos)];
$numTokens = 1;
$len = $tokenLen;
while ($matchLen > $len) {
$nextToken = $tokens[$i + $numTokens];
$nextTokenLen = \strlen($nextToken->value);
$numTokens++;
if ($matchLen < $len + $nextTokenLen) {
// Split trailing characters into a partial token.
$partialText = substr($nextToken->value, $matchLen - $len);
$newTokens[] = new Token(
$nextToken->id, $partialText, $nextToken->line, $nextToken->filePos
);
break;
}
$len += $nextTokenLen;
}
array_splice($tokens, $i, $numTokens, $newTokens);
$c -= $numTokens - \count($newTokens);
}
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) ? Tokens::T_DNUMBER : Tokens::T_LNUMBER;
}
}

View File

@@ -0,0 +1,17 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Token;
/** @internal */
interface TokenEmulatorInterface
{
public function isEmulationNeeded(string $code): bool;
/**
* @param Token[] $tokens
* @return Token[]
*/
public function emulate(string $code, array $tokens): array;
}

View File

@@ -22,7 +22,7 @@ class Arg extends NodeAbstract
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = []) { public function __construct(Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->value = $value; $this->value = $value;
$this->byRef = $byRef; $this->byRef = $byRef;
$this->unpack = $unpack; $this->unpack = $unpack;

View File

@@ -22,7 +22,7 @@ class Const_ extends NodeAbstract
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($name, Expr $value, array $attributes = []) { public function __construct($name, Expr $value, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->name = \is_string($name) ? new Identifier($name) : $name; $this->name = \is_string($name) ? new Identifier($name) : $name;
$this->value = $value; $this->value = $value;
} }

View File

@@ -19,7 +19,7 @@ class ArrayDimFetch extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, Expr $dim = null, array $attributes = []) { public function __construct(Expr $var, Expr $dim = null, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
$this->dim = $dim; $this->dim = $dim;
} }

View File

@@ -12,6 +12,8 @@ class ArrayItem extends Expr
public $value; public $value;
/** @var bool Whether to assign by reference */ /** @var bool Whether to assign by reference */
public $byRef; public $byRef;
/** @var bool Whether to unpack the argument */
public $unpack;
/** /**
* Constructs an array item node. * Constructs an array item node.
@@ -21,17 +23,18 @@ class ArrayItem extends Expr
* @param bool $byRef Whether to assign by reference * @param bool $byRef Whether to assign by reference
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = []) { public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->key = $key; $this->key = $key;
$this->value = $value; $this->value = $value;
$this->byRef = $byRef; $this->byRef = $byRef;
$this->unpack = $unpack;
} }
public function getSubNodeNames() : array { public function getSubNodeNames() : array {
return ['key', 'value', 'byRef']; return ['key', 'value', 'byRef', 'unpack'];
} }
public function getType() : string { public function getType() : string {
return 'Expr_ArrayItem'; return 'Expr_ArrayItem';
} }

View File

@@ -20,7 +20,7 @@ class Array_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $items = [], array $attributes = []) { public function __construct(array $items = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->items = $items; $this->items = $items;
} }

View File

@@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;
class ArrowFunction extends Expr implements FunctionLike
{
/** @var bool */
public $static;
/** @var bool */
public $byRef;
/** @var Node\Param[] */
public $params = [];
/** @var null|Node\Identifier|Node\Name|Node\NullableType */
public $returnType;
/** @var Expr */
public $expr;
/**
* @param array $subNodes Array of the following optional subnodes:
* 'static' => false : Whether the closure is static
* 'byRef' => false : Whether to return by reference
* 'params' => array() : Parameters
* 'returnType' => null : Return type
* 'expr' => Expr : Expression body
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->static = $subNodes['static'] ?? false;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->expr = $subNodes['expr'] ?? null;
}
public function getSubNodeNames() : array {
return ['static', 'byRef', 'params', 'returnType', 'expr'];
}
public function returnsByRef() : bool {
return $this->byRef;
}
public function getParams() : array {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
/**
* @return Node\Stmt\Return_[]
*/
public function getStmts() : array {
return [new Node\Stmt\Return_($this->expr)];
}
public function getType() : string {
return 'Expr_ArrowFunction';
}
}

View File

@@ -19,7 +19,7 @@ class Assign extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, Expr $expr, array $attributes = []) { public function __construct(Expr $var, Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -19,7 +19,7 @@ abstract class AssignOp extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, Expr $expr, array $attributes = []) { public function __construct(Expr $var, Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Coalesce extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Coalesce';
}
}

View File

@@ -19,7 +19,7 @@ class AssignRef extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, Expr $expr, array $attributes = []) { public function __construct(Expr $var, Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -19,7 +19,7 @@ abstract class BinaryOp extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $left, Expr $right, array $attributes = []) { public function __construct(Expr $left, Expr $right, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->left = $left; $this->left = $left;
$this->right = $right; $this->right = $right;
} }

View File

@@ -16,7 +16,7 @@ class BitwiseNot extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -16,7 +16,7 @@ class BooleanNot extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -16,7 +16,7 @@ abstract class Cast extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -6,6 +6,11 @@ use PhpParser\Node\Expr\Cast;
class Double extends Cast class Double extends Cast
{ {
// For use in "kind" attribute
const KIND_DOUBLE = 1; // "double" syntax
const KIND_FLOAT = 2; // "float" syntax
const KIND_REAL = 3; // "real" syntax
public function getType() : string { public function getType() : string {
return 'Expr_Cast_Double'; return 'Expr_Cast_Double';
} }

View File

@@ -21,7 +21,7 @@ class ClassConstFetch extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($class, $name, array $attributes = []) { public function __construct($class, $name, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->class = $class; $this->class = $class;
$this->name = \is_string($name) ? new Identifier($name) : $name; $this->name = \is_string($name) ? new Identifier($name) : $name;
} }

View File

@@ -16,7 +16,7 @@ class Clone_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -34,7 +34,7 @@ class Closure extends Expr implements FunctionLike
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $subNodes = [], array $attributes = []) { public function __construct(array $subNodes = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->static = $subNodes['static'] ?? false; $this->static = $subNodes['static'] ?? false;
$this->byRef = $subNodes['byRef'] ?? false; $this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? []; $this->params = $subNodes['params'] ?? [];

View File

@@ -19,7 +19,7 @@ class ClosureUse extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) { public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
$this->byRef = $byRef; $this->byRef = $byRef;
} }

View File

@@ -17,7 +17,7 @@ class ConstFetch extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Name $name, array $attributes = []) { public function __construct(Name $name, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->name = $name; $this->name = $name;
} }

View File

@@ -16,7 +16,7 @@ class Empty_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -18,7 +18,7 @@ class Error extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $attributes = []) { public function __construct(array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
} }
public function getSubNodeNames() : array { public function getSubNodeNames() : array {

View File

@@ -16,7 +16,7 @@ class ErrorSuppress extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -16,7 +16,7 @@ class Eval_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -20,7 +20,7 @@ class Exit_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr = null, array $attributes = []) { public function __construct(Expr $expr = null, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -20,7 +20,7 @@ class FuncCall extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($name, array $args = [], array $attributes = []) { public function __construct($name, array $args = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->name = $name; $this->name = $name;
$this->args = $args; $this->args = $args;
} }

View File

@@ -24,7 +24,7 @@ class Include_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, int $type, array $attributes = []) { public function __construct(Expr $expr, int $type, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
$this->type = $type; $this->type = $type;
} }

View File

@@ -20,7 +20,7 @@ class Instanceof_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, $class, array $attributes = []) { public function __construct(Expr $expr, $class, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
$this->class = $class; $this->class = $class;
} }

View File

@@ -16,7 +16,7 @@ class Isset_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $vars, array $attributes = []) { public function __construct(array $vars, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->vars = $vars; $this->vars = $vars;
} }

View File

@@ -16,7 +16,7 @@ class List_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $items, array $attributes = []) { public function __construct(array $items, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->items = $items; $this->items = $items;
} }

View File

@@ -24,7 +24,7 @@ class MethodCall extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) { public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name; $this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args; $this->args = $args;

View File

@@ -20,7 +20,7 @@ class New_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($class, array $args = [], array $attributes = []) { public function __construct($class, array $args = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->class = $class; $this->class = $class;
$this->args = $args; $this->args = $args;
} }

View File

@@ -16,7 +16,7 @@ class PostDec extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, array $attributes = []) { public function __construct(Expr $var, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
} }

View File

@@ -16,7 +16,7 @@ class PostInc extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, array $attributes = []) { public function __construct(Expr $var, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
} }

View File

@@ -16,7 +16,7 @@ class PreDec extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, array $attributes = []) { public function __construct(Expr $var, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
} }

View File

@@ -16,7 +16,7 @@ class PreInc extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, array $attributes = []) { public function __construct(Expr $var, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
} }

View File

@@ -16,7 +16,7 @@ class Print_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -20,7 +20,7 @@ class PropertyFetch extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $var, $name, array $attributes = []) { public function __construct(Expr $var, $name, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->var = $var; $this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name; $this->name = \is_string($name) ? new Identifier($name) : $name;
} }

View File

@@ -16,7 +16,7 @@ class ShellExec extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $parts, array $attributes = []) { public function __construct(array $parts, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->parts = $parts; $this->parts = $parts;
} }

View File

@@ -24,7 +24,7 @@ class StaticCall extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($class, $name, array $args = [], array $attributes = []) { public function __construct($class, $name, array $args = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->class = $class; $this->class = $class;
$this->name = \is_string($name) ? new Identifier($name) : $name; $this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args; $this->args = $args;

View File

@@ -21,7 +21,7 @@ class StaticPropertyFetch extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($class, $name, array $attributes = []) { public function __construct($class, $name, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->class = $class; $this->class = $class;
$this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name; $this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name;
} }

View File

@@ -22,7 +22,7 @@ class Ternary extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $cond, $if, Expr $else, array $attributes = []) { public function __construct(Expr $cond, $if, Expr $else, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->cond = $cond; $this->cond = $cond;
$this->if = $if; $this->if = $if;
$this->else = $else; $this->else = $else;

View File

@@ -16,7 +16,7 @@ class UnaryMinus extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -16,7 +16,7 @@ class UnaryPlus extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -16,7 +16,7 @@ class Variable extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($name, array $attributes = []) { public function __construct($name, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->name = $name; $this->name = $name;
} }

View File

@@ -16,7 +16,7 @@ class YieldFrom extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $expr, array $attributes = []) { public function __construct(Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -19,7 +19,7 @@ class Yield_ extends Expr
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Expr $value = null, Expr $key = null, array $attributes = []) { public function __construct(Expr $value = null, Expr $key = null, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->key = $key; $this->key = $key;
$this->value = $value; $this->value = $value;
} }

View File

@@ -25,7 +25,7 @@ class Identifier extends NodeAbstract
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(string $name, array $attributes = []) { public function __construct(string $name, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->name = $name; $this->name = $name;
} }

View File

@@ -6,9 +6,7 @@ use PhpParser\NodeAbstract;
class Name extends NodeAbstract class Name extends NodeAbstract
{ {
/** /** @var string[] Parts of the name */
* @var string[] Parts of the name
*/
public $parts; public $parts;
private static $specialClassNames = [ private static $specialClassNames = [
@@ -24,7 +22,7 @@ class Name extends NodeAbstract
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($name, array $attributes = []) { public function __construct($name, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->parts = self::prepareName($name); $this->parts = self::prepareName($name);
} }
@@ -237,7 +235,7 @@ class Name extends NodeAbstract
'Expected string, array of parts or Name instance' 'Expected string, array of parts or Name instance'
); );
} }
public function getType() : string { public function getType() : string {
return 'Name'; return 'Name';
} }

View File

@@ -16,7 +16,7 @@ class NullableType extends NodeAbstract
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($type, array $attributes = []) { public function __construct($type, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type; $this->type = \is_string($type) ? new Identifier($type) : $type;
} }

View File

@@ -6,7 +6,7 @@ use PhpParser\NodeAbstract;
class Param extends NodeAbstract class Param extends NodeAbstract
{ {
/** @var null|Identifier|Name|NullableType Typehint */ /** @var null|Identifier|Name|NullableType Type declaration */
public $type; public $type;
/** @var bool Whether parameter is passed by reference */ /** @var bool Whether parameter is passed by reference */
public $byRef; public $byRef;
@@ -20,18 +20,18 @@ class Param extends NodeAbstract
/** /**
* Constructs a parameter node. * Constructs a parameter node.
* *
* @param Expr\Variable|Expr\Error $var Parameter variable * @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value * @param null|Expr $default Default value
* @param null|string|Name|NullableType $type Typehint * @param null|string|Identifier|Name|NullableType $type Type declaration
* @param bool $byRef Whether is passed by reference * @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument * @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct( public function __construct(
$var, Expr $default = null, $type = null, $var, Expr $default = null, $type = null,
bool $byRef = false, bool $variadic = false, array $attributes = [] bool $byRef = false, bool $variadic = false, array $attributes = []
) { ) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type; $this->type = \is_string($type) ? new Identifier($type) : $type;
$this->byRef = $byRef; $this->byRef = $byRef;
$this->variadic = $variadic; $this->variadic = $variadic;
@@ -42,7 +42,7 @@ class Param extends NodeAbstract
public function getSubNodeNames() : array { public function getSubNodeNames() : array {
return ['type', 'byRef', 'variadic', 'var', 'default']; return ['type', 'byRef', 'variadic', 'var', 'default'];
} }
public function getType() : string { public function getType() : string {
return 'Param'; return 'Param';
} }

View File

@@ -16,7 +16,7 @@ class DNumber extends Scalar
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(float $value, array $attributes = []) { public function __construct(float $value, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->value = $value; $this->value = $value;
} }
@@ -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

@@ -17,7 +17,7 @@ class Encapsed extends Scalar
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $parts, array $attributes = []) { public function __construct(array $parts, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->parts = $parts; $this->parts = $parts;
} }

View File

@@ -16,7 +16,7 @@ class EncapsedStringPart extends Scalar
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(string $value, array $attributes = []) { public function __construct(string $value, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->value = $value; $this->value = $value;
} }

View File

@@ -23,7 +23,7 @@ class LNumber extends Scalar
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(int $value, array $attributes = []) { public function __construct(int $value, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->value = $value; $this->value = $value;
} }
@@ -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

@@ -12,7 +12,7 @@ abstract class MagicConst extends Scalar
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $attributes = []) { public function __construct(array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
} }
public function getSubNodeNames() : array { public function getSubNodeNames() : array {

View File

@@ -34,7 +34,7 @@ class String_ extends Scalar
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(string $value, array $attributes = []) { public function __construct(string $value, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->value = $value; $this->value = $value;
} }

View File

@@ -16,7 +16,7 @@ class Break_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Node\Expr $num = null, array $attributes = []) { public function __construct(Node\Expr $num = null, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->num = $num; $this->num = $num;
} }

View File

@@ -6,7 +6,7 @@ use PhpParser\Node;
class Case_ extends Node\Stmt class Case_ extends Node\Stmt
{ {
/** @var null|Node\Expr $cond Condition (null for default) */ /** @var null|Node\Expr Condition (null for default) */
public $cond; public $cond;
/** @var Node\Stmt[] Statements */ /** @var Node\Stmt[] Statements */
public $stmts; public $stmts;
@@ -19,7 +19,7 @@ class Case_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($cond, array $stmts = [], array $attributes = []) { public function __construct($cond, array $stmts = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->cond = $cond; $this->cond = $cond;
$this->stmts = $stmts; $this->stmts = $stmts;
} }

View File

@@ -25,7 +25,7 @@ class Catch_ extends Node\Stmt
public function __construct( public function __construct(
array $types, Expr\Variable $var, array $stmts = [], array $attributes = [] array $types, Expr\Variable $var, array $stmts = [], array $attributes = []
) { ) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->types = $types; $this->types = $types;
$this->var = $var; $this->var = $var;
$this->stmts = $stmts; $this->stmts = $stmts;

View File

@@ -19,7 +19,7 @@ class ClassConst extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $consts, int $flags = 0, array $attributes = []) { public function __construct(array $consts, int $flags = 0, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->flags = $flags; $this->flags = $flags;
$this->consts = $consts; $this->consts = $consts;
} }

View File

@@ -51,7 +51,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($name, array $subNodes = [], array $attributes = []) { public function __construct($name, array $subNodes = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0;
$this->byRef = $subNodes['byRef'] ?? false; $this->byRef = $subNodes['byRef'] ?? false;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name; $this->name = \is_string($name) ? new Node\Identifier($name) : $name;

View File

@@ -35,7 +35,7 @@ class Class_ extends ClassLike
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($name, array $subNodes = [], array $attributes = []) { public function __construct($name, array $subNodes = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0; $this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name; $this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->extends = $subNodes['extends'] ?? null; $this->extends = $subNodes['extends'] ?? null;

View File

@@ -16,7 +16,7 @@ class Const_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $consts, array $attributes = []) { public function __construct(array $consts, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->consts = $consts; $this->consts = $consts;
} }

View File

@@ -16,7 +16,7 @@ class Continue_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Node\Expr $num = null, array $attributes = []) { public function __construct(Node\Expr $num = null, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->num = $num; $this->num = $num;
} }

View File

@@ -19,7 +19,7 @@ class DeclareDeclare extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct($key, Node\Expr $value, array $attributes = []) { public function __construct($key, Node\Expr $value, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->key = \is_string($key) ? new Node\Identifier($key) : $key; $this->key = \is_string($key) ? new Node\Identifier($key) : $key;
$this->value = $value; $this->value = $value;
} }

View File

@@ -19,7 +19,7 @@ class Declare_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $declares, array $stmts = null, array $attributes = []) { public function __construct(array $declares, array $stmts = null, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->declares = $declares; $this->declares = $declares;
$this->stmts = $stmts; $this->stmts = $stmts;
} }

View File

@@ -19,7 +19,7 @@ class Do_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->cond = $cond; $this->cond = $cond;
$this->stmts = $stmts; $this->stmts = $stmts;
} }

View File

@@ -16,7 +16,7 @@ class Echo_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $exprs, array $attributes = []) { public function __construct(array $exprs, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->exprs = $exprs; $this->exprs = $exprs;
} }

View File

@@ -19,7 +19,7 @@ class ElseIf_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) { public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->cond = $cond; $this->cond = $cond;
$this->stmts = $stmts; $this->stmts = $stmts;
} }

View File

@@ -16,7 +16,7 @@ class Else_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $stmts = [], array $attributes = []) { public function __construct(array $stmts = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->stmts = $stmts; $this->stmts = $stmts;
} }

View File

@@ -19,7 +19,7 @@ class Expression extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Node\Expr $expr, array $attributes = []) { public function __construct(Node\Expr $expr, array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
} }

View File

@@ -16,7 +16,7 @@ class Finally_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $stmts = [], array $attributes = []) { public function __construct(array $stmts = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->stmts = $stmts; $this->stmts = $stmts;
} }

View File

@@ -26,7 +26,7 @@ class For_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(array $subNodes = [], array $attributes = []) { public function __construct(array $subNodes = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->init = $subNodes['init'] ?? []; $this->init = $subNodes['init'] ?? [];
$this->cond = $subNodes['cond'] ?? []; $this->cond = $subNodes['cond'] ?? [];
$this->loop = $subNodes['loop'] ?? []; $this->loop = $subNodes['loop'] ?? [];

View File

@@ -29,7 +29,7 @@ class Foreach_ extends Node\Stmt
* @param array $attributes Additional attributes * @param array $attributes Additional attributes
*/ */
public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) { public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) {
parent::__construct($attributes); $this->attributes = $attributes;
$this->expr = $expr; $this->expr = $expr;
$this->keyVar = $subNodes['keyVar'] ?? null; $this->keyVar = $subNodes['keyVar'] ?? null;
$this->byRef = $subNodes['byRef'] ?? false; $this->byRef = $subNodes['byRef'] ?? false;

Some files were not shown because too many files have changed in this diff Show More