mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-06-21 07:21:24 +02:00
Compare commits
202 Commits
v4.0.0beta
...
v4.8.0
Author | SHA1 | Date | |
---|---|---|---|
8c58eb4cd4 | |||
23d9c17770 | |||
31be7b4ed9 | |||
f6bf041583 | |||
1bf073a76c | |||
1721ae155c | |||
54fc023b25 | |||
f845568e1d | |||
b4ca4ce0fe | |||
303342630d | |||
1a1300aa2a | |||
3698f4b727 | |||
7b2ec6703f | |||
61328f89da | |||
21dce06dfb | |||
acaf3fecad | |||
a63b495fe6 | |||
2d1998938c | |||
739b4b4c60 | |||
17f478107a | |||
cd7c058e76 | |||
d8b19d7963 | |||
69c5d48afd | |||
6ec527bce7 | |||
fc25609ecb | |||
f545f18a87 | |||
c346bbfafe | |||
4abc531213 | |||
b58b19ed1d | |||
0d2d8f95a1 | |||
244db65dd1 | |||
53c2753d75 | |||
b5f5313d73 | |||
32f89662f3 | |||
f33f081c8f | |||
bd43ec7152 | |||
d86ca0f745 | |||
c8d1216531 | |||
8999a13cb2 | |||
c1eaa0d5cd | |||
ca5f7c9761 | |||
ad696ee75d | |||
6770deda3d | |||
568236a305 | |||
9dda080a9d | |||
3ec87ef757 | |||
a2443aaefa | |||
64f4d5b619 | |||
f56d90d4f8 | |||
40aa2282df | |||
46cbd9393a | |||
602af9060d | |||
bf086d9833 | |||
521addec91 | |||
88f3a669c1 | |||
ba9cf39999 | |||
f862853987 | |||
9a9981c347 | |||
2c42f64475 | |||
664c10121e | |||
5b1cd2e4f2 | |||
b76bbc3c51 | |||
eacc5dbe19 | |||
3226eb4086 | |||
54c37f6b3b | |||
0a80b2d8ee | |||
2f45e05042 | |||
69c105dde1 | |||
603203177e | |||
97e59c7a16 | |||
006acba066 | |||
005bb1dba7 | |||
40e7b67d69 | |||
5644a916bc | |||
e612609022 | |||
4fd36b9946 | |||
a1f72690ef | |||
2e2954ccdf | |||
3f718ee2c3 | |||
b9b45dd2bc | |||
a4b43edb03 | |||
3cf61fdd26 | |||
9484baf8f8 | |||
aad0e2896f | |||
624f71fa6f | |||
1bd73cc04c | |||
94d93f27a5 | |||
a167aa2061 | |||
993f29906b | |||
9d44edf85d | |||
aa97a9bb69 | |||
aa72c5d674 | |||
60d025a914 | |||
b2cecec6bc | |||
8012faea54 | |||
f3b19c19ef | |||
78d9985d11 | |||
57b8673ea7 | |||
5221f49a60 | |||
ae4e90d558 | |||
9de96821f7 | |||
b7e6361536 | |||
594bcae1fc | |||
d5180f0d95 | |||
1f95f9215c | |||
6b9dd7afe7 | |||
ba092652fe | |||
ec0d834c5f | |||
ea3657fc5f | |||
af8c729603 | |||
0ef61b49bb | |||
90ee36a7fc | |||
8aae5b59b8 | |||
49d73e829f | |||
80ead71da2 | |||
294b93fbca | |||
bc3ac5e5ea | |||
382ca0128f | |||
f42bbc2403 | |||
ebf6b1c33b | |||
a74b54ce8b | |||
8e4f972036 | |||
3d0f7843d4 | |||
69068722b7 | |||
fb3d89e463 | |||
d0230c5c77 | |||
dc323458b4 | |||
674c5610fb | |||
4fb48c0e18 | |||
d638dd9b25 | |||
c5316487a4 | |||
eebaa94647 | |||
0ed9065b4c | |||
5f73c4de80 | |||
146411bb86 | |||
e12891a9db | |||
a5c726bfbf | |||
fd93690705 | |||
83766c8c0e | |||
fa6ee28600 | |||
16c5d107ca | |||
d7d581c1d2 | |||
1973b6633a | |||
7f96481c80 | |||
41408081d7 | |||
4d6825c460 | |||
d4a5a6e84b | |||
c55798ccc6 | |||
82e8b33814 | |||
8d9ae46597 | |||
f6fc248ca3 | |||
84dcd179d2 | |||
3e02eb7108 | |||
2f67429ec9 | |||
0cd7207ca6 | |||
6751ac3c9d | |||
5aeb884ab5 | |||
b855c1ca23 | |||
53c732a96d | |||
bd088dc940 | |||
8b1c9c0409 | |||
df64d86cf4 | |||
aac539ef0a | |||
f967b867d5 | |||
6bbdaf6c16 | |||
35b8caf75e | |||
6526ea2497 | |||
3ce5e628a7 | |||
ee870e3464 | |||
21d27527ed | |||
7b201b63d2 | |||
01e85a26c6 | |||
e6452e8d15 | |||
9202d87f53 | |||
fa99c3fbfb | |||
2b0bd657bb | |||
81b7be3ba6 | |||
6121001576 | |||
7208b1c7ac | |||
e4a54fa90a | |||
3125b54e5e | |||
7f8ff1b9a4 | |||
91a462ce76 | |||
610617fe93 | |||
ff2d85dc6b | |||
b998d1e9b5 | |||
6aba7624ed | |||
9c18e3db49 | |||
ae52aadb43 | |||
9cea94000a | |||
bb87e28e7d | |||
7484acb88b | |||
bc5ba47b28 | |||
b9996315a6 | |||
de3470190c | |||
1c3eabb000 | |||
8d1e86b47f | |||
ff10cc9d08 | |||
14454326e9 | |||
3a45c1a121 | |||
ad9c42b66a | |||
08215e7646 |
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/doc export-ignore
|
||||
/test export-ignore
|
||||
/test_old export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
UPGRADE-*.md export-ignore
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ vendor/
|
||||
composer.lock
|
||||
grammar/kmyacc.exe
|
||||
grammar/y.output
|
||||
.phpunit.result.cache
|
||||
|
14
.travis.yml
14
.travis.yml
@ -1,6 +1,5 @@
|
||||
language: php
|
||||
dist: trusty
|
||||
sudo: false
|
||||
dist: xenial
|
||||
|
||||
cache:
|
||||
directories:
|
||||
@ -10,11 +9,18 @@ php:
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4
|
||||
- nightly
|
||||
|
||||
install:
|
||||
- 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:
|
||||
allow_failures:
|
||||
@ -23,7 +29,7 @@ matrix:
|
||||
|
||||
script:
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then vendor/bin/phpunit --coverage-clover build/logs/clover.xml; else vendor/bin/phpunit; fi
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.1' ]; then test_old/run-php-src.sh; fi
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.2' ]; then test_old/run-php-src.sh; fi
|
||||
|
||||
after_success:
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then php vendor/bin/coveralls; fi
|
||||
|
257
CHANGELOG.md
257
CHANGELOG.md
@ -1,7 +1,251 @@
|
||||
Version 4.0.0-dev
|
||||
Version 4.8.1-dev
|
||||
-----------------
|
||||
|
||||
Nothing yet.
|
||||
Version 4.8.0 (2020-08-09)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.0] Added support for nullsafe operator, represented using the new
|
||||
`Expr\NullsafePropertyFetch` and `Expr\NullsafeMethodCall` nodes.
|
||||
* Added `phpVersion` option to the emulative lexer, which allows controlling the target version to
|
||||
emulate (defaults to the latest available, currently PHP 8.0). This is useful to parse code that
|
||||
uses reserved keywords from newer PHP versions as identifiers.
|
||||
|
||||
Version 4.7.0 (2020-07-25)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Add `ParentConnectingVisitor` and `NodeConnectingVisitor` classes.
|
||||
* [PHP 8.0] Added support for match expressions. These are represented using a new `Expr\Match_`
|
||||
containing `MatchArm`s.
|
||||
* [PHP 8.0] Added support for trailing comma in closure use lists.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed missing error for unterminated comment with trailing newline (#688).
|
||||
* Compatibility with PHP 8.0 has been restored: Namespaced names are now always represented by
|
||||
`T_NAME_*` tokens, using emulationg on older PHP versions. Full support for reserved keywords
|
||||
in namespaced names is not yet present.
|
||||
|
||||
Version 4.6.0 (2020-07-02)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.0] Added support for trailing commas in parameter lists.
|
||||
* [PHP 8.0] Added support for constructor promotion. The parameter visibility is stored in
|
||||
`Node\Param::$flags`.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Comment tokens now always follow the PHP 8 interpretation, and do not include trailing
|
||||
whitespace.
|
||||
* As a result of the previous change, some whitespace issues when inserting a statement into a
|
||||
method containing only a comment, and using the formatting-preserving pretty printer, have been
|
||||
resolved.
|
||||
|
||||
Version 4.5.0 (2020-06-03)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.0] Added support for the mixed type. This means `mixed` types are now parsed as an
|
||||
`Identifier` rather than a `Name`.
|
||||
* [PHP 8.0] Added support for catching without capturing the exception. This means that
|
||||
`Catch_::$var` may now be null.
|
||||
|
||||
Version 4.4.0 (2020-04-10)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for passing union types in builders.
|
||||
* Added end line, token position and file position information for comments.
|
||||
* Added `getProperty()` method to `ClassLike` nodes.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed generation of invalid code when using the formatting preserving pretty printer, and
|
||||
inserting code next to certain nop statements. The formatting is still ugly though.
|
||||
* `getDocComment()` no longer requires that the very last comment before a node be a doc comment.
|
||||
There may not be non-doc comments between the doc comment and the declaration.
|
||||
* Allowed arbitrary expressions in `isset()` and `list()`, rather than just variables.
|
||||
In particular, this allows `isset(($x))`, which is legal PHP code.
|
||||
* [PHP 8.0] Add support for [variable syntax tweaks RFC](https://wiki.php.net/rfc/variable_syntax_tweaks).
|
||||
|
||||
Version 4.3.0 (2019-11-08)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 8.0] Added support for union types using a new `UnionType` node.
|
||||
|
||||
Version 4.2.5 (2019-10-25)
|
||||
--------------------------
|
||||
|
||||
### Changed
|
||||
|
||||
* Tests and documentation are no longer included in source archives. They can still be accessed
|
||||
by cloning the repository.
|
||||
* php-yacc is now used to generate the parser. This has no impact on users of the library.
|
||||
|
||||
Version 4.2.4 (2019-09-01)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added getProperties(), getConstants() and getTraitUses() to ClassLike. (#629, #630)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed flexible heredoc emulation to check for digits after the end label. This synchronizes
|
||||
behavior with the upcoming PHP 7.3.10 release.
|
||||
|
||||
Version 4.2.3 (2019-08-12)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 7.4] Add support for numeric literal separators. (#615)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed resolution of return types for arrow functions. (#613)
|
||||
* Fixed compatibility with PHP 7.4.
|
||||
|
||||
Version 4.2.2 (2019-05-25)
|
||||
--------------------------
|
||||
|
||||
### 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)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for PHP 7.3 flexible heredoc/nowdoc strings, completing support for PHP 7.3. There
|
||||
are two caveats for this feature:
|
||||
* In some rare, pathological cases flexible heredoc/nowdoc strings change the interpretation of
|
||||
existing doc strings. PHP-Parser will now use the new interpretation.
|
||||
* Flexible heredoc/nowdoc strings require special support from the lexer. Because this is not
|
||||
available on PHP versions before 7.3, support has to be emulated. This emulation is not perfect
|
||||
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
|
||||
recognized correctly.
|
||||
* Added `DONT_TRAVERSE_CURRENT_AND_CHILDREN` to `NodeTraverser` to skip both traversal of child
|
||||
nodes, and prevent subsequent visitors from visiting the current node.
|
||||
|
||||
Version 4.0.4 (2018-09-18)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* The following methods have been added to `BuilderFactory`:
|
||||
* `useTrait()` (fluent builder)
|
||||
* `traitUseAdaptation()` (fluent builder)
|
||||
* `useFunction()` (fluent builder)
|
||||
* `useConst()` (fluent builder)
|
||||
* `var()`
|
||||
* `propertyFetch()`
|
||||
|
||||
### Deprecated
|
||||
|
||||
* `Builder\Param::setTypeHint()` has been deprecated in favor of the newly introduced
|
||||
`Builder\Param::setType()`.
|
||||
|
||||
Version 4.0.3 (2018-07-15)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed possible undefined offset notice in formatting-preserving printer. (#513)
|
||||
|
||||
### Added
|
||||
|
||||
* Improved error recovery inside arrays.
|
||||
* Preserve trailing comment inside classes. **Note:** This change is possibly BC breaking if your
|
||||
code validates that classes can only contain certain statement types. After this change, classes
|
||||
can also contain Nop statements, while this was not previously possible. (#509)
|
||||
|
||||
Version 4.0.2 (2018-06-03)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Improved error recovery inside classes.
|
||||
* Support error recovery for `foreach` without `as`.
|
||||
* Support error recovery for parameters without variable (`function (Type ) {}`).
|
||||
* Support error recovery for functions without body (`function ($foo)`).
|
||||
|
||||
Version 4.0.1 (2018-03-25)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [PHP 7.3] Added support for trailing commas in function calls.
|
||||
* [PHP 7.3] Added support for by-reference array destructuring.
|
||||
* Added checks to node traverser to prevent replacing a statement with an expression or vice versa.
|
||||
This should prevent common mistakes in the implementation of node visitors.
|
||||
* Added the following methods to `BuilderFactory`, to simplify creation of expressions:
|
||||
* `funcCall()`
|
||||
* `methodCall()`
|
||||
* `staticCall()`
|
||||
* `new()`
|
||||
* `constFetch()`
|
||||
* `classConstFetch()`
|
||||
|
||||
Version 4.0.0 (2018-02-28)
|
||||
--------------------------
|
||||
|
||||
* No significant code changes since the beta 1 release.
|
||||
|
||||
Version 4.0.0-beta1 (2018-01-27)
|
||||
--------------------------------
|
||||
@ -108,10 +352,13 @@ Version 4.0.0-alpha1 (2017-10-18)
|
||||
* The `BuilderAbstract` class has been removed. It's functionality is moved into `BuilderHelpers`.
|
||||
However, this is an internal class and should not be used directly.
|
||||
|
||||
Version 3.1.5-dev
|
||||
-----------------
|
||||
Version 3.1.5 (2018-02-28)
|
||||
--------------------------
|
||||
|
||||
Nothing yet.
|
||||
### Fixed
|
||||
|
||||
* Fixed duplicate comment assignment in switch statements. (#469)
|
||||
* Improve compatibility with PHP-Scoper. (#477)
|
||||
|
||||
Version 3.1.4 (2018-01-25)
|
||||
--------------------------
|
||||
|
44
LICENSE
44
LICENSE
@ -1,31 +1,29 @@
|
||||
Copyright (c) 2011-2018 by Nikita Popov.
|
||||
BSD 3-Clause License
|
||||
|
||||
Some rights reserved.
|
||||
Copyright (c) 2011, Nikita Popov
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
22
README.md
22
README.md
@ -3,12 +3,12 @@ PHP Parser
|
||||
|
||||
[](https://travis-ci.org/nikic/PHP-Parser) [](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.4 parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
manipulation.
|
||||
|
||||
[**Documentation for version 3.x**][doc_3_x] (stable; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
||||
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.4).
|
||||
|
||||
[Documentation for version 4.x][doc_master] (beta; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.2).
|
||||
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
||||
|
||||
Features
|
||||
--------
|
||||
@ -183,10 +183,16 @@ Documentation
|
||||
|
||||
1. [Introduction](doc/0_Introduction.markdown)
|
||||
2. [Usage of basic components](doc/2_Usage_of_basic_components.markdown)
|
||||
3. [Other node tree representations](doc/3_Other_node_tree_representations.markdown)
|
||||
|
||||
Component documentation:
|
||||
|
||||
* [Walking the AST](doc/component/Walking_the_AST.markdown)
|
||||
* Node visitors
|
||||
* Modifying the AST from a visitor
|
||||
* Short-circuiting traversals
|
||||
* Interleaved visitors
|
||||
* Simple node finding API
|
||||
* Parent and sibling references
|
||||
* [Name resolution](doc/component/Name_resolution.markdown)
|
||||
* Name resolver options
|
||||
* Name resolution context
|
||||
@ -194,7 +200,7 @@ Component documentation:
|
||||
* Converting AST back to PHP code
|
||||
* Customizing formatting
|
||||
* Formatting-preserving code transformations
|
||||
* [AST builders](component/AST_builders.markdown)
|
||||
* [AST builders](doc/component/AST_builders.markdown)
|
||||
* Fluent builders for AST nodes
|
||||
* [Lexer](doc/component/Lexer.markdown)
|
||||
* Lexer options
|
||||
@ -203,14 +209,16 @@ Component documentation:
|
||||
* [Error handling](doc/component/Error_handling.markdown)
|
||||
* Column information for errors
|
||||
* Error recovery (parsing of syntactically incorrect code)
|
||||
* [Constant expression evaluation](component/Constant_expression_evaluation.markdown)
|
||||
* [Constant expression evaluation](doc/component/Constant_expression_evaluation.markdown)
|
||||
* Evaluating constant/property/etc initializers
|
||||
* Handling errors and unsupported expressions
|
||||
* [JSON representation](doc/component/JSON_representation.markdown)
|
||||
* JSON encoding and decoding of ASTs
|
||||
* [Performance](doc/component/Performance.markdown)
|
||||
* Disabling XDebug
|
||||
* Reusing objects
|
||||
* Garbage collection impact
|
||||
* [Frequently asked questions](component/FAQ.markdown)
|
||||
* [Frequently asked questions](doc/component/FAQ.markdown)
|
||||
* Parent and sibling references
|
||||
|
||||
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
|
||||
|
@ -4,7 +4,7 @@ Upgrading from PHP-Parser 2.x to 3.0
|
||||
The backwards-incompatible changes in this release may be summarized as follows:
|
||||
|
||||
* The specific details of the node representation have changed in some cases, primarily to
|
||||
accomodate new PHP 7.1 features.
|
||||
accommodate new PHP 7.1 features.
|
||||
* There have been significant changes to the error recovery implementation. This may affect you,
|
||||
if you used the error recovery mode or have a custom lexer implementation.
|
||||
* A number of deprecated methods were removed.
|
||||
|
@ -6,7 +6,7 @@ Upgrading from PHP-Parser 3.x to 4.0
|
||||
PHP-Parser now requires PHP 7.0 or newer to run. It is however still possible to *parse* PHP 5.2-5.6
|
||||
source code, while running on a newer version.
|
||||
|
||||
Because HHVM does not support PHP 7, HHVM is no longer supported.
|
||||
HHVM is no longer actively supported.
|
||||
|
||||
### Changes to the node structure
|
||||
|
||||
@ -17,7 +17,7 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
||||
comparisons or strict-mode may require adjustment. The following is an exhaustive list of all
|
||||
affected subnodes:
|
||||
|
||||
* `Const::$name`
|
||||
* `Const_::$name`
|
||||
* `NullableType::$type` (for simple types)
|
||||
* `Param::$type` (for simple types)
|
||||
* `Expr\ClassConstFetch::$name`
|
||||
@ -29,8 +29,8 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
||||
* `Stmt\Class_::$name`
|
||||
* `Stmt\ClassMethod::$name`
|
||||
* `Stmt\ClassMethod::$returnType` (for simple types)
|
||||
* `Stmt\Function::$name`
|
||||
* `Stmt\Function::$returnType` (for simple types)
|
||||
* `Stmt\Function_::$name`
|
||||
* `Stmt\Function_::$returnType` (for simple types)
|
||||
* `Stmt\Goto_::$name`
|
||||
* `Stmt\Interface_::$name`
|
||||
* `Stmt\Label::$name`
|
||||
@ -48,7 +48,7 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
||||
* The `name` subnode of `StaticVar` has been renamed to `var` and now contains a `Variable` rather
|
||||
than a plain string.
|
||||
* The `var` subnode of `ClosureUse` now contains a `Variable` rather than a plain string.
|
||||
* The `var` subnode of `Catch` now contains a `Variable` rather than a plain string.
|
||||
* The `var` subnode of `Catch_` now contains a `Variable` rather than a plain string.
|
||||
* The `alias` subnode of `UseUse` is now `null` if no explicit alias is given. As such,
|
||||
`use Foo\Bar` and `use Foo\Bar as Bar` are now represented differently. The `getAlias()` method
|
||||
can be used to get the effective alias, even if it is not explicitly given.
|
||||
@ -62,7 +62,7 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
||||
|
||||
### Removed functionality
|
||||
|
||||
* Removed `type` subnode on `Class`, `ClassMethod` and `Property` nodes. Use `flags` instead.
|
||||
* Removed `type` subnode on `Class_`, `ClassMethod` and `Property` nodes. Use `flags` instead.
|
||||
* The `ClassConst::isStatic()` method has been removed. Constants cannot have a static modifier.
|
||||
* The `NodeTraverser` no longer accepts `false` as a return value from a `leaveNode()` method.
|
||||
`NodeTraverser::REMOVE_NODE` should be returned instead.
|
||||
@ -74,4 +74,4 @@ Because HHVM does not support PHP 7, HHVM is no longer supported.
|
||||
`Unserializer\XML`, as well as the interfaces `Serializer` and `Unserializer` no longer exist.
|
||||
* The `BuilderAbstract` class has been removed. It's functionality is moved into `BuilderHelpers`.
|
||||
However, this is an internal class and should not be used directly.
|
||||
* The `Autoloader` class has been removed in favor of relying on the Composer autoloader.
|
||||
* The `Autoloader` class has been removed in favor of relying on the Composer autoloader.
|
||||
|
@ -45,14 +45,15 @@ $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
|
||||
foreach ($files as $file) {
|
||||
if (strpos($file, '<?php') === 0) {
|
||||
$code = $file;
|
||||
echo "====> Code $code\n";
|
||||
fwrite(STDERR, "====> Code $code\n");
|
||||
} else {
|
||||
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);
|
||||
echo "====> File $file:\n";
|
||||
fwrite(STDERR, "====> File $file:\n");
|
||||
}
|
||||
|
||||
if ($attributes['with-recovery']) {
|
||||
@ -60,7 +61,7 @@ foreach ($files as $file) {
|
||||
$stmts = $parser->parse($code, $errorHandler);
|
||||
foreach ($errorHandler->getErrors() as $error) {
|
||||
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
|
||||
echo $message . "\n";
|
||||
fwrite(STDERR, $message . "\n");
|
||||
}
|
||||
if (null === $stmts) {
|
||||
continue;
|
||||
@ -70,22 +71,26 @@ foreach ($files as $file) {
|
||||
$stmts = $parser->parse($code);
|
||||
} catch (PhpParser\Error $error) {
|
||||
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
|
||||
die($message . "\n");
|
||||
fwrite(STDERR, $message . "\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($operations as $operation) {
|
||||
if ('dump' === $operation) {
|
||||
echo "==> Node dump:\n";
|
||||
fwrite(STDERR, "==> Node dump:\n");
|
||||
echo $dumper->dump($stmts, $code), "\n";
|
||||
} elseif ('pretty-print' === $operation) {
|
||||
echo "==> Pretty print:\n";
|
||||
fwrite(STDERR, "==> Pretty print:\n");
|
||||
echo $prettyPrinter->prettyPrintFile($stmts), "\n";
|
||||
} elseif ('json-dump' === $operation) {
|
||||
fwrite(STDERR, "==> JSON dump:\n");
|
||||
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
|
||||
} elseif ('var-dump' === $operation) {
|
||||
echo "==> var_dump():\n";
|
||||
fwrite(STDERR, "==> var_dump():\n");
|
||||
var_dump($stmts);
|
||||
} elseif ('resolve-names' === $operation) {
|
||||
echo "==> Resolved names.\n";
|
||||
fwrite(STDERR, "==> Resolved names.\n");
|
||||
$stmts = $traverser->traverse($stmts);
|
||||
}
|
||||
}
|
||||
@ -101,9 +106,9 @@ function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) {
|
||||
|
||||
function showHelp($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 ...]
|
||||
or: php-parse [operations] "<?php code"
|
||||
Turn PHP source code into an abstract syntax tree.
|
||||
@ -112,6 +117,7 @@ Operations is a list of the following options (--dump by default):
|
||||
|
||||
-d, --dump Dump nodes using NodeDumper
|
||||
-p, --pretty-print Pretty print file using PrettyPrinter\Standard
|
||||
-j, --json-dump Print json_encode() result
|
||||
--var-dump var_dump() nodes (for exact structure)
|
||||
-N, --resolve-names Resolve names using NodeVisitor\NameResolver
|
||||
-c, --with-column-info Show column-numbers for errors (if available)
|
||||
@ -127,6 +133,7 @@ Example:
|
||||
|
||||
OUTPUT
|
||||
);
|
||||
exit($error ? 1 : 0);
|
||||
}
|
||||
|
||||
function parseArgs($args) {
|
||||
@ -155,8 +162,9 @@ function parseArgs($args) {
|
||||
case '-p':
|
||||
$operations[] = 'pretty-print';
|
||||
break;
|
||||
case '--serialize-xml':
|
||||
$operations[] = 'serialize-xml';
|
||||
case '--json-dump':
|
||||
case '-j':
|
||||
$operations[] = 'json-dump';
|
||||
break;
|
||||
case '--var-dump':
|
||||
$operations[] = 'var-dump';
|
||||
|
@ -1,8 +1,11 @@
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"description": "A PHP parser written in PHP",
|
||||
"keywords": ["php", "parser"],
|
||||
"type": "library",
|
||||
"description": "A PHP parser written in PHP",
|
||||
"keywords": [
|
||||
"php",
|
||||
"parser"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
@ -14,17 +17,25 @@
|
||||
"ext-tokenizer": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6"
|
||||
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0",
|
||||
"ircmaxell/php-yacc": "^0.0.6"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpParser\\": "lib/PhpParser"
|
||||
}
|
||||
},
|
||||
"bin": ["bin/php-parse"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.0-dev"
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"PhpParser\\": "test/PhpParser/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/php-parse"
|
||||
]
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
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.4 parser **written in PHP itself**.
|
||||
|
||||
What is this for?
|
||||
-----------------
|
||||
@ -14,7 +14,7 @@ There are other ways of processing source code. One that PHP supports natively i
|
||||
token stream generated by [`token_get_all`][2]. The token stream is much more low level than
|
||||
the AST and thus has different applications: It allows to also analyze the exact formatting of
|
||||
a file. On the other hand the token stream is much harder to deal with for more complex analysis.
|
||||
For example an AST abstracts away the fact that in PHP variables can be written as `$foo`, but also
|
||||
For example, an AST abstracts away the fact that, in PHP, variables can be written as `$foo`, but also
|
||||
as `$$bar`, `${'foobar'}` or even `${!${''}=barfoo()}`. You don't have to worry about recognizing
|
||||
all the different syntaxes from a stream of tokens.
|
||||
|
||||
@ -26,17 +26,17 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
|
||||
What can it parse?
|
||||
------------------
|
||||
|
||||
The parser supports parsing PHP 5.2-7.2.
|
||||
The parser supports parsing PHP 5.2-7.4.
|
||||
|
||||
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
|
||||
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.4 source code running on PHP 7.0, for example. This emulation is somewhat
|
||||
hacky and not perfect, but it should work well on any sane code.
|
||||
|
||||
What output does it produce?
|
||||
----------------------------
|
||||
|
||||
The parser produces an [Abstract Syntax Tree][1] (AST) also known as a node tree. How this looks like
|
||||
The parser produces an [Abstract Syntax Tree][1] (AST) also known as a node tree. How this looks
|
||||
can best be seen in an example. The program `<?php echo 'Hi', 'World';` will give you a node tree
|
||||
roughly looking like this:
|
||||
|
||||
@ -56,7 +56,7 @@ array(
|
||||
```
|
||||
|
||||
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).
|
||||
So using it for formatting analysis is not possible.
|
||||
|
@ -41,7 +41,7 @@ Kind | Behavior
|
||||
`ParserFactory::ONLY_PHP7` | Parse code as PHP 7.
|
||||
`ParserFactory::ONLY_PHP5` | Parse code as PHP 5.
|
||||
|
||||
Unless you have strong reason to use something else, `PREFER_PHP7` is a reasonable default.
|
||||
Unless you have a strong reason to use something else, `PREFER_PHP7` is a reasonable default.
|
||||
|
||||
The `create()` method optionally accepts a `Lexer` instance as the second argument. Some use cases
|
||||
that require customized lexers are discussed in the [lexer documentation](component/Lexer.markdown).
|
||||
@ -50,10 +50,18 @@ Subsequently you can pass PHP code (including the opening `<?php` tag) to the `p
|
||||
create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will be thrown:
|
||||
|
||||
```php
|
||||
<?php
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
$code = '<?php // some code';
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
printLine('Hello World!!!');
|
||||
CODE;
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
|
||||
try {
|
||||
@ -66,27 +74,68 @@ try {
|
||||
|
||||
A parser instance can be reused to parse multiple files.
|
||||
|
||||
Node tree
|
||||
---------
|
||||
Node dumping
|
||||
------------
|
||||
|
||||
If you use the above code with `$code = "<?php echo 'Hi ', hi\\getTarget();"` the parser will
|
||||
generate a node tree looking like this:
|
||||
To dump the abstract syntax tree in human readable form, a `NodeDumper` can be used:
|
||||
|
||||
```php
|
||||
<?php
|
||||
use PhpParser\NodeDumper;
|
||||
|
||||
$nodeDumper = new NodeDumper;
|
||||
echo $nodeDumper->dump($stmts), "\n";
|
||||
```
|
||||
|
||||
For the sample code from the previous section, this will produce the following output:
|
||||
|
||||
```
|
||||
array(
|
||||
0: Stmt_Echo(
|
||||
exprs: array(
|
||||
0: Scalar_String(
|
||||
value: Hi
|
||||
0: Stmt_Function(
|
||||
byRef: false
|
||||
name: Identifier(
|
||||
name: printLine
|
||||
)
|
||||
params: array(
|
||||
0: Param(
|
||||
type: null
|
||||
byRef: false
|
||||
variadic: false
|
||||
var: Expr_Variable(
|
||||
name: msg
|
||||
)
|
||||
default: null
|
||||
)
|
||||
1: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: hi
|
||||
1: getTarget
|
||||
)
|
||||
returnType: null
|
||||
stmts: array(
|
||||
0: Stmt_Echo(
|
||||
exprs: array(
|
||||
0: Expr_Variable(
|
||||
name: msg
|
||||
)
|
||||
1: Scalar_String(
|
||||
value:
|
||||
|
||||
)
|
||||
)
|
||||
args: array(
|
||||
)
|
||||
)
|
||||
)
|
||||
1: Stmt_Expression(
|
||||
expr: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: printLine
|
||||
)
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
value: Scalar_String(
|
||||
value: Hello World!!!
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -94,10 +143,30 @@ array(
|
||||
)
|
||||
```
|
||||
|
||||
Thus `$stmts` will contain an array with only one node, with this node being an instance of
|
||||
`PhpParser\Node\Stmt\Echo_`.
|
||||
You can also use the `php-parse` script to obtain such a node dump by calling it either with a file
|
||||
name or code string:
|
||||
|
||||
As PHP is a large language there are approximately 140 different nodes. In order to make work
|
||||
```sh
|
||||
vendor/bin/php-parse file.php
|
||||
vendor/bin/php-parse "<?php foo();"
|
||||
```
|
||||
|
||||
This can be very helpful if you want to quickly check how certain syntax is represented in the AST.
|
||||
|
||||
Node tree structure
|
||||
-------------------
|
||||
|
||||
Looking at the node dump above, you can see that `$stmts` for this example code is an array of two
|
||||
nodes, a `Stmt_Function` and a `Stmt_Expression`. The corresponding class names are:
|
||||
|
||||
* `Stmt_Function -> PhpParser\Node\Stmt\Function_`
|
||||
* `Stmt_Expression -> PhpParser\Node\Stmt\Expression`
|
||||
|
||||
The additional `_` at the end of the first class name is necessary, because `Function` is a
|
||||
reserved keyword. Many node class names in this library have a trailing `_` to avoid clashing with
|
||||
a keyword.
|
||||
|
||||
As PHP is a large language there are approximately 140 different nodes. In order to make working
|
||||
with them easier they are grouped into three categories:
|
||||
|
||||
* `PhpParser\Node\Stmt`s are statement nodes, i.e. language constructs that do not return
|
||||
@ -113,8 +182,9 @@ with them easier they are grouped into three categories:
|
||||
* There are some nodes not in either of these groups, for example names (`PhpParser\Node\Name`)
|
||||
and call arguments (`PhpParser\Node\Arg`).
|
||||
|
||||
Some node class names have a trailing `_`. This is used whenever the class name would otherwise clash
|
||||
with a PHP keyword.
|
||||
The `Node\Stmt\Expression` node is somewhat confusing in that it contains both the terms "statement"
|
||||
and "expression". This node distinguishes `expr`, which is a `Node\Expr`, from `expr;`, which is
|
||||
an "expression statement" represented by `Node\Stmt\Expression` and containing `expr` as a sub-node.
|
||||
|
||||
Every node has a (possibly zero) number of subnodes. You can access subnodes by writing
|
||||
`$node->subNodeName`. The `Stmt\Echo_` node has only one subnode `exprs`. So in order to access it
|
||||
@ -173,7 +243,7 @@ try {
|
||||
|
||||
The above code will output:
|
||||
|
||||
<?php echo 'Hello ', hi\getTarget();
|
||||
echo 'Hello ', hi\getTarget();
|
||||
|
||||
As you can see the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
|
||||
again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
|
||||
@ -184,6 +254,8 @@ single expression using `prettyPrintExpr()`.
|
||||
The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `<?php` tag
|
||||
and handle inline HTML as the first/last statement more gracefully.
|
||||
|
||||
> Read more: [Pretty printing documentation](component/Pretty_printing.markdown)
|
||||
|
||||
Node traversation
|
||||
-----------------
|
||||
|
||||
@ -267,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.
|
||||
|
||||
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
|
||||
case the current node will be removed from the parent array. Furthermore it is possible to return
|
||||
@ -278,10 +351,12 @@ be `array(A, X, Y, Z, C)`.
|
||||
Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
|
||||
class, which will define empty default implementations for all the above methods.
|
||||
|
||||
> Read more: [Walking the AST](component/Walking_the_AST.markdown)
|
||||
|
||||
The NameResolver node visitor
|
||||
-----------------------------
|
||||
|
||||
One visitor is already bundled with the package: `PhpParser\NodeVisitor\NameResolver`. This visitor
|
||||
One visitor that is already bundled with the package is `PhpParser\NodeVisitor\NameResolver`. This visitor
|
||||
helps you work with namespaced code by trying to resolve most names to fully qualified ones.
|
||||
|
||||
For example, consider the following code:
|
||||
@ -292,7 +367,7 @@ For example, consider the following code:
|
||||
In order to know that `B\C` really is `A\C` you would need to track aliases and namespaces yourself.
|
||||
The `NameResolver` takes care of that and resolves names as far as possible.
|
||||
|
||||
After running it most names will be fully qualified. The only names that will stay unqualified are
|
||||
After running it, most names will be fully qualified. The only names that will stay unqualified are
|
||||
unqualified function and constant names. These are resolved at runtime and thus the visitor can't
|
||||
know which function they are referring to. In most cases this is a non-issue as the global functions
|
||||
are meant.
|
||||
@ -300,6 +375,8 @@ are meant.
|
||||
Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations
|
||||
that contains the namespaced name instead of only the shortname that is available via `name`.
|
||||
|
||||
> Read more: [Name resolution documentation](component/Name_resolution.markdown)
|
||||
|
||||
Example: Converting namespaced code to pseudo namespaces
|
||||
--------------------------------------------------------
|
||||
|
||||
@ -333,7 +410,7 @@ $files = new \RegexIterator($files, '/\.php$/');
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
// read the file that should be converted
|
||||
$code = file_get_contents($file);
|
||||
$code = file_get_contents($file->getPathName());
|
||||
|
||||
// parse
|
||||
$stmts = $parser->parse($code);
|
||||
|
@ -1,228 +0,0 @@
|
||||
Other node tree representations
|
||||
===============================
|
||||
|
||||
It is possible to convert the AST into several textual representations, which serve different uses.
|
||||
|
||||
Simple serialization
|
||||
--------------------
|
||||
|
||||
It is possible to serialize the node tree using `serialize()` and also unserialize it using
|
||||
`unserialize()`. The output is not human readable and not easily processable from anything
|
||||
but PHP, but it is compact and generates quickly. The main application thus is in caching.
|
||||
|
||||
Human readable dumping
|
||||
----------------------
|
||||
|
||||
Furthermore it is possible to dump nodes into a human readable format using the `dump` method of
|
||||
`PhpParser\NodeDumper`. This can be used for debugging.
|
||||
|
||||
```php
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
printLine('Hello World!!!');
|
||||
CODE;
|
||||
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
|
||||
$nodeDumper = new PhpParser\NodeDumper;
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
echo $nodeDumper->dump($stmts), "\n";
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
The above script will have an output looking roughly like this:
|
||||
|
||||
```
|
||||
array(
|
||||
0: Stmt_Function(
|
||||
byRef: false
|
||||
params: array(
|
||||
0: Param(
|
||||
name: msg
|
||||
default: null
|
||||
type: null
|
||||
byRef: false
|
||||
)
|
||||
)
|
||||
stmts: array(
|
||||
0: Stmt_Echo(
|
||||
exprs: array(
|
||||
0: Expr_Variable(
|
||||
name: msg
|
||||
)
|
||||
1: Scalar_String(
|
||||
value:
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
name: printLine
|
||||
)
|
||||
1: Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: printLine
|
||||
)
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
value: Scalar_String(
|
||||
value: Hello World!!!
|
||||
)
|
||||
byRef: false
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
JSON encoding
|
||||
-------------
|
||||
|
||||
Nodes (and comments) implement the `JsonSerializable` interface. As such, it is possible to JSON
|
||||
encode the AST directly using `json_encode()`:
|
||||
|
||||
```php
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
printLine('Hello World!!!');
|
||||
CODE;
|
||||
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
This will result in the following output (which includes attributes):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"nodeType": "Stmt_Function",
|
||||
"byRef": false,
|
||||
"name": "printLine",
|
||||
"params": [
|
||||
{
|
||||
"nodeType": "Param",
|
||||
"type": null,
|
||||
"byRef": false,
|
||||
"variadic": false,
|
||||
"name": "msg",
|
||||
"default": null,
|
||||
"attributes": {
|
||||
"startLine": 3,
|
||||
"endLine": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": null,
|
||||
"stmts": [
|
||||
{
|
||||
"nodeType": "Stmt_Echo",
|
||||
"exprs": [
|
||||
{
|
||||
"nodeType": "Expr_Variable",
|
||||
"name": "msg",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "Scalar_String",
|
||||
"value": "\n",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4,
|
||||
"kind": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 3,
|
||||
"endLine": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "Expr_FuncCall",
|
||||
"name": {
|
||||
"nodeType": "Name",
|
||||
"parts": [
|
||||
"printLine"
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 7,
|
||||
"endLine": 7
|
||||
}
|
||||
},
|
||||
"args": [
|
||||
{
|
||||
"nodeType": "Arg",
|
||||
"value": {
|
||||
"nodeType": "Scalar_String",
|
||||
"value": "Hello World!!!",
|
||||
"attributes": {
|
||||
"startLine": 7,
|
||||
"endLine": 7,
|
||||
"kind": 1
|
||||
}
|
||||
},
|
||||
"byRef": false,
|
||||
"unpack": false,
|
||||
"attributes": {
|
||||
"startLine": 7,
|
||||
"endLine": 7
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 7,
|
||||
"endLine": 7
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The JSON representation may be converted back into a node tree using the `JsonDecoder`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$nodeDecoder = new PhpParser\NodeDecoder();
|
||||
$ast = $nodeDecoder->decode($json);
|
||||
```
|
||||
|
||||
Note that not all ASTs can be represented using JSON. In particular:
|
||||
|
||||
* JSON only supports UTF-8 strings.
|
||||
* JSON does not support non-finite floating-point numbers. This can occur if the original source
|
||||
code contains non-representable floating-pointing literals such as `1e1000`.
|
||||
|
||||
If the node tree is not representable in JSON, the initial `json_encode()` call will fail.
|
@ -6,11 +6,17 @@ Guide
|
||||
|
||||
1. [Introduction](0_Introduction.markdown)
|
||||
2. [Usage of basic components](2_Usage_of_basic_components.markdown)
|
||||
3. [Other node tree representations](3_Other_node_tree_representations.markdown)
|
||||
|
||||
Component documentation
|
||||
-----------------------
|
||||
|
||||
* [Walking the AST](component/Walking_the_AST.markdown)
|
||||
* Node visitors
|
||||
* Modifying the AST from a visitor
|
||||
* Short-circuiting traversals
|
||||
* Interleaved visitors
|
||||
* Simple node finding API
|
||||
* Parent and sibling references
|
||||
* [Name resolution](component/Name_resolution.markdown)
|
||||
* Name resolver options
|
||||
* Name resolution context
|
||||
@ -30,6 +36,8 @@ Component documentation
|
||||
* [Constant expression evaluation](component/Constant_expression_evaluation.markdown)
|
||||
* Evaluating constant/property/etc initializers
|
||||
* Handling errors and unsupported expressions
|
||||
* [JSON representation](component/JSON_representation.markdown)
|
||||
* JSON encoding and decoding of ASTs
|
||||
* [Performance](component/Performance.markdown)
|
||||
* Disabling XDebug
|
||||
* Reusing objects
|
||||
|
@ -1,7 +1,7 @@
|
||||
AST builders
|
||||
============
|
||||
|
||||
When PHP-Parser is used to generate (or modify) code, by first creating an Abstract Syntax Tree and
|
||||
When PHP-Parser is used to generate (or modify) code by first creating an Abstract Syntax Tree and
|
||||
then using the [pretty printer](Pretty_printing.markdown) to convert it to PHP code, it can often
|
||||
be tedious to manually construct AST nodes. The project provides a number of utilities to simplify
|
||||
the construction of common AST nodes.
|
||||
@ -28,17 +28,27 @@ use PhpParser\Node;
|
||||
|
||||
$factory = new BuilderFactory;
|
||||
$node = $factory->namespace('Name\Space')
|
||||
->addStmt($factory->use('Some\Other\Thingy')->as('SomeOtherClass'))
|
||||
->addStmt($factory->use('Some\Other\Thingy')->as('SomeClass'))
|
||||
->addStmt($factory->useFunction('strlen'))
|
||||
->addStmt($factory->useConst('PHP_VERSION'))
|
||||
->addStmt($factory->class('SomeOtherClass')
|
||||
->extend('SomeClass')
|
||||
->implement('A\Few', '\Interfaces')
|
||||
->makeAbstract() // ->makeFinal()
|
||||
|
||||
->addStmt($factory->useTrait('FirstTrait'))
|
||||
|
||||
->addStmt($factory->useTrait('SecondTrait', 'ThirdTrait')
|
||||
->and('AnotherTrait')
|
||||
->with($factory->traitUseAdaptation('foo')->as('bar'))
|
||||
->with($factory->traitUseAdaptation('AnotherTrait', 'baz')->as('test'))
|
||||
->with($factory->traitUseAdaptation('AnotherTrait', 'func')->insteadof('SecondTrait')))
|
||||
|
||||
->addStmt($factory->method('someMethod')
|
||||
->makePublic()
|
||||
->makeAbstract() // ->makeFinal()
|
||||
->setReturnType('bool')
|
||||
->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
|
||||
->setReturnType('bool') // ->makeReturnByRef()
|
||||
->addParam($factory->param('someParam')->setType('SomeClass'))
|
||||
->setDocComment('/**
|
||||
* This method does something.
|
||||
*
|
||||
@ -74,8 +84,16 @@ This will produce the following output with the standard pretty printer:
|
||||
namespace Name\Space;
|
||||
|
||||
use Some\Other\Thingy as SomeClass;
|
||||
use function strlen;
|
||||
use const PHP_VERSION;
|
||||
abstract class SomeOtherClass extends SomeClass implements A\Few, \Interfaces
|
||||
{
|
||||
use FirstTrait;
|
||||
use SecondTrait, ThirdTrait, AnotherTrait {
|
||||
foo as bar;
|
||||
AnotherTrait::baz as test;
|
||||
AnotherTrait::func insteadof SecondTrait;
|
||||
}
|
||||
protected $someProperty;
|
||||
private $anotherProperty = array(1, 2, 3);
|
||||
/**
|
||||
@ -98,9 +116,23 @@ The `BuilderFactory` also provides a number of additional helper methods, which
|
||||
nodes. The following methods are currently available:
|
||||
|
||||
* `val($value)`: Creates an AST node for a literal value like `42` or `[1, 2, 3]`.
|
||||
* `var($name)`: Creates variable node.
|
||||
* `args(array $args)`: Creates an array of function/method arguments, including the required `Arg`
|
||||
wrappers. Also converts literals to AST nodes.
|
||||
* `funcCall($name, array $args = [])`: Create a function call node. Converts `$name` to a `Name`
|
||||
node and normalizes arguments.
|
||||
* `methodCall(Expr $var, $name, array $args = [])`: Create a method call node. Converts `$name` to
|
||||
an `Identifier` node and normalizes arguments.
|
||||
* `staticCall($class, $name, array $args = [])`: Create a static method call node. Converts
|
||||
`$class` to a `Name` node, `$name` to an `Identifier` node and normalizes arguments.
|
||||
* `new($class, array $args = [])`: Create a "new" (object creation) node. Converts `$class` to a
|
||||
`Name` node.
|
||||
* `constFetch($name)`: Create a constant fetch node. Converts `$name` to a `Name` node.
|
||||
* `classConstFetch($class, $name)`: Create a class constant fetch node. Converts `$class` to a
|
||||
`Name` node and `$name` to an `Identifier` node.
|
||||
* `propertyFetch($var, $name)`: Creates a property fetch node. Converts `$name` to an `Identifier`
|
||||
node.
|
||||
* `concat(...$exprs)`: Create a tree of `BinaryOp\Concat` nodes for the given expressions.
|
||||
|
||||
These methods may be expanded on an as-needed basis. Please open an issue or PR if a common
|
||||
operation is missing.
|
||||
operation is missing.
|
||||
|
@ -27,7 +27,7 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
Before using column information its availability needs to be checked with `$e->hasColumnInfo()`, as the precise
|
||||
Before using column information, its availability needs to be checked with `$e->hasColumnInfo()`, as the precise
|
||||
location of an error cannot always be determined. The methods for retrieving column information also have to be passed
|
||||
the source code of the parsed file. An example for printing an error:
|
||||
|
||||
|
@ -7,28 +7,21 @@ Frequently Asked Questions
|
||||
How can the parent of a node be obtained?
|
||||
-----
|
||||
|
||||
The AST does not store parent nodes by default. However, it is easy to add a custom parent node
|
||||
attribute using a custom node visitor:
|
||||
The AST does not store parent nodes by default. However, the `ParentConnectingVisitor` can be used to achieve this:
|
||||
|
||||
```php
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitor\ParentConnectingVisitor;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
class ParentConnector extends NodeVisitorAbstract {
|
||||
private $stack;
|
||||
public function beforeTraverse(array $nodes) {
|
||||
$this->stack = [];
|
||||
}
|
||||
public function enterNode(Node $node) {
|
||||
if (!empty($this->stack)) {
|
||||
$node->setAttribute('parent', $this->stack[count($this->stack)-1]);
|
||||
}
|
||||
$this->stack[] = $node;
|
||||
}
|
||||
public function leaveNode(Node $node) {
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
$code = '...';
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new ParentConnectingVisitor);
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$ast = $parser->parse($code);
|
||||
$ast = $traverser->traverse($ast);
|
||||
```
|
||||
|
||||
After running this visitor, the parent node can be obtained through `$node->getAttribute('parent')`.
|
||||
@ -36,33 +29,27 @@ After running this visitor, the parent node can be obtained through `$node->getA
|
||||
How can the next/previous sibling of a node be obtained?
|
||||
-----
|
||||
|
||||
Again, siblings are not stored by default, but the visitor from the previous entry can be easily
|
||||
extended to store the previous / next node with a common parent as well:
|
||||
Again, siblings are not stored by default, but the `NodeConnectingVisitor` can be used to store
|
||||
the previous / next node with a common parent as well:
|
||||
|
||||
```php
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitor\NodeConnectingVisitor;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
class NodeConnector extends NodeVisitorAbstract {
|
||||
private $stack;
|
||||
private $prev;
|
||||
public function beforeTraverse(array $nodes) {
|
||||
$this->stack = [];
|
||||
$this->prev = null;
|
||||
}
|
||||
public function enterNode(Node $node) {
|
||||
if (!empty($this->stack)) {
|
||||
$node->setAttribute('parent', $this->stack[count($this->stack)-1]);
|
||||
}
|
||||
if ($this->prev && $this->prev->getAttribute('parent') == $node->getAttribute('parent')) {
|
||||
$node->setAttribute('prev', $this->prev);
|
||||
$this->prev->setAttribute('next', $node);
|
||||
}
|
||||
$this->stack[] = $node;
|
||||
}
|
||||
public function leaveNode(Node $node) {
|
||||
$this->prev = $node;
|
||||
array_pop($this->stack);
|
||||
}
|
||||
}
|
||||
$code = '...';
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NodeConnectingVisitor);
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
$ast = $parser->parse($code);
|
||||
$ast = $traverser->traverse($ast);
|
||||
```
|
||||
|
||||
After running this visitor, the parent node can be obtained through `$node->getAttribute('parent')`,
|
||||
the previous node can be obtained through `$node->getAttribute('previous')`, and the next node can be
|
||||
obtained through `$node->getAttribute('next')`.
|
||||
|
||||
`ParentConnectingVisitor` and `NodeConnectingVisitor` should not be used at the same time. The latter
|
||||
includes the functionality of the former.
|
||||
|
131
doc/component/JSON_representation.markdown
Normal file
131
doc/component/JSON_representation.markdown
Normal file
@ -0,0 +1,131 @@
|
||||
JSON representation
|
||||
===================
|
||||
|
||||
Nodes (and comments) implement the `JsonSerializable` interface. As such, it is possible to JSON
|
||||
encode the AST directly using `json_encode()`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
|
||||
/** @param string $msg */
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
CODE;
|
||||
|
||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
This will result in the following output (which includes attributes):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"nodeType": "Stmt_Function",
|
||||
"byRef": false,
|
||||
"name": {
|
||||
"nodeType": "Identifier",
|
||||
"name": "printLine",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"nodeType": "Param",
|
||||
"type": null,
|
||||
"byRef": false,
|
||||
"variadic": false,
|
||||
"var": {
|
||||
"nodeType": "Expr_Variable",
|
||||
"name": "msg",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
},
|
||||
"default": null,
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": null,
|
||||
"stmts": [
|
||||
{
|
||||
"nodeType": "Stmt_Echo",
|
||||
"exprs": [
|
||||
{
|
||||
"nodeType": "Expr_Variable",
|
||||
"name": "msg",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "Scalar_String",
|
||||
"value": "\n",
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5,
|
||||
"kind": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 5,
|
||||
"endLine": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"comments": [
|
||||
{
|
||||
"nodeType": "Comment_Doc",
|
||||
"text": "\/** @param string $msg *\/",
|
||||
"line": 3,
|
||||
"filePos": 9,
|
||||
"tokenPos": 2
|
||||
}
|
||||
],
|
||||
"endLine": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The JSON representation may be converted back into an AST using the `JsonDecoder`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$jsonDecoder = new PhpParser\JsonDecoder();
|
||||
$ast = $jsonDecoder->decode($json);
|
||||
```
|
||||
|
||||
Note that not all ASTs can be represented using JSON. In particular:
|
||||
|
||||
* JSON only supports UTF-8 strings.
|
||||
* JSON does not support non-finite floating-point numbers. This can occur if the original source
|
||||
code contains non-representable floating-pointing literals such as `1e1000`.
|
||||
|
||||
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`.
|
@ -107,9 +107,9 @@ function handleHaltCompiler(): string;
|
||||
function getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null): int;
|
||||
```
|
||||
|
||||
The `startLexing()` method is invoked with the source code that is to be lexed (including the opening tag) whenever the
|
||||
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens. The
|
||||
passes `ErrorHandler` should be used to report lexing errors.
|
||||
The `startLexing()` method is invoked whenever the `parse()` method of the parser is called and is passed the source
|
||||
code that is to be lexed (including the opening tag). It can be used to reset state or preprocess the source code or tokens. The
|
||||
passed `ErrorHandler` should be used to report lexing errors.
|
||||
|
||||
The `getTokens()` method returns the current token array, in the usual `token_get_all()` format. This method is not
|
||||
used by the parser (which uses `getNextToken()`), but is useful in combination with the token position attributes.
|
||||
|
@ -17,9 +17,9 @@ the `xdebug.default_enable=0` ini option does *not* disable XDebug. The *only* w
|
||||
XDebug is to not load the extension in the first place.
|
||||
|
||||
If you are building a command-line utility for use by developers (who often have XDebug enabled),
|
||||
you may want to consider automatically restarting PHP with XDebug unloaded. See the composer
|
||||
[XdebugHandler](https://github.com/composer/composer/blob/master/src/Composer/XdebugHandler.php)
|
||||
for an implementation of such functionality.
|
||||
you may want to consider automatically restarting PHP with XDebug unloaded. The
|
||||
[composer/xdebug-handler](https://github.com/composer/xdebug-handler) package can be used to do
|
||||
this.
|
||||
|
||||
If you do run with XDebug, you may need to increase the `xdebug.max_nesting_level` option to a
|
||||
higher level, such as 3000. While the parser itself is recursion free, most other code working on
|
||||
|
@ -52,8 +52,8 @@ For automated code refactoring, migration and similar, you will usually only wan
|
||||
portion of the code and leave the remainder alone. The basic pretty printer is not suitable for
|
||||
this, because it will also reformat parts of the code which have not been modified.
|
||||
|
||||
Since PHP-Parser 4.0 an experimental formatting-preserving pretty-printing mode is available, which
|
||||
attempts to preserve the formatting of code, those AST nodes have not changed, and only reformat
|
||||
Since PHP-Parser 4.0, an experimental formatting-preserving pretty-printing mode is available, which
|
||||
attempts to preserve the formatting of code (those AST nodes that have not changed) and only reformat
|
||||
code which has been modified or newly inserted.
|
||||
|
||||
Use of the formatting-preservation functionality requires some additional preparatory steps:
|
||||
@ -86,7 +86,7 @@ $newCode = $printer->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
|
||||
```
|
||||
|
||||
If you make use of the name resolution functionality, you will likely want to disable the
|
||||
`replaceNames` option. This will add resolved names as attributes, instead of directlying modifying
|
||||
`replaceNodes` option. This will add resolved names as attributes, instead of directlying modifying
|
||||
the AST and causing spurious changes to the pretty printed code. For more information, see the
|
||||
[name resolution documentation](Name_resolution.markdown).
|
||||
|
||||
|
337
doc/component/Walking_the_AST.markdown
Normal file
337
doc/component/Walking_the_AST.markdown
Normal file
@ -0,0 +1,337 @@
|
||||
Walking the AST
|
||||
===============
|
||||
|
||||
The most common way to work with the AST is by using a node traverser and one or more node visitors.
|
||||
As a basic example, the following code changes all literal integers in the AST into strings (e.g.,
|
||||
`42` becomes `'42'`.)
|
||||
|
||||
```php
|
||||
use PhpParser\{Node, NodeTraverser, NodeVisitorAbstract};
|
||||
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new class extends NodeVisitorAbstract {
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Scalar\LNumber) {
|
||||
return new Node\Scalar\String_((string) $node->value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$stmts = ...;
|
||||
$modifiedStmts = $traverser->traverse($stmts);
|
||||
```
|
||||
|
||||
Node visitors
|
||||
-------------
|
||||
|
||||
Each node visitor implements an interface with following four methods:
|
||||
|
||||
```php
|
||||
interface NodeVisitor {
|
||||
public function beforeTraverse(array $nodes);
|
||||
public function enterNode(Node $node);
|
||||
public function leaveNode(Node $node);
|
||||
public function afterTraverse(array $nodes);
|
||||
}
|
||||
```
|
||||
|
||||
The `beforeTraverse()` and `afterTraverse()` methods are called before and after the traversal
|
||||
respectively, and are passed the entire AST. They can be used to perform any necessary state
|
||||
setup or cleanup.
|
||||
|
||||
The `enterNode()` method is called when a node is first encountered, before its children are
|
||||
processed ("preorder"). The `leaveNode()` method is called after all children have been visited
|
||||
("postorder").
|
||||
|
||||
For example, if we have the following excerpt of an AST
|
||||
|
||||
```
|
||||
Expr_FuncCall(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: printLine
|
||||
)
|
||||
)
|
||||
args: array(
|
||||
0: Arg(
|
||||
value: Scalar_String(
|
||||
value: Hello World!!!
|
||||
)
|
||||
byRef: false
|
||||
unpack: false
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
then the enter/leave methods will be called in the following order:
|
||||
|
||||
```
|
||||
enterNode(Expr_FuncCall)
|
||||
enterNode(Name)
|
||||
leaveNode(Name)
|
||||
enterNode(Arg)
|
||||
enterNode(Scalar_String)
|
||||
leaveNode(Scalar_String)
|
||||
leaveNode(Arg)
|
||||
leaveNode(Expr_FuncCall)
|
||||
```
|
||||
|
||||
A common pattern is that `enterNode` is used to collect some information and then `leaveNode`
|
||||
performs modifications based on that. At the time when `leaveNode` is called, all the code inside
|
||||
the node will have already been visited and necessary information collected.
|
||||
|
||||
As you usually do not want to implement all four methods, it is recommended that you extend
|
||||
`NodeVisitorAbstract` instead of implementing the interface directly. The abstract class provides
|
||||
empty default implementations.
|
||||
|
||||
Modifying the AST
|
||||
-----------------
|
||||
|
||||
There are a number of ways in which the AST can be modified from inside a node visitor. The first
|
||||
and simplest is to simply change AST properties inside the visitor:
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Scalar\LNumber) {
|
||||
// increment all integer literals
|
||||
$node->value++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The second is to replace a node entirely by returning a new node:
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Expr\BinaryOp\BooleanAnd) {
|
||||
// Convert all $a && $b expressions into !($a && $b)
|
||||
return new Node\Expr\BooleanNot($node);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Doing this is supported both inside enterNode and leaveNode. However, you have to be mindful about
|
||||
where you perform the replacement: If a node is replaced in enterNode, then the recursive traversal
|
||||
will also consider the children of the new node. If you aren't careful, this can lead to infinite
|
||||
recursion. For example, let's take the previous code sample and use enterNode instead:
|
||||
|
||||
```php
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Node\Expr\BinaryOp\BooleanAnd) {
|
||||
// Convert all $a && $b expressions into !($a && $b)
|
||||
return new Node\Expr\BooleanNot($node);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now `$a && $b` will be replaced by `!($a && $b)`. Then the traverser will go into the first (and
|
||||
only) child of `!($a && $b)`, which is `$a && $b`. The transformation applies again and we end up
|
||||
with `!!($a && $b)`. This will continue until PHP hits the memory limit.
|
||||
|
||||
Finally, two special replacement types are supported only by leaveNode. The first is removal of a
|
||||
node:
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Return_) {
|
||||
// Remove all return statements
|
||||
return NodeTraverser::REMOVE_NODE;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Node removal only works if the parent structure is an array. This means that usually it only makes
|
||||
sense to remove nodes of type `Node\Stmt`, as they always occur inside statement lists (and a few
|
||||
more node types like `Arg` or `Expr\ArrayItem`, which are also always part of lists).
|
||||
|
||||
On the other hand, removing a `Node\Expr` does not make sense: If you have `$a * $b`, there is no
|
||||
meaningful way in which the `$a` part could be removed. If you want to remove an expression, you
|
||||
generally want to remove it together with a surrounding expression statement:
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Expression
|
||||
&& $node->expr instanceof Node\Expr\FuncCall
|
||||
&& $node->expr->name instanceof Node\Name
|
||||
&& $node->expr->name->toString() === 'var_dump'
|
||||
) {
|
||||
return NodeTraverser::REMOVE_NODE;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This example will remove all calls to `var_dump()` which occur as expression statements. This means
|
||||
that `var_dump($a);` will be removed, but `if (var_dump($a))` will not be removed (and there is no
|
||||
obvious way in which it can be removed).
|
||||
|
||||
Next to removing nodes, it is also possible to replace one node with multiple nodes. Again, this
|
||||
only works inside leaveNode and only if the parent structure is an array.
|
||||
|
||||
```php
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Return_ && $node->expr !== null) {
|
||||
// Convert "return foo();" into "$retval = foo(); return $retval;"
|
||||
$var = new Node\Expr\Variable('retval');
|
||||
return [
|
||||
new Node\Stmt\Expression(new Node\Expr\Assign($var, $node->expr)),
|
||||
new Node\Stmt\Return_($var),
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Short-circuiting traversal
|
||||
--------------------------
|
||||
|
||||
An AST can easily contain thousands of nodes, and traversing over all of them may be slow,
|
||||
especially if you have more than one visitor. In some cases, it is possible to avoid a full
|
||||
traversal.
|
||||
|
||||
If you are looking for all class declarations in a file (and assuming you're not interested in
|
||||
anonymous classes), you know that once you've seen a class declaration, there is no point in also
|
||||
checking all it's child nodes, because PHP does not allow nesting classes. In this case, you can
|
||||
instruct the traverser to not recurse into the class node:
|
||||
|
||||
```
|
||||
private $classes = [];
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Class_) {
|
||||
$this->classes[] = $node;
|
||||
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Of course, this option is only available in enterNode, because it's already too late by the time
|
||||
leaveNode is reached.
|
||||
|
||||
If you are only looking for one specific node, it is also possible to abort the traversal entirely
|
||||
after finding it. For example, if you are looking for the node of a class with a certain name (and
|
||||
discounting exotic cases like conditionally defining a class two times), you can stop traversal
|
||||
once you found it:
|
||||
|
||||
```
|
||||
private $class = null;
|
||||
public function enterNode(Node $node) {
|
||||
if ($node instanceof Node\Stmt\Class_ &&
|
||||
$node->namespacedName->toString() === 'Foo\Bar\Baz'
|
||||
) {
|
||||
$this->class = $node;
|
||||
return NodeTraverser::STOP_TRAVERSAL;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This works both in enterNode and leaveNode. Note that this particular case can also be more easily
|
||||
handled using a NodeFinder, which will be introduced below.
|
||||
|
||||
Multiple visitors
|
||||
-----------------
|
||||
|
||||
A single traverser can be used with multiple visitors:
|
||||
|
||||
```php
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor($visitorA);
|
||||
$traverser->addVisitor($visitorB);
|
||||
$stmts = $traverser->traverse($stmts);
|
||||
```
|
||||
|
||||
It is important to understand that if a traverser is run with multiple visitors, the visitors will
|
||||
be interleaved. Given the following AST excerpt
|
||||
|
||||
```
|
||||
Stmt_Return(
|
||||
expr: Expr_Variable(
|
||||
name: foobar
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
the following method calls will be performed:
|
||||
|
||||
```
|
||||
$visitorA->enterNode(Stmt_Return)
|
||||
$visitorB->enterNode(Stmt_Return)
|
||||
$visitorA->enterNode(Expr_Variable)
|
||||
$visitorB->enterNode(Expr_Variable)
|
||||
$visitorA->leaveNode(Expr_Variable)
|
||||
$visitorB->leaveNode(Expr_Variable)
|
||||
$visitorA->leaveNode(Stmt_Return)
|
||||
$visitorB->leaveNode(Stmt_Return)
|
||||
```
|
||||
|
||||
That is, when visiting a node, enterNode and leaveNode will always be called for all visitors.
|
||||
Running multiple visitors in parallel improves performance, as the AST only has to be traversed
|
||||
once. However, it is not always possible to write visitors in a way that allows interleaved
|
||||
execution. In this case, you can always fall back to performing multiple traversals:
|
||||
|
||||
```php
|
||||
$traverserA = new NodeTraverser;
|
||||
$traverserA->addVisitor($visitorA);
|
||||
$traverserB = new NodeTraverser;
|
||||
$traverserB->addVisitor($visitorB);
|
||||
$stmts = $traverserA->traverser($stmts);
|
||||
$stmts = $traverserB->traverser($stmts);
|
||||
```
|
||||
|
||||
When using multiple visitors, it is important to understand how they interact with the various
|
||||
special enterNode/leaveNode return values:
|
||||
|
||||
* If *any* visitor returns `DONT_TRAVERSE_CHILDREN`, the children will be skipped for *all*
|
||||
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 a visitor returns a replacement node, subsequent visitors will be passed the replacement node,
|
||||
not the original one.
|
||||
* If a visitor returns `REMOVE_NODE`, subsequent visitors will not see this node.
|
||||
* If a visitor returns an array of replacement nodes, subsequent visitors will see neither the node
|
||||
that was replaced, nor the replacement nodes.
|
||||
|
||||
Simple node finding
|
||||
-------------------
|
||||
|
||||
While the node visitor mechanism is very flexible, creating a node visitor can be overly cumbersome
|
||||
for minor tasks. For this reason a `NodeFinder` is provided, which can find AST nodes that either
|
||||
satisfy a certain callback, or which are instances of a certain node type. A couple of examples are
|
||||
shown in the following:
|
||||
|
||||
```php
|
||||
use PhpParser\{Node, NodeFinder};
|
||||
|
||||
$nodeFinder = new NodeFinder;
|
||||
|
||||
// Find all class nodes.
|
||||
$classes = $nodeFinder->findInstanceOf($stmts, Node\Stmt\Class_::class);
|
||||
|
||||
// Find all classes that extend another class
|
||||
$extendingClasses = $nodeFinder->find($stmts, function(Node $node) {
|
||||
return $node instanceof Node\Stmt\Class_
|
||||
&& $node->extends !== null;
|
||||
});
|
||||
|
||||
// Find first class occurring in the AST. Returns null if no class exists.
|
||||
$class = $nodeFinder->findFirstInstanceOf($stmts, Node\Stmt\Class_::class);
|
||||
|
||||
// Find first class that has name $name
|
||||
$class = $nodeFinder->findFirst($stmts, function(Node $node) use ($name) {
|
||||
return $node instanceof Node\Stmt\Class_
|
||||
&& $node->resolvedName->toString() === $name;
|
||||
});
|
||||
```
|
||||
|
||||
Internally, the `NodeFinder` also uses a node traverser. It only simplifies the interface for a
|
||||
common use case.
|
||||
|
||||
Parent and sibling references
|
||||
-----------------------------
|
||||
|
||||
The node visitor mechanism is somewhat rigid, in that it prescribes an order in which nodes should
|
||||
be accessed: From parents to children. However, it can often be convenient to operate in the
|
||||
reverse direction: When working on a node, you might want to check if the parent node satisfies a
|
||||
certain property.
|
||||
|
||||
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.
|
@ -21,8 +21,10 @@ applied to it:
|
||||
Building the parser
|
||||
===================
|
||||
|
||||
In order to rebuild the parser, you need [moriyoshi's fork of kmyacc](https://github.com/moriyoshi/kmyacc-forked).
|
||||
After you compiled/installed it, run the `rebuildParsers.php` script.
|
||||
Run `php grammar/rebuildParsers.php` to rebuild the parsers. Additional options:
|
||||
|
||||
By default only the `Parser.php` is built. If you want to additionally emit debug symbols and create `y.output`, run the
|
||||
script with `--debug`. If you want to retain the preprocessed grammar pass `--keep-tmp-grammar`.
|
||||
* The `KMYACC` environment variable can be used to specify an alternative `kmyacc` binary.
|
||||
By default the `phpyacc` dev dependency will be used. To use the original `kmyacc`, you
|
||||
need to compile [moriyoshi's fork](https://github.com/moriyoshi/kmyacc-forked).
|
||||
* The `--debug` option enables emission of debug symbols and creates the `y.output` file.
|
||||
* The `--keep-tmp-grammar` option preserves the preprocessed grammar file.
|
||||
|
@ -16,7 +16,7 @@ top_statement_list_ex:
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -27,7 +27,8 @@ 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_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_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
|
||||
| T_MATCH
|
||||
;
|
||||
|
||||
semi_reserved:
|
||||
@ -48,13 +49,14 @@ reserved_non_modifiers_identifier:
|
||||
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
namespace_name_parts:
|
||||
T_STRING { init($1); }
|
||||
| namespace_name_parts T_NS_SEPARATOR T_STRING { push($1, $3); }
|
||||
namespace_name:
|
||||
T_STRING { $$ = Name[$1]; }
|
||||
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||
;
|
||||
|
||||
namespace_name:
|
||||
namespace_name_parts { $$ = Name[$1]; }
|
||||
legacy_namespace_name:
|
||||
namespace_name { $$ = $1; }
|
||||
| T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; }
|
||||
;
|
||||
|
||||
plain_variable:
|
||||
@ -90,16 +92,11 @@ use_type:
|
||||
| T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; }
|
||||
;
|
||||
|
||||
/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
|
||||
group_use_declaration:
|
||||
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, $2]; }
|
||||
| T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($4, stackAttributes(#4)), $7, $2]; }
|
||||
| T_USE namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($2, stackAttributes(#2)), $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| T_USE T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
|
||||
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
;
|
||||
|
||||
unprefixed_use_declarations:
|
||||
@ -126,8 +123,10 @@ unprefixed_use_declaration:
|
||||
;
|
||||
|
||||
use_declaration:
|
||||
unprefixed_use_declaration { $$ = $1; }
|
||||
| T_NS_SEPARATOR unprefixed_use_declaration { $$ = $2; }
|
||||
legacy_namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| legacy_namespace_name T_AS identifier
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
inline_use_declaration:
|
||||
@ -160,7 +159,7 @@ inner_statement_list_ex:
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -335,8 +334,8 @@ case_list:
|
||||
;
|
||||
|
||||
case:
|
||||
T_CASE expr case_separator inner_statement_list { $$ = Stmt\Case_[$2, $4]; }
|
||||
| T_DEFAULT case_separator inner_statement_list { $$ = Stmt\Case_[null, $3]; }
|
||||
T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; }
|
||||
| T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; }
|
||||
;
|
||||
|
||||
case_separator:
|
||||
@ -454,11 +453,17 @@ static_var:
|
||||
| plain_variable '=' static_scalar { $$ = Stmt\StaticVar[$1, $3]; }
|
||||
;
|
||||
|
||||
class_statement_list:
|
||||
class_statement_list class_statement { push($1, $2); }
|
||||
class_statement_list_ex:
|
||||
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
|
||||
| /* empty */ { init(); }
|
||||
;
|
||||
|
||||
class_statement_list:
|
||||
class_statement_list_ex
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
class_statement:
|
||||
variable_modifiers property_declaration_list ';'
|
||||
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
||||
@ -573,6 +578,7 @@ expr:
|
||||
| variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
|
||||
| variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$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]; }
|
||||
| T_INC variable { $$ = Expr\PreInc [$2]; }
|
||||
| variable T_DEC { $$ = Expr\PostDec[$1]; }
|
||||
@ -622,7 +628,10 @@ expr:
|
||||
| T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
|
||||
| T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
|
||||
| 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_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; }
|
||||
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
|
||||
@ -723,9 +732,10 @@ class_name:
|
||||
;
|
||||
|
||||
name:
|
||||
namespace_name_parts { $$ = Name[$1]; }
|
||||
| T_NS_SEPARATOR namespace_name_parts { $$ = Name\FullyQualified[$2]; }
|
||||
| T_NAMESPACE T_NS_SEPARATOR namespace_name_parts { $$ = Name\Relative[$3]; }
|
||||
T_STRING { $$ = Name[$1]; }
|
||||
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||
| T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; }
|
||||
| T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; }
|
||||
;
|
||||
|
||||
class_name_reference:
|
||||
@ -785,11 +795,9 @@ common_scalar:
|
||||
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
|
||||
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
|
||||
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
$$ = new Scalar\String_(Scalar\String_::parseDocString($1, $2, false), $attrs); }
|
||||
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), false); }
|
||||
| T_START_HEREDOC T_END_HEREDOC
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
$$ = new Scalar\String_('', $attrs); }
|
||||
{ $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), false); }
|
||||
;
|
||||
|
||||
static_scalar:
|
||||
@ -850,8 +858,7 @@ scalar:
|
||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
| T_START_HEREDOC encaps_list T_END_HEREDOC
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
parseEncapsedDoc($2, true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
|
||||
;
|
||||
|
||||
static_array_pair_list:
|
||||
@ -981,6 +988,7 @@ array_pair:
|
||||
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
|
||||
;
|
||||
|
||||
encaps_list:
|
||||
|
278
grammar/php7.y
278
grammar/php7.y
@ -16,7 +16,7 @@ top_statement_list_ex:
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -27,7 +27,8 @@ 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_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_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
|
||||
| T_MATCH
|
||||
;
|
||||
|
||||
semi_reserved:
|
||||
@ -48,13 +49,14 @@ reserved_non_modifiers_identifier:
|
||||
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
|
||||
;
|
||||
|
||||
namespace_name_parts:
|
||||
T_STRING { init($1); }
|
||||
| namespace_name_parts T_NS_SEPARATOR T_STRING { push($1, $3); }
|
||||
namespace_name:
|
||||
T_STRING { $$ = Name[$1]; }
|
||||
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||
;
|
||||
|
||||
namespace_name:
|
||||
namespace_name_parts { $$ = Name[$1]; }
|
||||
legacy_namespace_name:
|
||||
namespace_name { $$ = $1; }
|
||||
| T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; }
|
||||
;
|
||||
|
||||
plain_variable:
|
||||
@ -104,16 +106,11 @@ use_type:
|
||||
| T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; }
|
||||
;
|
||||
|
||||
/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
|
||||
group_use_declaration:
|
||||
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, $2]; }
|
||||
| T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($4, stackAttributes(#4)), $7, $2]; }
|
||||
| T_USE namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($2, stackAttributes(#2)), $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| T_USE T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
|
||||
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
;
|
||||
|
||||
unprefixed_use_declarations:
|
||||
@ -153,8 +150,10 @@ unprefixed_use_declaration:
|
||||
;
|
||||
|
||||
use_declaration:
|
||||
unprefixed_use_declaration { $$ = $1; }
|
||||
| T_NS_SEPARATOR unprefixed_use_declaration { $$ = $2; }
|
||||
legacy_namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| legacy_namespace_name T_AS identifier
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
inline_use_declaration:
|
||||
@ -196,7 +195,7 @@ inner_statement_list_ex:
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes, $this->endAttributes);
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
@ -232,7 +231,7 @@ non_empty_statement:
|
||||
| T_RETURN optional_expr semi { $$ = Stmt\Return_[$2]; }
|
||||
| T_GLOBAL global_var_list semi { $$ = Stmt\Global_[$2]; }
|
||||
| T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; }
|
||||
| T_ECHO expr_list semi { $$ = Stmt\Echo_[$2]; }
|
||||
| T_ECHO expr_list_forbid_comma semi { $$ = Stmt\Echo_[$2]; }
|
||||
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
|
||||
| expr semi { $$ = Stmt\Expression[$1]; }
|
||||
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
|
||||
@ -240,6 +239,8 @@ non_empty_statement:
|
||||
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
||||
| T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
|
||||
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
|
||||
| T_FOREACH '(' expr error ')' foreach_statement
|
||||
{ $$ = Stmt\Foreach_[$3, new Expr\Error(stackAttributes(#4)), ['stmts' => $6]]; }
|
||||
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
|
||||
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||
@ -267,7 +268,7 @@ name_union:
|
||||
;
|
||||
|
||||
catch:
|
||||
T_CATCH '(' name_union plain_variable ')' '{' inner_statement_list '}'
|
||||
T_CATCH '(' name_union optional_plain_variable ')' '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Catch_[$3, $4, $7]; }
|
||||
;
|
||||
|
||||
@ -277,7 +278,7 @@ optional_finally:
|
||||
;
|
||||
|
||||
variables_list:
|
||||
non_empty_variables_list no_comma { $$ = $1; }
|
||||
non_empty_variables_list optional_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
non_empty_variables_list:
|
||||
@ -295,9 +296,14 @@ optional_ellipsis:
|
||||
| T_ELLIPSIS { $$ = true; }
|
||||
;
|
||||
|
||||
block_or_error:
|
||||
'{' inner_statement_list '}' { $$ = $2; }
|
||||
| error { $$ = []; }
|
||||
;
|
||||
|
||||
function_declaration_statement:
|
||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
|
||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
|
||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8]]; }
|
||||
;
|
||||
|
||||
class_declaration_statement:
|
||||
@ -383,8 +389,8 @@ case_list:
|
||||
;
|
||||
|
||||
case:
|
||||
T_CASE expr case_separator inner_statement_list { $$ = Stmt\Case_[$2, $4]; }
|
||||
| T_DEFAULT case_separator inner_statement_list { $$ = Stmt\Case_[null, $3]; }
|
||||
T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; }
|
||||
| T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; }
|
||||
;
|
||||
|
||||
case_separator:
|
||||
@ -392,6 +398,25 @@ case_separator:
|
||||
| ';'
|
||||
;
|
||||
|
||||
match:
|
||||
T_MATCH '(' expr ')' '{' match_arm_list '}' { $$ = Expr\Match_[$3, $6]; }
|
||||
;
|
||||
|
||||
match_arm_list:
|
||||
/* empty */ { $$ = []; }
|
||||
| non_empty_match_arm_list optional_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
non_empty_match_arm_list:
|
||||
match_arm { init($1); }
|
||||
| non_empty_match_arm_list ',' match_arm { push($1, $3); }
|
||||
;
|
||||
|
||||
match_arm:
|
||||
expr_list_allow_comma T_DOUBLE_ARROW expr { $$ = Node\MatchArm[$1, $3]; }
|
||||
| T_DEFAULT optional_comma T_DOUBLE_ARROW expr { $$ = Node\MatchArm[null, $4]; }
|
||||
;
|
||||
|
||||
while_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
|
||||
@ -433,7 +458,7 @@ foreach_variable:
|
||||
;
|
||||
|
||||
parameter_list:
|
||||
non_empty_parameter_list no_comma { $$ = $1; }
|
||||
non_empty_parameter_list optional_comma { $$ = $1; }
|
||||
| /* empty */ { $$ = array(); }
|
||||
;
|
||||
|
||||
@ -442,16 +467,28 @@ non_empty_parameter_list:
|
||||
| non_empty_parameter_list ',' parameter { push($1, $3); }
|
||||
;
|
||||
|
||||
optional_visibility_modifier:
|
||||
/* empty */ { $$ = 0; }
|
||||
| T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
|
||||
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
|
||||
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
|
||||
;
|
||||
|
||||
parameter:
|
||||
optional_param_type optional_ref optional_ellipsis plain_variable
|
||||
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
||||
| optional_param_type optional_ref optional_ellipsis plain_variable '=' expr
|
||||
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
||||
optional_visibility_modifier optional_type optional_ref optional_ellipsis plain_variable
|
||||
{ $$ = new Node\Param($5, null, $2, $3, $4, attributes(), $1);
|
||||
$this->checkParam($$); }
|
||||
| optional_visibility_modifier optional_type optional_ref optional_ellipsis plain_variable '=' expr
|
||||
{ $$ = new Node\Param($5, $7, $2, $3, $4, attributes(), $1);
|
||||
$this->checkParam($$); }
|
||||
| optional_visibility_modifier optional_type optional_ref optional_ellipsis error
|
||||
{ $$ = new Node\Param(Expr\Error[], null, $2, $3, $4, attributes(), $1); }
|
||||
;
|
||||
|
||||
type_expr:
|
||||
type { $$ = $1; }
|
||||
| '?' type { $$ = Node\NullableType[$2]; }
|
||||
| union_type { $$ = Node\UnionType[$1]; }
|
||||
;
|
||||
|
||||
type:
|
||||
@ -460,7 +497,12 @@ type:
|
||||
| T_CALLABLE { $$ = Node\Identifier['callable']; }
|
||||
;
|
||||
|
||||
optional_param_type:
|
||||
union_type:
|
||||
type '|' type { init($1, $3); }
|
||||
| union_type '|' type { push($1, $3); }
|
||||
;
|
||||
|
||||
optional_type:
|
||||
/* empty */ { $$ = null; }
|
||||
| type_expr { $$ = $1; }
|
||||
;
|
||||
@ -468,11 +510,12 @@ optional_param_type:
|
||||
optional_return_type:
|
||||
/* empty */ { $$ = null; }
|
||||
| ':' type_expr { $$ = $2; }
|
||||
| ':' error { $$ = null; }
|
||||
;
|
||||
|
||||
argument_list:
|
||||
'(' ')' { $$ = array(); }
|
||||
| '(' non_empty_argument_list no_comma ')' { $$ = $2; }
|
||||
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
|
||||
;
|
||||
|
||||
non_empty_argument_list:
|
||||
@ -513,20 +556,28 @@ static_var:
|
||||
| plain_variable '=' expr { $$ = Stmt\StaticVar[$1, $3]; }
|
||||
;
|
||||
|
||||
class_statement_list:
|
||||
class_statement_list class_statement { push($1, $2); }
|
||||
class_statement_list_ex:
|
||||
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
|
||||
| /* empty */ { init(); }
|
||||
;
|
||||
|
||||
class_statement_list:
|
||||
class_statement_list_ex
|
||||
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
class_statement:
|
||||
variable_modifiers property_declaration_list ';'
|
||||
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
||||
variable_modifiers optional_type property_declaration_list ';'
|
||||
{ $attrs = attributes();
|
||||
$$ = new Stmt\Property($1, $3, $attrs, $2); $this->checkProperty($$, #1); }
|
||||
| method_modifiers T_CONST class_const_list ';'
|
||||
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
|
||||
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
|
||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
|
||||
$this->checkClassMethod($$, #1); }
|
||||
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
||||
| error { $$ = null; /* will be skipped */ }
|
||||
;
|
||||
|
||||
trait_adaptations:
|
||||
@ -562,7 +613,7 @@ trait_method_reference:
|
||||
|
||||
method_body:
|
||||
';' /* abstract method */ { $$ = null; }
|
||||
| '{' inner_statement_list '}' { $$ = $2; }
|
||||
| block_or_error { $$ = $1; }
|
||||
;
|
||||
|
||||
variable_modifiers:
|
||||
@ -608,10 +659,14 @@ property_declaration:
|
||||
| property_decl_name '=' expr { $$ = Stmt\PropertyProperty[$1, $3]; }
|
||||
;
|
||||
|
||||
expr_list:
|
||||
expr_list_forbid_comma:
|
||||
non_empty_expr_list no_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
expr_list_allow_comma:
|
||||
non_empty_expr_list optional_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
non_empty_expr_list:
|
||||
non_empty_expr_list ',' expr { push($1, $3); }
|
||||
| expr { init($1); }
|
||||
@ -619,7 +674,7 @@ non_empty_expr_list:
|
||||
|
||||
for_expr:
|
||||
/* empty */ { $$ = array(); }
|
||||
| expr_list { $$ = $1; }
|
||||
| expr_list_forbid_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
expr:
|
||||
@ -629,6 +684,7 @@ expr:
|
||||
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
|
||||
| new_expr { $$ = $1; }
|
||||
| match { $$ = $1; }
|
||||
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
||||
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
|
||||
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }
|
||||
@ -642,6 +698,7 @@ expr:
|
||||
| variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
|
||||
| variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$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]; }
|
||||
| T_INC variable { $$ = Expr\PreInc [$2]; }
|
||||
| variable T_DEC { $$ = Expr\PostDec[$1]; }
|
||||
@ -681,7 +738,7 @@ expr:
|
||||
| expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; }
|
||||
| expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; }
|
||||
| expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }
|
||||
| T_ISSET '(' variables_list ')' { $$ = Expr\Isset_[$3]; }
|
||||
| T_ISSET '(' expr_list_allow_comma ')' { $$ = Expr\Isset_[$3]; }
|
||||
| T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; }
|
||||
| T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; }
|
||||
| T_INCLUDE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; }
|
||||
@ -689,7 +746,10 @@ expr:
|
||||
| T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
|
||||
| T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
|
||||
| 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_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; }
|
||||
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
|
||||
@ -707,12 +767,18 @@ expr:
|
||||
| T_YIELD expr { $$ = Expr\Yield_[$2, null]; }
|
||||
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $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
|
||||
'{' inner_statement_list '}'
|
||||
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $9]]; }
|
||||
block_or_error
|
||||
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; }
|
||||
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
|
||||
'{' inner_statement_list '}'
|
||||
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $10]]; }
|
||||
block_or_error
|
||||
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9]]; }
|
||||
;
|
||||
|
||||
anonymous_class:
|
||||
@ -733,7 +799,7 @@ lexical_vars:
|
||||
;
|
||||
|
||||
lexical_var_list:
|
||||
non_empty_lexical_var_list no_comma { $$ = $1; }
|
||||
non_empty_lexical_var_list optional_comma { $$ = $1; }
|
||||
;
|
||||
|
||||
non_empty_lexical_var_list:
|
||||
@ -758,20 +824,22 @@ class_name:
|
||||
;
|
||||
|
||||
name:
|
||||
namespace_name_parts { $$ = Name[$1]; }
|
||||
| T_NS_SEPARATOR namespace_name_parts { $$ = Name\FullyQualified[$2]; }
|
||||
| T_NAMESPACE T_NS_SEPARATOR namespace_name_parts { $$ = Name\Relative[$3]; }
|
||||
T_STRING { $$ = Name[$1]; }
|
||||
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||
| T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; }
|
||||
| T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; }
|
||||
;
|
||||
|
||||
class_name_reference:
|
||||
class_name { $$ = $1; }
|
||||
| new_variable { $$ = $1; }
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
class_name_or_var:
|
||||
class_name { $$ = $1; }
|
||||
| dereferencable { $$ = $1; }
|
||||
| fully_dereferencable { $$ = $1; }
|
||||
;
|
||||
|
||||
exit_expr:
|
||||
@ -793,9 +861,20 @@ ctor_arguments:
|
||||
|
||||
constant:
|
||||
name { $$ = Expr\ConstFetch[$1]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
|
||||
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
|
||||
| T_FILE { $$ = Scalar\MagicConst\File[]; }
|
||||
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
|
||||
| T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; }
|
||||
| T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; }
|
||||
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
|
||||
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
|
||||
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
|
||||
;
|
||||
|
||||
class_constant:
|
||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
|
||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
/* We interpret and isolated FOO:: as an unfinished class constant fetch. It could also be
|
||||
/* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be
|
||||
an unfinished static property fetch or unfinished scoped call. */
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error
|
||||
{ $$ = Expr\ClassConstFetch[$1, new Expr\Error(stackAttributes(#3))]; $this->errorState = 2; }
|
||||
@ -815,33 +894,23 @@ dereferencable_scalar:
|
||||
| T_CONSTANT_ENCAPSED_STRING
|
||||
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
|
||||
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
|
||||
| '"' encaps_list '"'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
;
|
||||
|
||||
scalar:
|
||||
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
|
||||
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
|
||||
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
|
||||
| T_FILE { $$ = Scalar\MagicConst\File[]; }
|
||||
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
|
||||
| T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; }
|
||||
| T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; }
|
||||
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
|
||||
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
|
||||
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
|
||||
| dereferencable_scalar { $$ = $1; }
|
||||
| constant { $$ = $1; }
|
||||
| class_constant { $$ = $1; }
|
||||
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
$$ = new Scalar\String_(Scalar\String_::parseDocString($1, $2), $attrs); }
|
||||
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
|
||||
| T_START_HEREDOC T_END_HEREDOC
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
$$ = new Scalar\String_('', $attrs); }
|
||||
| '"' encaps_list '"'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
{ $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), true); }
|
||||
| T_START_HEREDOC encaps_list T_END_HEREDOC
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
parseEncapsedDoc($2, true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
|
||||
;
|
||||
|
||||
optional_expr:
|
||||
@ -849,10 +918,16 @@ optional_expr:
|
||||
| expr { $$ = $1; }
|
||||
;
|
||||
|
||||
dereferencable:
|
||||
fully_dereferencable:
|
||||
variable { $$ = $1; }
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
| dereferencable_scalar { $$ = $1; }
|
||||
| class_constant { $$ = $1; }
|
||||
;
|
||||
|
||||
array_object_dereferencable:
|
||||
fully_dereferencable { $$ = $1; }
|
||||
| constant { $$ = $1; }
|
||||
;
|
||||
|
||||
callable_expr:
|
||||
@ -863,18 +938,27 @@ callable_expr:
|
||||
|
||||
callable_variable:
|
||||
simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| constant '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| function_call { $$ = $1; }
|
||||
| dereferencable T_OBJECT_OPERATOR property_name argument_list
|
||||
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
|
||||
{ $$ = Expr\MethodCall[$1, $3, $4]; }
|
||||
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
|
||||
{ $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
|
||||
;
|
||||
|
||||
optional_plain_variable:
|
||||
/* empty */ { $$ = null; }
|
||||
| plain_variable { $$ = $1; }
|
||||
;
|
||||
|
||||
variable:
|
||||
callable_variable { $$ = $1; }
|
||||
| static_member { $$ = $1; }
|
||||
| dereferencable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| array_object_dereferencable T_OBJECT_OPERATOR property_name
|
||||
{ $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name
|
||||
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
;
|
||||
|
||||
simple_variable:
|
||||
@ -899,6 +983,7 @@ new_variable:
|
||||
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
||||
| new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||
@ -907,32 +992,19 @@ new_variable:
|
||||
|
||||
member_name:
|
||||
identifier_ex { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
;
|
||||
|
||||
property_name:
|
||||
identifier { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
list_expr:
|
||||
T_LIST '(' list_expr_elements ')' { $$ = Expr\List_[$3]; }
|
||||
;
|
||||
|
||||
list_expr_elements:
|
||||
list_expr_elements ',' list_expr_element { push($1, $3); }
|
||||
| list_expr_element { init($1); }
|
||||
;
|
||||
|
||||
list_expr_element:
|
||||
variable { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW variable { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| /* empty */ { $$ = null; }
|
||||
T_LIST '(' inner_array_pair_list ')' { $$ = Expr\List_[$3]; }
|
||||
;
|
||||
|
||||
array_pair_list:
|
||||
@ -940,16 +1012,25 @@ array_pair_list:
|
||||
{ $$ = $1; $end = count($$)-1; if ($$[$end] === null) array_pop($$); }
|
||||
;
|
||||
|
||||
comma_or_error:
|
||||
','
|
||||
| error
|
||||
{ /* do nothing -- prevent default action of $$=$1. See #551. */ }
|
||||
;
|
||||
|
||||
inner_array_pair_list:
|
||||
inner_array_pair_list ',' array_pair { push($1, $3); }
|
||||
inner_array_pair_list comma_or_error array_pair { push($1, $3); }
|
||||
| array_pair { init($1); }
|
||||
;
|
||||
|
||||
array_pair:
|
||||
expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
|
||||
| /* empty */ { $$ = null; }
|
||||
;
|
||||
|
||||
@ -972,6 +1053,7 @@ encaps_var:
|
||||
plain_variable { $$ = $1; }
|
||||
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
|
||||
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
|
||||
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
|
||||
|
@ -13,10 +13,10 @@ $tmpResultFile = __DIR__ . '/tmp_parser.php';
|
||||
$resultDir = __DIR__ . '/../lib/PhpParser/Parser';
|
||||
$tokensResultsFile = $resultDir . '/Tokens.php';
|
||||
|
||||
// check for kmyacc.exe binary in this directory, otherwise fall back to global name
|
||||
$kmyacc = __DIR__ . '/kmyacc.exe';
|
||||
if (!file_exists($kmyacc)) {
|
||||
$kmyacc = 'kmyacc';
|
||||
$kmyacc = getenv('KMYACC');
|
||||
if (!$kmyacc) {
|
||||
// Use phpyacc from dev dependencies by default.
|
||||
$kmyacc = __DIR__ . '/../vendor/bin/phpyacc';
|
||||
}
|
||||
|
||||
$options = array_flip($argv);
|
||||
@ -59,8 +59,7 @@ foreach ($grammarFileToName as $grammarFile => $name) {
|
||||
$additionalArgs = $optionDebug ? '-t -v' : '';
|
||||
|
||||
echo "Building $name parser.\n";
|
||||
$output = trim(shell_exec("$kmyacc $additionalArgs -l -m $skeletonFile -p $name $tmpGrammarFile 2>&1"));
|
||||
echo "Output: \"$output\"\n";
|
||||
$output = execCmd("$kmyacc $additionalArgs -m $skeletonFile -p $name $tmpGrammarFile");
|
||||
|
||||
$resultCode = file_get_contents($tmpResultFile);
|
||||
$resultCode = removeTrailingWhitespace($resultCode);
|
||||
@ -70,8 +69,7 @@ foreach ($grammarFileToName as $grammarFile => $name) {
|
||||
unlink($tmpResultFile);
|
||||
|
||||
echo "Building token definition.\n";
|
||||
$output = trim(shell_exec("$kmyacc -l -m $tokensTemplate $tmpGrammarFile 2>&1"));
|
||||
assert($output === '');
|
||||
$output = execCmd("$kmyacc -m $tokensTemplate $tmpGrammarFile");
|
||||
rename($tmpResultFile, $tokensResultsFile);
|
||||
|
||||
if (!$optionKeepTmpGrammar) {
|
||||
@ -166,15 +164,6 @@ function resolveMacros($code) {
|
||||
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
|
||||
}
|
||||
|
||||
if ('parseEncapsedDoc' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
||||
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, ' . $args[1] . '); } }'
|
||||
. ' $s->value = preg_replace(\'~(\r\n|\n|\r)\z~\', \'\', $s->value);'
|
||||
. ' if (\'\' === $s->value) array_pop(' . $args[0] . ');';
|
||||
}
|
||||
|
||||
if ('makeNop' == $name) {
|
||||
assertArgs(3, $args, $name);
|
||||
|
||||
@ -184,6 +173,15 @@ function resolveMacros($code) {
|
||||
. ' 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->createCommentNopAttributes($startAttributes[\'comments\'])); }'
|
||||
. ' else { ' . $args[0] . ' = null; }';
|
||||
}
|
||||
|
||||
if ('strKind' == $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
@ -192,15 +190,6 @@ function resolveMacros($code) {
|
||||
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
|
||||
}
|
||||
|
||||
if ('setDocStringAttrs' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return $args[0] . '[\'kind\'] = strpos(' . $args[1] . ', "\'") === false '
|
||||
. '? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; '
|
||||
. 'preg_match(\'/\A[bB]?<<<[ \t]*[\\\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\\\'"]?(?:\r\n|\n|\r)\z/\', ' . $args[1] . ', $matches); '
|
||||
. $args[0] . '[\'docLabel\'] = $matches[1];';
|
||||
}
|
||||
|
||||
if ('prependLeadingComments' == $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
@ -240,6 +229,15 @@ function ensureDirExists($dir) {
|
||||
}
|
||||
}
|
||||
|
||||
function execCmd($cmd) {
|
||||
$output = trim(shell_exec("$cmd 2>&1"));
|
||||
if ($output !== "") {
|
||||
echo "> " . $cmd . "\n";
|
||||
echo $output;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
/// Regex helper functions ///
|
||||
//////////////////////////////
|
||||
|
@ -10,7 +10,7 @@
|
||||
%right T_YIELD
|
||||
%right T_DOUBLE_ARROW
|
||||
%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 '?' ':'
|
||||
%right T_COALESCE
|
||||
%left T_BOOLEAN_OR
|
||||
@ -41,8 +41,6 @@
|
||||
%token T_VARIABLE
|
||||
%token T_NUM_STRING
|
||||
%token T_INLINE_HTML
|
||||
%token T_CHARACTER
|
||||
%token T_BAD_CHARACTER
|
||||
%token T_ENCAPSED_AND_WHITESPACE
|
||||
%token T_CONSTANT_ENCAPSED_STRING
|
||||
%token T_ECHO
|
||||
@ -57,6 +55,7 @@
|
||||
%token T_ENDDECLARE
|
||||
%token T_AS
|
||||
%token T_SWITCH
|
||||
%token T_MATCH
|
||||
%token T_ENDSWITCH
|
||||
%token T_CASE
|
||||
%token T_DEFAULT
|
||||
@ -64,6 +63,7 @@
|
||||
%token T_CONTINUE
|
||||
%token T_GOTO
|
||||
%token T_FUNCTION
|
||||
%token T_FN
|
||||
%token T_CONST
|
||||
%token T_RETURN
|
||||
%token T_TRY
|
||||
@ -85,6 +85,7 @@
|
||||
%token T_EXTENDS
|
||||
%token T_IMPLEMENTS
|
||||
%token T_OBJECT_OPERATOR
|
||||
%token T_NULLSAFE_OBJECT_OPERATOR
|
||||
%token T_DOUBLE_ARROW
|
||||
%token T_LIST
|
||||
%token T_ARRAY
|
||||
@ -95,12 +96,6 @@
|
||||
%token T_FUNC_C
|
||||
%token T_LINE
|
||||
%token T_FILE
|
||||
%token T_COMMENT
|
||||
%token T_DOC_COMMENT
|
||||
%token T_OPEN_TAG
|
||||
%token T_OPEN_TAG_WITH_ECHO
|
||||
%token T_CLOSE_TAG
|
||||
%token T_WHITESPACE
|
||||
%token T_START_HEREDOC
|
||||
%token T_END_HEREDOC
|
||||
%token T_DOLLAR_OPEN_CURLY_BRACES
|
||||
@ -111,3 +106,6 @@
|
||||
%token T_DIR
|
||||
%token T_NS_SEPARATOR
|
||||
%token T_ELLIPSIS
|
||||
%token T_NAME_FULLY_QUALIFIED
|
||||
%token T_NAME_QUALIFIED
|
||||
%token T_NAME_RELATIVE
|
@ -12,7 +12,7 @@ class Param implements PhpParser\Builder
|
||||
|
||||
protected $default = null;
|
||||
|
||||
/** @var string|Node\Name|Node\NullableType|null */
|
||||
/** @var Node\Identifier|Node\Name|Node\NullableType|null */
|
||||
protected $type = null;
|
||||
|
||||
protected $byRef = false;
|
||||
@ -42,13 +42,13 @@ class Param implements PhpParser\Builder
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type hint for the parameter.
|
||||
* Sets type for the parameter.
|
||||
*
|
||||
* @param string|Node\Name|Node\NullableType $type Type hint to use
|
||||
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setTypeHint($type) {
|
||||
public function setType($type) {
|
||||
$this->type = BuilderHelpers::normalizeType($type);
|
||||
if ($this->type == 'void') {
|
||||
throw new \LogicException('Parameter type cannot be void');
|
||||
@ -57,6 +57,19 @@ class Param implements PhpParser\Builder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type for the parameter.
|
||||
*
|
||||
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*
|
||||
* @deprecated Use setType() instead
|
||||
*/
|
||||
public function setTypeHint($type) {
|
||||
return $this->setType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the parameter accept the value by reference.
|
||||
*
|
||||
|
@ -4,6 +4,9 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Property implements PhpParser\Builder
|
||||
@ -14,6 +17,9 @@ class Property implements PhpParser\Builder
|
||||
protected $default = null;
|
||||
protected $attributes = [];
|
||||
|
||||
/** @var null|Identifier|Name|NullableType */
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Creates a property builder.
|
||||
*
|
||||
@ -95,6 +101,19 @@ class Property implements PhpParser\Builder
|
||||
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.
|
||||
*
|
||||
@ -106,7 +125,8 @@ class Property implements PhpParser\Builder
|
||||
[
|
||||
new Stmt\PropertyProperty($this->name, $this->default)
|
||||
],
|
||||
$this->attributes
|
||||
$this->attributes,
|
||||
$this->type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
64
lib/PhpParser/Builder/TraitUse.php
Normal file
64
lib/PhpParser/Builder/TraitUse.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class TraitUse implements Builder
|
||||
{
|
||||
protected $traits = [];
|
||||
protected $adaptations = [];
|
||||
|
||||
/**
|
||||
* Creates a trait use builder.
|
||||
*
|
||||
* @param Node\Name|string ...$traits Names of used traits
|
||||
*/
|
||||
public function __construct(...$traits) {
|
||||
foreach ($traits as $trait) {
|
||||
$this->and($trait);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds used trait.
|
||||
*
|
||||
* @param Node\Name|string $trait Trait name
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function and($trait) {
|
||||
$this->traits[] = BuilderHelpers::normalizeName($trait);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds trait adaptation.
|
||||
*
|
||||
* @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function with($adaptation) {
|
||||
$adaptation = BuilderHelpers::normalizeNode($adaptation);
|
||||
|
||||
if (!$adaptation instanceof Stmt\TraitUseAdaptation) {
|
||||
throw new \LogicException('Adaptation must have type TraitUseAdaptation');
|
||||
}
|
||||
|
||||
$this->adaptations[] = $adaptation;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built node.
|
||||
*
|
||||
* @return Node The built node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
return new Stmt\TraitUse($this->traits, $this->adaptations);
|
||||
}
|
||||
}
|
148
lib/PhpParser/Builder/TraitUseAdaptation.php
Normal file
148
lib/PhpParser/Builder/TraitUseAdaptation.php
Normal file
@ -0,0 +1,148 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Builder;
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class TraitUseAdaptation implements Builder
|
||||
{
|
||||
const TYPE_UNDEFINED = 0;
|
||||
const TYPE_ALIAS = 1;
|
||||
const TYPE_PRECEDENCE = 2;
|
||||
|
||||
/** @var int Type of building adaptation */
|
||||
protected $type;
|
||||
|
||||
protected $trait;
|
||||
protected $method;
|
||||
|
||||
protected $modifier = null;
|
||||
protected $alias = null;
|
||||
|
||||
protected $insteadof = [];
|
||||
|
||||
/**
|
||||
* Creates a trait use adaptation builder.
|
||||
*
|
||||
* @param Node\Name|string|null $trait Name of adaptated trait
|
||||
* @param Node\Identifier|string $method Name of adaptated method
|
||||
*/
|
||||
public function __construct($trait, $method) {
|
||||
$this->type = self::TYPE_UNDEFINED;
|
||||
|
||||
$this->trait = is_null($trait)? null: BuilderHelpers::normalizeName($trait);
|
||||
$this->method = BuilderHelpers::normalizeIdentifier($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets alias of method.
|
||||
*
|
||||
* @param Node\Identifier|string $alias Alias for adaptated method
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function as($alias) {
|
||||
if ($this->type === self::TYPE_UNDEFINED) {
|
||||
$this->type = self::TYPE_ALIAS;
|
||||
}
|
||||
|
||||
if ($this->type !== self::TYPE_ALIAS) {
|
||||
throw new \LogicException('Cannot set alias for not alias adaptation buider');
|
||||
}
|
||||
|
||||
$this->alias = $alias;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets adaptated method public.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePublic() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets adaptated method protected.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makeProtected() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets adaptated method private.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function makePrivate() {
|
||||
$this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds overwritten traits.
|
||||
*
|
||||
* @param Node\Name|string ...$traits Traits for overwrite
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function insteadof(...$traits) {
|
||||
if ($this->type === self::TYPE_UNDEFINED) {
|
||||
if (is_null($this->trait)) {
|
||||
throw new \LogicException('Precedence adaptation must have trait');
|
||||
}
|
||||
|
||||
$this->type = self::TYPE_PRECEDENCE;
|
||||
}
|
||||
|
||||
if ($this->type !== self::TYPE_PRECEDENCE) {
|
||||
throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider');
|
||||
}
|
||||
|
||||
foreach ($traits as $trait) {
|
||||
$this->insteadof[] = BuilderHelpers::normalizeName($trait);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function setModifier(int $modifier) {
|
||||
if ($this->type === self::TYPE_UNDEFINED) {
|
||||
$this->type = self::TYPE_ALIAS;
|
||||
}
|
||||
|
||||
if ($this->type !== self::TYPE_ALIAS) {
|
||||
throw new \LogicException('Cannot set access modifier for not alias adaptation buider');
|
||||
}
|
||||
|
||||
if (is_null($this->modifier)) {
|
||||
$this->modifier = $modifier;
|
||||
} else {
|
||||
throw new \LogicException('Multiple access type modifiers are not allowed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the built node.
|
||||
*
|
||||
* @return Node The built node
|
||||
*/
|
||||
public function getNode() : Node {
|
||||
switch ($this->type) {
|
||||
case self::TYPE_ALIAS:
|
||||
return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias);
|
||||
case self::TYPE_PRECEDENCE:
|
||||
return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof);
|
||||
default:
|
||||
throw new \LogicException('Type of adaptation is not defined');
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ namespace PhpParser;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
|
||||
@ -54,6 +56,34 @@ class BuilderFactory
|
||||
return new Builder\Trait_($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trait use builder.
|
||||
*
|
||||
* @param Node\Name|string ...$traits Trait names
|
||||
*
|
||||
* @return Builder\TraitUse The create trait use builder
|
||||
*/
|
||||
public function useTrait(...$traits) : Builder\TraitUse {
|
||||
return new Builder\TraitUse(...$traits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trait use adaptation builder.
|
||||
*
|
||||
* @param Node\Name|string|null $trait Trait name
|
||||
* @param Node\Identifier|string $method Method name
|
||||
*
|
||||
* @return Builder\TraitUseAdaptation The create trait use adaptation builder
|
||||
*/
|
||||
public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation {
|
||||
if ($method === null) {
|
||||
$method = $trait;
|
||||
$trait = null;
|
||||
}
|
||||
|
||||
return new Builder\TraitUseAdaptation($trait, $method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a method builder.
|
||||
*
|
||||
@ -101,14 +131,36 @@ class BuilderFactory
|
||||
/**
|
||||
* Creates a namespace/class use builder.
|
||||
*
|
||||
* @param string|Node\Name Name to alias
|
||||
* @param Node\Name|string $name Name of the entity (namespace or class) to alias
|
||||
*
|
||||
* @return Builder\Use_ The create use builder
|
||||
* @return Builder\Use_ The created use builder
|
||||
*/
|
||||
public function use($name) : Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function use builder.
|
||||
*
|
||||
* @param Node\Name|string $name Name of the function to alias
|
||||
*
|
||||
* @return Builder\Use_ The created use function builder
|
||||
*/
|
||||
public function useFunction($name) : Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_FUNCTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a constant use builder.
|
||||
*
|
||||
* @param Node\Name|string $name Name of the const to alias
|
||||
*
|
||||
* @return Builder\Use_ The created use const builder
|
||||
*/
|
||||
public function useConst($name) : Builder\Use_ {
|
||||
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates node a for a literal value.
|
||||
*
|
||||
@ -120,6 +172,21 @@ class BuilderFactory
|
||||
return BuilderHelpers::normalizeValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates variable node.
|
||||
*
|
||||
* @param string|Expr $name Name
|
||||
*
|
||||
* @return Expr\Variable
|
||||
*/
|
||||
public function var($name) : Expr\Variable {
|
||||
if (!\is_string($name) && !$name instanceof Expr) {
|
||||
throw new \LogicException('Variable name must be string or Expr');
|
||||
}
|
||||
|
||||
return new Expr\Variable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an argument list.
|
||||
*
|
||||
@ -141,6 +208,108 @@ class BuilderFactory
|
||||
return $normalizedArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function call node.
|
||||
*
|
||||
* @param string|Name|Expr $name Function name
|
||||
* @param array $args Function arguments
|
||||
*
|
||||
* @return Expr\FuncCall
|
||||
*/
|
||||
public function funcCall($name, array $args = []) : Expr\FuncCall {
|
||||
return new Expr\FuncCall(
|
||||
BuilderHelpers::normalizeNameOrExpr($name),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a method call node.
|
||||
*
|
||||
* @param Expr $var Variable the method is called on
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array $args Method arguments
|
||||
*
|
||||
* @return Expr\MethodCall
|
||||
*/
|
||||
public function methodCall(Expr $var, $name, array $args = []) : Expr\MethodCall {
|
||||
return new Expr\MethodCall(
|
||||
$var,
|
||||
BuilderHelpers::normalizeIdentifierOrExpr($name),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a static method call node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param array $args Method arguments
|
||||
*
|
||||
* @return Expr\StaticCall
|
||||
*/
|
||||
public function staticCall($class, $name, array $args = []) : Expr\StaticCall {
|
||||
return new Expr\StaticCall(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
BuilderHelpers::normalizeIdentifierOrExpr($name),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object creation node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param array $args Constructor arguments
|
||||
*
|
||||
* @return Expr\New_
|
||||
*/
|
||||
public function new($class, array $args = []) : Expr\New_ {
|
||||
return new Expr\New_(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
$this->args($args)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a constant fetch node.
|
||||
*
|
||||
* @param string|Name $name Constant name
|
||||
*
|
||||
* @return Expr\ConstFetch
|
||||
*/
|
||||
public function constFetch($name) : Expr\ConstFetch {
|
||||
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a property fetch node.
|
||||
*
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Property name
|
||||
*
|
||||
* @return Expr\PropertyFetch
|
||||
*/
|
||||
public function propertyFetch(Expr $var, $name) : Expr\PropertyFetch {
|
||||
return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a class constant fetch node.
|
||||
*
|
||||
* @param string|Name|Expr $class Class name
|
||||
* @param string|Identifier $name Constant name
|
||||
*
|
||||
* @return Expr\ClassConstFetch
|
||||
*/
|
||||
public function classConstFetch($class, $name): Expr\ClassConstFetch {
|
||||
return new Expr\ClassConstFetch(
|
||||
BuilderHelpers::normalizeNameOrExpr($class),
|
||||
BuilderHelpers::normalizeIdentifier($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates nested Concat nodes from a list of expressions.
|
||||
*
|
||||
@ -161,12 +330,16 @@ class BuilderFactory
|
||||
return $lastConcat;
|
||||
}
|
||||
|
||||
private function normalizeStringExpr($expr) {
|
||||
/**
|
||||
* @param string|Expr $expr
|
||||
* @return Expr
|
||||
*/
|
||||
private function normalizeStringExpr($expr) : Expr {
|
||||
if ($expr instanceof Expr) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
if (is_string($expr)) {
|
||||
if (\is_string($expr)) {
|
||||
return new String_($expr);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\UnionType;
|
||||
|
||||
/**
|
||||
* This class defines helpers used in the implementation of builders. Don't use it directly.
|
||||
@ -56,13 +57,74 @@ final class BuilderHelpers
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts plain string names to PhpParser\Node\Name.
|
||||
* Normalizes strings to Identifier.
|
||||
*
|
||||
* @param string|Identifier $name The identifier to normalize
|
||||
*
|
||||
* @return Identifier The normalized identifier
|
||||
*/
|
||||
public static function normalizeIdentifier($name) : Identifier {
|
||||
if ($name instanceof Identifier) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if (\is_string($name)) {
|
||||
return new Identifier($name);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected string or instance of Node\Identifier');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes strings to Identifier, also allowing expressions.
|
||||
*
|
||||
* @param string|Identifier|Expr $name The identifier to normalize
|
||||
*
|
||||
* @return Identifier|Expr The normalized identifier or expression
|
||||
*/
|
||||
public static function normalizeIdentifierOrExpr($name) {
|
||||
if ($name instanceof Identifier || $name instanceof Expr) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
if (\is_string($name)) {
|
||||
return new Identifier($name);
|
||||
}
|
||||
|
||||
throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts string names to Name nodes.
|
||||
*
|
||||
* @param Name|string $name The name to normalize
|
||||
*
|
||||
* @return Name The normalized name
|
||||
*/
|
||||
public static function normalizeName($name) : Name {
|
||||
return self::normalizeNameCommon($name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts string names to Name nodes, while also allowing expressions.
|
||||
*
|
||||
* @param Expr|Name|string $name The name to normalize
|
||||
*
|
||||
* @return Name|Expr The normalized name or expression
|
||||
*/
|
||||
public static function normalizeNameOrExpr($name) {
|
||||
return self::normalizeNameCommon($name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a name: Converts string names to Name nodes, optionally allowing expressions.
|
||||
*
|
||||
* @param Expr|Name|string $name The name to normalize
|
||||
* @param bool $allowExpr Whether to also allow expressions
|
||||
*
|
||||
* @return Name|Expr The normalized name, or expression (if allowed)
|
||||
*/
|
||||
private static function normalizeNameCommon($name, bool $allowExpr) {
|
||||
if ($name instanceof Name) {
|
||||
return $name;
|
||||
} elseif (is_string($name)) {
|
||||
@ -79,7 +141,16 @@ final class BuilderHelpers
|
||||
}
|
||||
}
|
||||
|
||||
throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
|
||||
if ($allowExpr) {
|
||||
if ($name instanceof Expr) {
|
||||
return $name;
|
||||
}
|
||||
throw new \LogicException(
|
||||
'Name must be a string or an instance of Node\Name or Node\Expr'
|
||||
);
|
||||
} else {
|
||||
throw new \LogicException('Name must be a string or an instance of Node\Name');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,16 +159,19 @@ final class BuilderHelpers
|
||||
* In particular, builtin types become Identifiers, custom types become Names and nullables
|
||||
* are wrapped in NullableType nodes.
|
||||
*
|
||||
* @param string|Name|Identifier|NullableType $type The type to normalize
|
||||
* @param string|Name|Identifier|NullableType|UnionType $type The type to normalize
|
||||
*
|
||||
* @return Name|Identifier|NullableType The normalized type
|
||||
* @return Name|Identifier|NullableType|UnionType The normalized type
|
||||
*/
|
||||
public static function normalizeType($type) {
|
||||
if (!is_string($type)) {
|
||||
if (!$type instanceof Name && !$type instanceof Identifier
|
||||
&& !$type instanceof NullableType) {
|
||||
if (
|
||||
!$type instanceof Name && !$type instanceof Identifier &&
|
||||
!$type instanceof NullableType && !$type instanceof UnionType
|
||||
) {
|
||||
throw new \LogicException(
|
||||
'Type must be a string, or an instance of Name, Identifier or NullableType');
|
||||
'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType'
|
||||
);
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
@ -109,7 +183,7 @@ final class BuilderHelpers
|
||||
}
|
||||
|
||||
$builtinTypes = [
|
||||
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object'
|
||||
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed'
|
||||
];
|
||||
|
||||
$lowerType = strtolower($type);
|
||||
@ -123,7 +197,11 @@ final class BuilderHelpers
|
||||
throw new \LogicException('void type cannot be nullable');
|
||||
}
|
||||
|
||||
return $nullable ? new Node\NullableType($type) : $type;
|
||||
if ($nullable && (string) $type === 'mixed') {
|
||||
throw new \LogicException('mixed type cannot be nullable');
|
||||
}
|
||||
|
||||
return $nullable ? new NullableType($type) : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,9 +5,12 @@ namespace PhpParser;
|
||||
class Comment implements \JsonSerializable
|
||||
{
|
||||
protected $text;
|
||||
protected $line;
|
||||
protected $filePos;
|
||||
protected $tokenPos;
|
||||
protected $startLine;
|
||||
protected $startFilePos;
|
||||
protected $startTokenPos;
|
||||
protected $endLine;
|
||||
protected $endFilePos;
|
||||
protected $endTokenPos;
|
||||
|
||||
/**
|
||||
* Constructs a comment node.
|
||||
@ -18,12 +21,17 @@ class Comment implements \JsonSerializable
|
||||
* @param int $startTokenPos Token offset the comment started on
|
||||
*/
|
||||
public function __construct(
|
||||
string $text, int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1
|
||||
string $text,
|
||||
int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1,
|
||||
int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1
|
||||
) {
|
||||
$this->text = $text;
|
||||
$this->line = $startLine;
|
||||
$this->filePos = $startFilePos;
|
||||
$this->tokenPos = $startTokenPos;
|
||||
$this->startLine = $startLine;
|
||||
$this->startFilePos = $startFilePos;
|
||||
$this->startTokenPos = $startTokenPos;
|
||||
$this->endLine = $endLine;
|
||||
$this->endFilePos = $endFilePos;
|
||||
$this->endTokenPos = $endTokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,28 +46,88 @@ class Comment implements \JsonSerializable
|
||||
/**
|
||||
* Gets the line number the comment started on.
|
||||
*
|
||||
* @return int Line number
|
||||
* @return int Line number (or -1 if not available)
|
||||
*/
|
||||
public function getLine() : int {
|
||||
return $this->line;
|
||||
public function getStartLine() : int {
|
||||
return $this->startLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file offset the comment started on.
|
||||
*
|
||||
* @return int File offset
|
||||
* @return int File offset (or -1 if not available)
|
||||
*/
|
||||
public function getFilePos() : int {
|
||||
return $this->filePos;
|
||||
public function getStartFilePos() : int {
|
||||
return $this->startFilePos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token offset the comment started on.
|
||||
*
|
||||
* @return int Token offset (or -1 if not available)
|
||||
*/
|
||||
public function getStartTokenPos() : int {
|
||||
return $this->startTokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number the comment ends on.
|
||||
*
|
||||
* @return int Line number (or -1 if not available)
|
||||
*/
|
||||
public function getEndLine() : int {
|
||||
return $this->endLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file offset the comment ends on.
|
||||
*
|
||||
* @return int File offset (or -1 if not available)
|
||||
*/
|
||||
public function getEndFilePos() : int {
|
||||
return $this->endFilePos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token offset the comment ends on.
|
||||
*
|
||||
* @return int Token offset (or -1 if not available)
|
||||
*/
|
||||
public function getEndTokenPos() : int {
|
||||
return $this->endTokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number the comment started on.
|
||||
*
|
||||
* @deprecated Use getStartLine() instead
|
||||
*
|
||||
* @return int Line number
|
||||
*/
|
||||
public function getLine() : int {
|
||||
return $this->startLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file offset the comment started on.
|
||||
*
|
||||
* @deprecated Use getStartFilePos() instead
|
||||
*
|
||||
* @return int File offset
|
||||
*/
|
||||
public function getFilePos() : int {
|
||||
return $this->startFilePos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token offset the comment started on.
|
||||
*
|
||||
* @deprecated Use getStartTokenPos() instead
|
||||
*
|
||||
* @return int Token offset
|
||||
*/
|
||||
public function getTokenPos() : int {
|
||||
return $this->tokenPos;
|
||||
return $this->startTokenPos;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,9 +227,13 @@ class Comment implements \JsonSerializable
|
||||
return [
|
||||
'nodeType' => $type,
|
||||
'text' => $this->text,
|
||||
'line' => $this->line,
|
||||
'filePos' => $this->filePos,
|
||||
'tokenPos' => $this->tokenPos,
|
||||
// TODO: Rename these to include "start".
|
||||
'line' => $this->startLine,
|
||||
'filePos' => $this->startFilePos,
|
||||
'tokenPos' => $this->startTokenPos,
|
||||
'endLine' => $this->endLine,
|
||||
'endFilePos' => $this->endFilePos,
|
||||
'endTokenPos' => $this->endTokenPos,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class Error extends \RuntimeException
|
||||
* (or start line of error -- deprecated)
|
||||
*/
|
||||
public function __construct(string $message, $attributes = []) {
|
||||
$this->rawMessage = (string) $message;
|
||||
$this->rawMessage = $message;
|
||||
if (is_array($attributes)) {
|
||||
$this->attributes = $attributes;
|
||||
} else {
|
||||
@ -61,7 +61,7 @@ class Error extends \RuntimeException
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attributes of the node/token the error occured at.
|
||||
* Sets the attributes of the node/token the error occurred at.
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
@ -76,7 +76,7 @@ class Error extends \RuntimeException
|
||||
* @param string $message Error message
|
||||
*/
|
||||
public function setRawMessage(string $message) {
|
||||
$this->rawMessage = (string) $message;
|
||||
$this->rawMessage = $message;
|
||||
$this->updateMessage();
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ class Error extends \RuntimeException
|
||||
* @param int $line Error start line
|
||||
*/
|
||||
public function setStartLine(int $line) {
|
||||
$this->attributes['startLine'] = (int) $line;
|
||||
$this->attributes['startLine'] = $line;
|
||||
$this->updateMessage();
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,8 @@ class PrintableNewAnonClassNode extends Expr
|
||||
public static function fromNewNode(Expr\New_ $newNode) {
|
||||
$class = $newNode->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(
|
||||
$newNode->args, $class->extends, $class->implements,
|
||||
$class->stmts, $newNode->getAttributes()
|
||||
|
@ -73,7 +73,9 @@ class JsonDecoder
|
||||
}
|
||||
|
||||
return new $className(
|
||||
$value['text'], $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1
|
||||
$value['text'],
|
||||
$value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1,
|
||||
$value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,13 @@ class Lexer
|
||||
protected $tokenMap;
|
||||
protected $dropTokens;
|
||||
|
||||
protected $usedAttributes;
|
||||
private $attributeStartLineUsed;
|
||||
private $attributeEndLineUsed;
|
||||
private $attributeStartTokenPosUsed;
|
||||
private $attributeEndTokenPosUsed;
|
||||
private $attributeStartFilePosUsed;
|
||||
private $attributeEndFilePosUsed;
|
||||
private $attributeCommentsUsed;
|
||||
|
||||
/**
|
||||
* Creates a Lexer.
|
||||
@ -28,21 +34,27 @@ class Lexer
|
||||
* first three. For more info see getNextToken() docs.
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
// map from internal tokens to PhpParser tokens
|
||||
// Create Map from internal tokens to PhpParser tokens.
|
||||
$this->defineCompatibilityTokens();
|
||||
$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
|
||||
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 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);
|
||||
$defaultAttributes = ['comments', 'startLine', 'endLine'];
|
||||
$usedAttributes = array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, true);
|
||||
|
||||
// Create individual boolean properties to make these checks faster.
|
||||
$this->attributeStartLineUsed = isset($usedAttributes['startLine']);
|
||||
$this->attributeEndLineUsed = isset($usedAttributes['endLine']);
|
||||
$this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']);
|
||||
$this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']);
|
||||
$this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']);
|
||||
$this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']);
|
||||
$this->attributeCommentsUsed = isset($usedAttributes['comments']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +85,7 @@ class Lexer
|
||||
|
||||
error_clear_last();
|
||||
$this->tokens = @token_get_all($code);
|
||||
$this->handleErrors($errorHandler);
|
||||
$this->postprocessTokens($errorHandler);
|
||||
|
||||
if (false !== $scream) {
|
||||
ini_set('xdebug.scream', $scream);
|
||||
@ -81,13 +93,9 @@ class Lexer
|
||||
}
|
||||
|
||||
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
|
||||
$tokens = [];
|
||||
for ($i = $start; $i < $end; $i++) {
|
||||
$chr = $this->code[$i];
|
||||
if ($chr === 'b' || $chr === 'B') {
|
||||
// HHVM does not treat b" tokens correctly, so ignore these
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($chr === "\0") {
|
||||
// PHP cuts error message after null byte, so need special case
|
||||
$errorMsg = 'Unexpected null byte';
|
||||
@ -97,6 +105,7 @@ class Lexer
|
||||
);
|
||||
}
|
||||
|
||||
$tokens[] = [\T_BAD_CHARACTER, $chr, $line];
|
||||
$errorHandler->handleError(new Error($errorMsg, [
|
||||
'startLine' => $line,
|
||||
'endLine' => $line,
|
||||
@ -104,6 +113,7 @@ class Lexer
|
||||
'endFilePos' => $i,
|
||||
]));
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,43 +127,100 @@ class Lexer
|
||||
&& substr($token[1], -2) !== '*/';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
protected function postprocessTokens(ErrorHandler $errorHandler) {
|
||||
// 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.
|
||||
//
|
||||
// Additionally, we canonicalize to the PHP 8 comment format here, which does not include
|
||||
// the trailing whitespace anymore.
|
||||
//
|
||||
// We also canonicalize to the PHP 8 T_NAME_* tokens.
|
||||
|
||||
$filePos = 0;
|
||||
$line = 1;
|
||||
foreach ($this->tokens as $token) {
|
||||
$numTokens = \count($this->tokens);
|
||||
for ($i = 0; $i < $numTokens; $i++) {
|
||||
$token = $this->tokens[$i];
|
||||
|
||||
// Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token.
|
||||
// In this case we only need to emit an error.
|
||||
if ($token[0] === \T_BAD_CHARACTER) {
|
||||
$this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler);
|
||||
}
|
||||
|
||||
if ($token[0] === \T_COMMENT && substr($token[1], 0, 2) !== '/*'
|
||||
&& preg_match('/(\r\n|\n|\r)$/D', $token[1], $matches)) {
|
||||
$trailingNewline = $matches[0];
|
||||
$token[1] = substr($token[1], 0, -strlen($trailingNewline));
|
||||
$this->tokens[$i] = $token;
|
||||
if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) {
|
||||
// Move trailing newline into following T_WHITESPACE token, if it already exists.
|
||||
$this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1];
|
||||
$this->tokens[$i + 1][2]--;
|
||||
} else {
|
||||
// Otherwise, we need to create a new T_WHITESPACE token.
|
||||
array_splice($this->tokens, $i + 1, 0, [
|
||||
[\T_WHITESPACE, $trailingNewline, $line],
|
||||
]);
|
||||
$numTokens++;
|
||||
}
|
||||
}
|
||||
|
||||
// Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING
|
||||
// into a single token.
|
||||
// TODO: Also handle reserved keywords in namespaced names.
|
||||
if (\is_array($token)
|
||||
&& ($token[0] === \T_NS_SEPARATOR || $token[0] === \T_STRING || $token[0] === \T_NAMESPACE)) {
|
||||
$lastWasSeparator = $token[0] === \T_NS_SEPARATOR;
|
||||
$text = $token[1];
|
||||
for ($j = $i + 1; isset($this->tokens[$j]); $j++) {
|
||||
if ($lastWasSeparator) {
|
||||
if ($this->tokens[$j][0] !== \T_STRING) {
|
||||
break;
|
||||
}
|
||||
$lastWasSeparator = false;
|
||||
} else {
|
||||
if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) {
|
||||
break;
|
||||
}
|
||||
$lastWasSeparator = true;
|
||||
}
|
||||
$text .= $this->tokens[$j][1];
|
||||
}
|
||||
if ($lastWasSeparator) {
|
||||
// Trailing separator is not part of the name.
|
||||
$j--;
|
||||
$text = substr($text, 0, -1);
|
||||
}
|
||||
if ($j > $i + 1) {
|
||||
if ($token[0] === \T_NS_SEPARATOR) {
|
||||
$type = \T_NAME_FULLY_QUALIFIED;
|
||||
} else if ($token[0] === \T_NAMESPACE) {
|
||||
$type = \T_NAME_RELATIVE;
|
||||
} else {
|
||||
$type = \T_NAME_QUALIFIED;
|
||||
}
|
||||
$token = [$type, $text, $line];
|
||||
array_splice($this->tokens, $i, $j - $i, [$token]);
|
||||
$numTokens -= $j - $i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
$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(
|
||||
$badCharTokens = $this->handleInvalidCharacterRange(
|
||||
$filePos, $nextFilePos, $line, $errorHandler);
|
||||
$filePos = (int) $nextFilePos;
|
||||
|
||||
array_splice($this->tokens, $i, 0, $badCharTokens);
|
||||
$numTokens += \count($badCharTokens);
|
||||
$i += \count($badCharTokens);
|
||||
}
|
||||
|
||||
$filePos += $tokenLen;
|
||||
@ -176,8 +243,9 @@ class Lexer
|
||||
$this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line];
|
||||
} else {
|
||||
// Invalid characters at the end of the input
|
||||
$this->handleInvalidCharacterRange(
|
||||
$badCharTokens = $this->handleInvalidCharacterRange(
|
||||
$filePos, \strlen($this->code), $line, $errorHandler);
|
||||
$this->tokens = array_merge($this->tokens, $badCharTokens);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -230,13 +298,13 @@ class Lexer
|
||||
$token = "\0";
|
||||
}
|
||||
|
||||
if (isset($this->usedAttributes['startLine'])) {
|
||||
if ($this->attributeStartLineUsed) {
|
||||
$startAttributes['startLine'] = $this->line;
|
||||
}
|
||||
if (isset($this->usedAttributes['startTokenPos'])) {
|
||||
if ($this->attributeStartTokenPosUsed) {
|
||||
$startAttributes['startTokenPos'] = $this->pos;
|
||||
}
|
||||
if (isset($this->usedAttributes['startFilePos'])) {
|
||||
if ($this->attributeStartFilePosUsed) {
|
||||
$startAttributes['startFilePos'] = $this->filePos;
|
||||
}
|
||||
|
||||
@ -262,27 +330,33 @@ class Lexer
|
||||
$this->line += substr_count($value, "\n");
|
||||
$this->filePos += \strlen($value);
|
||||
} else {
|
||||
$origLine = $this->line;
|
||||
$origFilePos = $this->filePos;
|
||||
$this->line += substr_count($token[1], "\n");
|
||||
$this->filePos += \strlen($token[1]);
|
||||
|
||||
if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) {
|
||||
if (isset($this->usedAttributes['comments'])) {
|
||||
if ($this->attributeCommentsUsed) {
|
||||
$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);
|
||||
? new Comment\Doc($token[1],
|
||||
$origLine, $origFilePos, $this->pos,
|
||||
$this->line, $this->filePos - 1, $this->pos)
|
||||
: new Comment($token[1],
|
||||
$origLine, $origFilePos, $this->pos,
|
||||
$this->line, $this->filePos - 1, $this->pos);
|
||||
$startAttributes['comments'][] = $comment;
|
||||
}
|
||||
}
|
||||
|
||||
$this->line += substr_count($token[1], "\n");
|
||||
$this->filePos += \strlen($token[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($this->usedAttributes['endLine'])) {
|
||||
if ($this->attributeEndLineUsed) {
|
||||
$endAttributes['endLine'] = $this->line;
|
||||
}
|
||||
if (isset($this->usedAttributes['endTokenPos'])) {
|
||||
if ($this->attributeEndTokenPosUsed) {
|
||||
$endAttributes['endTokenPos'] = $this->pos;
|
||||
}
|
||||
if (isset($this->usedAttributes['endFilePos'])) {
|
||||
if ($this->attributeEndFilePosUsed) {
|
||||
$endAttributes['endFilePos'] = $this->filePos - 1;
|
||||
}
|
||||
|
||||
@ -329,6 +403,36 @@ class Lexer
|
||||
return substr($textAfter, strlen($matches[0]));
|
||||
}
|
||||
|
||||
private function defineCompatibilityTokens() {
|
||||
// PHP 7.4
|
||||
if (!defined('T_BAD_CHARACTER')) {
|
||||
\define('T_BAD_CHARACTER', -1);
|
||||
}
|
||||
if (!defined('T_FN')) {
|
||||
\define('T_FN', -2);
|
||||
}
|
||||
if (!defined('T_COALESCE_EQUAL')) {
|
||||
\define('T_COALESCE_EQUAL', -3);
|
||||
}
|
||||
|
||||
// PHP 8.0
|
||||
if (!defined('T_NAME_QUALIFIED')) {
|
||||
\define('T_NAME_QUALIFIED', -4);
|
||||
}
|
||||
if (!defined('T_NAME_FULLY_QUALIFIED')) {
|
||||
\define('T_NAME_FULLY_QUALIFIED', -5);
|
||||
}
|
||||
if (!defined('T_NAME_RELATIVE')) {
|
||||
\define('T_NAME_RELATIVE', -6);
|
||||
}
|
||||
if (!defined('T_MATCH')) {
|
||||
\define('T_MATCH', -7);
|
||||
}
|
||||
if (!defined('T_NULLSAFE_OBJECT_OPERATOR')) {
|
||||
\define('T_NULLSAFE_OBJECT_OPERATOR', -8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the token map.
|
||||
*
|
||||
@ -373,6 +477,15 @@ class Lexer
|
||||
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
|
||||
}
|
||||
|
||||
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
|
||||
$tokenMap[\T_FN] = Tokens::T_FN;
|
||||
$tokenMap[\T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL;
|
||||
$tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED;
|
||||
$tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED;
|
||||
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
|
||||
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
|
||||
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
|
||||
|
||||
return $tokenMap;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,262 @@
|
||||
|
||||
namespace PhpParser\Lexer;
|
||||
|
||||
class Emulative extends \PhpParser\Lexer
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Lexer;
|
||||
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
|
||||
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface;
|
||||
use PhpParser\Parser\Tokens;
|
||||
|
||||
class Emulative extends Lexer
|
||||
{
|
||||
/* No features requiring emulation have been added in PHP > 7.0 */
|
||||
const PHP_7_3 = '7.3dev';
|
||||
const PHP_7_4 = '7.4dev';
|
||||
const PHP_8_0 = '8.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-Z0-9_\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 string */
|
||||
private $targetPhpVersion;
|
||||
|
||||
/**
|
||||
* @param mixed[] $options Lexer options. In addition to the usual options,
|
||||
* accepts a 'phpVersion' string that specifies the
|
||||
* version to emulated. Defaults to newest supported.
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_0;
|
||||
unset($options['phpVersion']);
|
||||
|
||||
parent::__construct($options);
|
||||
|
||||
$this->tokenEmulators[] = new FnTokenEmulator();
|
||||
$this->tokenEmulators[] = new MatchTokenEmulator();
|
||||
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator();
|
||||
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator();
|
||||
$this->tokenEmulators[] = new NullsafeTokenEmulator();
|
||||
}
|
||||
|
||||
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
|
||||
$this->patches = [];
|
||||
|
||||
if ($this->isEmulationNeeded($code) === false) {
|
||||
// Nothing to emulate, yay
|
||||
parent::startLexing($code, $errorHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
$collector = new ErrorHandler\Collecting();
|
||||
|
||||
// 1. emulation of heredoc and nowdoc new syntax
|
||||
$preparedCode = $this->processHeredocNowdoc($code);
|
||||
parent::startLexing($preparedCode, $collector);
|
||||
$this->fixupTokens();
|
||||
|
||||
$errors = $collector->getErrors();
|
||||
if (!empty($errors)) {
|
||||
$this->fixupErrors($errors);
|
||||
foreach ($errors as $error) {
|
||||
$errorHandler->handleError($error);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->tokenEmulators as $tokenEmulator) {
|
||||
$emulatorPhpVersion = $tokenEmulator->getPhpVersion();
|
||||
if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
|
||||
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=')
|
||||
&& $tokenEmulator->isEmulationNeeded($code)) {
|
||||
$this->tokens = $tokenEmulator->emulate($code, $this->tokens);
|
||||
} else if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
|
||||
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<')
|
||||
&& $tokenEmulator->isEmulationNeeded($code)) {
|
||||
$this->tokens = $tokenEmulator->reverseEmulate($code, $this->tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isHeredocNowdocEmulationNeeded(string $code): bool
|
||||
{
|
||||
// skip version where this works without emulation
|
||||
if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strpos($code, '<<<') !== false;
|
||||
}
|
||||
|
||||
private function processHeredocNowdoc(string $code): string
|
||||
{
|
||||
if ($this->isHeredocNowdocEmulationNeeded($code) === false) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
|
||||
// No heredoc/nowdoc found
|
||||
return $code;
|
||||
}
|
||||
|
||||
// Keep track of how much we need to adjust string offsets due to the modifications we
|
||||
// already made
|
||||
$posDelta = 0;
|
||||
foreach ($matches as $match) {
|
||||
$indentation = $match['indentation'][0];
|
||||
$indentationStart = $match['indentation'][1];
|
||||
|
||||
$separator = $match['separator'][0];
|
||||
$separatorStart = $match['separator'][1];
|
||||
|
||||
if ($indentation === '' && $separator !== '') {
|
||||
// Ordinary heredoc/nowdoc
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($indentation !== '') {
|
||||
// Remove indentation
|
||||
$indentationLen = strlen($indentation);
|
||||
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
|
||||
$this->patches[] = [$indentationStart + $posDelta, 'add', $indentation];
|
||||
$posDelta -= $indentationLen;
|
||||
}
|
||||
|
||||
if ($separator === '') {
|
||||
// Insert newline as separator
|
||||
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
|
||||
$this->patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
|
||||
$posDelta += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
private function isEmulationNeeded(string $code): bool
|
||||
{
|
||||
foreach ($this->tokenEmulators as $emulativeToken) {
|
||||
if ($emulativeToken->isEmulationNeeded($code)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->isHeredocNowdocEmulationNeeded($code);
|
||||
}
|
||||
|
||||
private function fixupTokens()
|
||||
{
|
||||
if (\count($this->patches) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load first patch
|
||||
$patchIdx = 0;
|
||||
|
||||
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
|
||||
|
||||
// We use a manual loop over the tokens, because we modify the array on the fly
|
||||
$pos = 0;
|
||||
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
|
||||
$token = $this->tokens[$i];
|
||||
if (\is_string($token)) {
|
||||
// We assume that patches don't apply to string tokens
|
||||
$pos += \strlen($token);
|
||||
continue;
|
||||
}
|
||||
|
||||
$len = \strlen($token[1]);
|
||||
$posDelta = 0;
|
||||
while ($patchPos >= $pos && $patchPos < $pos + $len) {
|
||||
$patchTextLen = \strlen($patchText);
|
||||
if ($patchType === 'remove') {
|
||||
if ($patchPos === $pos && $patchTextLen === $len) {
|
||||
// Remove token entirely
|
||||
array_splice($this->tokens, $i, 1, []);
|
||||
$i--;
|
||||
$c--;
|
||||
} else {
|
||||
// Remove from token string
|
||||
$this->tokens[$i][1] = substr_replace(
|
||||
$token[1], '', $patchPos - $pos + $posDelta, $patchTextLen
|
||||
);
|
||||
$posDelta -= $patchTextLen;
|
||||
}
|
||||
} elseif ($patchType === 'add') {
|
||||
// Insert into the token string
|
||||
$this->tokens[$i][1] = substr_replace(
|
||||
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
|
||||
);
|
||||
$posDelta += $patchTextLen;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Fetch the next patch
|
||||
$patchIdx++;
|
||||
if ($patchIdx >= \count($this->patches)) {
|
||||
// No more patches, we're done
|
||||
return;
|
||||
}
|
||||
|
||||
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
|
||||
|
||||
// Multiple patches may apply to the same token. Reload the current one to check
|
||||
// If the new patch applies
|
||||
$token = $this->tokens[$i];
|
||||
}
|
||||
|
||||
$pos += $len;
|
||||
}
|
||||
|
||||
// A patch did not apply
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixup line and position information in errors.
|
||||
*
|
||||
* @param Error[] $errors
|
||||
*/
|
||||
private function fixupErrors(array $errors) {
|
||||
foreach ($errors as $error) {
|
||||
$attrs = $error->getAttributes();
|
||||
|
||||
$posDelta = 0;
|
||||
$lineDelta = 0;
|
||||
foreach ($this->patches as $patch) {
|
||||
list($patchPos, $patchType, $patchText) = $patch;
|
||||
if ($patchPos >= $attrs['startFilePos']) {
|
||||
// No longer relevant
|
||||
break;
|
||||
}
|
||||
|
||||
if ($patchType === 'add') {
|
||||
$posDelta += strlen($patchText);
|
||||
$lineDelta += substr_count($patchText, "\n");
|
||||
} else {
|
||||
$posDelta -= strlen($patchText);
|
||||
$lineDelta -= substr_count($patchText, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
$attrs['startFilePos'] += $posDelta;
|
||||
$attrs['endFilePos'] += $posDelta;
|
||||
$attrs['startLine'] += $lineDelta;
|
||||
$attrs['endLine'] += $lineDelta;
|
||||
$error->setAttributes($attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_7_4;
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool
|
||||
{
|
||||
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
|
||||
$line = 1;
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
if (isset($tokens[$i + 1])) {
|
||||
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
|
||||
array_splice($tokens, $i, 2, [
|
||||
[\T_COALESCE_EQUAL, '??=', $line]
|
||||
]);
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (\is_array($tokens[$i])) {
|
||||
$line += substr_count($tokens[$i][1], "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
// ??= was not valid code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
}
|
23
lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php
Normal file
23
lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
final class FnTokenEmulator extends KeywordEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_7_4;
|
||||
}
|
||||
|
||||
public function getKeywordString(): string
|
||||
{
|
||||
return 'fn';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int
|
||||
{
|
||||
return \T_FN;
|
||||
}
|
||||
}
|
60
lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php
Normal file
60
lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
abstract class KeywordEmulator implements TokenEmulatorInterface
|
||||
{
|
||||
abstract function getKeywordString(): string;
|
||||
abstract function getKeywordToken(): int;
|
||||
|
||||
public function isEmulationNeeded(string $code): bool
|
||||
{
|
||||
return strpos($code, $this->getKeywordString()) !== false;
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
$keywordString = $this->getKeywordString();
|
||||
foreach ($tokens as $i => $token) {
|
||||
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString) {
|
||||
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i);
|
||||
if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === \T_OBJECT_OPERATOR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokens[$i][0] = $this->getKeywordToken();
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $tokens
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
private function getPreviousNonSpaceToken(array $tokens, int $start)
|
||||
{
|
||||
for ($i = $start - 1; $i >= 0; --$i) {
|
||||
if ($tokens[$i][0] === T_WHITESPACE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $tokens[$i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
$keywordToken = $this->getKeywordToken();
|
||||
foreach ($tokens as $i => $token) {
|
||||
if ($token[0] === $keywordToken) {
|
||||
$tokens[$i][0] = \T_STRING;
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
}
|
23
lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php
Normal file
23
lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
final class MatchTokenEmulator extends KeywordEmulator
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_0;
|
||||
}
|
||||
|
||||
public function getKeywordString(): string
|
||||
{
|
||||
return 'match';
|
||||
}
|
||||
|
||||
public function getKeywordToken(): int
|
||||
{
|
||||
return \T_MATCH;
|
||||
}
|
||||
}
|
47
lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php
Normal file
47
lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
final class NullsafeTokenEmulator implements TokenEmulatorInterface
|
||||
{
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_8_0;
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code): bool
|
||||
{
|
||||
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
|
||||
$line = 1;
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
if (isset($tokens[$i + 1])) {
|
||||
if ($tokens[$i] === '?' && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
|
||||
array_splice($tokens, $i, 2, [
|
||||
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
|
||||
]);
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (\is_array($tokens[$i])) {
|
||||
$line += substr_count($tokens[$i][1], "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
// ?-> was not valid code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
use PhpParser\Lexer\Emulative;
|
||||
|
||||
final class NumericLiteralSeparatorEmulator implements TokenEmulatorInterface
|
||||
{
|
||||
const BIN = '(?:0b[01]+(?:_[01]+)*)';
|
||||
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
|
||||
const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
|
||||
const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
|
||||
const EXP = '(?:e[+-]?' . self::DEC . ')';
|
||||
const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
|
||||
const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
|
||||
|
||||
public function getPhpVersion(): string
|
||||
{
|
||||
return Emulative::PHP_7_4;
|
||||
}
|
||||
|
||||
public function isEmulationNeeded(string $code) : bool
|
||||
{
|
||||
return preg_match('~[0-9]_[0-9]~', $code)
|
||||
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
|
||||
}
|
||||
|
||||
public function emulate(string $code, array $tokens): array
|
||||
{
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way
|
||||
$codeOffset = 0;
|
||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||
$token = $tokens[$i];
|
||||
$tokenLen = \strlen(\is_array($token) ? $token[1] : $token);
|
||||
|
||||
if ($token[0] !== T_LNUMBER && $token[0] !== T_DNUMBER) {
|
||||
$codeOffset += $tokenLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
$res = preg_match(self::NUMBER, $code, $matches, 0, $codeOffset);
|
||||
assert($res, "No number at number token position");
|
||||
|
||||
$match = $matches[0];
|
||||
$matchLen = \strlen($match);
|
||||
if ($matchLen === $tokenLen) {
|
||||
// Original token already holds the full number.
|
||||
$codeOffset += $tokenLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
$tokenKind = $this->resolveIntegerOrFloatToken($match);
|
||||
$newTokens = [[$tokenKind, $match, $token[2]]];
|
||||
|
||||
$numTokens = 1;
|
||||
$len = $tokenLen;
|
||||
while ($matchLen > $len) {
|
||||
$nextToken = $tokens[$i + $numTokens];
|
||||
$nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken;
|
||||
$nextTokenLen = \strlen($nextTokenText);
|
||||
|
||||
$numTokens++;
|
||||
if ($matchLen < $len + $nextTokenLen) {
|
||||
// Split trailing characters into a partial token.
|
||||
assert(is_array($nextToken), "Partial token should be an array token");
|
||||
$partialText = substr($nextTokenText, $matchLen - $len);
|
||||
$newTokens[] = [$nextToken[0], $partialText, $nextToken[2]];
|
||||
break;
|
||||
}
|
||||
|
||||
$len += $nextTokenLen;
|
||||
}
|
||||
|
||||
array_splice($tokens, $i, $numTokens, $newTokens);
|
||||
$c -= $numTokens - \count($newTokens);
|
||||
$codeOffset += $matchLen;
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
private function resolveIntegerOrFloatToken(string $str): int
|
||||
{
|
||||
$str = str_replace('_', '', $str);
|
||||
|
||||
if (stripos($str, '0b') === 0) {
|
||||
$num = bindec($str);
|
||||
} elseif (stripos($str, '0x') === 0) {
|
||||
$num = hexdec($str);
|
||||
} elseif (stripos($str, '0') === 0 && ctype_digit($str)) {
|
||||
$num = octdec($str);
|
||||
} else {
|
||||
$num = +$str;
|
||||
}
|
||||
|
||||
return is_float($num) ? T_DNUMBER : T_LNUMBER;
|
||||
}
|
||||
|
||||
public function reverseEmulate(string $code, array $tokens): array
|
||||
{
|
||||
// Numeric separators were not legal code previously, don't bother.
|
||||
return $tokens;
|
||||
}
|
||||
}
|
21
lib/PhpParser/Lexer/TokenEmulator/TokenEmulatorInterface.php
Normal file
21
lib/PhpParser/Lexer/TokenEmulator/TokenEmulatorInterface.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Lexer\TokenEmulator;
|
||||
|
||||
/** @internal */
|
||||
interface TokenEmulatorInterface
|
||||
{
|
||||
public function getPhpVersion(): string;
|
||||
|
||||
public function isEmulationNeeded(string $code): bool;
|
||||
|
||||
/**
|
||||
* @return array Modified Tokens
|
||||
*/
|
||||
public function emulate(string $code, array $tokens): array;
|
||||
|
||||
/**
|
||||
* @return array Modified Tokens
|
||||
*/
|
||||
public function reverseEmulate(string $code, array $tokens): array;
|
||||
}
|
@ -95,8 +95,6 @@ interface Node
|
||||
/**
|
||||
* Gets the doc comment of the node.
|
||||
*
|
||||
* The doc comment has to be the last comment associated with the node.
|
||||
*
|
||||
* @return null|Comment\Doc Doc comment object or null
|
||||
*/
|
||||
public function getDocComment();
|
||||
|
@ -22,7 +22,7 @@ class Arg extends NodeAbstract
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->value = $value;
|
||||
$this->byRef = $byRef;
|
||||
$this->unpack = $unpack;
|
||||
|
@ -22,7 +22,7 @@ class Const_ extends NodeAbstract
|
||||
* @param array $attributes Additional 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->value = $value;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class ArrayDimFetch extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $dim = null, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->dim = $dim;
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ class ArrayItem extends Expr
|
||||
public $value;
|
||||
/** @var bool Whether to assign by reference */
|
||||
public $byRef;
|
||||
/** @var bool Whether to unpack the argument */
|
||||
public $unpack;
|
||||
|
||||
/**
|
||||
* Constructs an array item node.
|
||||
@ -21,17 +23,18 @@ class ArrayItem extends Expr
|
||||
* @param bool $byRef Whether to assign by reference
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
|
||||
$this->attributes = $attributes;
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->byRef = $byRef;
|
||||
$this->unpack = $unpack;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['key', 'value', 'byRef'];
|
||||
return ['key', 'value', 'byRef', 'unpack'];
|
||||
}
|
||||
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_ArrayItem';
|
||||
}
|
||||
|
@ -10,17 +10,17 @@ class Array_ extends Expr
|
||||
const KIND_LONG = 1; // array() syntax
|
||||
const KIND_SHORT = 2; // [] syntax
|
||||
|
||||
/** @var ArrayItem[] Items */
|
||||
/** @var (ArrayItem|null)[] Items */
|
||||
public $items;
|
||||
|
||||
/**
|
||||
* Constructs an array node.
|
||||
*
|
||||
* @param ArrayItem[] $items Items of the array
|
||||
* @param (ArrayItem|null)[] $items Items of the array
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $items = [], array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
|
71
lib/PhpParser/Node/Expr/ArrowFunction.php
Normal file
71
lib/PhpParser/Node/Expr/ArrowFunction.php
Normal 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|Node\UnionType */
|
||||
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';
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ class Assign extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ abstract class AssignOp extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
12
lib/PhpParser/Node/Expr/AssignOp/Coalesce.php
Normal file
12
lib/PhpParser/Node/Expr/AssignOp/Coalesce.php
Normal 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';
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ class AssignRef extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
@ -12,14 +12,14 @@ abstract class BinaryOp extends Expr
|
||||
public $right;
|
||||
|
||||
/**
|
||||
* Constructs a bitwise and node.
|
||||
* Constructs a binary operator node.
|
||||
*
|
||||
* @param Expr $left The left hand side expression
|
||||
* @param Expr $right The right hand side expression
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $left, Expr $right, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->left = $left;
|
||||
$this->right = $right;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class BitwiseNot extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class BooleanNot extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ abstract class Cast extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,11 @@ use PhpParser\Node\Expr\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 {
|
||||
return 'Expr_Cast_Double';
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class ClassConstFetch extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($class, $name, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->class = $class;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class Clone_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class Closure extends Expr implements FunctionLike
|
||||
public $params;
|
||||
/** @var ClosureUse[] use()s */
|
||||
public $uses;
|
||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType Return type */
|
||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
|
||||
public $returnType;
|
||||
/** @var Node\Stmt[] Statements */
|
||||
public $stmts;
|
||||
@ -34,7 +34,7 @@ class Closure extends Expr implements FunctionLike
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $subNodes = [], array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->static = $subNodes['static'] ?? false;
|
||||
$this->byRef = $subNodes['byRef'] ?? false;
|
||||
$this->params = $subNodes['params'] ?? [];
|
||||
|
@ -19,7 +19,7 @@ class ClosureUse extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->byRef = $byRef;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class ConstFetch extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Name $name, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class Empty_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ class Error extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
|
@ -16,7 +16,7 @@ class ErrorSuppress extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class Eval_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class Exit_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr = null, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -16,11 +16,11 @@ class FuncCall extends Expr
|
||||
* Constructs a function call node.
|
||||
*
|
||||
* @param Node\Name|Expr $name Function name
|
||||
* @param Node\Arg[] $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
* @param Node\Arg[] $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, array $args = [], array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->name = $name;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class Include_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, int $type, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class Instanceof_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, $class, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
$this->class = $class;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class Isset_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $vars, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->vars = $vars;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class List_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $items, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
|
31
lib/PhpParser/Node/Expr/Match_.php
Normal file
31
lib/PhpParser/Node/Expr/Match_.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\MatchArm;
|
||||
|
||||
class Match_ extends Node\Expr
|
||||
{
|
||||
/** @var Node\Expr */
|
||||
public $cond;
|
||||
/** @var MatchArm[] */
|
||||
public $arms;
|
||||
|
||||
/**
|
||||
* @param MatchArm[] $arms
|
||||
*/
|
||||
public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->cond = $cond;
|
||||
$this->arms = $arms;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['cond', 'arms'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_Match';
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ class MethodCall extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
$this->args = $args;
|
||||
|
@ -20,7 +20,7 @@ class New_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($class, array $args = [], array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->class = $class;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
40
lib/PhpParser/Node/Expr/NullsafeMethodCall.php
Normal file
40
lib/PhpParser/Node/Expr/NullsafeMethodCall.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
class NullsafeMethodCall extends Expr
|
||||
{
|
||||
/** @var Expr Variable holding object */
|
||||
public $var;
|
||||
/** @var Identifier|Expr Method name */
|
||||
public $name;
|
||||
/** @var Arg[] Arguments */
|
||||
public $args;
|
||||
|
||||
/**
|
||||
* Constructs a nullsafe method call node.
|
||||
*
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Method name
|
||||
* @param Arg[] $args Arguments
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['var', 'name', 'args'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_NullsafeMethodCall';
|
||||
}
|
||||
}
|
35
lib/PhpParser/Node/Expr/NullsafePropertyFetch.php
Normal file
35
lib/PhpParser/Node/Expr/NullsafePropertyFetch.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
|
||||
class NullsafePropertyFetch extends Expr
|
||||
{
|
||||
/** @var Expr Variable holding object */
|
||||
public $var;
|
||||
/** @var Identifier|Expr Property name */
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Constructs a nullsafe property fetch node.
|
||||
*
|
||||
* @param Expr $var Variable holding object
|
||||
* @param string|Identifier|Expr $name Property name
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, $name, array $attributes = []) {
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['var', 'name'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'Expr_NullsafePropertyFetch';
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ class PostDec extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class PostInc extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class PreDec extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class PreInc extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class Print_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class PropertyFetch extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $var, $name, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->var = $var;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ class ShellExec extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $parts, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->parts = $parts;
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ class StaticCall extends Expr
|
||||
{
|
||||
/** @var Node\Name|Expr Class name */
|
||||
public $class;
|
||||
/** @var string|Identifier|Expr Method name */
|
||||
/** @var Identifier|Expr Method name */
|
||||
public $name;
|
||||
/** @var Node\Arg[] Arguments */
|
||||
public $args;
|
||||
@ -24,7 +24,7 @@ class StaticCall extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($class, $name, array $args = [], array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->class = $class;
|
||||
$this->name = \is_string($name) ? new Identifier($name) : $name;
|
||||
$this->args = $args;
|
||||
|
@ -21,7 +21,7 @@ class StaticPropertyFetch extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($class, $name, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->class = $class;
|
||||
$this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class Ternary extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $cond, $if, Expr $else, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->cond = $cond;
|
||||
$this->if = $if;
|
||||
$this->else = $else;
|
||||
|
@ -16,7 +16,7 @@ class UnaryMinus extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class UnaryPlus extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class Variable extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ class YieldFrom extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $expr, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->expr = $expr;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ class Yield_ extends Expr
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Expr $value = null, Expr $key = null, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ interface FunctionLike extends Node
|
||||
/**
|
||||
* Get the declared return type or null
|
||||
*
|
||||
* @return null|Identifier|Node\Name|Node\NullableType
|
||||
* @return null|Identifier|Node\Name|Node\NullableType|Node\UnionType
|
||||
*/
|
||||
public function getReturnType();
|
||||
|
||||
|
@ -25,7 +25,7 @@ class Identifier extends NodeAbstract
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(string $name, array $attributes = []) {
|
||||
parent::__construct($attributes);
|
||||
$this->attributes = $attributes;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
|
31
lib/PhpParser/Node/MatchArm.php
Normal file
31
lib/PhpParser/Node/MatchArm.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class MatchArm extends NodeAbstract
|
||||
{
|
||||
/** @var null|Node\Expr[] */
|
||||
public $conds;
|
||||
/** @var Node\Expr */
|
||||
public $body;
|
||||
|
||||
/**
|
||||
* @param null|Node\Expr[] $conds
|
||||
*/
|
||||
public function __construct($conds, Node\Expr $body, array $attributes = []) {
|
||||
$this->conds = $conds;
|
||||
$this->body = $body;
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() : array {
|
||||
return ['conds', 'body'];
|
||||
}
|
||||
|
||||
public function getType() : string {
|
||||
return 'MatchArm';
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user