mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-07-16 20:06:32 +02:00
Compare commits
185 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
11e2663a5b | ||
|
11e2dcd96c | ||
|
0ffddce52d | ||
|
6bb5176bc4 | ||
|
cad49f8ed3 | ||
|
570e980a20 | ||
|
a50b4310f7 | ||
|
8863f92b58 | ||
|
3182d12b55 | ||
|
1df465cd90 | ||
|
f59bbe44bf | ||
|
2e11deec46 | ||
|
a4fe65bf60 | ||
|
e072fd2c30 | ||
|
7027899d7f | ||
|
2f1fd784fe | ||
|
0ef6c55a3f | ||
|
8216e878be | ||
|
617d0220b9 | ||
|
a951e9e24d | ||
|
b30e7e73d5 | ||
|
ff24d1d61a | ||
|
e55f8c6b30 | ||
|
3ee592b6aa | ||
|
3fe2422e34 | ||
|
2d589921f2 | ||
|
cdb731fa8b | ||
|
e727475d08 | ||
|
34bea19b6e | ||
|
678ccbe072 | ||
|
5d83adcc0e | ||
|
3bf0082455 | ||
|
d3eb10aca1 | ||
|
a6e34665fd | ||
|
f4b835f7d8 | ||
|
210577fe3c | ||
|
f09f22760e | ||
|
d4cb98ae38 | ||
|
68d2a52b42 | ||
|
63f8699143 | ||
|
4122ff3a91 | ||
|
6f1f206862 | ||
|
99a24b6a55 | ||
|
63a79e8daa | ||
|
6a21234e58 | ||
|
7064539974 | ||
|
4bfc4595ed | ||
|
54f19a0a66 | ||
|
8da6d7ac62 | ||
|
f6e1fbf3a2 | ||
|
50953a2691 | ||
|
0a20979a62 | ||
|
a45fb2a621 | ||
|
08501991d4 | ||
|
b5234eacd0 | ||
|
632ead3a82 | ||
|
13549aa794 | ||
|
d2c645f163 | ||
|
def24f2224 | ||
|
cfeb195205 | ||
|
ace6c67a8a | ||
|
0483391aca | ||
|
9aebf377fc | ||
|
5a43015499 | ||
|
6608f01670 | ||
|
55c4269232 | ||
|
c4304c76bd | ||
|
b099e8fc76 | ||
|
acf16edc8e | ||
|
a8b5ed4306 | ||
|
c758510a37 | ||
|
feed91cf0f | ||
|
fe14cf3672 | ||
|
3fb73520c1 | ||
|
c35cc4b2cb | ||
|
e69ebbbfd9 | ||
|
0b258d9a9e | ||
|
2d193bb0e4 | ||
|
49e9951f2c | ||
|
eccf1bf464 | ||
|
b67560d388 | ||
|
4432ba399e | ||
|
37ac4ea9c2 | ||
|
4848a0d734 | ||
|
8eb194ea1f | ||
|
e03d63cffb | ||
|
ce91d139b5 | ||
|
33d7c8d3d8 | ||
|
6b409b96bb | ||
|
f68e1a43ff | ||
|
f767b9fd9f | ||
|
2a4bb7ef2c | ||
|
4abdcde5f1 | ||
|
d46f261ef9 | ||
|
38aa0920c9 | ||
|
a8223f228a | ||
|
8165cf69fa | ||
|
46221a0914 | ||
|
3b87eb721c | ||
|
e3471d94d3 | ||
|
c6d052fc58 | ||
|
8008d07bef | ||
|
7284a4d019 | ||
|
d3d1ee470a | ||
|
893a5bce3f | ||
|
bec74aceda | ||
|
7c09e096c9 | ||
|
0f64504317 | ||
|
5e36ef732e | ||
|
dbe56d23de | ||
|
c64986fa55 | ||
|
bc7a9bf9c2 | ||
|
2816485126 | ||
|
1d1bc8a364 | ||
|
d520bc9e1d | ||
|
51e0b30843 | ||
|
eff72eeffa | ||
|
658f1be311 | ||
|
b9b65a2996 | ||
|
1b479e7592 | ||
|
b5351f883a | ||
|
88be6127fa | ||
|
8a97fa157f | ||
|
1c13d05035 | ||
|
c7dc3ce552 | ||
|
9f6ad686a7 | ||
|
1899471f80 | ||
|
8505acd151 | ||
|
c3e20d9970 | ||
|
4c22c62783 | ||
|
f66a32e2df | ||
|
75abbbd2d4 | ||
|
39b046007d | ||
|
e3872b8906 | ||
|
4a40a84cf6 | ||
|
88e519766f | ||
|
35306de32f | ||
|
ad365b1beb | ||
|
4bc82432de | ||
|
fd6052e37d | ||
|
bd722809f7 | ||
|
56356e4aec | ||
|
aaee038b91 | ||
|
f9d35fe11e | ||
|
8bcaa4261e | ||
|
feb6bf7a0c | ||
|
0cee2088ea | ||
|
78e08fc178 | ||
|
3aadc15e2e | ||
|
a98350581c | ||
|
7f398769a0 | ||
|
98f7f39d1c | ||
|
722119502f | ||
|
544aee1671 | ||
|
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 |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.y]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,9 +1,10 @@
|
|||||||
|
/.github export-ignore
|
||||||
/doc export-ignore
|
/doc export-ignore
|
||||||
/test export-ignore
|
/test export-ignore
|
||||||
/test_old export-ignore
|
/test_old export-ignore
|
||||||
|
.editorconfig export-ignore
|
||||||
.gitattributes export-ignore
|
.gitattributes export-ignore
|
||||||
.gitignore export-ignore
|
.gitignore export-ignore
|
||||||
.travis.yml export-ignore
|
|
||||||
CHANGELOG.md export-ignore
|
CHANGELOG.md export-ignore
|
||||||
phpunit.xml.dist export-ignore
|
phpunit.xml.dist export-ignore
|
||||||
UPGRADE-*.md export-ignore
|
UPGRADE-*.md export-ignore
|
||||||
|
88
.github/workflows/main.yml
vendored
Normal file
88
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
|
||||||
|
name: Main
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests_70:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
name: "PHP 7.0 Unit Tests"
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: "actions/checkout@v2"
|
||||||
|
- name: "Install PHP"
|
||||||
|
uses: "shivammathur/setup-php@v2"
|
||||||
|
with:
|
||||||
|
coverage: "xdebug"
|
||||||
|
php-version: "7.0"
|
||||||
|
tools: composer:v2
|
||||||
|
- name: "Install dependencies"
|
||||||
|
run: |
|
||||||
|
composer require php-coveralls/php-coveralls:^2.2 --dev --no-update
|
||||||
|
composer update --no-progress --prefer-dist
|
||||||
|
- name: "Tests"
|
||||||
|
run: "php vendor/bin/phpunit --coverage-clover build/logs/clover.xml"
|
||||||
|
- name: Coveralls
|
||||||
|
env:
|
||||||
|
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: "php vendor/bin/php-coveralls"
|
||||||
|
if: ${{ success() }}
|
||||||
|
tests:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
name: "PHP ${{ matrix.php-version }} Unit Tests"
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-version:
|
||||||
|
- "7.1"
|
||||||
|
- "7.2"
|
||||||
|
- "7.3"
|
||||||
|
- "7.4"
|
||||||
|
- "8.0"
|
||||||
|
- "8.1"
|
||||||
|
- "8.2"
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: "actions/checkout@v2"
|
||||||
|
- name: "Install PHP"
|
||||||
|
uses: "shivammathur/setup-php@v2"
|
||||||
|
with:
|
||||||
|
coverage: "none"
|
||||||
|
php-version: "${{ matrix.php-version }}"
|
||||||
|
tools: composer:v2
|
||||||
|
- name: "Install dependencies"
|
||||||
|
run: "composer update --no-progress --prefer-dist ${{ matrix.flags }}"
|
||||||
|
- name: "PHPUnit"
|
||||||
|
run: "php vendor/bin/phpunit"
|
||||||
|
test_old_73_80:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
name: "PHP 7.3 Code on PHP 8.0 Integration Tests"
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: "actions/checkout@v2"
|
||||||
|
- name: "Install PHP"
|
||||||
|
uses: "shivammathur/setup-php@v2"
|
||||||
|
with:
|
||||||
|
coverage: "none"
|
||||||
|
php-version: "8.0"
|
||||||
|
tools: composer:v2
|
||||||
|
- name: "Install PHP 8 dependencies"
|
||||||
|
run: "composer update --no-progress --prefer-dist"
|
||||||
|
- name: "Tests"
|
||||||
|
run: "test_old/run-php-src.sh 7.3.21"
|
||||||
|
test_old_80_70:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
name: "PHP 8.1 Code on PHP 7.0 Integration Tests"
|
||||||
|
steps:
|
||||||
|
- name: "Checkout"
|
||||||
|
uses: "actions/checkout@v2"
|
||||||
|
- name: "Install PHP"
|
||||||
|
uses: "shivammathur/setup-php@v2"
|
||||||
|
with:
|
||||||
|
coverage: "none"
|
||||||
|
php-version: "7.0"
|
||||||
|
tools: composer:v2
|
||||||
|
- name: "Install PHP 8 dependencies"
|
||||||
|
run: "composer update --no-progress --prefer-dist"
|
||||||
|
- name: "Tests"
|
||||||
|
run: "test_old/run-php-src.sh 8.1.6"
|
35
.travis.yml
35
.travis.yml
@@ -1,35 +0,0 @@
|
|||||||
language: php
|
|
||||||
dist: xenial
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.composer/cache
|
|
||||||
|
|
||||||
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
|
|
||||||
- |
|
|
||||||
if [ $TRAVIS_PHP_VERSION = 'nightly' ]; then
|
|
||||||
composer install --prefer-dist --ignore-platform-reqs;
|
|
||||||
else
|
|
||||||
composer install --prefer-dist;
|
|
||||||
fi
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- php: nightly
|
|
||||||
fast_finish: true
|
|
||||||
|
|
||||||
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.2' ]; then test_old/run-php-src.sh; fi
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then php vendor/bin/coveralls; fi
|
|
291
CHANGELOG.md
291
CHANGELOG.md
@@ -1,7 +1,292 @@
|
|||||||
Version 4.5.1-dev
|
Version 4.15.5 (2023-05-19)
|
||||||
-----------------
|
---------------------------
|
||||||
|
|
||||||
Nothing yet.
|
### Added
|
||||||
|
|
||||||
|
* Added `makePrivate()`, `makeProtected()`, `makePublic()` and `makeReadonly()` methods to
|
||||||
|
`Builder\Param` to allow the creation of promoted parameters.
|
||||||
|
|
||||||
|
Version 4.15.4 (2023-03-05)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed formatting-preservation for alternative if syntax with trailing comments.
|
||||||
|
|
||||||
|
Version 4.15.3 (2023-01-16)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Support readonly property with PHP 8.2 DNF type.
|
||||||
|
* Fixed PHP attribute group and PHP-Parser attribute mixup in EnumCase builder.
|
||||||
|
|
||||||
|
Version 4.15.2 (2022-11-12)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed parsing of large hex float literals that contain an "e" character.
|
||||||
|
* Fixed tests to pass on 32-bit.
|
||||||
|
* Fixed generation of invalid code when using formatting-preserving pretty printer with code that
|
||||||
|
uses inline HTML.
|
||||||
|
|
||||||
|
Version 4.15.1 (2022-09-04)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed formatting preservation when adding *multiple* attributes to a class/method/etc that
|
||||||
|
previously had none. This fixes a regression in the 4.15.0 release.
|
||||||
|
|
||||||
|
Version 4.15.0 (2022-09-03)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* PHP 8.2: Added support for `true` type.
|
||||||
|
* PHP 8.2: Added support for DNF types.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Support `readonly` as a function name.
|
||||||
|
* Added `__serialize` and `__unserialize` to magic method list.
|
||||||
|
* Fixed bounds check in `Name::slice()`.
|
||||||
|
* Fixed formatting preservation when adding attributes to a class/method/etc that previously had none.
|
||||||
|
|
||||||
|
Version 4.14.0 (2022-05-31)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Added support for readonly classes.
|
||||||
|
* Added `rawValue` attribute to `LNumber`, `DNumber` and `String_` nodes, which stores the unparsed
|
||||||
|
value of the literal (e.g. `"1_000"` rather than `1000`).
|
||||||
|
|
||||||
|
Version 4.13.2 (2021-11-30)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Added builders for enums and enum cases.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* NullsafeMethodCall now extends from CallLike.
|
||||||
|
* The `namespacedName` property populated by the `NameResolver` is now declared on relevant nodes,
|
||||||
|
to avoid a dynamic property deprecation warning with PHP 8.2.
|
||||||
|
|
||||||
|
Version 4.13.1 (2021-11-03)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Support reserved keywords as enum cases.
|
||||||
|
* Support array unpacking in constant expression evaluator.
|
||||||
|
|
||||||
|
Version 4.13.0 (2021-09-20)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* [PHP 8.1] Added support for intersection types using a new `IntersectionType` node. Additionally
|
||||||
|
a `ComplexType` parent class for `NullableType`, `UnionType` and `IntersectionType` has been
|
||||||
|
added.
|
||||||
|
* [PHP 8.1] Added support for explicit octal literals.
|
||||||
|
* [PHP 8.1] Added support for first-class callables. These are represented using a call whose first
|
||||||
|
argument is a `VariadicPlaceholder`. The representation is intended to be forward-compatible with
|
||||||
|
partial function application, just like the PHP feature itself. Call nodes now extend from
|
||||||
|
`Expr\CallLike`, which provides an `isFirstClassCallable()` method to determine whether a
|
||||||
|
placeholder id present. `getArgs()` can be used to assert that the call is not a first-class
|
||||||
|
callable and returns `Arg[]` rather than `array<Arg|VariadicPlaceholder>`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Multiple modifiers for promoted properties are now accepted. In particular this allows something
|
||||||
|
like `public readonly` for promoted properties.
|
||||||
|
* Formatting-preserving pretty printing for comments in array literals has been fixed.
|
||||||
|
|
||||||
|
Version 4.12.0 (2021-07-21)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* [PHP 8.1] Added support for readonly properties (through a new `MODIFIER_READONLY`).
|
||||||
|
* [PHP 8.1] Added support for final class constants.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed compatibility with PHP 8.1. `&` tokens are now canonicalized to the
|
||||||
|
`T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` and `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG` tokens
|
||||||
|
used in PHP 8.1. This happens unconditionally, regardless of whether the emulative lexer is used.
|
||||||
|
|
||||||
|
Version 4.11.0 (2021-07-03)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* `BuilderFactory::args()` now accepts named arguments.
|
||||||
|
* `BuilderFactory::attribute()` has been added.
|
||||||
|
* An `addAttribute()` method accepting an `Attribute` or `AttributeGroup` has been adde to all
|
||||||
|
builders that accept attributes, such as `Builder\Class_`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* `NameResolver` now handles enums.
|
||||||
|
* `PrettyPrinter` now prints backing enum type.
|
||||||
|
* Builder methods for types now property handle `never` type.
|
||||||
|
|
||||||
|
Version 4.10.5 (2021-05-03)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* [PHP 8.1] Added support for enums. These are represented using the `Stmt\Enum_` and
|
||||||
|
`Stmt\EnumCase` nodes.
|
||||||
|
* [PHP 8.1] Added support for never type. This type will now be returned as an `Identifier` rather
|
||||||
|
than `Name`.
|
||||||
|
* Added `ClassConst` builder.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Non-UTF-8 code units in strings will now be hex-encoded.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed precedence of arrow functions.
|
||||||
|
|
||||||
|
Version 4.10.4 (2020-12-20)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed position information for variable-variables (#741).
|
||||||
|
* Fixed position information for traits/interfaces preceded by if statement (#738).
|
||||||
|
|
||||||
|
Version 4.10.3 (2020-12-03)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed formatting-preserving pretty printing for `"{$x}"`.
|
||||||
|
* Ternary expressions are now treated as non-associative in the pretty printer, in order to
|
||||||
|
generate code that is compatible with the parentheses requirement introduced in PHP 8.
|
||||||
|
* Removed no longer necessary `error_clear_last()` call in lexer, which may interfere with fatal
|
||||||
|
error handlers if invoked during shutdown.
|
||||||
|
|
||||||
|
|
||||||
|
Version 4.10.2 (2020-09-26)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fixed check for token emulation conflicts with other libraries.
|
||||||
|
|
||||||
|
Version 4.10.1 (2020-09-23)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Added support for recovering from a missing semicolon after a property or class constant
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Fix spurious whitespace in formatting-preserving pretty printer when both removing and adding
|
||||||
|
elements at the start of a list.
|
||||||
|
* Fix incorrect case-sensitivity in keyword token emulation.
|
||||||
|
|
||||||
|
Version 4.10.0 (2020-09-19)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* [PHP 8.0] Added support for attributes. These are represented using a new `AttributeGroup` node
|
||||||
|
containing `Attribute` nodes. A new `attrGroups` subnode is available on all node types that
|
||||||
|
support attributes, i.e. `Stmt\Class_`, `Stmt\Trait_`, `Stmt\Interface_`, `Stmt\Function_`,
|
||||||
|
`Stmt\ClassMethod`, `Stmt\ClassConst`, `Stmt\Property`, `Expr\Closure`, `Expr\ArrowFunction` and
|
||||||
|
`Param`.
|
||||||
|
* [PHP 8.0] Added support for nullsafe properties inside interpolated strings, in line with an
|
||||||
|
upstream change.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Improved compatibility with other libraries that use forward compatibility defines for PHP tokens.
|
||||||
|
|
||||||
|
Version 4.9.1 (2020-08-30)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Added support for removing the first element of a list to the formatting-preserving pretty
|
||||||
|
printer.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Allow member modifiers as part of namespaced names. These were missed when support for other
|
||||||
|
keywords was added.
|
||||||
|
|
||||||
|
Version 4.9.0 (2020-08-18)
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* [PHP 8.0] Added support for named arguments, represented using a new `name` subnode on `Arg`.
|
||||||
|
* [PHP 8.0] Added support for static return type, represented like a normal class return type.
|
||||||
|
* [PHP 8.0] Added support for throw expression, represented using a new `Expr\Throw_` node. For
|
||||||
|
backwards compatibility reasons, throw expressions in statement context continue to be
|
||||||
|
represented using `Stmt\Throw_`.
|
||||||
|
* [PHP 8.0] Added support for keywords as parts of namespaced names.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
* Emit parentheses for class constant fetch with complex left-hand-side.
|
||||||
|
* Emit parentheses for new/instanceof on complex class expression.
|
||||||
|
|
||||||
|
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)
|
Version 4.5.0 (2020-06-03)
|
||||||
--------------------------
|
--------------------------
|
||||||
|
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
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are met:
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
notice, this list of conditions and the following disclaimer.
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
copyright notice, this list of conditions and the following
|
this list of conditions and the following disclaimer in the documentation
|
||||||
disclaimer in the documentation and/or other materials provided
|
and/or other materials provided with the distribution.
|
||||||
with the distribution.
|
|
||||||
|
|
||||||
* The names of the contributors may not be used to endorse or
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
promote products derived from this software without specific
|
contributors may be used to endorse or promote products derived from
|
||||||
prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
12
README.md
12
README.md
@@ -1,12 +1,12 @@
|
|||||||
PHP Parser
|
PHP Parser
|
||||||
==========
|
==========
|
||||||
|
|
||||||
[](https://travis-ci.org/nikic/PHP-Parser) [](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
|
[](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
|
||||||
|
|
||||||
This is a PHP 5.2 to PHP 7.4 parser written in PHP. Its purpose is to simplify static code analysis and
|
This is a PHP 5.2 to PHP 8.2 parser written in PHP. Its purpose is to simplify static code analysis and
|
||||||
manipulation.
|
manipulation.
|
||||||
|
|
||||||
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.4).
|
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.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).
|
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ Features
|
|||||||
|
|
||||||
The main features provided by this library are:
|
The main features provided by this library are:
|
||||||
|
|
||||||
* Parsing PHP 5 and PHP 7 code into an abstract syntax tree (AST).
|
* Parsing PHP 5, PHP 7, and PHP 8 code into an abstract syntax tree (AST).
|
||||||
* Invalid code can be parsed into a partial AST.
|
* Invalid code can be parsed into a partial AST.
|
||||||
* The AST contains accurate location information.
|
* The AST contains accurate location information.
|
||||||
* Dumping the AST in human-readable form.
|
* Dumping the AST in human-readable form.
|
||||||
@@ -215,11 +215,11 @@ Component documentation:
|
|||||||
* [JSON representation](doc/component/JSON_representation.markdown)
|
* [JSON representation](doc/component/JSON_representation.markdown)
|
||||||
* JSON encoding and decoding of ASTs
|
* JSON encoding and decoding of ASTs
|
||||||
* [Performance](doc/component/Performance.markdown)
|
* [Performance](doc/component/Performance.markdown)
|
||||||
* Disabling XDebug
|
* Disabling Xdebug
|
||||||
* Reusing objects
|
* Reusing objects
|
||||||
* Garbage collection impact
|
* Garbage collection impact
|
||||||
* [Frequently asked questions](doc/component/FAQ.markdown)
|
* [Frequently asked questions](doc/component/FAQ.markdown)
|
||||||
* Parent and sibling references
|
* Parent and sibling references
|
||||||
|
|
||||||
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
|
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
|
||||||
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc
|
[doc_4_x]: https://github.com/nikic/PHP-Parser/tree/4.x/doc
|
||||||
|
@@ -10,7 +10,7 @@ foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php
|
|||||||
|
|
||||||
ini_set('xdebug.max_nesting_level', 3000);
|
ini_set('xdebug.max_nesting_level', 3000);
|
||||||
|
|
||||||
// Disable XDebug var_dump() output truncation
|
// Disable Xdebug var_dump() output truncation
|
||||||
ini_set('xdebug.var_display_max_children', -1);
|
ini_set('xdebug.var_display_max_children', -1);
|
||||||
ini_set('xdebug.var_display_max_data', -1);
|
ini_set('xdebug.var_display_max_data', -1);
|
||||||
ini_set('xdebug.var_display_max_depth', -1);
|
ini_set('xdebug.var_display_max_depth', -1);
|
||||||
|
@@ -17,12 +17,12 @@
|
|||||||
"ext-tokenizer": "*"
|
"ext-tokenizer": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0",
|
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0",
|
||||||
"ircmaxell/php-yacc": "0.0.5"
|
"ircmaxell/php-yacc": "^0.0.7"
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "4.3-dev"
|
"dev-master": "4.9-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
Introduction
|
Introduction
|
||||||
============
|
============
|
||||||
|
|
||||||
This project is a PHP 5.2 to PHP 7.4 parser **written in PHP itself**.
|
This project is a PHP 5.2 to PHP 8.0 parser **written in PHP itself**.
|
||||||
|
|
||||||
What is this for?
|
What is this for?
|
||||||
-----------------
|
-----------------
|
||||||
@@ -26,7 +26,11 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
|
|||||||
What can it parse?
|
What can it parse?
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
The parser supports parsing PHP 5.2-7.4.
|
The parser supports parsing PHP 5.2-8.0, with the following exceptions:
|
||||||
|
|
||||||
|
* Namespaced names containing whitespace (e.g. `Foo \ Bar` instead of `Foo\Bar`) are not supported.
|
||||||
|
These are illegal in PHP 8, but are legal in earlier version. However, PHP-Parser does not
|
||||||
|
support them for any version.
|
||||||
|
|
||||||
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
|
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
|
||||||
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
|
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
|
||||||
|
@@ -19,7 +19,7 @@ ini_set('xdebug.max_nesting_level', 3000);
|
|||||||
```
|
```
|
||||||
|
|
||||||
This ensures that there will be no errors when traversing highly nested node trees. However, it is
|
This ensures that there will be no errors when traversing highly nested node trees. However, it is
|
||||||
preferable to disable XDebug completely, as it can easily make this library more than five times
|
preferable to disable Xdebug completely, as it can easily make this library more than five times
|
||||||
slower.
|
slower.
|
||||||
|
|
||||||
Parsing
|
Parsing
|
||||||
|
@@ -39,7 +39,7 @@ Component documentation
|
|||||||
* [JSON representation](component/JSON_representation.markdown)
|
* [JSON representation](component/JSON_representation.markdown)
|
||||||
* JSON encoding and decoding of ASTs
|
* JSON encoding and decoding of ASTs
|
||||||
* [Performance](component/Performance.markdown)
|
* [Performance](component/Performance.markdown)
|
||||||
* Disabling XDebug
|
* Disabling Xdebug
|
||||||
* Reusing objects
|
* Reusing objects
|
||||||
* Garbage collection impact
|
* Garbage collection impact
|
||||||
* [Frequently asked questions](component/FAQ.markdown)
|
* [Frequently asked questions](component/FAQ.markdown)
|
||||||
|
@@ -36,7 +36,7 @@ if ($e->hasColumnInfo()) {
|
|||||||
echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
|
echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
|
||||||
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
|
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
|
||||||
// or:
|
// or:
|
||||||
echo $e->getMessageWithColumnInfo();
|
echo $e->getMessageWithColumnInfo($code);
|
||||||
} else {
|
} else {
|
||||||
echo $e->getMessage();
|
echo $e->getMessage();
|
||||||
}
|
}
|
||||||
|
@@ -7,28 +7,21 @@ Frequently Asked Questions
|
|||||||
How can the parent of a node be obtained?
|
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
|
The AST does not store parent nodes by default. However, the `ParentConnectingVisitor` can be used to achieve this:
|
||||||
attribute using a custom node visitor:
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use PhpParser\Node;
|
use PhpParser\NodeTraverser;
|
||||||
use PhpParser\NodeVisitorAbstract;
|
use PhpParser\NodeVisitor\ParentConnectingVisitor;
|
||||||
|
use PhpParser\ParserFactory;
|
||||||
|
|
||||||
class ParentConnector extends NodeVisitorAbstract {
|
$code = '...';
|
||||||
private $stack;
|
|
||||||
public function beforeTraverse(array $nodes) {
|
$traverser = new NodeTraverser;
|
||||||
$this->stack = [];
|
$traverser->addVisitor(new ParentConnectingVisitor);
|
||||||
}
|
|
||||||
public function enterNode(Node $node) {
|
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||||
if (!empty($this->stack)) {
|
$ast = $parser->parse($code);
|
||||||
$node->setAttribute('parent', $this->stack[count($this->stack)-1]);
|
$ast = $traverser->traverse($ast);
|
||||||
}
|
|
||||||
$this->stack[] = $node;
|
|
||||||
}
|
|
||||||
public function leaveNode(Node $node) {
|
|
||||||
array_pop($this->stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
After running this visitor, the parent node can be obtained through `$node->getAttribute('parent')`.
|
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?
|
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
|
Again, siblings are not stored by default, but the `NodeConnectingVisitor` can be used to store
|
||||||
extended to store the previous / next node with a common parent as well:
|
the previous / next node with a common parent as well:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use PhpParser\Node;
|
use PhpParser\NodeTraverser;
|
||||||
use PhpParser\NodeVisitorAbstract;
|
use PhpParser\NodeVisitor\NodeConnectingVisitor;
|
||||||
|
use PhpParser\ParserFactory;
|
||||||
|
|
||||||
class NodeConnector extends NodeVisitorAbstract {
|
$code = '...';
|
||||||
private $stack;
|
|
||||||
private $prev;
|
$traverser = new NodeTraverser;
|
||||||
public function beforeTraverse(array $nodes) {
|
$traverser->addVisitor(new NodeConnectingVisitor);
|
||||||
$this->stack = [];
|
|
||||||
$this->prev = null;
|
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||||
}
|
$ast = $parser->parse($code);
|
||||||
public function enterNode(Node $node) {
|
$ast = $traverser->traverse($ast);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
@@ -8,20 +8,20 @@ described in the following.
|
|||||||
Xdebug
|
Xdebug
|
||||||
------
|
------
|
||||||
|
|
||||||
Running PHP with XDebug adds a lot of overhead, especially for code that performs many method calls.
|
Running PHP with Xdebug adds a lot of overhead, especially for code that performs many method calls.
|
||||||
Just by loading XDebug (without enabling profiling or other more intrusive XDebug features), you
|
Just by loading Xdebug (without enabling profiling or other more intrusive Xdebug features), you
|
||||||
can expect that code using PHP-Parser will be approximately *five times slower*.
|
can expect that code using PHP-Parser will be approximately *five times slower*.
|
||||||
|
|
||||||
As such, you should make sure that XDebug is not loaded when using this library. Note that setting
|
As such, you should make sure that Xdebug is not loaded when using this library. Note that setting
|
||||||
the `xdebug.default_enable=0` ini option does *not* disable XDebug. The *only* way to disable
|
the `xdebug.default_enable=0` ini option does *not* disable Xdebug. The *only* way to disable
|
||||||
XDebug is to not load the extension in the first place.
|
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),
|
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. The
|
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
|
[composer/xdebug-handler](https://github.com/composer/xdebug-handler) package can be used to do
|
||||||
this.
|
this.
|
||||||
|
|
||||||
If you do run with XDebug, you may need to increase the `xdebug.max_nesting_level` option to a
|
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
|
higher level, such as 3000. While the parser itself is recursion free, most other code working on
|
||||||
the AST uses recursion and will generate an error if the value of this option is too low.
|
the AST uses recursion and will generate an error if the value of this option is too low.
|
||||||
|
|
||||||
|
@@ -193,7 +193,7 @@ anonymous classes), you know that once you've seen a class declaration, there is
|
|||||||
checking all it's child nodes, because PHP does not allow nesting classes. In this case, you can
|
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:
|
instruct the traverser to not recurse into the class node:
|
||||||
|
|
||||||
```
|
```php
|
||||||
private $classes = [];
|
private $classes = [];
|
||||||
public function enterNode(Node $node) {
|
public function enterNode(Node $node) {
|
||||||
if ($node instanceof Node\Stmt\Class_) {
|
if ($node instanceof Node\Stmt\Class_) {
|
||||||
@@ -211,7 +211,7 @@ after finding it. For example, if you are looking for the node of a class with a
|
|||||||
discounting exotic cases like conditionally defining a class two times), you can stop traversal
|
discounting exotic cases like conditionally defining a class two times), you can stop traversal
|
||||||
once you found it:
|
once you found it:
|
||||||
|
|
||||||
```
|
```php
|
||||||
private $class = null;
|
private $class = null;
|
||||||
public function enterNode(Node $node) {
|
public function enterNode(Node $node) {
|
||||||
if ($node instanceof Node\Stmt\Class_ &&
|
if ($node instanceof Node\Stmt\Class_ &&
|
||||||
@@ -251,7 +251,7 @@ Stmt_Return(
|
|||||||
|
|
||||||
the following method calls will be performed:
|
the following method calls will be performed:
|
||||||
|
|
||||||
```
|
```php
|
||||||
$visitorA->enterNode(Stmt_Return)
|
$visitorA->enterNode(Stmt_Return)
|
||||||
$visitorB->enterNode(Stmt_Return)
|
$visitorB->enterNode(Stmt_Return)
|
||||||
$visitorA->enterNode(Expr_Variable)
|
$visitorA->enterNode(Expr_Variable)
|
||||||
|
@@ -20,6 +20,11 @@ top_statement_list:
|
|||||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
ampersand:
|
||||||
|
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||||
|
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
|
||||||
|
;
|
||||||
|
|
||||||
reserved_non_modifiers:
|
reserved_non_modifiers:
|
||||||
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
|
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
|
||||||
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
|
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
|
||||||
@@ -28,6 +33,7 @@ reserved_non_modifiers:
|
|||||||
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
|
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
|
||||||
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
|
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
|
||||||
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
|
| 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:
|
semi_reserved:
|
||||||
@@ -48,13 +54,14 @@ reserved_non_modifiers_identifier:
|
|||||||
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
|
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
namespace_name_parts:
|
namespace_name:
|
||||||
T_STRING { init($1); }
|
T_STRING { $$ = Name[$1]; }
|
||||||
| namespace_name_parts T_NS_SEPARATOR T_STRING { push($1, $3); }
|
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
namespace_name:
|
legacy_namespace_name:
|
||||||
namespace_name_parts { $$ = Name[$1]; }
|
namespace_name { $$ = $1; }
|
||||||
|
| T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
plain_variable:
|
plain_variable:
|
||||||
@@ -90,16 +97,11 @@ use_type:
|
|||||||
| T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; }
|
| T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; }
|
||||||
;
|
;
|
||||||
|
|
||||||
/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
|
|
||||||
group_use_declaration:
|
group_use_declaration:
|
||||||
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, $2]; }
|
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
|
||||||
| T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||||
{ $$ = Stmt\GroupUse[new Name($4, stackAttributes(#4)), $7, $2]; }
|
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||||
| 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]; }
|
|
||||||
;
|
;
|
||||||
|
|
||||||
unprefixed_use_declarations:
|
unprefixed_use_declarations:
|
||||||
@@ -126,8 +128,10 @@ unprefixed_use_declaration:
|
|||||||
;
|
;
|
||||||
|
|
||||||
use_declaration:
|
use_declaration:
|
||||||
unprefixed_use_declaration { $$ = $1; }
|
legacy_namespace_name
|
||||||
| T_NS_SEPARATOR unprefixed_use_declaration { $$ = $2; }
|
{ $$ = 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:
|
inline_use_declaration:
|
||||||
@@ -247,7 +251,12 @@ variables_list:
|
|||||||
|
|
||||||
optional_ref:
|
optional_ref:
|
||||||
/* empty */ { $$ = false; }
|
/* empty */ { $$ = false; }
|
||||||
| '&' { $$ = true; }
|
| ampersand { $$ = true; }
|
||||||
|
;
|
||||||
|
|
||||||
|
optional_arg_ref:
|
||||||
|
/* empty */ { $$ = false; }
|
||||||
|
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; }
|
||||||
;
|
;
|
||||||
|
|
||||||
optional_ellipsis:
|
optional_ellipsis:
|
||||||
@@ -255,8 +264,13 @@ optional_ellipsis:
|
|||||||
| T_ELLIPSIS { $$ = true; }
|
| T_ELLIPSIS { $$ = true; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
identifier_maybe_readonly:
|
||||||
|
identifier { $$ = $1; }
|
||||||
|
| T_READONLY { $$ = Node\Identifier[$1]; }
|
||||||
|
;
|
||||||
|
|
||||||
function_declaration_statement:
|
function_declaration_statement:
|
||||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
|
||||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
|
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -379,7 +393,7 @@ new_else_single:
|
|||||||
|
|
||||||
foreach_variable:
|
foreach_variable:
|
||||||
variable { $$ = array($1, false); }
|
variable { $$ = array($1, false); }
|
||||||
| '&' variable { $$ = array($2, true); }
|
| ampersand variable { $$ = array($2, true); }
|
||||||
| list_expr { $$ = array($1, false); }
|
| list_expr { $$ = array($1, false); }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -394,9 +408,9 @@ non_empty_parameter_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
parameter:
|
parameter:
|
||||||
optional_param_type optional_ref optional_ellipsis plain_variable
|
optional_param_type optional_arg_ref optional_ellipsis plain_variable
|
||||||
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
||||||
| optional_param_type optional_ref optional_ellipsis plain_variable '=' static_scalar
|
| optional_param_type optional_arg_ref optional_ellipsis plain_variable '=' static_scalar
|
||||||
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -429,7 +443,7 @@ non_empty_argument_list:
|
|||||||
|
|
||||||
argument:
|
argument:
|
||||||
expr { $$ = Node\Arg[$1, false, false]; }
|
expr { $$ = Node\Arg[$1, false, false]; }
|
||||||
| '&' variable { $$ = Node\Arg[$2, true, false]; }
|
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
|
||||||
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
|
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -563,8 +577,8 @@ expr:
|
|||||||
variable { $$ = $1; }
|
variable { $$ = $1; }
|
||||||
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
|
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||||
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
|
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||||
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
|
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
|
||||||
| variable '=' '&' new_expr { $$ = Expr\AssignRef[$1, $4]; }
|
| variable '=' ampersand new_expr { $$ = Expr\AssignRef[$1, $4]; }
|
||||||
| new_expr { $$ = $1; }
|
| new_expr { $$ = $1; }
|
||||||
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
||||||
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
|
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
|
||||||
@@ -590,7 +604,8 @@ expr:
|
|||||||
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
||||||
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
||||||
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
||||||
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||||
|
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||||
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
||||||
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
||||||
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
||||||
@@ -679,9 +694,7 @@ array_expr:
|
|||||||
|
|
||||||
scalar_dereference:
|
scalar_dereference:
|
||||||
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||||
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
|
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[Scalar\String_::fromString($1, attributes()), $3]; }
|
||||||
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
|
|
||||||
$$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
|
|
||||||
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||||
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||||
/* alternative array syntax missing intentionally */
|
/* alternative array syntax missing intentionally */
|
||||||
@@ -713,8 +726,13 @@ lexical_var:
|
|||||||
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
name_readonly:
|
||||||
|
T_READONLY { $$ = Name[$1]; }
|
||||||
|
;
|
||||||
|
|
||||||
function_call:
|
function_call:
|
||||||
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||||
|
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
|
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
|
||||||
{ $$ = Expr\StaticCall[$1, $3, $4]; }
|
{ $$ = Expr\StaticCall[$1, $3, $4]; }
|
||||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
|
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
|
||||||
@@ -733,9 +751,10 @@ class_name:
|
|||||||
;
|
;
|
||||||
|
|
||||||
name:
|
name:
|
||||||
namespace_name_parts { $$ = Name[$1]; }
|
T_STRING { $$ = Name[$1]; }
|
||||||
| T_NS_SEPARATOR namespace_name_parts { $$ = Name\FullyQualified[$2]; }
|
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||||
| T_NAMESPACE T_NS_SEPARATOR namespace_name_parts { $$ = Name\Relative[$3]; }
|
| T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; }
|
||||||
|
| T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
class_name_reference:
|
class_name_reference:
|
||||||
@@ -782,10 +801,8 @@ ctor_arguments:
|
|||||||
|
|
||||||
common_scalar:
|
common_scalar:
|
||||||
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
|
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
|
||||||
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
|
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
|
||||||
| T_CONSTANT_ENCAPSED_STRING
|
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes(), false); }
|
||||||
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
|
|
||||||
$$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
|
|
||||||
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
|
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
|
||||||
| T_FILE { $$ = Scalar\MagicConst\File[]; }
|
| T_FILE { $$ = Scalar\MagicConst\File[]; }
|
||||||
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
|
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
|
||||||
@@ -816,7 +833,10 @@ static_operation:
|
|||||||
| static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
| static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
||||||
| static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
| static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
||||||
| static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
| static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
||||||
| static_scalar '&' static_scalar { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
| static_scalar T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG static_scalar
|
||||||
|
{ $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||||
|
| static_scalar T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG static_scalar
|
||||||
|
{ $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||||
| static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
| static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
||||||
| static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
| static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
||||||
| static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
| static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
||||||
@@ -986,8 +1006,8 @@ non_empty_array_pair_list:
|
|||||||
array_pair:
|
array_pair:
|
||||||
expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||||
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||||
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||||
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||||
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
|
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
411
grammar/php7.y
411
grammar/php7.y
@@ -20,6 +20,11 @@ top_statement_list:
|
|||||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
ampersand:
|
||||||
|
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||||
|
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
|
||||||
|
;
|
||||||
|
|
||||||
reserved_non_modifiers:
|
reserved_non_modifiers:
|
||||||
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
|
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
|
||||||
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
|
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
|
||||||
@@ -28,19 +33,20 @@ reserved_non_modifiers:
|
|||||||
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
|
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
|
||||||
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
|
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
|
||||||
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
|
| 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 | T_ENUM
|
||||||
;
|
;
|
||||||
|
|
||||||
semi_reserved:
|
semi_reserved:
|
||||||
reserved_non_modifiers
|
reserved_non_modifiers
|
||||||
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
|
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY
|
||||||
;
|
;
|
||||||
|
|
||||||
identifier_ex:
|
identifier_maybe_reserved:
|
||||||
T_STRING { $$ = Node\Identifier[$1]; }
|
T_STRING { $$ = Node\Identifier[$1]; }
|
||||||
| semi_reserved { $$ = Node\Identifier[$1]; }
|
| semi_reserved { $$ = Node\Identifier[$1]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
identifier:
|
identifier_not_reserved:
|
||||||
T_STRING { $$ = Node\Identifier[$1]; }
|
T_STRING { $$ = Node\Identifier[$1]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -48,13 +54,20 @@ reserved_non_modifiers_identifier:
|
|||||||
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
|
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
namespace_name_parts:
|
namespace_declaration_name:
|
||||||
T_STRING { init($1); }
|
T_STRING { $$ = Name[$1]; }
|
||||||
| namespace_name_parts T_NS_SEPARATOR T_STRING { push($1, $3); }
|
| semi_reserved { $$ = Name[$1]; }
|
||||||
|
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
namespace_name:
|
namespace_name:
|
||||||
namespace_name_parts { $$ = Name[$1]; }
|
T_STRING { $$ = Name[$1]; }
|
||||||
|
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||||
|
;
|
||||||
|
|
||||||
|
legacy_namespace_name:
|
||||||
|
namespace_name { $$ = $1; }
|
||||||
|
| T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
plain_variable:
|
plain_variable:
|
||||||
@@ -74,6 +87,31 @@ no_comma:
|
|||||||
optional_comma:
|
optional_comma:
|
||||||
/* empty */
|
/* empty */
|
||||||
| ','
|
| ','
|
||||||
|
;
|
||||||
|
|
||||||
|
attribute_decl:
|
||||||
|
class_name { $$ = Node\Attribute[$1, []]; }
|
||||||
|
| class_name argument_list { $$ = Node\Attribute[$1, $2]; }
|
||||||
|
;
|
||||||
|
|
||||||
|
attribute_group:
|
||||||
|
attribute_decl { init($1); }
|
||||||
|
| attribute_group ',' attribute_decl { push($1, $3); }
|
||||||
|
;
|
||||||
|
|
||||||
|
attribute:
|
||||||
|
T_ATTRIBUTE attribute_group optional_comma ']' { $$ = Node\AttributeGroup[$2]; }
|
||||||
|
;
|
||||||
|
|
||||||
|
attributes:
|
||||||
|
attribute { init($1); }
|
||||||
|
| attributes attribute { push($1, $2); }
|
||||||
|
;
|
||||||
|
|
||||||
|
optional_attributes:
|
||||||
|
/* empty */ { $$ = []; }
|
||||||
|
| attributes { $$ = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
top_statement:
|
top_statement:
|
||||||
statement { $$ = $1; }
|
statement { $$ = $1; }
|
||||||
@@ -81,11 +119,11 @@ top_statement:
|
|||||||
| class_declaration_statement { $$ = $1; }
|
| class_declaration_statement { $$ = $1; }
|
||||||
| T_HALT_COMPILER
|
| T_HALT_COMPILER
|
||||||
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
|
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
|
||||||
| T_NAMESPACE namespace_name semi
|
| T_NAMESPACE namespace_declaration_name semi
|
||||||
{ $$ = Stmt\Namespace_[$2, null];
|
{ $$ = Stmt\Namespace_[$2, null];
|
||||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
|
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
|
||||||
$this->checkNamespace($$); }
|
$this->checkNamespace($$); }
|
||||||
| T_NAMESPACE namespace_name '{' top_statement_list '}'
|
| T_NAMESPACE namespace_declaration_name '{' top_statement_list '}'
|
||||||
{ $$ = Stmt\Namespace_[$2, $4];
|
{ $$ = Stmt\Namespace_[$2, $4];
|
||||||
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
|
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
|
||||||
$this->checkNamespace($$); }
|
$this->checkNamespace($$); }
|
||||||
@@ -104,16 +142,11 @@ use_type:
|
|||||||
| T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; }
|
| T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; }
|
||||||
;
|
;
|
||||||
|
|
||||||
/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
|
|
||||||
group_use_declaration:
|
group_use_declaration:
|
||||||
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, $2]; }
|
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
|
||||||
| T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||||
{ $$ = Stmt\GroupUse[new Name($4, stackAttributes(#4)), $7, $2]; }
|
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||||
| 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]; }
|
|
||||||
;
|
;
|
||||||
|
|
||||||
unprefixed_use_declarations:
|
unprefixed_use_declarations:
|
||||||
@@ -148,13 +181,15 @@ non_empty_inline_use_declarations:
|
|||||||
unprefixed_use_declaration:
|
unprefixed_use_declaration:
|
||||||
namespace_name
|
namespace_name
|
||||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||||
| namespace_name T_AS identifier
|
| namespace_name T_AS identifier_not_reserved
|
||||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||||
;
|
;
|
||||||
|
|
||||||
use_declaration:
|
use_declaration:
|
||||||
unprefixed_use_declaration { $$ = $1; }
|
legacy_namespace_name
|
||||||
| T_NS_SEPARATOR unprefixed_use_declaration { $$ = $2; }
|
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||||
|
| legacy_namespace_name T_AS identifier_not_reserved
|
||||||
|
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||||
;
|
;
|
||||||
|
|
||||||
inline_use_declaration:
|
inline_use_declaration:
|
||||||
@@ -173,7 +208,7 @@ non_empty_constant_declaration_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
constant_declaration:
|
constant_declaration:
|
||||||
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
|
identifier_not_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
class_const_list:
|
class_const_list:
|
||||||
@@ -186,7 +221,7 @@ non_empty_class_const_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
class_const:
|
class_const:
|
||||||
identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; }
|
identifier_maybe_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
inner_statement_list_ex:
|
inner_statement_list_ex:
|
||||||
@@ -234,7 +269,16 @@ non_empty_statement:
|
|||||||
| T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; }
|
| T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; }
|
||||||
| T_ECHO expr_list_forbid_comma semi { $$ = Stmt\Echo_[$2]; }
|
| T_ECHO expr_list_forbid_comma semi { $$ = Stmt\Echo_[$2]; }
|
||||||
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
|
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
|
||||||
| expr semi { $$ = Stmt\Expression[$1]; }
|
| expr semi {
|
||||||
|
$e = $1;
|
||||||
|
if ($e instanceof Expr\Throw_) {
|
||||||
|
// For backwards-compatibility reasons, convert throw in statement position into
|
||||||
|
// Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_).
|
||||||
|
$$ = Stmt\Throw_[$e->expr];
|
||||||
|
} else {
|
||||||
|
$$ = Stmt\Expression[$e];
|
||||||
|
}
|
||||||
|
}
|
||||||
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
|
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
|
||||||
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
|
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
|
||||||
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
|
||||||
@@ -245,9 +289,8 @@ non_empty_statement:
|
|||||||
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
|
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
|
||||||
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
||||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||||
| T_THROW expr semi { $$ = Stmt\Throw_[$2]; }
|
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
|
||||||
| T_GOTO identifier semi { $$ = Stmt\Goto_[$2]; }
|
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
|
||||||
| identifier ':' { $$ = Stmt\Label[$1]; }
|
|
||||||
| error { $$ = array(); /* means: no statement */ }
|
| error { $$ = array(); /* means: no statement */ }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -289,7 +332,12 @@ non_empty_variables_list:
|
|||||||
|
|
||||||
optional_ref:
|
optional_ref:
|
||||||
/* empty */ { $$ = false; }
|
/* empty */ { $$ = false; }
|
||||||
| '&' { $$ = true; }
|
| ampersand { $$ = true; }
|
||||||
|
;
|
||||||
|
|
||||||
|
optional_arg_ref:
|
||||||
|
/* empty */ { $$ = false; }
|
||||||
|
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; }
|
||||||
;
|
;
|
||||||
|
|
||||||
optional_ellipsis:
|
optional_ellipsis:
|
||||||
@@ -302,26 +350,58 @@ block_or_error:
|
|||||||
| error { $$ = []; }
|
| error { $$ = []; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
identifier_maybe_readonly:
|
||||||
|
identifier_not_reserved { $$ = $1; }
|
||||||
|
| T_READONLY { $$ = Node\Identifier[$1]; }
|
||||||
|
;
|
||||||
|
|
||||||
function_declaration_statement:
|
function_declaration_statement:
|
||||||
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
|
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||||
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8]]; }
|
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
|
||||||
|
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
|
||||||
|
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
class_declaration_statement:
|
class_declaration_statement:
|
||||||
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
|
class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
|
||||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
|
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
|
||||||
$this->checkClass($$, #2); }
|
$this->checkClass($$, #2); }
|
||||||
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
|
| attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
|
||||||
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
|
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
|
||||||
$this->checkInterface($$, #2); }
|
$this->checkClass($$, #3); }
|
||||||
| T_TRAIT identifier '{' class_statement_list '}'
|
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
|
||||||
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
|
{ $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]];
|
||||||
|
$this->checkInterface($$, #3); }
|
||||||
|
| optional_attributes T_TRAIT identifier_not_reserved '{' class_statement_list '}'
|
||||||
|
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
|
||||||
|
| optional_attributes T_ENUM identifier_not_reserved enum_scalar_type implements_list '{' class_statement_list '}'
|
||||||
|
{ $$ = Stmt\Enum_[$3, ['scalarType' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
|
||||||
|
$this->checkEnum($$, #3); }
|
||||||
|
;
|
||||||
|
|
||||||
|
enum_scalar_type:
|
||||||
|
/* empty */ { $$ = null; }
|
||||||
|
| ':' type { $$ = $2; }
|
||||||
|
|
||||||
|
enum_case_expr:
|
||||||
|
/* empty */ { $$ = null; }
|
||||||
|
| '=' expr { $$ = $2; }
|
||||||
;
|
;
|
||||||
|
|
||||||
class_entry_type:
|
class_entry_type:
|
||||||
T_CLASS { $$ = 0; }
|
T_CLASS { $$ = 0; }
|
||||||
| T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
|
| class_modifiers T_CLASS { $$ = $1; }
|
||||||
| T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; }
|
;
|
||||||
|
|
||||||
|
class_modifiers:
|
||||||
|
class_modifier { $$ = $1; }
|
||||||
|
| class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; }
|
||||||
|
;
|
||||||
|
|
||||||
|
class_modifier:
|
||||||
|
T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
|
||||||
|
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
|
||||||
|
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
|
||||||
;
|
;
|
||||||
|
|
||||||
extends_from:
|
extends_from:
|
||||||
@@ -374,7 +454,7 @@ non_empty_declare_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
declare_list_element:
|
declare_list_element:
|
||||||
identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
identifier_not_reserved '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
switch_case_list:
|
switch_case_list:
|
||||||
@@ -399,6 +479,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:
|
while_statement:
|
||||||
statement { $$ = toArray($1); }
|
statement { $$ = toArray($1); }
|
||||||
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
|
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
|
||||||
@@ -419,7 +518,8 @@ new_elseif_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
new_elseif:
|
new_elseif:
|
||||||
T_ELSEIF '(' expr ')' ':' inner_statement_list { $$ = Stmt\ElseIf_[$3, $6]; }
|
T_ELSEIF '(' expr ')' ':' inner_statement_list
|
||||||
|
{ $$ = Stmt\ElseIf_[$3, $6]; $this->fixupAlternativeElse($$); }
|
||||||
;
|
;
|
||||||
|
|
||||||
else_single:
|
else_single:
|
||||||
@@ -429,18 +529,19 @@ else_single:
|
|||||||
|
|
||||||
new_else_single:
|
new_else_single:
|
||||||
/* empty */ { $$ = null; }
|
/* empty */ { $$ = null; }
|
||||||
| T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; }
|
| T_ELSE ':' inner_statement_list
|
||||||
|
{ $$ = Stmt\Else_[$3]; $this->fixupAlternativeElse($$); }
|
||||||
;
|
;
|
||||||
|
|
||||||
foreach_variable:
|
foreach_variable:
|
||||||
variable { $$ = array($1, false); }
|
variable { $$ = array($1, false); }
|
||||||
| '&' variable { $$ = array($2, true); }
|
| ampersand variable { $$ = array($2, true); }
|
||||||
| list_expr { $$ = array($1, false); }
|
| list_expr { $$ = array($1, false); }
|
||||||
| array_short_syntax { $$ = array($1, false); }
|
| array_short_syntax { $$ = array($1, false); }
|
||||||
;
|
;
|
||||||
|
|
||||||
parameter_list:
|
parameter_list:
|
||||||
non_empty_parameter_list no_comma { $$ = $1; }
|
non_empty_parameter_list optional_comma { $$ = $1; }
|
||||||
| /* empty */ { $$ = array(); }
|
| /* empty */ { $$ = array(); }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -449,35 +550,102 @@ non_empty_parameter_list:
|
|||||||
| non_empty_parameter_list ',' parameter { push($1, $3); }
|
| non_empty_parameter_list ',' parameter { push($1, $3); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
optional_property_modifiers:
|
||||||
|
/* empty */ { $$ = 0; }
|
||||||
|
| optional_property_modifiers property_modifier
|
||||||
|
{ $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
|
||||||
|
;
|
||||||
|
|
||||||
|
property_modifier:
|
||||||
|
T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
|
||||||
|
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
|
||||||
|
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
|
||||||
|
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
|
||||||
|
;
|
||||||
|
|
||||||
parameter:
|
parameter:
|
||||||
optional_type optional_ref optional_ellipsis plain_variable
|
optional_attributes optional_property_modifiers optional_type_without_static
|
||||||
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
|
optional_arg_ref optional_ellipsis plain_variable
|
||||||
| optional_type optional_ref optional_ellipsis plain_variable '=' expr
|
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
|
||||||
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
|
$this->checkParam($$); }
|
||||||
| optional_type optional_ref optional_ellipsis error
|
| optional_attributes optional_property_modifiers optional_type_without_static
|
||||||
{ $$ = Node\Param[Expr\Error[], null, $1, $2, $3]; }
|
optional_arg_ref optional_ellipsis plain_variable '=' expr
|
||||||
|
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
|
||||||
|
$this->checkParam($$); }
|
||||||
|
| optional_attributes optional_property_modifiers optional_type_without_static
|
||||||
|
optional_arg_ref optional_ellipsis error
|
||||||
|
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
|
||||||
;
|
;
|
||||||
|
|
||||||
type_expr:
|
type_expr:
|
||||||
type { $$ = $1; }
|
type { $$ = $1; }
|
||||||
| '?' type { $$ = Node\NullableType[$2]; }
|
| '?' type { $$ = Node\NullableType[$2]; }
|
||||||
| union_type { $$ = Node\UnionType[$1]; }
|
| union_type { $$ = Node\UnionType[$1]; }
|
||||||
|
| intersection_type { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
type:
|
type:
|
||||||
|
type_without_static { $$ = $1; }
|
||||||
|
| T_STATIC { $$ = Node\Name['static']; }
|
||||||
|
;
|
||||||
|
|
||||||
|
type_without_static:
|
||||||
name { $$ = $this->handleBuiltinTypes($1); }
|
name { $$ = $this->handleBuiltinTypes($1); }
|
||||||
| T_ARRAY { $$ = Node\Identifier['array']; }
|
| T_ARRAY { $$ = Node\Identifier['array']; }
|
||||||
| T_CALLABLE { $$ = Node\Identifier['callable']; }
|
| T_CALLABLE { $$ = Node\Identifier['callable']; }
|
||||||
;
|
;
|
||||||
|
|
||||||
union_type:
|
union_type_element:
|
||||||
type '|' type { init($1, $3); }
|
type { $$ = $1; }
|
||||||
| union_type '|' type { push($1, $3); }
|
| '(' intersection_type ')' { $$ = $2; }
|
||||||
;
|
;
|
||||||
|
|
||||||
optional_type:
|
union_type:
|
||||||
|
union_type_element '|' union_type_element { init($1, $3); }
|
||||||
|
| union_type '|' union_type_element { push($1, $3); }
|
||||||
|
;
|
||||||
|
|
||||||
|
union_type_without_static_element:
|
||||||
|
type_without_static { $$ = $1; }
|
||||||
|
| '(' intersection_type_without_static ')' { $$ = $2; }
|
||||||
|
;
|
||||||
|
|
||||||
|
union_type_without_static:
|
||||||
|
union_type_without_static_element '|' union_type_without_static_element { init($1, $3); }
|
||||||
|
| union_type_without_static '|' union_type_without_static_element { push($1, $3); }
|
||||||
|
;
|
||||||
|
|
||||||
|
intersection_type_list:
|
||||||
|
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); }
|
||||||
|
| intersection_type_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
|
||||||
|
{ push($1, $3); }
|
||||||
|
;
|
||||||
|
|
||||||
|
intersection_type:
|
||||||
|
intersection_type_list { $$ = Node\IntersectionType[$1]; }
|
||||||
|
;
|
||||||
|
|
||||||
|
intersection_type_without_static_list:
|
||||||
|
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
|
||||||
|
{ init($1, $3); }
|
||||||
|
| intersection_type_without_static_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
|
||||||
|
{ push($1, $3); }
|
||||||
|
;
|
||||||
|
|
||||||
|
intersection_type_without_static:
|
||||||
|
intersection_type_without_static_list { $$ = Node\IntersectionType[$1]; }
|
||||||
|
;
|
||||||
|
|
||||||
|
type_expr_without_static:
|
||||||
|
type_without_static { $$ = $1; }
|
||||||
|
| '?' type_without_static { $$ = Node\NullableType[$2]; }
|
||||||
|
| union_type_without_static { $$ = Node\UnionType[$1]; }
|
||||||
|
| intersection_type_without_static { $$ = $1; }
|
||||||
|
;
|
||||||
|
|
||||||
|
optional_type_without_static:
|
||||||
/* empty */ { $$ = null; }
|
/* empty */ { $$ = null; }
|
||||||
| type_expr { $$ = $1; }
|
| type_expr_without_static { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
optional_return_type:
|
optional_return_type:
|
||||||
@@ -489,6 +657,11 @@ optional_return_type:
|
|||||||
argument_list:
|
argument_list:
|
||||||
'(' ')' { $$ = array(); }
|
'(' ')' { $$ = array(); }
|
||||||
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
|
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
|
||||||
|
| '(' variadic_placeholder ')' { init($2); }
|
||||||
|
;
|
||||||
|
|
||||||
|
variadic_placeholder:
|
||||||
|
T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
non_empty_argument_list:
|
non_empty_argument_list:
|
||||||
@@ -498,8 +671,10 @@ non_empty_argument_list:
|
|||||||
|
|
||||||
argument:
|
argument:
|
||||||
expr { $$ = Node\Arg[$1, false, false]; }
|
expr { $$ = Node\Arg[$1, false, false]; }
|
||||||
| '&' variable { $$ = Node\Arg[$2, true, false]; }
|
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
|
||||||
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
|
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
|
||||||
|
| identifier_maybe_reserved ':' expr
|
||||||
|
{ $$ = new Node\Arg($3, false, false, attributes(), $1); }
|
||||||
;
|
;
|
||||||
|
|
||||||
global_var_list:
|
global_var_list:
|
||||||
@@ -512,7 +687,7 @@ non_empty_global_var_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
global_var:
|
global_var:
|
||||||
simple_variable { $$ = Expr\Variable[$1]; }
|
simple_variable { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
static_var_list:
|
static_var_list:
|
||||||
@@ -541,15 +716,19 @@ class_statement_list:
|
|||||||
;
|
;
|
||||||
|
|
||||||
class_statement:
|
class_statement:
|
||||||
variable_modifiers optional_type property_declaration_list ';'
|
optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi
|
||||||
{ $attrs = attributes();
|
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
|
||||||
$$ = new Stmt\Property($1, $3, $attrs, $2); $this->checkProperty($$, #1); }
|
$this->checkProperty($$, #2); }
|
||||||
| method_modifiers T_CONST class_const_list ';'
|
| optional_attributes method_modifiers T_CONST class_const_list semi
|
||||||
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
|
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
|
||||||
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
|
$this->checkClassConst($$, #2); }
|
||||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
|
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')'
|
||||||
$this->checkClassMethod($$, #1); }
|
optional_return_type method_body
|
||||||
|
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
|
||||||
|
$this->checkClassMethod($$, #2); }
|
||||||
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
||||||
|
| optional_attributes T_CASE identifier_maybe_reserved enum_case_expr semi
|
||||||
|
{ $$ = Stmt\EnumCase[$3, $4, $1]; }
|
||||||
| error { $$ = null; /* will be skipped */ }
|
| error { $$ = null; /* will be skipped */ }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -566,22 +745,22 @@ trait_adaptation_list:
|
|||||||
trait_adaptation:
|
trait_adaptation:
|
||||||
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
|
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
|
||||||
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
|
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
|
||||||
| trait_method_reference T_AS member_modifier identifier_ex ';'
|
| trait_method_reference T_AS member_modifier identifier_maybe_reserved ';'
|
||||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
|
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
|
||||||
| trait_method_reference T_AS member_modifier ';'
|
| trait_method_reference T_AS member_modifier ';'
|
||||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
|
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
|
||||||
| trait_method_reference T_AS identifier ';'
|
| trait_method_reference T_AS identifier_not_reserved ';'
|
||||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
||||||
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
|
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
|
||||||
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
trait_method_reference_fully_qualified:
|
trait_method_reference_fully_qualified:
|
||||||
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
|
name T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved { $$ = array($1, $3); }
|
||||||
;
|
;
|
||||||
trait_method_reference:
|
trait_method_reference:
|
||||||
trait_method_reference_fully_qualified { $$ = $1; }
|
trait_method_reference_fully_qualified { $$ = $1; }
|
||||||
| identifier_ex { $$ = array(null, $1); }
|
| identifier_maybe_reserved { $$ = array(null, $1); }
|
||||||
;
|
;
|
||||||
|
|
||||||
method_body:
|
method_body:
|
||||||
@@ -611,6 +790,7 @@ member_modifier:
|
|||||||
| T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; }
|
| T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; }
|
||||||
| T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
|
| T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
|
||||||
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
|
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
|
||||||
|
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
|
||||||
;
|
;
|
||||||
|
|
||||||
property_declaration_list:
|
property_declaration_list:
|
||||||
@@ -655,8 +835,9 @@ expr:
|
|||||||
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
|
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||||
| array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; }
|
| array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||||
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
|
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
|
||||||
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
|
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
|
||||||
| new_expr { $$ = $1; }
|
| new_expr { $$ = $1; }
|
||||||
|
| match { $$ = $1; }
|
||||||
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
|
||||||
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
|
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
|
||||||
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }
|
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }
|
||||||
@@ -681,7 +862,8 @@ expr:
|
|||||||
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
||||||
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
||||||
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
||||||
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||||
|
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||||
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
||||||
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
||||||
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
||||||
@@ -739,23 +921,30 @@ expr:
|
|||||||
| T_YIELD expr { $$ = Expr\Yield_[$2, null]; }
|
| T_YIELD expr { $$ = Expr\Yield_[$2, null]; }
|
||||||
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; }
|
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; }
|
||||||
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
|
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
|
||||||
|
| T_THROW expr { $$ = Expr\Throw_[$2]; }
|
||||||
|
|
||||||
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
|
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
|
||||||
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8]]; }
|
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8, 'attrGroups' => []]]; }
|
||||||
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
|
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
|
||||||
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9]]; }
|
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => []]]; }
|
||||||
|
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
|
||||||
|
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
|
||||||
|
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
|
||||||
|
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => []]]; }
|
||||||
|
|
||||||
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
|
| attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
|
||||||
block_or_error
|
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => $1]]; }
|
||||||
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; }
|
| attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
|
||||||
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
|
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $4, 'params' => $6, 'returnType' => $8, 'expr' => $10, 'attrGroups' => $1]]; }
|
||||||
block_or_error
|
| attributes T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
|
||||||
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9]]; }
|
{ $$ = Expr\Closure[['static' => false, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
|
||||||
|
| attributes T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
|
||||||
|
{ $$ = Expr\Closure[['static' => true, 'byRef' => $4, 'params' => $6, 'uses' => $8, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
anonymous_class:
|
anonymous_class:
|
||||||
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
|
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
|
||||||
$this->checkClass($$[0], -1); }
|
$this->checkClass($$[0], -1); }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -771,7 +960,7 @@ lexical_vars:
|
|||||||
;
|
;
|
||||||
|
|
||||||
lexical_var_list:
|
lexical_var_list:
|
||||||
non_empty_lexical_var_list no_comma { $$ = $1; }
|
non_empty_lexical_var_list optional_comma { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
non_empty_lexical_var_list:
|
non_empty_lexical_var_list:
|
||||||
@@ -783,8 +972,13 @@ lexical_var:
|
|||||||
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
name_readonly:
|
||||||
|
T_READONLY { $$ = Name[$1]; }
|
||||||
|
;
|
||||||
|
|
||||||
function_call:
|
function_call:
|
||||||
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||||
|
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||||
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
|
||||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
|
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
|
||||||
{ $$ = Expr\StaticCall[$1, $3, $4]; }
|
{ $$ = Expr\StaticCall[$1, $3, $4]; }
|
||||||
@@ -796,9 +990,10 @@ class_name:
|
|||||||
;
|
;
|
||||||
|
|
||||||
name:
|
name:
|
||||||
namespace_name_parts { $$ = Name[$1]; }
|
T_STRING { $$ = Name[$1]; }
|
||||||
| T_NS_SEPARATOR namespace_name_parts { $$ = Name\FullyQualified[$2]; }
|
| T_NAME_QUALIFIED { $$ = Name[$1]; }
|
||||||
| T_NAMESPACE T_NS_SEPARATOR namespace_name_parts { $$ = Name\Relative[$3]; }
|
| T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; }
|
||||||
|
| T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
class_name_reference:
|
class_name_reference:
|
||||||
@@ -843,7 +1038,7 @@ constant:
|
|||||||
;
|
;
|
||||||
|
|
||||||
class_constant:
|
class_constant:
|
||||||
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
|
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved
|
||||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||||
/* We interpret an 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. */
|
an unfinished static property fetch or unfinished scoped call. */
|
||||||
@@ -862,9 +1057,7 @@ dereferencable_scalar:
|
|||||||
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
|
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
|
||||||
$$ = new Expr\Array_($3, $attrs); }
|
$$ = new Expr\Array_($3, $attrs); }
|
||||||
| array_short_syntax { $$ = $1; }
|
| array_short_syntax { $$ = $1; }
|
||||||
| T_CONSTANT_ENCAPSED_STRING
|
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
|
||||||
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
|
|
||||||
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
|
|
||||||
| '"' encaps_list '"'
|
| '"' encaps_list '"'
|
||||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||||
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
|
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||||
@@ -872,7 +1065,7 @@ dereferencable_scalar:
|
|||||||
|
|
||||||
scalar:
|
scalar:
|
||||||
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
|
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
|
||||||
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
|
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
|
||||||
| dereferencable_scalar { $$ = $1; }
|
| dereferencable_scalar { $$ = $1; }
|
||||||
| constant { $$ = $1; }
|
| constant { $$ = $1; }
|
||||||
| class_constant { $$ = $1; }
|
| class_constant { $$ = $1; }
|
||||||
@@ -908,12 +1101,14 @@ callable_expr:
|
|||||||
;
|
;
|
||||||
|
|
||||||
callable_variable:
|
callable_variable:
|
||||||
simple_variable { $$ = Expr\Variable[$1]; }
|
simple_variable { $$ = $1; }
|
||||||
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||||
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||||
| function_call { $$ = $1; }
|
| function_call { $$ = $1; }
|
||||||
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
|
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
|
||||||
{ $$ = Expr\MethodCall[$1, $3, $4]; }
|
{ $$ = Expr\MethodCall[$1, $3, $4]; }
|
||||||
|
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
|
||||||
|
{ $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
optional_plain_variable:
|
optional_plain_variable:
|
||||||
@@ -926,18 +1121,20 @@ variable:
|
|||||||
| static_member { $$ = $1; }
|
| static_member { $$ = $1; }
|
||||||
| array_object_dereferencable T_OBJECT_OPERATOR property_name
|
| array_object_dereferencable T_OBJECT_OPERATOR property_name
|
||||||
{ $$ = Expr\PropertyFetch[$1, $3]; }
|
{ $$ = Expr\PropertyFetch[$1, $3]; }
|
||||||
|
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name
|
||||||
|
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||||
;
|
;
|
||||||
|
|
||||||
simple_variable:
|
simple_variable:
|
||||||
T_VARIABLE { $$ = parseVar($1); }
|
plain_variable { $$ = $1; }
|
||||||
| '$' '{' expr '}' { $$ = $3; }
|
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
|
||||||
| '$' simple_variable { $$ = Expr\Variable[$2]; }
|
| '$' simple_variable { $$ = Expr\Variable[$2]; }
|
||||||
| '$' error { $$ = Expr\Error[]; $this->errorState = 2; }
|
| '$' error { $$ = Expr\Variable[Expr\Error[]]; $this->errorState = 2; }
|
||||||
;
|
;
|
||||||
|
|
||||||
static_member_prop_name:
|
static_member_prop_name:
|
||||||
simple_variable
|
simple_variable
|
||||||
{ $var = $1; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
|
{ $var = $1->name; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
|
||||||
;
|
;
|
||||||
|
|
||||||
static_member:
|
static_member:
|
||||||
@@ -946,10 +1143,11 @@ static_member:
|
|||||||
;
|
;
|
||||||
|
|
||||||
new_variable:
|
new_variable:
|
||||||
simple_variable { $$ = Expr\Variable[$1]; }
|
simple_variable { $$ = $1; }
|
||||||
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||||
| new_variable '{' 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_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
|
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||||
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
|
||||||
| new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
| new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
|
||||||
@@ -957,15 +1155,15 @@ new_variable:
|
|||||||
;
|
;
|
||||||
|
|
||||||
member_name:
|
member_name:
|
||||||
identifier_ex { $$ = $1; }
|
identifier_maybe_reserved { $$ = $1; }
|
||||||
| '{' expr '}' { $$ = $2; }
|
| '{' expr '}' { $$ = $2; }
|
||||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
| simple_variable { $$ = $1; }
|
||||||
;
|
;
|
||||||
|
|
||||||
property_name:
|
property_name:
|
||||||
identifier { $$ = $1; }
|
identifier_not_reserved { $$ = $1; }
|
||||||
| '{' expr '}' { $$ = $2; }
|
| '{' expr '}' { $$ = $2; }
|
||||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
| simple_variable { $$ = $1; }
|
||||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -991,10 +1189,10 @@ inner_array_pair_list:
|
|||||||
|
|
||||||
array_pair:
|
array_pair:
|
||||||
expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||||
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||||
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||||
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, 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 ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||||
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||||
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
|
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
|
||||||
| /* empty */ { $$ = null; }
|
| /* empty */ { $$ = null; }
|
||||||
@@ -1018,7 +1216,10 @@ encaps_str_varname:
|
|||||||
encaps_var:
|
encaps_var:
|
||||||
plain_variable { $$ = $1; }
|
plain_variable { $$ = $1; }
|
||||||
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||||
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
|
| plain_variable T_OBJECT_OPERATOR identifier_not_reserved
|
||||||
|
{ $$ = Expr\PropertyFetch[$1, $3]; }
|
||||||
|
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier_not_reserved
|
||||||
|
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
|
||||||
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
|
| 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 T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
|
||||||
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
|
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
|
||||||
|
184
grammar/phpyLang.php
Normal file
184
grammar/phpyLang.php
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
/// Utility regex constants ///
|
||||||
|
///////////////////////////////
|
||||||
|
|
||||||
|
const LIB = '(?(DEFINE)
|
||||||
|
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
|
||||||
|
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
|
||||||
|
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
|
||||||
|
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
|
||||||
|
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
|
||||||
|
)';
|
||||||
|
|
||||||
|
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]';
|
||||||
|
const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
|
||||||
|
|
||||||
|
///////////////////////////////
|
||||||
|
/// Preprocessing functions ///
|
||||||
|
///////////////////////////////
|
||||||
|
|
||||||
|
function preprocessGrammar($code) {
|
||||||
|
$code = resolveNodes($code);
|
||||||
|
$code = resolveMacros($code);
|
||||||
|
$code = resolveStackAccess($code);
|
||||||
|
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveNodes($code) {
|
||||||
|
return preg_replace_callback(
|
||||||
|
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
||||||
|
function($matches) {
|
||||||
|
// recurse
|
||||||
|
$matches['params'] = resolveNodes($matches['params']);
|
||||||
|
|
||||||
|
$params = magicSplit(
|
||||||
|
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
||||||
|
$matches['params']
|
||||||
|
);
|
||||||
|
|
||||||
|
$paramCode = '';
|
||||||
|
foreach ($params as $param) {
|
||||||
|
$paramCode .= $param . ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
|
||||||
|
},
|
||||||
|
$code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveMacros($code) {
|
||||||
|
return preg_replace_callback(
|
||||||
|
'~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
|
||||||
|
function($matches) {
|
||||||
|
// recurse
|
||||||
|
$matches['args'] = resolveMacros($matches['args']);
|
||||||
|
|
||||||
|
$name = $matches['name'];
|
||||||
|
$args = magicSplit(
|
||||||
|
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
||||||
|
$matches['args']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ('attributes' === $name) {
|
||||||
|
assertArgs(0, $args, $name);
|
||||||
|
return '$this->startAttributeStack[#1] + $this->endAttributes';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('stackAttributes' === $name) {
|
||||||
|
assertArgs(1, $args, $name);
|
||||||
|
return '$this->startAttributeStack[' . $args[0] . ']'
|
||||||
|
. ' + $this->endAttributeStack[' . $args[0] . ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('init' === $name) {
|
||||||
|
return '$$ = array(' . implode(', ', $args) . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('push' === $name) {
|
||||||
|
assertArgs(2, $args, $name);
|
||||||
|
|
||||||
|
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('pushNormalizing' === $name) {
|
||||||
|
assertArgs(2, $args, $name);
|
||||||
|
|
||||||
|
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
|
||||||
|
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('toArray' == $name) {
|
||||||
|
assertArgs(1, $args, $name);
|
||||||
|
|
||||||
|
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('parseVar' === $name) {
|
||||||
|
assertArgs(1, $args, $name);
|
||||||
|
|
||||||
|
return 'substr(' . $args[0] . ', 1)';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('parseEncapsed' === $name) {
|
||||||
|
assertArgs(3, $args, $name);
|
||||||
|
|
||||||
|
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
||||||
|
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('makeNop' === $name) {
|
||||||
|
assertArgs(3, $args, $name);
|
||||||
|
|
||||||
|
return '$startAttributes = ' . $args[1] . ';'
|
||||||
|
. ' if (isset($startAttributes[\'comments\']))'
|
||||||
|
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
|
||||||
|
. ' 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 ('prependLeadingComments' === $name) {
|
||||||
|
assertArgs(1, $args, $name);
|
||||||
|
|
||||||
|
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
||||||
|
. 'if (!empty($attrs[\'comments\'])) {'
|
||||||
|
. '$stmts[0]->setAttribute(\'comments\', '
|
||||||
|
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matches[0];
|
||||||
|
},
|
||||||
|
$code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertArgs($num, $args, $name) {
|
||||||
|
if ($num != count($args)) {
|
||||||
|
die('Wrong argument count for ' . $name . '().');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveStackAccess($code) {
|
||||||
|
$code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
|
||||||
|
$code = preg_replace('/#(\d+)/', '$$1', $code);
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTrailingWhitespace($code) {
|
||||||
|
$lines = explode("\n", $code);
|
||||||
|
$lines = array_map('rtrim', $lines);
|
||||||
|
return implode("\n", $lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
/// Regex helper functions ///
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
function regex($regex) {
|
||||||
|
return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
|
||||||
|
}
|
||||||
|
|
||||||
|
function magicSplit($regex, $string) {
|
||||||
|
$pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
|
||||||
|
|
||||||
|
foreach ($pieces as &$piece) {
|
||||||
|
$piece = trim($piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pieces === ['']) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pieces;
|
||||||
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
require __DIR__ . '/phpyLang.php';
|
||||||
|
|
||||||
$grammarFileToName = [
|
$grammarFileToName = [
|
||||||
__DIR__ . '/php5.y' => 'Php5',
|
__DIR__ . '/php5.y' => 'Php5',
|
||||||
__DIR__ . '/php7.y' => 'Php7',
|
__DIR__ . '/php7.y' => 'Php7',
|
||||||
@@ -16,28 +18,13 @@ $tokensResultsFile = $resultDir . '/Tokens.php';
|
|||||||
$kmyacc = getenv('KMYACC');
|
$kmyacc = getenv('KMYACC');
|
||||||
if (!$kmyacc) {
|
if (!$kmyacc) {
|
||||||
// Use phpyacc from dev dependencies by default.
|
// Use phpyacc from dev dependencies by default.
|
||||||
$kmyacc = PHP_BINARY . ' ' . __DIR__ . '/../vendor/bin/phpyacc';
|
$kmyacc = __DIR__ . '/../vendor/bin/phpyacc';
|
||||||
}
|
}
|
||||||
|
|
||||||
$options = array_flip($argv);
|
$options = array_flip($argv);
|
||||||
$optionDebug = isset($options['--debug']);
|
$optionDebug = isset($options['--debug']);
|
||||||
$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);
|
$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);
|
||||||
|
|
||||||
///////////////////////////////
|
|
||||||
/// Utility regex constants ///
|
|
||||||
///////////////////////////////
|
|
||||||
|
|
||||||
const LIB = '(?(DEFINE)
|
|
||||||
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
|
|
||||||
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
|
|
||||||
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
|
|
||||||
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
|
|
||||||
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
|
|
||||||
)';
|
|
||||||
|
|
||||||
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]';
|
|
||||||
const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
|
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
/// Main script ///
|
/// Main script ///
|
||||||
///////////////////
|
///////////////////
|
||||||
@@ -49,10 +36,7 @@ foreach ($grammarFileToName as $grammarFile => $name) {
|
|||||||
|
|
||||||
$grammarCode = file_get_contents($grammarFile);
|
$grammarCode = file_get_contents($grammarFile);
|
||||||
$grammarCode = str_replace('%tokens', $tokens, $grammarCode);
|
$grammarCode = str_replace('%tokens', $tokens, $grammarCode);
|
||||||
|
$grammarCode = preprocessGrammar($grammarCode);
|
||||||
$grammarCode = resolveNodes($grammarCode);
|
|
||||||
$grammarCode = resolveMacros($grammarCode);
|
|
||||||
$grammarCode = resolveStackAccess($grammarCode);
|
|
||||||
|
|
||||||
file_put_contents($tmpGrammarFile, $grammarCode);
|
file_put_contents($tmpGrammarFile, $grammarCode);
|
||||||
|
|
||||||
@@ -77,151 +61,9 @@ foreach ($grammarFileToName as $grammarFile => $name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////
|
////////////////////////////////
|
||||||
/// Preprocessing functions ///
|
/// Utility helper functions ///
|
||||||
///////////////////////////////
|
////////////////////////////////
|
||||||
|
|
||||||
function resolveNodes($code) {
|
|
||||||
return preg_replace_callback(
|
|
||||||
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
|
||||||
function($matches) {
|
|
||||||
// recurse
|
|
||||||
$matches['params'] = resolveNodes($matches['params']);
|
|
||||||
|
|
||||||
$params = magicSplit(
|
|
||||||
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
|
||||||
$matches['params']
|
|
||||||
);
|
|
||||||
|
|
||||||
$paramCode = '';
|
|
||||||
foreach ($params as $param) {
|
|
||||||
$paramCode .= $param . ', ';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
|
|
||||||
},
|
|
||||||
$code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveMacros($code) {
|
|
||||||
return preg_replace_callback(
|
|
||||||
'~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
|
|
||||||
function($matches) {
|
|
||||||
// recurse
|
|
||||||
$matches['args'] = resolveMacros($matches['args']);
|
|
||||||
|
|
||||||
$name = $matches['name'];
|
|
||||||
$args = magicSplit(
|
|
||||||
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
|
||||||
$matches['args']
|
|
||||||
);
|
|
||||||
|
|
||||||
if ('attributes' == $name) {
|
|
||||||
assertArgs(0, $args, $name);
|
|
||||||
return '$this->startAttributeStack[#1] + $this->endAttributes';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('stackAttributes' == $name) {
|
|
||||||
assertArgs(1, $args, $name);
|
|
||||||
return '$this->startAttributeStack[' . $args[0] . ']'
|
|
||||||
. ' + $this->endAttributeStack[' . $args[0] . ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('init' == $name) {
|
|
||||||
return '$$ = array(' . implode(', ', $args) . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('push' == $name) {
|
|
||||||
assertArgs(2, $args, $name);
|
|
||||||
|
|
||||||
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('pushNormalizing' == $name) {
|
|
||||||
assertArgs(2, $args, $name);
|
|
||||||
|
|
||||||
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
|
|
||||||
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('toArray' == $name) {
|
|
||||||
assertArgs(1, $args, $name);
|
|
||||||
|
|
||||||
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('parseVar' == $name) {
|
|
||||||
assertArgs(1, $args, $name);
|
|
||||||
|
|
||||||
return 'substr(' . $args[0] . ', 1)';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('parseEncapsed' == $name) {
|
|
||||||
assertArgs(3, $args, $name);
|
|
||||||
|
|
||||||
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
|
||||||
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('makeNop' == $name) {
|
|
||||||
assertArgs(3, $args, $name);
|
|
||||||
|
|
||||||
return '$startAttributes = ' . $args[1] . ';'
|
|
||||||
. ' if (isset($startAttributes[\'comments\']))'
|
|
||||||
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
|
|
||||||
. ' 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);
|
|
||||||
|
|
||||||
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
|
|
||||||
. '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
|
|
||||||
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('prependLeadingComments' == $name) {
|
|
||||||
assertArgs(1, $args, $name);
|
|
||||||
|
|
||||||
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
|
||||||
. 'if (!empty($attrs[\'comments\'])) {'
|
|
||||||
. '$stmts[0]->setAttribute(\'comments\', '
|
|
||||||
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $matches[0];
|
|
||||||
},
|
|
||||||
$code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertArgs($num, $args, $name) {
|
|
||||||
if ($num != count($args)) {
|
|
||||||
die('Wrong argument count for ' . $name . '().');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveStackAccess($code) {
|
|
||||||
$code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
|
|
||||||
$code = preg_replace('/#(\d+)/', '$$1', $code);
|
|
||||||
return $code;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeTrailingWhitespace($code) {
|
|
||||||
$lines = explode("\n", $code);
|
|
||||||
$lines = array_map('rtrim', $lines);
|
|
||||||
return implode("\n", $lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureDirExists($dir) {
|
function ensureDirExists($dir) {
|
||||||
if (!is_dir($dir)) {
|
if (!is_dir($dir)) {
|
||||||
@@ -237,25 +79,3 @@ function execCmd($cmd) {
|
|||||||
}
|
}
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////
|
|
||||||
/// Regex helper functions ///
|
|
||||||
//////////////////////////////
|
|
||||||
|
|
||||||
function regex($regex) {
|
|
||||||
return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
|
|
||||||
}
|
|
||||||
|
|
||||||
function magicSplit($regex, $string) {
|
|
||||||
$pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
|
|
||||||
|
|
||||||
foreach ($pieces as &$piece) {
|
|
||||||
$piece = trim($piece);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($pieces === ['']) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $pieces;
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for
|
/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for
|
||||||
* both. This is enforced by sharing this token file. */
|
* both. This is enforced by sharing this token file. */
|
||||||
|
|
||||||
|
%right T_THROW
|
||||||
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
|
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
|
||||||
%left ','
|
%left ','
|
||||||
%left T_LOGICAL_OR
|
%left T_LOGICAL_OR
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
%left T_BOOLEAN_AND
|
%left T_BOOLEAN_AND
|
||||||
%left '|'
|
%left '|'
|
||||||
%left '^'
|
%left '^'
|
||||||
%left '&'
|
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||||
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
|
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
|
||||||
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
|
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
|
||||||
%left T_SL T_SR
|
%left T_SL T_SR
|
||||||
@@ -41,8 +42,6 @@
|
|||||||
%token T_VARIABLE
|
%token T_VARIABLE
|
||||||
%token T_NUM_STRING
|
%token T_NUM_STRING
|
||||||
%token T_INLINE_HTML
|
%token T_INLINE_HTML
|
||||||
%token T_CHARACTER
|
|
||||||
%token T_BAD_CHARACTER
|
|
||||||
%token T_ENCAPSED_AND_WHITESPACE
|
%token T_ENCAPSED_AND_WHITESPACE
|
||||||
%token T_CONSTANT_ENCAPSED_STRING
|
%token T_CONSTANT_ENCAPSED_STRING
|
||||||
%token T_ECHO
|
%token T_ECHO
|
||||||
@@ -57,6 +56,7 @@
|
|||||||
%token T_ENDDECLARE
|
%token T_ENDDECLARE
|
||||||
%token T_AS
|
%token T_AS
|
||||||
%token T_SWITCH
|
%token T_SWITCH
|
||||||
|
%token T_MATCH
|
||||||
%token T_ENDSWITCH
|
%token T_ENDSWITCH
|
||||||
%token T_CASE
|
%token T_CASE
|
||||||
%token T_DEFAULT
|
%token T_DEFAULT
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
%token T_USE
|
%token T_USE
|
||||||
%token T_INSTEADOF
|
%token T_INSTEADOF
|
||||||
%token T_GLOBAL
|
%token T_GLOBAL
|
||||||
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
|
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
||||||
%token T_VAR
|
%token T_VAR
|
||||||
%token T_UNSET
|
%token T_UNSET
|
||||||
%token T_ISSET
|
%token T_ISSET
|
||||||
@@ -83,9 +83,11 @@
|
|||||||
%token T_CLASS
|
%token T_CLASS
|
||||||
%token T_TRAIT
|
%token T_TRAIT
|
||||||
%token T_INTERFACE
|
%token T_INTERFACE
|
||||||
|
%token T_ENUM
|
||||||
%token T_EXTENDS
|
%token T_EXTENDS
|
||||||
%token T_IMPLEMENTS
|
%token T_IMPLEMENTS
|
||||||
%token T_OBJECT_OPERATOR
|
%token T_OBJECT_OPERATOR
|
||||||
|
%token T_NULLSAFE_OBJECT_OPERATOR
|
||||||
%token T_DOUBLE_ARROW
|
%token T_DOUBLE_ARROW
|
||||||
%token T_LIST
|
%token T_LIST
|
||||||
%token T_ARRAY
|
%token T_ARRAY
|
||||||
@@ -96,12 +98,6 @@
|
|||||||
%token T_FUNC_C
|
%token T_FUNC_C
|
||||||
%token T_LINE
|
%token T_LINE
|
||||||
%token T_FILE
|
%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_START_HEREDOC
|
||||||
%token T_END_HEREDOC
|
%token T_END_HEREDOC
|
||||||
%token T_DOLLAR_OPEN_CURLY_BRACES
|
%token T_DOLLAR_OPEN_CURLY_BRACES
|
||||||
@@ -112,3 +108,8 @@
|
|||||||
%token T_DIR
|
%token T_DIR
|
||||||
%token T_NS_SEPARATOR
|
%token T_NS_SEPARATOR
|
||||||
%token T_ELLIPSIS
|
%token T_ELLIPSIS
|
||||||
|
%token T_NAME_FULLY_QUALIFIED
|
||||||
|
%token T_NAME_QUALIFIED
|
||||||
|
%token T_NAME_RELATIVE
|
||||||
|
%token T_ATTRIBUTE
|
||||||
|
%token T_ENUM
|
||||||
|
132
lib/PhpParser/Builder/ClassConst.php
Normal file
132
lib/PhpParser/Builder/ClassConst.php
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Builder;
|
||||||
|
|
||||||
|
use PhpParser;
|
||||||
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Const_;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
|
||||||
|
class ClassConst implements PhpParser\Builder
|
||||||
|
{
|
||||||
|
protected $flags = 0;
|
||||||
|
protected $attributes = [];
|
||||||
|
protected $constants = [];
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a class constant builder
|
||||||
|
*
|
||||||
|
* @param string|Identifier $name Name
|
||||||
|
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||||
|
*/
|
||||||
|
public function __construct($name, $value) {
|
||||||
|
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add another constant to const group
|
||||||
|
*
|
||||||
|
* @param string|Identifier $name Name
|
||||||
|
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addConst($name, $value) {
|
||||||
|
$this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the constant public.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makePublic() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the constant protected.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makeProtected() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the constant private.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makePrivate() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the constant final.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makeFinal() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets doc comment for the constant.
|
||||||
|
*
|
||||||
|
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function setDocComment($docComment) {
|
||||||
|
$this->attributes = [
|
||||||
|
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the built class node.
|
||||||
|
*
|
||||||
|
* @return Stmt\ClassConst The built constant node
|
||||||
|
*/
|
||||||
|
public function getNode(): PhpParser\Node {
|
||||||
|
return new Stmt\ClassConst(
|
||||||
|
$this->constants,
|
||||||
|
$this->flags,
|
||||||
|
$this->attributes,
|
||||||
|
$this->attributeGroups
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -4,6 +4,7 @@ namespace PhpParser\Builder;
|
|||||||
|
|
||||||
use PhpParser;
|
use PhpParser;
|
||||||
use PhpParser\BuilderHelpers;
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
use PhpParser\Node\Name;
|
use PhpParser\Node\Name;
|
||||||
use PhpParser\Node\Stmt;
|
use PhpParser\Node\Stmt;
|
||||||
|
|
||||||
@@ -20,6 +21,9 @@ class Class_ extends Declaration
|
|||||||
protected $properties = [];
|
protected $properties = [];
|
||||||
protected $methods = [];
|
protected $methods = [];
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a class builder.
|
* Creates a class builder.
|
||||||
*
|
*
|
||||||
@@ -63,7 +67,7 @@ class Class_ extends Declaration
|
|||||||
* @return $this The builder instance (for fluid interface)
|
* @return $this The builder instance (for fluid interface)
|
||||||
*/
|
*/
|
||||||
public function makeAbstract() {
|
public function makeAbstract() {
|
||||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
|
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -74,7 +78,13 @@ class Class_ extends Declaration
|
|||||||
* @return $this The builder instance (for fluid interface)
|
* @return $this The builder instance (for fluid interface)
|
||||||
*/
|
*/
|
||||||
public function makeFinal() {
|
public function makeFinal() {
|
||||||
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeReadonly() {
|
||||||
|
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -106,6 +116,19 @@ class Class_ extends Declaration
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the built class node.
|
* Returns the built class node.
|
||||||
*
|
*
|
||||||
@@ -117,6 +140,7 @@ class Class_ extends Declaration
|
|||||||
'extends' => $this->extends,
|
'extends' => $this->extends,
|
||||||
'implements' => $this->implements,
|
'implements' => $this->implements,
|
||||||
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
|
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
|
||||||
|
'attrGroups' => $this->attributeGroups,
|
||||||
], $this->attributes);
|
], $this->attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
85
lib/PhpParser/Builder/EnumCase.php
Normal file
85
lib/PhpParser/Builder/EnumCase.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Builder;
|
||||||
|
|
||||||
|
use PhpParser;
|
||||||
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
|
||||||
|
class EnumCase implements PhpParser\Builder
|
||||||
|
{
|
||||||
|
protected $name;
|
||||||
|
protected $value = null;
|
||||||
|
protected $attributes = [];
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an enum case builder.
|
||||||
|
*
|
||||||
|
* @param string|Identifier $name Name
|
||||||
|
*/
|
||||||
|
public function __construct($name) {
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value.
|
||||||
|
*
|
||||||
|
* @param Node\Expr|string|int $value
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setValue($value) {
|
||||||
|
$this->value = BuilderHelpers::normalizeValue($value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets doc comment for the constant.
|
||||||
|
*
|
||||||
|
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function setDocComment($docComment) {
|
||||||
|
$this->attributes = [
|
||||||
|
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the built enum case node.
|
||||||
|
*
|
||||||
|
* @return Stmt\EnumCase The built constant node
|
||||||
|
*/
|
||||||
|
public function getNode(): PhpParser\Node {
|
||||||
|
return new Stmt\EnumCase(
|
||||||
|
$this->name,
|
||||||
|
$this->value,
|
||||||
|
$this->attributeGroups,
|
||||||
|
$this->attributes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
117
lib/PhpParser/Builder/Enum_.php
Normal file
117
lib/PhpParser/Builder/Enum_.php
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Builder;
|
||||||
|
|
||||||
|
use PhpParser;
|
||||||
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
use PhpParser\Node\Stmt;
|
||||||
|
|
||||||
|
class Enum_ extends Declaration
|
||||||
|
{
|
||||||
|
protected $name;
|
||||||
|
protected $scalarType = null;
|
||||||
|
|
||||||
|
protected $implements = [];
|
||||||
|
|
||||||
|
protected $uses = [];
|
||||||
|
protected $enumCases = [];
|
||||||
|
protected $constants = [];
|
||||||
|
protected $methods = [];
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an enum builder.
|
||||||
|
*
|
||||||
|
* @param string $name Name of the enum
|
||||||
|
*/
|
||||||
|
public function __construct(string $name) {
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scalar type.
|
||||||
|
*
|
||||||
|
* @param string|Identifier $type
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setScalarType($scalarType) {
|
||||||
|
$this->scalarType = BuilderHelpers::normalizeType($scalarType);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements one or more interfaces.
|
||||||
|
*
|
||||||
|
* @param Name|string ...$interfaces Names of interfaces to implement
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function implement(...$interfaces) {
|
||||||
|
foreach ($interfaces as $interface) {
|
||||||
|
$this->implements[] = BuilderHelpers::normalizeName($interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a statement.
|
||||||
|
*
|
||||||
|
* @param Stmt|PhpParser\Builder $stmt The statement to add
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addStmt($stmt) {
|
||||||
|
$stmt = BuilderHelpers::normalizeNode($stmt);
|
||||||
|
|
||||||
|
$targets = [
|
||||||
|
Stmt\TraitUse::class => &$this->uses,
|
||||||
|
Stmt\EnumCase::class => &$this->enumCases,
|
||||||
|
Stmt\ClassConst::class => &$this->constants,
|
||||||
|
Stmt\ClassMethod::class => &$this->methods,
|
||||||
|
];
|
||||||
|
|
||||||
|
$class = \get_class($stmt);
|
||||||
|
if (!isset($targets[$class])) {
|
||||||
|
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$targets[$class][] = $stmt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the built class node.
|
||||||
|
*
|
||||||
|
* @return Stmt\Enum_ The built enum node
|
||||||
|
*/
|
||||||
|
public function getNode() : PhpParser\Node {
|
||||||
|
return new Stmt\Enum_($this->name, [
|
||||||
|
'scalarType' => $this->scalarType,
|
||||||
|
'implements' => $this->implements,
|
||||||
|
'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods),
|
||||||
|
'attrGroups' => $this->attributeGroups,
|
||||||
|
], $this->attributes);
|
||||||
|
}
|
||||||
|
}
|
@@ -61,8 +61,7 @@ abstract class FunctionLike extends Declaration
|
|||||||
/**
|
/**
|
||||||
* Sets the return type for PHP 7.
|
* Sets the return type for PHP 7.
|
||||||
*
|
*
|
||||||
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float,
|
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type
|
||||||
* bool, iterable, or a class/interface name.
|
|
||||||
*
|
*
|
||||||
* @return $this The builder instance (for fluid interface)
|
* @return $this The builder instance (for fluid interface)
|
||||||
*/
|
*/
|
||||||
|
@@ -12,6 +12,9 @@ class Function_ extends FunctionLike
|
|||||||
protected $name;
|
protected $name;
|
||||||
protected $stmts = [];
|
protected $stmts = [];
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a function builder.
|
* Creates a function builder.
|
||||||
*
|
*
|
||||||
@@ -34,6 +37,19 @@ class Function_ extends FunctionLike
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the built function node.
|
* Returns the built function node.
|
||||||
*
|
*
|
||||||
@@ -45,6 +61,7 @@ class Function_ extends FunctionLike
|
|||||||
'params' => $this->params,
|
'params' => $this->params,
|
||||||
'returnType' => $this->returnType,
|
'returnType' => $this->returnType,
|
||||||
'stmts' => $this->stmts,
|
'stmts' => $this->stmts,
|
||||||
|
'attrGroups' => $this->attributeGroups,
|
||||||
], $this->attributes);
|
], $this->attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ namespace PhpParser\Builder;
|
|||||||
|
|
||||||
use PhpParser;
|
use PhpParser;
|
||||||
use PhpParser\BuilderHelpers;
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
use PhpParser\Node\Name;
|
use PhpParser\Node\Name;
|
||||||
use PhpParser\Node\Stmt;
|
use PhpParser\Node\Stmt;
|
||||||
|
|
||||||
@@ -14,6 +15,9 @@ class Interface_ extends Declaration
|
|||||||
protected $constants = [];
|
protected $constants = [];
|
||||||
protected $methods = [];
|
protected $methods = [];
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an interface builder.
|
* Creates an interface builder.
|
||||||
*
|
*
|
||||||
@@ -61,6 +65,19 @@ class Interface_ extends Declaration
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the built interface node.
|
* Returns the built interface node.
|
||||||
*
|
*
|
||||||
@@ -70,6 +87,7 @@ class Interface_ extends Declaration
|
|||||||
return new Stmt\Interface_($this->name, [
|
return new Stmt\Interface_($this->name, [
|
||||||
'extends' => $this->extends,
|
'extends' => $this->extends,
|
||||||
'stmts' => array_merge($this->constants, $this->methods),
|
'stmts' => array_merge($this->constants, $this->methods),
|
||||||
|
'attrGroups' => $this->attributeGroups,
|
||||||
], $this->attributes);
|
], $this->attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,9 @@ class Method extends FunctionLike
|
|||||||
/** @var array|null */
|
/** @var array|null */
|
||||||
protected $stmts = [];
|
protected $stmts = [];
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a method builder.
|
* Creates a method builder.
|
||||||
*
|
*
|
||||||
@@ -112,6 +115,19 @@ class Method extends FunctionLike
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the built method node.
|
* Returns the built method node.
|
||||||
*
|
*
|
||||||
@@ -124,6 +140,7 @@ class Method extends FunctionLike
|
|||||||
'params' => $this->params,
|
'params' => $this->params,
|
||||||
'returnType' => $this->returnType,
|
'returnType' => $this->returnType,
|
||||||
'stmts' => $this->stmts,
|
'stmts' => $this->stmts,
|
||||||
|
'attrGroups' => $this->attributeGroups,
|
||||||
], $this->attributes);
|
], $this->attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,7 +37,7 @@ class Namespace_ extends Declaration
|
|||||||
/**
|
/**
|
||||||
* Returns the built node.
|
* Returns the built node.
|
||||||
*
|
*
|
||||||
* @return Node The built node
|
* @return Stmt\Namespace_ The built node
|
||||||
*/
|
*/
|
||||||
public function getNode() : Node {
|
public function getNode() : Node {
|
||||||
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
|
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
|
||||||
|
@@ -19,6 +19,11 @@ class Param implements PhpParser\Builder
|
|||||||
|
|
||||||
protected $variadic = false;
|
protected $variadic = false;
|
||||||
|
|
||||||
|
protected $flags = 0;
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a parameter builder.
|
* Creates a parameter builder.
|
||||||
*
|
*
|
||||||
@@ -44,7 +49,7 @@ class Param implements PhpParser\Builder
|
|||||||
/**
|
/**
|
||||||
* Sets type for the parameter.
|
* Sets type for the parameter.
|
||||||
*
|
*
|
||||||
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
|
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
|
||||||
*
|
*
|
||||||
* @return $this The builder instance (for fluid interface)
|
* @return $this The builder instance (for fluid interface)
|
||||||
*/
|
*/
|
||||||
@@ -60,7 +65,7 @@ class Param implements PhpParser\Builder
|
|||||||
/**
|
/**
|
||||||
* Sets type for the parameter.
|
* Sets type for the parameter.
|
||||||
*
|
*
|
||||||
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
|
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
|
||||||
*
|
*
|
||||||
* @return $this The builder instance (for fluid interface)
|
* @return $this The builder instance (for fluid interface)
|
||||||
*
|
*
|
||||||
@@ -92,6 +97,63 @@ class Param implements PhpParser\Builder
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the (promoted) parameter public.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makePublic() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PUBLIC);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the (promoted) parameter protected.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makeProtected() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PROTECTED);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the (promoted) parameter private.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makePrivate() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PRIVATE);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the (promoted) parameter readonly.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makeReadonly() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_READONLY);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the built parameter node.
|
* Returns the built parameter node.
|
||||||
*
|
*
|
||||||
@@ -100,7 +162,7 @@ class Param implements PhpParser\Builder
|
|||||||
public function getNode() : Node {
|
public function getNode() : Node {
|
||||||
return new Node\Param(
|
return new Node\Param(
|
||||||
new Node\Expr\Variable($this->name),
|
new Node\Expr\Variable($this->name),
|
||||||
$this->default, $this->type, $this->byRef, $this->variadic
|
$this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,10 +4,11 @@ namespace PhpParser\Builder;
|
|||||||
|
|
||||||
use PhpParser;
|
use PhpParser;
|
||||||
use PhpParser\BuilderHelpers;
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
use PhpParser\Node\Identifier;
|
use PhpParser\Node\Identifier;
|
||||||
use PhpParser\Node\Name;
|
use PhpParser\Node\Name;
|
||||||
use PhpParser\Node\NullableType;
|
|
||||||
use PhpParser\Node\Stmt;
|
use PhpParser\Node\Stmt;
|
||||||
|
use PhpParser\Node\ComplexType;
|
||||||
|
|
||||||
class Property implements PhpParser\Builder
|
class Property implements PhpParser\Builder
|
||||||
{
|
{
|
||||||
@@ -20,6 +21,9 @@ class Property implements PhpParser\Builder
|
|||||||
/** @var null|Identifier|Name|NullableType */
|
/** @var null|Identifier|Name|NullableType */
|
||||||
protected $type;
|
protected $type;
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a property builder.
|
* Creates a property builder.
|
||||||
*
|
*
|
||||||
@@ -73,6 +77,17 @@ class Property implements PhpParser\Builder
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the property readonly.
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function makeReadonly() {
|
||||||
|
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets default value for the property.
|
* Sets default value for the property.
|
||||||
*
|
*
|
||||||
@@ -104,7 +119,7 @@ class Property implements PhpParser\Builder
|
|||||||
/**
|
/**
|
||||||
* Sets the property type for PHP 7.4+.
|
* Sets the property type for PHP 7.4+.
|
||||||
*
|
*
|
||||||
* @param string|Name|NullableType|Identifier $type
|
* @param string|Name|Identifier|ComplexType $type
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
@@ -114,6 +129,19 @@ class Property implements PhpParser\Builder
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the built class node.
|
* Returns the built class node.
|
||||||
*
|
*
|
||||||
@@ -126,7 +154,8 @@ class Property implements PhpParser\Builder
|
|||||||
new Stmt\PropertyProperty($this->name, $this->default)
|
new Stmt\PropertyProperty($this->name, $this->default)
|
||||||
],
|
],
|
||||||
$this->attributes,
|
$this->attributes,
|
||||||
$this->type
|
$this->type,
|
||||||
|
$this->attributeGroups
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ namespace PhpParser\Builder;
|
|||||||
|
|
||||||
use PhpParser;
|
use PhpParser;
|
||||||
use PhpParser\BuilderHelpers;
|
use PhpParser\BuilderHelpers;
|
||||||
|
use PhpParser\Node;
|
||||||
use PhpParser\Node\Stmt;
|
use PhpParser\Node\Stmt;
|
||||||
|
|
||||||
class Trait_ extends Declaration
|
class Trait_ extends Declaration
|
||||||
@@ -13,6 +14,9 @@ class Trait_ extends Declaration
|
|||||||
protected $properties = [];
|
protected $properties = [];
|
||||||
protected $methods = [];
|
protected $methods = [];
|
||||||
|
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
protected $attributeGroups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an interface builder.
|
* Creates an interface builder.
|
||||||
*
|
*
|
||||||
@@ -45,6 +49,19 @@ class Trait_ extends Declaration
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an attribute group.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return $this The builder instance (for fluid interface)
|
||||||
|
*/
|
||||||
|
public function addAttribute($attribute) {
|
||||||
|
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the built trait node.
|
* Returns the built trait node.
|
||||||
*
|
*
|
||||||
@@ -53,7 +70,8 @@ class Trait_ extends Declaration
|
|||||||
public function getNode() : PhpParser\Node {
|
public function getNode() : PhpParser\Node {
|
||||||
return new Stmt\Trait_(
|
return new Stmt\Trait_(
|
||||||
$this->name, [
|
$this->name, [
|
||||||
'stmts' => array_merge($this->uses, $this->properties, $this->methods)
|
'stmts' => array_merge($this->uses, $this->properties, $this->methods),
|
||||||
|
'attrGroups' => $this->attributeGroups,
|
||||||
], $this->attributes
|
], $this->attributes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -39,7 +39,7 @@ class Use_ implements Builder
|
|||||||
/**
|
/**
|
||||||
* Returns the built node.
|
* Returns the built node.
|
||||||
*
|
*
|
||||||
* @return Node The built node
|
* @return Stmt\Use_ The built node
|
||||||
*/
|
*/
|
||||||
public function getNode() : Node {
|
public function getNode() : Node {
|
||||||
return new Stmt\Use_([
|
return new Stmt\Use_([
|
||||||
|
@@ -12,6 +12,21 @@ use PhpParser\Node\Stmt\Use_;
|
|||||||
|
|
||||||
class BuilderFactory
|
class BuilderFactory
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Creates an attribute node.
|
||||||
|
*
|
||||||
|
* @param string|Name $name Name of the attribute
|
||||||
|
* @param array $args Attribute named arguments
|
||||||
|
*
|
||||||
|
* @return Node\Attribute
|
||||||
|
*/
|
||||||
|
public function attribute($name, array $args = []) : Node\Attribute {
|
||||||
|
return new Node\Attribute(
|
||||||
|
BuilderHelpers::normalizeName($name),
|
||||||
|
$this->args($args)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a namespace builder.
|
* Creates a namespace builder.
|
||||||
*
|
*
|
||||||
@@ -56,6 +71,17 @@ class BuilderFactory
|
|||||||
return new Builder\Trait_($name);
|
return new Builder\Trait_($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an enum builder.
|
||||||
|
*
|
||||||
|
* @param string $name Name of the enum
|
||||||
|
*
|
||||||
|
* @return Builder\Enum_ The created enum builder
|
||||||
|
*/
|
||||||
|
public function enum(string $name) : Builder\Enum_ {
|
||||||
|
return new Builder\Enum_($name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a trait use builder.
|
* Creates a trait use builder.
|
||||||
*
|
*
|
||||||
@@ -161,6 +187,29 @@ class BuilderFactory
|
|||||||
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
|
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a class constant builder.
|
||||||
|
*
|
||||||
|
* @param string|Identifier $name Name
|
||||||
|
* @param Node\Expr|bool|null|int|float|string|array $value Value
|
||||||
|
*
|
||||||
|
* @return Builder\ClassConst The created use const builder
|
||||||
|
*/
|
||||||
|
public function classConst($name, $value) : Builder\ClassConst {
|
||||||
|
return new Builder\ClassConst($name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an enum case builder.
|
||||||
|
*
|
||||||
|
* @param string|Identifier $name Name
|
||||||
|
*
|
||||||
|
* @return Builder\EnumCase The created use const builder
|
||||||
|
*/
|
||||||
|
public function enumCase($name) : Builder\EnumCase {
|
||||||
|
return new Builder\EnumCase($name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates node a for a literal value.
|
* Creates node a for a literal value.
|
||||||
*
|
*
|
||||||
@@ -198,12 +247,14 @@ class BuilderFactory
|
|||||||
*/
|
*/
|
||||||
public function args(array $args) : array {
|
public function args(array $args) : array {
|
||||||
$normalizedArgs = [];
|
$normalizedArgs = [];
|
||||||
foreach ($args as $arg) {
|
foreach ($args as $key => $arg) {
|
||||||
if ($arg instanceof Arg) {
|
if (!($arg instanceof Arg)) {
|
||||||
$normalizedArgs[] = $arg;
|
$arg = new Arg(BuilderHelpers::normalizeValue($arg));
|
||||||
} else {
|
|
||||||
$normalizedArgs[] = new Arg(BuilderHelpers::normalizeValue($arg));
|
|
||||||
}
|
}
|
||||||
|
if (\is_string($key)) {
|
||||||
|
$arg->name = BuilderHelpers::normalizeIdentifier($key);
|
||||||
|
}
|
||||||
|
$normalizedArgs[] = $arg;
|
||||||
}
|
}
|
||||||
return $normalizedArgs;
|
return $normalizedArgs;
|
||||||
}
|
}
|
||||||
|
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
namespace PhpParser;
|
namespace PhpParser;
|
||||||
|
|
||||||
|
use PhpParser\Node\ComplexType;
|
||||||
use PhpParser\Node\Expr;
|
use PhpParser\Node\Expr;
|
||||||
use PhpParser\Node\Identifier;
|
use PhpParser\Node\Identifier;
|
||||||
use PhpParser\Node\Name;
|
use PhpParser\Node\Name;
|
||||||
use PhpParser\Node\NullableType;
|
use PhpParser\Node\NullableType;
|
||||||
use PhpParser\Node\Scalar;
|
use PhpParser\Node\Scalar;
|
||||||
use PhpParser\Node\Stmt;
|
use PhpParser\Node\Stmt;
|
||||||
use PhpParser\Node\UnionType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class defines helpers used in the implementation of builders. Don't use it directly.
|
* This class defines helpers used in the implementation of builders. Don't use it directly.
|
||||||
@@ -27,7 +27,9 @@ final class BuilderHelpers
|
|||||||
public static function normalizeNode($node) : Node {
|
public static function normalizeNode($node) : Node {
|
||||||
if ($node instanceof Builder) {
|
if ($node instanceof Builder) {
|
||||||
return $node->getNode();
|
return $node->getNode();
|
||||||
} elseif ($node instanceof Node) {
|
}
|
||||||
|
|
||||||
|
if ($node instanceof Node) {
|
||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +104,27 @@ final class BuilderHelpers
|
|||||||
* @return Name The normalized name
|
* @return Name The normalized name
|
||||||
*/
|
*/
|
||||||
public static function normalizeName($name) : Name {
|
public static function normalizeName($name) : Name {
|
||||||
return self::normalizeNameCommon($name, false);
|
if ($name instanceof Name) {
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($name)) {
|
||||||
|
if (!$name) {
|
||||||
|
throw new \LogicException('Name cannot be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($name[0] === '\\') {
|
||||||
|
return new Name\FullyQualified(substr($name, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === strpos($name, 'namespace\\')) {
|
||||||
|
return new Name\Relative(substr($name, strlen('namespace\\')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Name($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \LogicException('Name must be a string or an instance of Node\Name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,44 +135,17 @@ final class BuilderHelpers
|
|||||||
* @return Name|Expr The normalized name or expression
|
* @return Name|Expr The normalized name or expression
|
||||||
*/
|
*/
|
||||||
public static function normalizeNameOrExpr($name) {
|
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)) {
|
|
||||||
if (!$name) {
|
|
||||||
throw new \LogicException('Name cannot be empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($name[0] === '\\') {
|
|
||||||
return new Name\FullyQualified(substr($name, 1));
|
|
||||||
} elseif (0 === strpos($name, 'namespace\\')) {
|
|
||||||
return new Name\Relative(substr($name, strlen('namespace\\')));
|
|
||||||
} else {
|
|
||||||
return new Name($name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($allowExpr) {
|
|
||||||
if ($name instanceof Expr) {
|
if ($name instanceof Expr) {
|
||||||
return $name;
|
return $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!is_string($name) && !($name instanceof Name)) {
|
||||||
throw new \LogicException(
|
throw new \LogicException(
|
||||||
'Name must be a string or an instance of Node\Name or Node\Expr'
|
'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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self::normalizeName($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,18 +154,18 @@ final class BuilderHelpers
|
|||||||
* In particular, builtin types become Identifiers, custom types become Names and nullables
|
* In particular, builtin types become Identifiers, custom types become Names and nullables
|
||||||
* are wrapped in NullableType nodes.
|
* are wrapped in NullableType nodes.
|
||||||
*
|
*
|
||||||
* @param string|Name|Identifier|NullableType|UnionType $type The type to normalize
|
* @param string|Name|Identifier|ComplexType $type The type to normalize
|
||||||
*
|
*
|
||||||
* @return Name|Identifier|NullableType|UnionType The normalized type
|
* @return Name|Identifier|ComplexType The normalized type
|
||||||
*/
|
*/
|
||||||
public static function normalizeType($type) {
|
public static function normalizeType($type) {
|
||||||
if (!is_string($type)) {
|
if (!is_string($type)) {
|
||||||
if (
|
if (
|
||||||
!$type instanceof Name && !$type instanceof Identifier &&
|
!$type instanceof Name && !$type instanceof Identifier &&
|
||||||
!$type instanceof NullableType && !$type instanceof UnionType
|
!$type instanceof ComplexType
|
||||||
) {
|
) {
|
||||||
throw new \LogicException(
|
throw new \LogicException(
|
||||||
'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType'
|
'Type must be a string, or an instance of Name, Identifier or ComplexType'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return $type;
|
return $type;
|
||||||
@@ -183,7 +178,20 @@ final class BuilderHelpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
$builtinTypes = [
|
$builtinTypes = [
|
||||||
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed'
|
'array',
|
||||||
|
'callable',
|
||||||
|
'bool',
|
||||||
|
'int',
|
||||||
|
'float',
|
||||||
|
'string',
|
||||||
|
'iterable',
|
||||||
|
'void',
|
||||||
|
'object',
|
||||||
|
'null',
|
||||||
|
'false',
|
||||||
|
'mixed',
|
||||||
|
'never',
|
||||||
|
'true',
|
||||||
];
|
];
|
||||||
|
|
||||||
$lowerType = strtolower($type);
|
$lowerType = strtolower($type);
|
||||||
@@ -193,12 +201,11 @@ final class BuilderHelpers
|
|||||||
$type = self::normalizeName($type);
|
$type = self::normalizeName($type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($nullable && (string) $type === 'void') {
|
$notNullableTypes = [
|
||||||
throw new \LogicException('void type cannot be nullable');
|
'void', 'mixed', 'never',
|
||||||
}
|
];
|
||||||
|
if ($nullable && in_array((string) $type, $notNullableTypes)) {
|
||||||
if ($nullable && (string) $type === 'mixed') {
|
throw new \LogicException(sprintf('%s type cannot be nullable', $type));
|
||||||
throw new \LogicException('mixed type cannot be nullable');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $nullable ? new NullableType($type) : $type;
|
return $nullable ? new NullableType($type) : $type;
|
||||||
@@ -215,21 +222,33 @@ final class BuilderHelpers
|
|||||||
public static function normalizeValue($value) : Expr {
|
public static function normalizeValue($value) : Expr {
|
||||||
if ($value instanceof Node\Expr) {
|
if ($value instanceof Node\Expr) {
|
||||||
return $value;
|
return $value;
|
||||||
} elseif (is_null($value)) {
|
}
|
||||||
|
|
||||||
|
if (is_null($value)) {
|
||||||
return new Expr\ConstFetch(
|
return new Expr\ConstFetch(
|
||||||
new Name('null')
|
new Name('null')
|
||||||
);
|
);
|
||||||
} elseif (is_bool($value)) {
|
}
|
||||||
|
|
||||||
|
if (is_bool($value)) {
|
||||||
return new Expr\ConstFetch(
|
return new Expr\ConstFetch(
|
||||||
new Name($value ? 'true' : 'false')
|
new Name($value ? 'true' : 'false')
|
||||||
);
|
);
|
||||||
} elseif (is_int($value)) {
|
}
|
||||||
|
|
||||||
|
if (is_int($value)) {
|
||||||
return new Scalar\LNumber($value);
|
return new Scalar\LNumber($value);
|
||||||
} elseif (is_float($value)) {
|
}
|
||||||
|
|
||||||
|
if (is_float($value)) {
|
||||||
return new Scalar\DNumber($value);
|
return new Scalar\DNumber($value);
|
||||||
} elseif (is_string($value)) {
|
}
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
return new Scalar\String_($value);
|
return new Scalar\String_($value);
|
||||||
} elseif (is_array($value)) {
|
}
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
$items = [];
|
$items = [];
|
||||||
$lastKey = -1;
|
$lastKey = -1;
|
||||||
foreach ($value as $itemKey => $itemValue) {
|
foreach ($value as $itemKey => $itemValue) {
|
||||||
@@ -248,9 +267,9 @@ final class BuilderHelpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Expr\Array_($items);
|
return new Expr\Array_($items);
|
||||||
} else {
|
|
||||||
throw new \LogicException('Invalid value');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new \LogicException('Invalid value');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -263,11 +282,33 @@ final class BuilderHelpers
|
|||||||
public static function normalizeDocComment($docComment) : Comment\Doc {
|
public static function normalizeDocComment($docComment) : Comment\Doc {
|
||||||
if ($docComment instanceof Comment\Doc) {
|
if ($docComment instanceof Comment\Doc) {
|
||||||
return $docComment;
|
return $docComment;
|
||||||
} elseif (is_string($docComment)) {
|
}
|
||||||
|
|
||||||
|
if (is_string($docComment)) {
|
||||||
return new Comment\Doc($docComment);
|
return new Comment\Doc($docComment);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes a attribute: Converts attribute to the Attribute Group if needed.
|
||||||
|
*
|
||||||
|
* @param Node\Attribute|Node\AttributeGroup $attribute
|
||||||
|
*
|
||||||
|
* @return Node\AttributeGroup The Attribute Group
|
||||||
|
*/
|
||||||
|
public static function normalizeAttribute($attribute) : Node\AttributeGroup
|
||||||
|
{
|
||||||
|
if ($attribute instanceof Node\AttributeGroup) {
|
||||||
|
return $attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($attribute instanceof Node\Attribute)) {
|
||||||
|
throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Node\AttributeGroup([$attribute]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,4 +323,13 @@ final class BuilderHelpers
|
|||||||
Stmt\Class_::verifyModifier($modifiers, $modifier);
|
Stmt\Class_::verifyModifier($modifiers, $modifier);
|
||||||
return $modifiers | $modifier;
|
return $modifiers | $modifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a modifier and returns new modifier bitmask.
|
||||||
|
* @return int New modifiers
|
||||||
|
*/
|
||||||
|
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
|
||||||
|
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
|
||||||
|
return $existingModifiers | $modifierToSet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace PhpParser;
|
namespace PhpParser;
|
||||||
|
|
||||||
|
use function array_merge;
|
||||||
use PhpParser\Node\Expr;
|
use PhpParser\Node\Expr;
|
||||||
use PhpParser\Node\Scalar;
|
use PhpParser\Node\Scalar;
|
||||||
|
|
||||||
@@ -150,6 +151,8 @@ class ConstExprEvaluator
|
|||||||
foreach ($expr->items as $item) {
|
foreach ($expr->items as $item) {
|
||||||
if (null !== $item->key) {
|
if (null !== $item->key) {
|
||||||
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
|
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
|
||||||
|
} elseif ($item->unpack) {
|
||||||
|
$array = array_merge($array, $this->evaluate($item->value));
|
||||||
} else {
|
} else {
|
||||||
$array[] = $this->evaluate($item->value);
|
$array[] = $this->evaluate($item->value);
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,8 @@ use PhpParser\Node\Expr;
|
|||||||
*/
|
*/
|
||||||
class PrintableNewAnonClassNode extends Expr
|
class PrintableNewAnonClassNode extends Expr
|
||||||
{
|
{
|
||||||
|
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||||
|
public $attrGroups;
|
||||||
/** @var Node\Arg[] Arguments */
|
/** @var Node\Arg[] Arguments */
|
||||||
public $args;
|
public $args;
|
||||||
/** @var null|Node\Name Name of extended class */
|
/** @var null|Node\Name Name of extended class */
|
||||||
@@ -27,9 +29,11 @@ class PrintableNewAnonClassNode extends Expr
|
|||||||
public $stmts;
|
public $stmts;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
array $args, Node\Name $extends = null, array $implements, array $stmts, array $attributes
|
array $attrGroups, array $args, Node\Name $extends = null, array $implements,
|
||||||
|
array $stmts, array $attributes
|
||||||
) {
|
) {
|
||||||
parent::__construct($attributes);
|
parent::__construct($attributes);
|
||||||
|
$this->attrGroups = $attrGroups;
|
||||||
$this->args = $args;
|
$this->args = $args;
|
||||||
$this->extends = $extends;
|
$this->extends = $extends;
|
||||||
$this->implements = $implements;
|
$this->implements = $implements;
|
||||||
@@ -42,7 +46,7 @@ class PrintableNewAnonClassNode extends Expr
|
|||||||
// We don't assert that $class->name is null here, to allow consumers to assign unique names
|
// 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.
|
// to anonymous classes for their own purposes. We simplify ignore the name here.
|
||||||
return new self(
|
return new self(
|
||||||
$newNode->args, $class->extends, $class->implements,
|
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
|
||||||
$class->stmts, $newNode->getAttributes()
|
$class->stmts, $newNode->getAttributes()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -52,6 +56,6 @@ class PrintableNewAnonClassNode extends Expr
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['args', 'extends', 'implements', 'stmts'];
|
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,7 +33,7 @@ class TokenStream
|
|||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function haveParens(int $startPos, int $endPos) : bool {
|
public function haveParens(int $startPos, int $endPos) : bool {
|
||||||
return $this->haveTokenImmediativelyBefore($startPos, '(')
|
return $this->haveTokenImmediatelyBefore($startPos, '(')
|
||||||
&& $this->haveTokenImmediatelyAfter($endPos, ')');
|
&& $this->haveTokenImmediatelyAfter($endPos, ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,8 @@ class TokenStream
|
|||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function haveBraces(int $startPos, int $endPos) : bool {
|
public function haveBraces(int $startPos, int $endPos) : bool {
|
||||||
return $this->haveTokenImmediativelyBefore($startPos, '{')
|
return ($this->haveTokenImmediatelyBefore($startPos, '{')
|
||||||
|
|| $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN))
|
||||||
&& $this->haveTokenImmediatelyAfter($endPos, '}');
|
&& $this->haveTokenImmediatelyAfter($endPos, '}');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ class TokenStream
|
|||||||
*
|
*
|
||||||
* @return bool Whether the expected token was found
|
* @return bool Whether the expected token was found
|
||||||
*/
|
*/
|
||||||
public function haveTokenImmediativelyBefore(int $pos, $expectedTokenType) : bool {
|
public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType) : bool {
|
||||||
$tokens = $this->tokens;
|
$tokens = $this->tokens;
|
||||||
$pos--;
|
$pos--;
|
||||||
for (; $pos >= 0; $pos--) {
|
for (; $pos >= 0; $pos--) {
|
||||||
@@ -170,7 +171,7 @@ class TokenStream
|
|||||||
return $pos;
|
return $pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findRight($pos, $findTokenType) {
|
public function findRight(int $pos, $findTokenType) {
|
||||||
$tokens = $this->tokens;
|
$tokens = $this->tokens;
|
||||||
for ($count = \count($tokens); $pos < $count; $pos++) {
|
for ($count = \count($tokens); $pos < $count; $pos++) {
|
||||||
$type = $tokens[$pos][0];
|
$type = $tokens[$pos][0];
|
||||||
@@ -181,6 +182,35 @@ class TokenStream
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the given position range contains a certain token type.
|
||||||
|
*
|
||||||
|
* @param int $startPos Starting position (inclusive)
|
||||||
|
* @param int $endPos Ending position (exclusive)
|
||||||
|
* @param int|string $tokenType Token type to look for
|
||||||
|
* @return bool Whether the token occurs in the given range
|
||||||
|
*/
|
||||||
|
public function haveTokenInRange(int $startPos, int $endPos, $tokenType) {
|
||||||
|
$tokens = $this->tokens;
|
||||||
|
for ($pos = $startPos; $pos < $endPos; $pos++) {
|
||||||
|
if ($tokens[$pos][0] === $tokenType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function haveBracesInRange(int $startPos, int $endPos) {
|
||||||
|
return $this->haveTokenInRange($startPos, $endPos, '{')
|
||||||
|
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|
||||||
|
|| $this->haveTokenInRange($startPos, $endPos, '}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function haveTagInRange(int $startPos, int $endPos): bool {
|
||||||
|
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|
||||||
|
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get indentation before token position.
|
* Get indentation before token position.
|
||||||
*
|
*
|
||||||
|
@@ -15,6 +15,7 @@ class Lexer
|
|||||||
|
|
||||||
protected $tokenMap;
|
protected $tokenMap;
|
||||||
protected $dropTokens;
|
protected $dropTokens;
|
||||||
|
protected $identifierTokens;
|
||||||
|
|
||||||
private $attributeStartLineUsed;
|
private $attributeStartLineUsed;
|
||||||
private $attributeEndLineUsed;
|
private $attributeEndLineUsed;
|
||||||
@@ -34,13 +35,10 @@ class Lexer
|
|||||||
* first three. For more info see getNextToken() docs.
|
* first three. For more info see getNextToken() docs.
|
||||||
*/
|
*/
|
||||||
public function __construct(array $options = []) {
|
public function __construct(array $options = []) {
|
||||||
// map from internal tokens to PhpParser tokens
|
// Create Map from internal tokens to PhpParser tokens.
|
||||||
|
$this->defineCompatibilityTokens();
|
||||||
$this->tokenMap = $this->createTokenMap();
|
$this->tokenMap = $this->createTokenMap();
|
||||||
|
$this->identifierTokens = $this->createIdentifierTokenMap();
|
||||||
// Compatibility define for PHP < 7.4
|
|
||||||
if (!defined('T_BAD_CHARACTER')) {
|
|
||||||
\define('T_BAD_CHARACTER', -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// map of tokens to drop while lexing (the map is only used for isset lookup,
|
// map of tokens to drop while lexing (the map is only used for isset lookup,
|
||||||
// that's why the value is simply set to 1; the value is never actually used.)
|
// that's why the value is simply set to 1; the value is never actually used.)
|
||||||
@@ -87,9 +85,8 @@ class Lexer
|
|||||||
|
|
||||||
$scream = ini_set('xdebug.scream', '0');
|
$scream = ini_set('xdebug.scream', '0');
|
||||||
|
|
||||||
error_clear_last();
|
|
||||||
$this->tokens = @token_get_all($code);
|
$this->tokens = @token_get_all($code);
|
||||||
$this->handleErrors($errorHandler);
|
$this->postprocessTokens($errorHandler);
|
||||||
|
|
||||||
if (false !== $scream) {
|
if (false !== $scream) {
|
||||||
ini_set('xdebug.scream', $scream);
|
ini_set('xdebug.scream', $scream);
|
||||||
@@ -131,40 +128,17 @@ class Lexer
|
|||||||
&& substr($token[1], -2) !== '*/';
|
&& substr($token[1], -2) !== '*/';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function postprocessTokens(ErrorHandler $errorHandler) {
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PHP_VERSION_ID >= 80000) {
|
|
||||||
// PHP 8 converts the "bad character" case into a parse error, rather than treating
|
|
||||||
// it as a lexing warning. To preserve previous behavior, we need to assume that an
|
|
||||||
// error occurred.
|
|
||||||
// TODO: We should handle this the same way as PHP 8: Only generate T_BAD_CHARACTER
|
|
||||||
// token here (for older PHP versions) and leave generationg of the actual parse error
|
|
||||||
// to the parser. This will also save the full token scan on PHP 8 here.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null !== error_get_last();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function handleErrors(ErrorHandler $errorHandler) {
|
|
||||||
if (!$this->errorMayHaveOccurred()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PHP's error handling for token_get_all() is rather bad, so if we want detailed
|
// 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
|
// error information we need to compute it ourselves. Invalid character errors are
|
||||||
// detected by finding "gaps" in the token array. Unterminated comments are detected
|
// detected by finding "gaps" in the token array. Unterminated comments are detected
|
||||||
// by checking if a trailing comment has a "*/" at the end.
|
// by checking if a trailing comment has a "*/" at the end.
|
||||||
|
//
|
||||||
|
// Additionally, we perform a number of canonicalizations here:
|
||||||
|
// * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore.
|
||||||
|
// * Use PHP 8.0 T_NAME_* tokens.
|
||||||
|
// * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and
|
||||||
|
// T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types.
|
||||||
|
|
||||||
$filePos = 0;
|
$filePos = 0;
|
||||||
$line = 1;
|
$line = 1;
|
||||||
@@ -178,6 +152,79 @@ class Lexer
|
|||||||
$this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler);
|
$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.
|
||||||
|
if (\is_array($token)
|
||||||
|
&& ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) {
|
||||||
|
$lastWasSeparator = $token[0] === \T_NS_SEPARATOR;
|
||||||
|
$text = $token[1];
|
||||||
|
for ($j = $i + 1; isset($this->tokens[$j]); $j++) {
|
||||||
|
if ($lastWasSeparator) {
|
||||||
|
if (!isset($this->identifierTokens[$this->tokens[$j][0]])) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($token === '&') {
|
||||||
|
$next = $i + 1;
|
||||||
|
while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) {
|
||||||
|
$next++;
|
||||||
|
}
|
||||||
|
$followedByVarOrVarArg = isset($this->tokens[$next]) &&
|
||||||
|
($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS);
|
||||||
|
$this->tokens[$i] = $token = [
|
||||||
|
$followedByVarOrVarArg
|
||||||
|
? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||||
|
: \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
|
||||||
|
'&',
|
||||||
|
$line,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$tokenValue = \is_string($token) ? $token : $token[1];
|
$tokenValue = \is_string($token) ? $token : $token[1];
|
||||||
$tokenLen = \strlen($tokenValue);
|
$tokenLen = \strlen($tokenValue);
|
||||||
|
|
||||||
@@ -292,7 +339,8 @@ class Lexer
|
|||||||
$value = $token[1];
|
$value = $token[1];
|
||||||
$id = $this->tokenMap[$token[0]];
|
$id = $this->tokenMap[$token[0]];
|
||||||
if (\T_CLOSE_TAG === $token[0]) {
|
if (\T_CLOSE_TAG === $token[0]) {
|
||||||
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n");
|
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n")
|
||||||
|
|| false !== strpos($token[1], "\r");
|
||||||
} elseif (\T_INLINE_HTML === $token[0]) {
|
} elseif (\T_INLINE_HTML === $token[0]) {
|
||||||
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
|
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
|
||||||
}
|
}
|
||||||
@@ -373,6 +421,66 @@ class Lexer
|
|||||||
return substr($textAfter, strlen($matches[0]));
|
return substr($textAfter, strlen($matches[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function defineCompatibilityTokens() {
|
||||||
|
static $compatTokensDefined = false;
|
||||||
|
if ($compatTokensDefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$compatTokens = [
|
||||||
|
// PHP 7.4
|
||||||
|
'T_BAD_CHARACTER',
|
||||||
|
'T_FN',
|
||||||
|
'T_COALESCE_EQUAL',
|
||||||
|
// PHP 8.0
|
||||||
|
'T_NAME_QUALIFIED',
|
||||||
|
'T_NAME_FULLY_QUALIFIED',
|
||||||
|
'T_NAME_RELATIVE',
|
||||||
|
'T_MATCH',
|
||||||
|
'T_NULLSAFE_OBJECT_OPERATOR',
|
||||||
|
'T_ATTRIBUTE',
|
||||||
|
// PHP 8.1
|
||||||
|
'T_ENUM',
|
||||||
|
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
|
||||||
|
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
|
||||||
|
'T_READONLY',
|
||||||
|
];
|
||||||
|
|
||||||
|
// PHP-Parser might be used together with another library that also emulates some or all
|
||||||
|
// of these tokens. Perform a sanity-check that all already defined tokens have been
|
||||||
|
// assigned a unique ID.
|
||||||
|
$usedTokenIds = [];
|
||||||
|
foreach ($compatTokens as $token) {
|
||||||
|
if (\defined($token)) {
|
||||||
|
$tokenId = \constant($token);
|
||||||
|
$clashingToken = $usedTokenIds[$tokenId] ?? null;
|
||||||
|
if ($clashingToken !== null) {
|
||||||
|
throw new \Error(sprintf(
|
||||||
|
'Token %s has same ID as token %s, ' .
|
||||||
|
'you may be using a library with broken token emulation',
|
||||||
|
$token, $clashingToken
|
||||||
|
));
|
||||||
|
}
|
||||||
|
$usedTokenIds[$tokenId] = $token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now define any tokens that have not yet been emulated. Try to assign IDs from -1
|
||||||
|
// downwards, but skip any IDs that may already be in use.
|
||||||
|
$newTokenId = -1;
|
||||||
|
foreach ($compatTokens as $token) {
|
||||||
|
if (!\defined($token)) {
|
||||||
|
while (isset($usedTokenIds[$newTokenId])) {
|
||||||
|
$newTokenId--;
|
||||||
|
}
|
||||||
|
\define($token, $newTokenId);
|
||||||
|
$newTokenId--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$compatTokensDefined = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the token map.
|
* Creates the token map.
|
||||||
*
|
*
|
||||||
@@ -417,6 +525,36 @@ class Lexer
|
|||||||
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
|
$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;
|
||||||
|
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
|
||||||
|
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
|
||||||
|
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
|
||||||
|
$tokenMap[\T_ENUM] = Tokens::T_ENUM;
|
||||||
|
$tokenMap[\T_READONLY] = Tokens::T_READONLY;
|
||||||
|
|
||||||
return $tokenMap;
|
return $tokenMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function createIdentifierTokenMap(): array {
|
||||||
|
// Based on semi_reserved production.
|
||||||
|
return array_fill_keys([
|
||||||
|
\T_STRING,
|
||||||
|
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY,
|
||||||
|
\T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND,
|
||||||
|
\T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE,
|
||||||
|
\T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH,
|
||||||
|
\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_FN,
|
||||||
|
\T_MATCH,
|
||||||
|
], true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,61 +5,94 @@ namespace PhpParser\Lexer;
|
|||||||
use PhpParser\Error;
|
use PhpParser\Error;
|
||||||
use PhpParser\ErrorHandler;
|
use PhpParser\ErrorHandler;
|
||||||
use PhpParser\Lexer;
|
use PhpParser\Lexer;
|
||||||
|
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
|
||||||
|
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
|
||||||
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
|
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
|
||||||
|
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
|
||||||
|
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
|
||||||
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
|
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
|
||||||
|
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
|
||||||
|
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
|
||||||
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
|
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
|
||||||
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface;
|
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
|
||||||
use PhpParser\Parser\Tokens;
|
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
|
||||||
|
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
|
||||||
|
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
|
||||||
|
|
||||||
class Emulative extends Lexer
|
class Emulative extends Lexer
|
||||||
{
|
{
|
||||||
const PHP_7_3 = '7.3.0dev';
|
const PHP_7_3 = '7.3dev';
|
||||||
const PHP_7_4 = '7.4.0dev';
|
const PHP_7_4 = '7.4dev';
|
||||||
|
const PHP_8_0 = '8.0dev';
|
||||||
const T_COALESCE_EQUAL = 1007;
|
const PHP_8_1 = '8.1dev';
|
||||||
const T_FN = 1008;
|
const PHP_8_2 = '8.2dev';
|
||||||
|
|
||||||
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 */
|
/** @var mixed[] Patches used to reverse changes introduced in the code */
|
||||||
private $patches = [];
|
private $patches = [];
|
||||||
|
|
||||||
/** @var TokenEmulatorInterface[] */
|
/** @var TokenEmulator[] */
|
||||||
private $tokenEmulators = [];
|
private $emulators = [];
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $targetPhpVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed[] $options
|
* @param mixed[] $options Lexer options. In addition to the usual options,
|
||||||
|
* accepts a 'phpVersion' string that specifies the
|
||||||
|
* version to emulate. Defaults to newest supported.
|
||||||
*/
|
*/
|
||||||
public function __construct(array $options = [])
|
public function __construct(array $options = [])
|
||||||
{
|
{
|
||||||
|
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_2;
|
||||||
|
unset($options['phpVersion']);
|
||||||
|
|
||||||
parent::__construct($options);
|
parent::__construct($options);
|
||||||
|
|
||||||
$this->tokenEmulators[] = new FnTokenEmulator();
|
$emulators = [
|
||||||
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator();
|
new FlexibleDocStringEmulator(),
|
||||||
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator();
|
new FnTokenEmulator(),
|
||||||
|
new MatchTokenEmulator(),
|
||||||
|
new CoaleseEqualTokenEmulator(),
|
||||||
|
new NumericLiteralSeparatorEmulator(),
|
||||||
|
new NullsafeTokenEmulator(),
|
||||||
|
new AttributeEmulator(),
|
||||||
|
new EnumTokenEmulator(),
|
||||||
|
new ReadonlyTokenEmulator(),
|
||||||
|
new ExplicitOctalEmulator(),
|
||||||
|
new ReadonlyFunctionTokenEmulator(),
|
||||||
|
];
|
||||||
|
|
||||||
$this->tokenMap[self::T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL;
|
// Collect emulators that are relevant for the PHP version we're running
|
||||||
$this->tokenMap[self::T_FN] = Tokens::T_FN;
|
// and the PHP version we're targeting for emulation.
|
||||||
|
foreach ($emulators as $emulator) {
|
||||||
|
$emulatorPhpVersion = $emulator->getPhpVersion();
|
||||||
|
if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) {
|
||||||
|
$this->emulators[] = $emulator;
|
||||||
|
} else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
|
||||||
|
$this->emulators[] = new ReverseEmulator($emulator);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
|
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
|
||||||
$this->patches = [];
|
$emulators = array_filter($this->emulators, function($emulator) use($code) {
|
||||||
|
return $emulator->isEmulationNeeded($code);
|
||||||
|
});
|
||||||
|
|
||||||
if ($this->isEmulationNeeded($code) === false) {
|
if (empty($emulators)) {
|
||||||
// Nothing to emulate, yay
|
// Nothing to emulate, yay
|
||||||
parent::startLexing($code, $errorHandler);
|
parent::startLexing($code, $errorHandler);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$collector = new ErrorHandler\Collecting();
|
$this->patches = [];
|
||||||
|
foreach ($emulators as $emulator) {
|
||||||
|
$code = $emulator->preprocessCode($code, $this->patches);
|
||||||
|
}
|
||||||
|
|
||||||
// 1. emulation of heredoc and nowdoc new syntax
|
$collector = new ErrorHandler\Collecting();
|
||||||
$preparedCode = $this->processHeredocNowdoc($code);
|
parent::startLexing($code, $collector);
|
||||||
parent::startLexing($preparedCode, $collector);
|
$this->sortPatches();
|
||||||
$this->fixupTokens();
|
$this->fixupTokens();
|
||||||
|
|
||||||
$errors = $collector->getErrors();
|
$errors = $collector->getErrors();
|
||||||
@@ -70,78 +103,28 @@ REGEX;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add token emulation
|
foreach ($emulators as $emulator) {
|
||||||
foreach ($this->tokenEmulators as $emulativeToken) {
|
$this->tokens = $emulator->emulate($code, $this->tokens);
|
||||||
if ($emulativeToken->isEmulationNeeded($code)) {
|
|
||||||
$this->tokens = $emulativeToken->emulate($code, $this->tokens);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isHeredocNowdocEmulationNeeded(string $code): bool
|
private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool {
|
||||||
|
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
|
||||||
|
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool {
|
||||||
|
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
|
||||||
|
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sortPatches()
|
||||||
{
|
{
|
||||||
// skip version where this works without emulation
|
// Patches may be contributed by different emulators.
|
||||||
if (version_compare(\PHP_VERSION, self::PHP_7_3, '>=')) {
|
// Make sure they are sorted by increasing patch position.
|
||||||
return false;
|
usort($this->patches, function($p1, $p2) {
|
||||||
}
|
return $p1[0] <=> $p2[0];
|
||||||
|
});
|
||||||
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()
|
private function fixupTokens()
|
||||||
@@ -160,7 +143,20 @@ REGEX;
|
|||||||
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
|
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
|
||||||
$token = $this->tokens[$i];
|
$token = $this->tokens[$i];
|
||||||
if (\is_string($token)) {
|
if (\is_string($token)) {
|
||||||
// We assume that patches don't apply to string tokens
|
if ($patchPos === $pos) {
|
||||||
|
// Only support replacement for string tokens.
|
||||||
|
assert($patchType === 'replace');
|
||||||
|
$this->tokens[$i] = $patchText;
|
||||||
|
|
||||||
|
// Fetch the next patch
|
||||||
|
$patchIdx++;
|
||||||
|
if ($patchIdx >= \count($this->patches)) {
|
||||||
|
// No more patches, we're done
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
|
||||||
|
}
|
||||||
|
|
||||||
$pos += \strlen($token);
|
$pos += \strlen($token);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -188,6 +184,11 @@ REGEX;
|
|||||||
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
|
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
|
||||||
);
|
);
|
||||||
$posDelta += $patchTextLen;
|
$posDelta += $patchTextLen;
|
||||||
|
} else if ($patchType === 'replace') {
|
||||||
|
// Replace inside the token string
|
||||||
|
$this->tokens[$i][1] = substr_replace(
|
||||||
|
$token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
@@ -234,7 +235,7 @@ REGEX;
|
|||||||
if ($patchType === 'add') {
|
if ($patchType === 'add') {
|
||||||
$posDelta += strlen($patchText);
|
$posDelta += strlen($patchText);
|
||||||
$lineDelta += substr_count($patchText, "\n");
|
$lineDelta += substr_count($patchText, "\n");
|
||||||
} else {
|
} else if ($patchType === 'remove') {
|
||||||
$posDelta -= strlen($patchText);
|
$posDelta -= strlen($patchText);
|
||||||
$lineDelta -= substr_count($patchText, "\n");
|
$lineDelta -= substr_count($patchText, "\n");
|
||||||
}
|
}
|
||||||
|
56
lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php
Normal file
56
lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
|
final class AttributeEmulator extends TokenEmulator
|
||||||
|
{
|
||||||
|
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 ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') {
|
||||||
|
array_splice($tokens, $i, 2, [
|
||||||
|
[\T_ATTRIBUTE, '#[', $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
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function preprocessCode(string $code, array &$patches): string {
|
||||||
|
$pos = 0;
|
||||||
|
while (false !== $pos = strpos($code, '#[', $pos)) {
|
||||||
|
// Replace #[ with %[
|
||||||
|
$code[$pos] = '%';
|
||||||
|
$patches[] = [$pos, 'replace', '#'];
|
||||||
|
$pos += 2;
|
||||||
|
}
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
}
|
@@ -4,15 +4,15 @@ namespace PhpParser\Lexer\TokenEmulator;
|
|||||||
|
|
||||||
use PhpParser\Lexer\Emulative;
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
|
final class CoaleseEqualTokenEmulator extends TokenEmulator
|
||||||
{
|
{
|
||||||
public function isEmulationNeeded(string $code) : bool
|
public function getPhpVersion(): string
|
||||||
{
|
{
|
||||||
// skip version where this is supported
|
return Emulative::PHP_7_4;
|
||||||
if (version_compare(\PHP_VERSION, Emulative::PHP_7_4, '>=')) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isEmulationNeeded(string $code): bool
|
||||||
|
{
|
||||||
return strpos($code, '??=') !== false;
|
return strpos($code, '??=') !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
|
|||||||
if (isset($tokens[$i + 1])) {
|
if (isset($tokens[$i + 1])) {
|
||||||
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
|
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
|
||||||
array_splice($tokens, $i, 2, [
|
array_splice($tokens, $i, 2, [
|
||||||
[Emulative::T_COALESCE_EQUAL, '??=', $line]
|
[\T_COALESCE_EQUAL, '??=', $line]
|
||||||
]);
|
]);
|
||||||
$c--;
|
$c--;
|
||||||
continue;
|
continue;
|
||||||
@@ -38,4 +38,10 @@ final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
|
|||||||
|
|
||||||
return $tokens;
|
return $tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function reverseEmulate(string $code, array $tokens): array
|
||||||
|
{
|
||||||
|
// ??= was not valid code previously, don't bother.
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
31
lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php
Normal file
31
lib/PhpParser/Lexer/TokenEmulator/EnumTokenEmulator.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
|
final class EnumTokenEmulator extends KeywordEmulator
|
||||||
|
{
|
||||||
|
public function getPhpVersion(): string
|
||||||
|
{
|
||||||
|
return Emulative::PHP_8_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKeywordString(): string
|
||||||
|
{
|
||||||
|
return 'enum';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKeywordToken(): int
|
||||||
|
{
|
||||||
|
return \T_ENUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isKeywordContext(array $tokens, int $pos): bool
|
||||||
|
{
|
||||||
|
return parent::isKeywordContext($tokens, $pos)
|
||||||
|
&& isset($tokens[$pos + 2])
|
||||||
|
&& $tokens[$pos + 1][0] === \T_WHITESPACE
|
||||||
|
&& $tokens[$pos + 2][0] === \T_STRING;
|
||||||
|
}
|
||||||
|
}
|
44
lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php
Normal file
44
lib/PhpParser/Lexer/TokenEmulator/ExplicitOctalEmulator.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
|
class ExplicitOctalEmulator extends TokenEmulator {
|
||||||
|
public function getPhpVersion(): string {
|
||||||
|
return Emulative::PHP_8_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmulationNeeded(string $code): bool {
|
||||||
|
return strpos($code, '0o') !== false || strpos($code, '0O') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function emulate(string $code, array $tokens): array {
|
||||||
|
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
||||||
|
if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' &&
|
||||||
|
isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING &&
|
||||||
|
preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])
|
||||||
|
) {
|
||||||
|
$tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]);
|
||||||
|
array_splice($tokens, $i, 2, [
|
||||||
|
[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]],
|
||||||
|
]);
|
||||||
|
$c--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveIntegerOrFloatToken(string $str): int
|
||||||
|
{
|
||||||
|
$str = substr($str, 1);
|
||||||
|
$str = str_replace('_', '', $str);
|
||||||
|
$num = octdec($str);
|
||||||
|
return is_float($num) ? \T_DNUMBER : \T_LNUMBER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseEmulate(string $code, array $tokens): array {
|
||||||
|
// Explicit octals were not legal code previously, don't bother.
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,76 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
|
final class FlexibleDocStringEmulator extends TokenEmulator
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
public function getPhpVersion(): string
|
||||||
|
{
|
||||||
|
return Emulative::PHP_7_3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmulationNeeded(string $code) : bool
|
||||||
|
{
|
||||||
|
return strpos($code, '<<<') !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function emulate(string $code, array $tokens): array
|
||||||
|
{
|
||||||
|
// Handled by preprocessing + fixup.
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseEmulate(string $code, array $tokens): array
|
||||||
|
{
|
||||||
|
// Not supported.
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function preprocessCode(string $code, array &$patches): string {
|
||||||
|
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);
|
||||||
|
$patches[] = [$indentationStart + $posDelta, 'add', $indentation];
|
||||||
|
$posDelta -= $indentationLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($separator === '') {
|
||||||
|
// Insert newline as separator
|
||||||
|
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
|
||||||
|
$patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
|
||||||
|
$posDelta += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
}
|
@@ -4,50 +4,20 @@ namespace PhpParser\Lexer\TokenEmulator;
|
|||||||
|
|
||||||
use PhpParser\Lexer\Emulative;
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
final class FnTokenEmulator implements TokenEmulatorInterface
|
final class FnTokenEmulator extends KeywordEmulator
|
||||||
{
|
{
|
||||||
public function isEmulationNeeded(string $code) : bool
|
public function getPhpVersion(): string
|
||||||
{
|
{
|
||||||
// skip version where this is supported
|
return Emulative::PHP_7_4;
|
||||||
if (version_compare(\PHP_VERSION, Emulative::PHP_7_4, '>=')) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return strpos($code, 'fn') !== false;
|
public function getKeywordString(): string
|
||||||
}
|
|
||||||
|
|
||||||
public function emulate(string $code, array $tokens): array
|
|
||||||
{
|
{
|
||||||
// We need to manually iterate and manage a count because we'll change
|
return 'fn';
|
||||||
// the tokens array on the way
|
|
||||||
foreach ($tokens as $i => $token) {
|
|
||||||
if ($token[0] === T_STRING && $token[1] === 'fn') {
|
|
||||||
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i);
|
|
||||||
if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === T_OBJECT_OPERATOR) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$tokens[$i][0] = Emulative::T_FN;
|
public function getKeywordToken(): int
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed[] $tokens
|
|
||||||
* @return mixed[]|null
|
|
||||||
*/
|
|
||||||
private function getPreviousNonSpaceToken(array $tokens, int $start)
|
|
||||||
{
|
{
|
||||||
for ($i = $start - 1; $i >= 0; --$i) {
|
return \T_FN;
|
||||||
if ($tokens[$i][0] === T_WHITESPACE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tokens[$i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
62
lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php
Normal file
62
lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
abstract class KeywordEmulator extends TokenEmulator
|
||||||
|
{
|
||||||
|
abstract function getKeywordString(): string;
|
||||||
|
abstract function getKeywordToken(): int;
|
||||||
|
|
||||||
|
public function isEmulationNeeded(string $code): bool
|
||||||
|
{
|
||||||
|
return strpos(strtolower($code), $this->getKeywordString()) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isKeywordContext(array $tokens, int $pos): bool
|
||||||
|
{
|
||||||
|
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
|
||||||
|
return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
&& $this->isKeywordContext($tokens, $i)) {
|
||||||
|
$tokens[$i][0] = $this->getKeywordToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed[] $tokens
|
||||||
|
* @return array|string|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;
|
||||||
|
}
|
||||||
|
}
|
67
lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php
Normal file
67
lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
|
final class NullsafeTokenEmulator extends TokenEmulator
|
||||||
|
{
|
||||||
|
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 ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
|
||||||
|
array_splice($tokens, $i, 2, [
|
||||||
|
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
|
||||||
|
]);
|
||||||
|
$c--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle ?-> inside encapsed string.
|
||||||
|
if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
|
||||||
|
&& $tokens[$i - 1][0] === \T_VARIABLE
|
||||||
|
&& preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches)
|
||||||
|
) {
|
||||||
|
$replacement = [
|
||||||
|
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line],
|
||||||
|
[\T_STRING, $matches[1], $line],
|
||||||
|
];
|
||||||
|
if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) {
|
||||||
|
$replacement[] = [
|
||||||
|
\T_ENCAPSED_AND_WHITESPACE,
|
||||||
|
\substr($tokens[$i][1], \strlen($matches[0])),
|
||||||
|
$line
|
||||||
|
];
|
||||||
|
}
|
||||||
|
array_splice($tokens, $i, 1, $replacement);
|
||||||
|
$c += \count($replacement) - 1;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@@ -4,7 +4,7 @@ namespace PhpParser\Lexer\TokenEmulator;
|
|||||||
|
|
||||||
use PhpParser\Lexer\Emulative;
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
final class NumericLiteralSeparatorEmulator implements TokenEmulatorInterface
|
final class NumericLiteralSeparatorEmulator extends TokenEmulator
|
||||||
{
|
{
|
||||||
const BIN = '(?:0b[01]+(?:_[01]+)*)';
|
const BIN = '(?:0b[01]+(?:_[01]+)*)';
|
||||||
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
|
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
|
||||||
@@ -14,13 +14,13 @@ final class NumericLiteralSeparatorEmulator implements TokenEmulatorInterface
|
|||||||
const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
|
const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
|
||||||
const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
|
const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
|
||||||
|
|
||||||
public function isEmulationNeeded(string $code) : bool
|
public function getPhpVersion(): string
|
||||||
{
|
{
|
||||||
// skip version where this is supported
|
return Emulative::PHP_7_4;
|
||||||
if (version_compare(\PHP_VERSION, Emulative::PHP_7_4, '>=')) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isEmulationNeeded(string $code) : bool
|
||||||
|
{
|
||||||
return preg_match('~[0-9]_[0-9]~', $code)
|
return preg_match('~[0-9]_[0-9]~', $code)
|
||||||
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
|
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
|
||||||
}
|
}
|
||||||
@@ -96,4 +96,10 @@ final class NumericLiteralSeparatorEmulator implements TokenEmulatorInterface
|
|||||||
|
|
||||||
return is_float($num) ? T_DNUMBER : T_LNUMBER;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In PHP 8.1, "readonly(" was special cased in the lexer in order to support functions with
|
||||||
|
* name readonly. In PHP 8.2, this may conflict with readonly properties having a DNF type. For
|
||||||
|
* this reason, PHP 8.2 instead treats this as T_READONLY and then handles it specially in the
|
||||||
|
* parser. This emulator only exists to handle this special case, which is skipped by the
|
||||||
|
* PHP 8.1 ReadonlyTokenEmulator.
|
||||||
|
*/
|
||||||
|
class ReadonlyFunctionTokenEmulator extends KeywordEmulator {
|
||||||
|
public function getKeywordString(): string {
|
||||||
|
return 'readonly';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKeywordToken(): int {
|
||||||
|
return \T_READONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPhpVersion(): string {
|
||||||
|
return Emulative::PHP_8_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseEmulate(string $code, array $tokens): array {
|
||||||
|
// Don't bother
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
}
|
36
lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php
Normal file
36
lib/PhpParser/Lexer/TokenEmulator/ReadonlyTokenEmulator.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
use PhpParser\Lexer\Emulative;
|
||||||
|
|
||||||
|
final class ReadonlyTokenEmulator extends KeywordEmulator
|
||||||
|
{
|
||||||
|
public function getPhpVersion(): string
|
||||||
|
{
|
||||||
|
return Emulative::PHP_8_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKeywordString(): string
|
||||||
|
{
|
||||||
|
return 'readonly';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKeywordToken(): int
|
||||||
|
{
|
||||||
|
return \T_READONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isKeywordContext(array $tokens, int $pos): bool
|
||||||
|
{
|
||||||
|
if (!parent::isKeywordContext($tokens, $pos)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Support "function readonly("
|
||||||
|
return !(isset($tokens[$pos + 1]) &&
|
||||||
|
($tokens[$pos + 1][0] === '(' ||
|
||||||
|
($tokens[$pos + 1][0] === \T_WHITESPACE &&
|
||||||
|
isset($tokens[$pos + 2]) &&
|
||||||
|
$tokens[$pos + 2][0] === '(')));
|
||||||
|
}
|
||||||
|
}
|
36
lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php
Normal file
36
lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverses emulation direction of the inner emulator.
|
||||||
|
*/
|
||||||
|
final class ReverseEmulator extends TokenEmulator
|
||||||
|
{
|
||||||
|
/** @var TokenEmulator Inner emulator */
|
||||||
|
private $emulator;
|
||||||
|
|
||||||
|
public function __construct(TokenEmulator $emulator) {
|
||||||
|
$this->emulator = $emulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPhpVersion(): string {
|
||||||
|
return $this->emulator->getPhpVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmulationNeeded(string $code): bool {
|
||||||
|
return $this->emulator->isEmulationNeeded($code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function emulate(string $code, array $tokens): array {
|
||||||
|
return $this->emulator->reverseEmulate($code, $tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reverseEmulate(string $code, array $tokens): array {
|
||||||
|
return $this->emulator->emulate($code, $tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function preprocessCode(string $code, array &$patches): string {
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
}
|
25
lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php
Normal file
25
lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Lexer\TokenEmulator;
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
abstract class TokenEmulator
|
||||||
|
{
|
||||||
|
abstract public function getPhpVersion(): string;
|
||||||
|
|
||||||
|
abstract public function isEmulationNeeded(string $code): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array Modified Tokens
|
||||||
|
*/
|
||||||
|
abstract public function emulate(string $code, array $tokens): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array Modified Tokens
|
||||||
|
*/
|
||||||
|
abstract public function reverseEmulate(string $code, array $tokens): array;
|
||||||
|
|
||||||
|
public function preprocessCode(string $code, array &$patches): string {
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,14 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace PhpParser\Lexer\TokenEmulator;
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
interface TokenEmulatorInterface
|
|
||||||
{
|
|
||||||
public function isEmulationNeeded(string $code): bool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array Modified Tokens
|
|
||||||
*/
|
|
||||||
public function emulate(string $code, array $tokens): array;
|
|
||||||
}
|
|
@@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
namespace PhpParser\Node;
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\Node\VariadicPlaceholder;
|
||||||
use PhpParser\NodeAbstract;
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
class Arg extends NodeAbstract
|
class Arg extends NodeAbstract
|
||||||
{
|
{
|
||||||
|
/** @var Identifier|null Parameter name (for named parameters) */
|
||||||
|
public $name;
|
||||||
/** @var Expr Value to pass */
|
/** @var Expr Value to pass */
|
||||||
public $value;
|
public $value;
|
||||||
/** @var bool Whether to pass by ref */
|
/** @var bool Whether to pass by ref */
|
||||||
@@ -20,16 +23,21 @@ class Arg extends NodeAbstract
|
|||||||
* @param bool $byRef Whether to pass by ref
|
* @param bool $byRef Whether to pass by ref
|
||||||
* @param bool $unpack Whether to unpack the argument
|
* @param bool $unpack Whether to unpack the argument
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
|
* @param Identifier|null $name Parameter name (for named parameters)
|
||||||
*/
|
*/
|
||||||
public function __construct(Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = []) {
|
public function __construct(
|
||||||
|
Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [],
|
||||||
|
Identifier $name = null
|
||||||
|
) {
|
||||||
$this->attributes = $attributes;
|
$this->attributes = $attributes;
|
||||||
|
$this->name = $name;
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
$this->byRef = $byRef;
|
$this->byRef = $byRef;
|
||||||
$this->unpack = $unpack;
|
$this->unpack = $unpack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['value', 'byRef', 'unpack'];
|
return ['name', 'value', 'byRef', 'unpack'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
|
34
lib/PhpParser/Node/Attribute.php
Normal file
34
lib/PhpParser/Node/Attribute.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
|
class Attribute extends NodeAbstract
|
||||||
|
{
|
||||||
|
/** @var Name Attribute name */
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/** @var Arg[] Attribute arguments */
|
||||||
|
public $args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Node\Name $name Attribute name
|
||||||
|
* @param Arg[] $args Attribute arguments
|
||||||
|
* @param array $attributes Additional node attributes
|
||||||
|
*/
|
||||||
|
public function __construct(Name $name, array $args = [], array $attributes = []) {
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
$this->name = $name;
|
||||||
|
$this->args = $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames() : array {
|
||||||
|
return ['name', 'args'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType() : string {
|
||||||
|
return 'Attribute';
|
||||||
|
}
|
||||||
|
}
|
29
lib/PhpParser/Node/AttributeGroup.php
Normal file
29
lib/PhpParser/Node/AttributeGroup.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
|
class AttributeGroup extends NodeAbstract
|
||||||
|
{
|
||||||
|
/** @var Attribute[] Attributes */
|
||||||
|
public $attrs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Attribute[] $attrs PHP attributes
|
||||||
|
* @param array $attributes Additional node attributes
|
||||||
|
*/
|
||||||
|
public function __construct(array $attrs, array $attributes = []) {
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
$this->attrs = $attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames() : array {
|
||||||
|
return ['attrs'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType() : string {
|
||||||
|
return 'AttributeGroup';
|
||||||
|
}
|
||||||
|
}
|
14
lib/PhpParser/Node/ComplexType.php
Normal file
14
lib/PhpParser/Node/ComplexType.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a base class for complex types, including nullable types and union types.
|
||||||
|
*
|
||||||
|
* It does not provide any shared behavior and exists only for type-checking purposes.
|
||||||
|
*/
|
||||||
|
abstract class ComplexType extends NodeAbstract
|
||||||
|
{
|
||||||
|
}
|
@@ -4,9 +4,6 @@ namespace PhpParser\Node;
|
|||||||
|
|
||||||
use PhpParser\NodeAbstract;
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
/**
|
|
||||||
* @property Name $namespacedName Namespaced name (for class constants, if using NameResolver)
|
|
||||||
*/
|
|
||||||
class Const_ extends NodeAbstract
|
class Const_ extends NodeAbstract
|
||||||
{
|
{
|
||||||
/** @var Identifier Name */
|
/** @var Identifier Name */
|
||||||
@@ -14,6 +11,9 @@ class Const_ extends NodeAbstract
|
|||||||
/** @var Expr Value */
|
/** @var Expr Value */
|
||||||
public $value;
|
public $value;
|
||||||
|
|
||||||
|
/** @var Name|null Namespaced name (if using NameResolver) */
|
||||||
|
public $namespacedName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a const node for use in class const and const statements.
|
* Constructs a const node for use in class const and const statements.
|
||||||
*
|
*
|
||||||
|
@@ -10,13 +10,13 @@ class Array_ extends Expr
|
|||||||
const KIND_LONG = 1; // array() syntax
|
const KIND_LONG = 1; // array() syntax
|
||||||
const KIND_SHORT = 2; // [] syntax
|
const KIND_SHORT = 2; // [] syntax
|
||||||
|
|
||||||
/** @var ArrayItem[] Items */
|
/** @var (ArrayItem|null)[] Items */
|
||||||
public $items;
|
public $items;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an array node.
|
* Constructs an array node.
|
||||||
*
|
*
|
||||||
* @param ArrayItem[] $items Items of the array
|
* @param (ArrayItem|null)[] $items Items of the array
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct(array $items = [], array $attributes = []) {
|
public function __construct(array $items = [], array $attributes = []) {
|
||||||
|
@@ -17,11 +17,13 @@ class ArrowFunction extends Expr implements FunctionLike
|
|||||||
/** @var Node\Param[] */
|
/** @var Node\Param[] */
|
||||||
public $params = [];
|
public $params = [];
|
||||||
|
|
||||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType */
|
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
|
||||||
public $returnType;
|
public $returnType;
|
||||||
|
|
||||||
/** @var Expr */
|
/** @var Expr */
|
||||||
public $expr;
|
public $expr;
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $subNodes Array of the following optional subnodes:
|
* @param array $subNodes Array of the following optional subnodes:
|
||||||
@@ -30,6 +32,7 @@ class ArrowFunction extends Expr implements FunctionLike
|
|||||||
* 'params' => array() : Parameters
|
* 'params' => array() : Parameters
|
||||||
* 'returnType' => null : Return type
|
* 'returnType' => null : Return type
|
||||||
* 'expr' => Expr : Expression body
|
* 'expr' => Expr : Expression body
|
||||||
|
* 'attrGroups' => array() : PHP attribute groups
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct(array $subNodes = [], array $attributes = []) {
|
public function __construct(array $subNodes = [], array $attributes = []) {
|
||||||
@@ -39,11 +42,12 @@ class ArrowFunction extends Expr implements FunctionLike
|
|||||||
$this->params = $subNodes['params'] ?? [];
|
$this->params = $subNodes['params'] ?? [];
|
||||||
$returnType = $subNodes['returnType'] ?? null;
|
$returnType = $subNodes['returnType'] ?? null;
|
||||||
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
||||||
$this->expr = $subNodes['expr'] ?? null;
|
$this->expr = $subNodes['expr'];
|
||||||
|
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['static', 'byRef', 'params', 'returnType', 'expr'];
|
return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function returnsByRef() : bool {
|
public function returnsByRef() : bool {
|
||||||
@@ -58,6 +62,10 @@ class ArrowFunction extends Expr implements FunctionLike
|
|||||||
return $this->returnType;
|
return $this->returnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAttrGroups() : array {
|
||||||
|
return $this->attrGroups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Node\Stmt\Return_[]
|
* @return Node\Stmt\Return_[]
|
||||||
*/
|
*/
|
||||||
|
39
lib/PhpParser/Node/Expr/CallLike.php
Normal file
39
lib/PhpParser/Node/Expr/CallLike.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node\Expr;
|
||||||
|
|
||||||
|
use PhpParser\Node\Arg;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\VariadicPlaceholder;
|
||||||
|
|
||||||
|
abstract class CallLike extends Expr {
|
||||||
|
/**
|
||||||
|
* Return raw arguments, which may be actual Args, or VariadicPlaceholders for first-class
|
||||||
|
* callables.
|
||||||
|
*
|
||||||
|
* @return array<Arg|VariadicPlaceholder>
|
||||||
|
*/
|
||||||
|
abstract public function getRawArgs(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this call expression is actually a first class callable.
|
||||||
|
*/
|
||||||
|
public function isFirstClassCallable(): bool {
|
||||||
|
foreach ($this->getRawArgs() as $arg) {
|
||||||
|
if ($arg instanceof VariadicPlaceholder) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that this is not a first-class callable and return only ordinary Args.
|
||||||
|
*
|
||||||
|
* @return Arg[]
|
||||||
|
*/
|
||||||
|
public function getArgs(): array {
|
||||||
|
assert(!$this->isFirstClassCallable());
|
||||||
|
return $this->getRawArgs();
|
||||||
|
}
|
||||||
|
}
|
@@ -16,10 +16,12 @@ class Closure extends Expr implements FunctionLike
|
|||||||
public $params;
|
public $params;
|
||||||
/** @var ClosureUse[] use()s */
|
/** @var ClosureUse[] use()s */
|
||||||
public $uses;
|
public $uses;
|
||||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
|
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
|
||||||
public $returnType;
|
public $returnType;
|
||||||
/** @var Node\Stmt[] Statements */
|
/** @var Node\Stmt[] Statements */
|
||||||
public $stmts;
|
public $stmts;
|
||||||
|
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a lambda function node.
|
* Constructs a lambda function node.
|
||||||
@@ -31,6 +33,7 @@ class Closure extends Expr implements FunctionLike
|
|||||||
* 'uses' => array(): use()s
|
* 'uses' => array(): use()s
|
||||||
* 'returnType' => null : Return type
|
* 'returnType' => null : Return type
|
||||||
* 'stmts' => array(): Statements
|
* 'stmts' => array(): Statements
|
||||||
|
* 'attrGroups' => array(): PHP attributes groups
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct(array $subNodes = [], array $attributes = []) {
|
public function __construct(array $subNodes = [], array $attributes = []) {
|
||||||
@@ -42,10 +45,11 @@ class Closure extends Expr implements FunctionLike
|
|||||||
$returnType = $subNodes['returnType'] ?? null;
|
$returnType = $subNodes['returnType'] ?? null;
|
||||||
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
||||||
$this->stmts = $subNodes['stmts'] ?? [];
|
$this->stmts = $subNodes['stmts'] ?? [];
|
||||||
|
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
|
return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function returnsByRef() : bool {
|
public function returnsByRef() : bool {
|
||||||
@@ -65,6 +69,10 @@ class Closure extends Expr implements FunctionLike
|
|||||||
return $this->stmts;
|
return $this->stmts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAttrGroups() : array {
|
||||||
|
return $this->attrGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
return 'Expr_Closure';
|
return 'Expr_Closure';
|
||||||
}
|
}
|
||||||
|
@@ -5,18 +5,18 @@ namespace PhpParser\Node\Expr;
|
|||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
use PhpParser\Node\Expr;
|
use PhpParser\Node\Expr;
|
||||||
|
|
||||||
class FuncCall extends Expr
|
class FuncCall extends CallLike
|
||||||
{
|
{
|
||||||
/** @var Node\Name|Expr Function name */
|
/** @var Node\Name|Expr Function name */
|
||||||
public $name;
|
public $name;
|
||||||
/** @var Node\Arg[] Arguments */
|
/** @var array<Node\Arg|Node\VariadicPlaceholder> Arguments */
|
||||||
public $args;
|
public $args;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a function call node.
|
* Constructs a function call node.
|
||||||
*
|
*
|
||||||
* @param Node\Name|Expr $name Function name
|
* @param Node\Name|Expr $name Function name
|
||||||
* @param Node\Arg[] $args Arguments
|
* @param array<Node\Arg|Node\VariadicPlaceholder> $args Arguments
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($name, array $args = [], array $attributes = []) {
|
public function __construct($name, array $args = [], array $attributes = []) {
|
||||||
@@ -32,4 +32,8 @@ class FuncCall extends Expr
|
|||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
return 'Expr_FuncCall';
|
return 'Expr_FuncCall';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRawArgs(): array {
|
||||||
|
return $this->args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
}
|
@@ -5,14 +5,15 @@ namespace PhpParser\Node\Expr;
|
|||||||
use PhpParser\Node\Arg;
|
use PhpParser\Node\Arg;
|
||||||
use PhpParser\Node\Expr;
|
use PhpParser\Node\Expr;
|
||||||
use PhpParser\Node\Identifier;
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\VariadicPlaceholder;
|
||||||
|
|
||||||
class MethodCall extends Expr
|
class MethodCall extends CallLike
|
||||||
{
|
{
|
||||||
/** @var Expr Variable holding object */
|
/** @var Expr Variable holding object */
|
||||||
public $var;
|
public $var;
|
||||||
/** @var Identifier|Expr Method name */
|
/** @var Identifier|Expr Method name */
|
||||||
public $name;
|
public $name;
|
||||||
/** @var Arg[] Arguments */
|
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||||
public $args;
|
public $args;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +21,7 @@ class MethodCall extends Expr
|
|||||||
*
|
*
|
||||||
* @param Expr $var Variable holding object
|
* @param Expr $var Variable holding object
|
||||||
* @param string|Identifier|Expr $name Method name
|
* @param string|Identifier|Expr $name Method name
|
||||||
* @param Arg[] $args Arguments
|
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
|
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
|
||||||
@@ -37,4 +38,8 @@ class MethodCall extends Expr
|
|||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
return 'Expr_MethodCall';
|
return 'Expr_MethodCall';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRawArgs(): array {
|
||||||
|
return $this->args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,20 +3,22 @@
|
|||||||
namespace PhpParser\Node\Expr;
|
namespace PhpParser\Node\Expr;
|
||||||
|
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Arg;
|
||||||
use PhpParser\Node\Expr;
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\VariadicPlaceholder;
|
||||||
|
|
||||||
class New_ extends Expr
|
class New_ extends CallLike
|
||||||
{
|
{
|
||||||
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
|
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
|
||||||
public $class;
|
public $class;
|
||||||
/** @var Node\Arg[] Arguments */
|
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||||
public $args;
|
public $args;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a function call node.
|
* Constructs a function call node.
|
||||||
*
|
*
|
||||||
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
|
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
|
||||||
* @param Node\Arg[] $args Arguments
|
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($class, array $args = [], array $attributes = []) {
|
public function __construct($class, array $args = [], array $attributes = []) {
|
||||||
@@ -32,4 +34,8 @@ class New_ extends Expr
|
|||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
return 'Expr_New';
|
return 'Expr_New';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRawArgs(): array {
|
||||||
|
return $this->args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
45
lib/PhpParser/Node/Expr/NullsafeMethodCall.php
Normal file
45
lib/PhpParser/Node/Expr/NullsafeMethodCall.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node\Expr;
|
||||||
|
|
||||||
|
use PhpParser\Node\Arg;
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\VariadicPlaceholder;
|
||||||
|
|
||||||
|
class NullsafeMethodCall extends CallLike
|
||||||
|
{
|
||||||
|
/** @var Expr Variable holding object */
|
||||||
|
public $var;
|
||||||
|
/** @var Identifier|Expr Method name */
|
||||||
|
public $name;
|
||||||
|
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||||
|
public $args;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a nullsafe method call node.
|
||||||
|
*
|
||||||
|
* @param Expr $var Variable holding object
|
||||||
|
* @param string|Identifier|Expr $name Method name
|
||||||
|
* @param array<Arg|VariadicPlaceholder> $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';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRawArgs(): array {
|
||||||
|
return $this->args;
|
||||||
|
}
|
||||||
|
}
|
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';
|
||||||
|
}
|
||||||
|
}
|
@@ -3,16 +3,18 @@
|
|||||||
namespace PhpParser\Node\Expr;
|
namespace PhpParser\Node\Expr;
|
||||||
|
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\Arg;
|
||||||
use PhpParser\Node\Expr;
|
use PhpParser\Node\Expr;
|
||||||
use PhpParser\Node\Identifier;
|
use PhpParser\Node\Identifier;
|
||||||
|
use PhpParser\Node\VariadicPlaceholder;
|
||||||
|
|
||||||
class StaticCall extends Expr
|
class StaticCall extends CallLike
|
||||||
{
|
{
|
||||||
/** @var Node\Name|Expr Class name */
|
/** @var Node\Name|Expr Class name */
|
||||||
public $class;
|
public $class;
|
||||||
/** @var Identifier|Expr Method name */
|
/** @var Identifier|Expr Method name */
|
||||||
public $name;
|
public $name;
|
||||||
/** @var Node\Arg[] Arguments */
|
/** @var array<Arg|VariadicPlaceholder> Arguments */
|
||||||
public $args;
|
public $args;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +22,7 @@ class StaticCall extends Expr
|
|||||||
*
|
*
|
||||||
* @param Node\Name|Expr $class Class name
|
* @param Node\Name|Expr $class Class name
|
||||||
* @param string|Identifier|Expr $name Method name
|
* @param string|Identifier|Expr $name Method name
|
||||||
* @param Node\Arg[] $args Arguments
|
* @param array<Arg|VariadicPlaceholder> $args Arguments
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($class, $name, array $args = [], array $attributes = []) {
|
public function __construct($class, $name, array $args = [], array $attributes = []) {
|
||||||
@@ -37,4 +39,8 @@ class StaticCall extends Expr
|
|||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
return 'Expr_StaticCall';
|
return 'Expr_StaticCall';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRawArgs(): array {
|
||||||
|
return $this->args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
30
lib/PhpParser/Node/Expr/Throw_.php
Normal file
30
lib/PhpParser/Node/Expr/Throw_.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node\Expr;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
class Throw_ extends Node\Expr
|
||||||
|
{
|
||||||
|
/** @var Node\Expr Expression */
|
||||||
|
public $expr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a throw expression node.
|
||||||
|
*
|
||||||
|
* @param Node\Expr $expr Expression
|
||||||
|
* @param array $attributes Additional attributes
|
||||||
|
*/
|
||||||
|
public function __construct(Node\Expr $expr, array $attributes = []) {
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
$this->expr = $expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames() : array {
|
||||||
|
return ['expr'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType() : string {
|
||||||
|
return 'Expr_Throw';
|
||||||
|
}
|
||||||
|
}
|
@@ -16,21 +16,28 @@ interface FunctionLike extends Node
|
|||||||
/**
|
/**
|
||||||
* List of parameters
|
* List of parameters
|
||||||
*
|
*
|
||||||
* @return Node\Param[]
|
* @return Param[]
|
||||||
*/
|
*/
|
||||||
public function getParams() : array;
|
public function getParams() : array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the declared return type or null
|
* Get the declared return type or null
|
||||||
*
|
*
|
||||||
* @return null|Identifier|Node\Name|Node\NullableType|Node\UnionType
|
* @return null|Identifier|Name|ComplexType
|
||||||
*/
|
*/
|
||||||
public function getReturnType();
|
public function getReturnType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function body
|
* The function body
|
||||||
*
|
*
|
||||||
* @return Node\Stmt[]|null
|
* @return Stmt[]|null
|
||||||
*/
|
*/
|
||||||
public function getStmts();
|
public function getStmts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get PHP attribute groups.
|
||||||
|
*
|
||||||
|
* @return AttributeGroup[]
|
||||||
|
*/
|
||||||
|
public function getAttrGroups() : array;
|
||||||
}
|
}
|
||||||
|
30
lib/PhpParser/Node/IntersectionType.php
Normal file
30
lib/PhpParser/Node/IntersectionType.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
|
class IntersectionType extends ComplexType
|
||||||
|
{
|
||||||
|
/** @var (Identifier|Name)[] Types */
|
||||||
|
public $types;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an intersection type.
|
||||||
|
*
|
||||||
|
* @param (Identifier|Name)[] $types Types
|
||||||
|
* @param array $attributes Additional attributes
|
||||||
|
*/
|
||||||
|
public function __construct(array $types, array $attributes = []) {
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
$this->types = $types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames() : array {
|
||||||
|
return ['types'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType() : string {
|
||||||
|
return 'IntersectionType';
|
||||||
|
}
|
||||||
|
}
|
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';
|
||||||
|
}
|
||||||
|
}
|
@@ -85,7 +85,7 @@ class Name extends NodeAbstract
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string representation of the name itself, without taking taking the name type into
|
* Returns a string representation of the name itself, without taking the name type into
|
||||||
* account (e.g., not including a leading backslash for fully qualified names).
|
* account (e.g., not including a leading backslash for fully qualified names).
|
||||||
*
|
*
|
||||||
* @return string String representation
|
* @return string String representation
|
||||||
@@ -162,7 +162,7 @@ class Name extends NodeAbstract
|
|||||||
$realLength = $numParts - $realOffset;
|
$realLength = $numParts - $realOffset;
|
||||||
} else {
|
} else {
|
||||||
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
|
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
|
||||||
if ($realLength < 0 || $realLength > $numParts) {
|
if ($realLength < 0 || $realLength > $numParts - $realOffset) {
|
||||||
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
|
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace PhpParser\Node;
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
use PhpParser\NodeAbstract;
|
class NullableType extends ComplexType
|
||||||
|
|
||||||
class NullableType extends NodeAbstract
|
|
||||||
{
|
{
|
||||||
/** @var Identifier|Name Type */
|
/** @var Identifier|Name Type */
|
||||||
public $type;
|
public $type;
|
||||||
|
@@ -6,7 +6,7 @@ use PhpParser\NodeAbstract;
|
|||||||
|
|
||||||
class Param extends NodeAbstract
|
class Param extends NodeAbstract
|
||||||
{
|
{
|
||||||
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
|
/** @var null|Identifier|Name|ComplexType Type declaration */
|
||||||
public $type;
|
public $type;
|
||||||
/** @var bool Whether parameter is passed by reference */
|
/** @var bool Whether parameter is passed by reference */
|
||||||
public $byRef;
|
public $byRef;
|
||||||
@@ -16,20 +16,29 @@ class Param extends NodeAbstract
|
|||||||
public $var;
|
public $var;
|
||||||
/** @var null|Expr Default value */
|
/** @var null|Expr Default value */
|
||||||
public $default;
|
public $default;
|
||||||
|
/** @var int */
|
||||||
|
public $flags;
|
||||||
|
/** @var AttributeGroup[] PHP attribute groups */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a parameter node.
|
* Constructs a parameter node.
|
||||||
*
|
*
|
||||||
* @param Expr\Variable|Expr\Error $var Parameter variable
|
* @param Expr\Variable|Expr\Error $var Parameter variable
|
||||||
* @param null|Expr $default Default value
|
* @param null|Expr $default Default value
|
||||||
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
|
* @param null|string|Identifier|Name|ComplexType $type Type declaration
|
||||||
* @param bool $byRef Whether is passed by reference
|
* @param bool $byRef Whether is passed by reference
|
||||||
* @param bool $variadic Whether this is a variadic argument
|
* @param bool $variadic Whether this is a variadic argument
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
|
* @param int $flags Optional visibility flags
|
||||||
|
* @param AttributeGroup[] $attrGroups PHP attribute groups
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
$var, Expr $default = null, $type = null,
|
$var, Expr $default = null, $type = null,
|
||||||
bool $byRef = false, bool $variadic = false, array $attributes = []
|
bool $byRef = false, bool $variadic = false,
|
||||||
|
array $attributes = [],
|
||||||
|
int $flags = 0,
|
||||||
|
array $attrGroups = []
|
||||||
) {
|
) {
|
||||||
$this->attributes = $attributes;
|
$this->attributes = $attributes;
|
||||||
$this->type = \is_string($type) ? new Identifier($type) : $type;
|
$this->type = \is_string($type) ? new Identifier($type) : $type;
|
||||||
@@ -37,10 +46,12 @@ class Param extends NodeAbstract
|
|||||||
$this->variadic = $variadic;
|
$this->variadic = $variadic;
|
||||||
$this->var = $var;
|
$this->var = $var;
|
||||||
$this->default = $default;
|
$this->default = $default;
|
||||||
|
$this->flags = $flags;
|
||||||
|
$this->attrGroups = $attrGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['type', 'byRef', 'variadic', 'var', 'default'];
|
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
|
@@ -24,6 +24,17 @@ class DNumber extends Scalar
|
|||||||
return ['value'];
|
return ['value'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed[] $attributes
|
||||||
|
*/
|
||||||
|
public static function fromString(string $str, array $attributes = []): DNumber
|
||||||
|
{
|
||||||
|
$attributes['rawValue'] = $str;
|
||||||
|
$float = self::parse($str);
|
||||||
|
|
||||||
|
return new DNumber($float, $attributes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*
|
*
|
||||||
@@ -36,13 +47,7 @@ class DNumber extends Scalar
|
|||||||
public static function parse(string $str) : float {
|
public static function parse(string $str) : float {
|
||||||
$str = str_replace('_', '', $str);
|
$str = str_replace('_', '', $str);
|
||||||
|
|
||||||
// if string contains any of .eE just cast it to float
|
// Check whether this is one of the special integer notations.
|
||||||
if (false !== strpbrk($str, '.eE')) {
|
|
||||||
return (float) $str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise it's an integer notation that overflowed into a float
|
|
||||||
// if it starts with 0 it's one of the special integer notations
|
|
||||||
if ('0' === $str[0]) {
|
if ('0' === $str[0]) {
|
||||||
// hex
|
// hex
|
||||||
if ('x' === $str[1] || 'X' === $str[1]) {
|
if ('x' === $str[1] || 'X' === $str[1]) {
|
||||||
@@ -54,11 +59,13 @@ class DNumber extends Scalar
|
|||||||
return bindec($str);
|
return bindec($str);
|
||||||
}
|
}
|
||||||
|
|
||||||
// oct
|
// oct, but only if the string does not contain any of '.eE'.
|
||||||
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
|
if (false === strpbrk($str, '.eE')) {
|
||||||
// so that only the digits before that are used
|
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit
|
||||||
|
// (8 or 9) so that only the digits before that are used.
|
||||||
return octdec(substr($str, 0, strcspn($str, '89')));
|
return octdec(substr($str, 0, strcspn($str, '89')));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dec
|
// dec
|
||||||
return (float) $str;
|
return (float) $str;
|
||||||
|
@@ -41,6 +41,8 @@ class LNumber extends Scalar
|
|||||||
* @return LNumber The constructed LNumber, including kind attribute
|
* @return LNumber The constructed LNumber, including kind attribute
|
||||||
*/
|
*/
|
||||||
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
|
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
|
||||||
|
$attributes['rawValue'] = $str;
|
||||||
|
|
||||||
$str = str_replace('_', '', $str);
|
$str = str_replace('_', '', $str);
|
||||||
|
|
||||||
if ('0' !== $str[0] || '0' === $str) {
|
if ('0' !== $str[0] || '0' === $str) {
|
||||||
@@ -62,6 +64,11 @@ class LNumber extends Scalar
|
|||||||
throw new Error('Invalid numeric literal', $attributes);
|
throw new Error('Invalid numeric literal', $attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip optional explicit octal prefix.
|
||||||
|
if ('o' === $str[1] || 'O' === $str[1]) {
|
||||||
|
$str = substr($str, 2);
|
||||||
|
}
|
||||||
|
|
||||||
// use intval instead of octdec to get proper cutting behavior with malformed numbers
|
// use intval instead of octdec to get proper cutting behavior with malformed numbers
|
||||||
$attributes['kind'] = LNumber::KIND_OCT;
|
$attributes['kind'] = LNumber::KIND_OCT;
|
||||||
return new LNumber(intval($str, 8), $attributes);
|
return new LNumber(intval($str, 8), $attributes);
|
||||||
|
@@ -42,6 +42,22 @@ class String_ extends Scalar
|
|||||||
return ['value'];
|
return ['value'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
|
||||||
|
*/
|
||||||
|
public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self
|
||||||
|
{
|
||||||
|
$attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B')))
|
||||||
|
? Scalar\String_::KIND_SINGLE_QUOTED
|
||||||
|
: Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||||
|
|
||||||
|
$attributes['rawValue'] = $str;
|
||||||
|
|
||||||
|
$string = self::parse($str, $parseUnicodeEscape);
|
||||||
|
|
||||||
|
return new self($string, $attributes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*
|
*
|
||||||
|
@@ -10,6 +10,8 @@ class ClassConst extends Node\Stmt
|
|||||||
public $flags;
|
public $flags;
|
||||||
/** @var Node\Const_[] Constant declarations */
|
/** @var Node\Const_[] Constant declarations */
|
||||||
public $consts;
|
public $consts;
|
||||||
|
/** @var Node\AttributeGroup[] */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a class const list node.
|
* Constructs a class const list node.
|
||||||
@@ -17,15 +19,22 @@ class ClassConst extends Node\Stmt
|
|||||||
* @param Node\Const_[] $consts Constant declarations
|
* @param Node\Const_[] $consts Constant declarations
|
||||||
* @param int $flags Modifiers
|
* @param int $flags Modifiers
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
|
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
|
||||||
*/
|
*/
|
||||||
public function __construct(array $consts, int $flags = 0, array $attributes = []) {
|
public function __construct(
|
||||||
|
array $consts,
|
||||||
|
int $flags = 0,
|
||||||
|
array $attributes = [],
|
||||||
|
array $attrGroups = []
|
||||||
|
) {
|
||||||
$this->attributes = $attributes;
|
$this->attributes = $attributes;
|
||||||
$this->flags = $flags;
|
$this->flags = $flags;
|
||||||
$this->consts = $consts;
|
$this->consts = $consts;
|
||||||
|
$this->attrGroups = $attrGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['flags', 'consts'];
|
return ['attrGroups', 'flags', 'consts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,6 +65,15 @@ class ClassConst extends Node\Stmt
|
|||||||
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
|
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether constant is final.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFinal() : bool {
|
||||||
|
return (bool) ($this->flags & Class_::MODIFIER_FINAL);
|
||||||
|
}
|
||||||
|
|
||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
return 'Stmt_ClassConst';
|
return 'Stmt_ClassConst';
|
||||||
}
|
}
|
||||||
|
@@ -4,15 +4,17 @@ namespace PhpParser\Node\Stmt;
|
|||||||
|
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
|
|
||||||
/**
|
|
||||||
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
|
|
||||||
*/
|
|
||||||
abstract class ClassLike extends Node\Stmt
|
abstract class ClassLike extends Node\Stmt
|
||||||
{
|
{
|
||||||
/** @var Node\Identifier|null Name */
|
/** @var Node\Identifier|null Name */
|
||||||
public $name;
|
public $name;
|
||||||
/** @var Node\Stmt[] Statements */
|
/** @var Node\Stmt[] Statements */
|
||||||
public $stmts;
|
public $stmts;
|
||||||
|
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
|
/** @var Node\Name|null Namespaced name (if using NameResolver) */
|
||||||
|
public $namespacedName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return TraitUse[]
|
* @return TraitUse[]
|
||||||
|
@@ -15,10 +15,12 @@ class ClassMethod extends Node\Stmt implements FunctionLike
|
|||||||
public $name;
|
public $name;
|
||||||
/** @var Node\Param[] Parameters */
|
/** @var Node\Param[] Parameters */
|
||||||
public $params;
|
public $params;
|
||||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
|
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
|
||||||
public $returnType;
|
public $returnType;
|
||||||
/** @var Node\Stmt[]|null Statements */
|
/** @var Node\Stmt[]|null Statements */
|
||||||
public $stmts;
|
public $stmts;
|
||||||
|
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
private static $magicNames = [
|
private static $magicNames = [
|
||||||
'__construct' => true,
|
'__construct' => true,
|
||||||
@@ -36,6 +38,8 @@ class ClassMethod extends Node\Stmt implements FunctionLike
|
|||||||
'__clone' => true,
|
'__clone' => true,
|
||||||
'__invoke' => true,
|
'__invoke' => true,
|
||||||
'__debuginfo' => true,
|
'__debuginfo' => true,
|
||||||
|
'__serialize' => true,
|
||||||
|
'__unserialize' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,6 +52,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
|
|||||||
* 'params' => array() : Parameters
|
* 'params' => array() : Parameters
|
||||||
* 'returnType' => null : Return type
|
* 'returnType' => null : Return type
|
||||||
* 'stmts' => array() : Statements
|
* 'stmts' => array() : Statements
|
||||||
|
* 'attrGroups' => array() : PHP attribute groups
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
||||||
@@ -59,10 +64,11 @@ class ClassMethod extends Node\Stmt implements FunctionLike
|
|||||||
$returnType = $subNodes['returnType'] ?? null;
|
$returnType = $subNodes['returnType'] ?? null;
|
||||||
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
||||||
$this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : [];
|
$this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : [];
|
||||||
|
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['flags', 'byRef', 'name', 'params', 'returnType', 'stmts'];
|
return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function returnsByRef() : bool {
|
public function returnsByRef() : bool {
|
||||||
@@ -81,6 +87,10 @@ class ClassMethod extends Node\Stmt implements FunctionLike
|
|||||||
return $this->stmts;
|
return $this->stmts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAttrGroups() : array {
|
||||||
|
return $this->attrGroups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the method is explicitly or implicitly public.
|
* Whether the method is explicitly or implicitly public.
|
||||||
*
|
*
|
||||||
|
@@ -13,6 +13,7 @@ class Class_ extends ClassLike
|
|||||||
const MODIFIER_STATIC = 8;
|
const MODIFIER_STATIC = 8;
|
||||||
const MODIFIER_ABSTRACT = 16;
|
const MODIFIER_ABSTRACT = 16;
|
||||||
const MODIFIER_FINAL = 32;
|
const MODIFIER_FINAL = 32;
|
||||||
|
const MODIFIER_READONLY = 64;
|
||||||
|
|
||||||
const VISIBILITY_MODIFIER_MASK = 7; // 1 | 2 | 4
|
const VISIBILITY_MODIFIER_MASK = 7; // 1 | 2 | 4
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ class Class_ extends ClassLike
|
|||||||
* 'extends' => null : Name of extended class
|
* 'extends' => null : Name of extended class
|
||||||
* 'implements' => array(): Names of implemented interfaces
|
* 'implements' => array(): Names of implemented interfaces
|
||||||
* 'stmts' => array(): Statements
|
* 'stmts' => array(): Statements
|
||||||
|
* 'attrGroups' => array(): PHP attribute groups
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
||||||
@@ -41,10 +43,11 @@ class Class_ extends ClassLike
|
|||||||
$this->extends = $subNodes['extends'] ?? null;
|
$this->extends = $subNodes['extends'] ?? null;
|
||||||
$this->implements = $subNodes['implements'] ?? [];
|
$this->implements = $subNodes['implements'] ?? [];
|
||||||
$this->stmts = $subNodes['stmts'] ?? [];
|
$this->stmts = $subNodes['stmts'] ?? [];
|
||||||
|
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['flags', 'name', 'extends', 'implements', 'stmts'];
|
return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,6 +68,10 @@ class Class_ extends ClassLike
|
|||||||
return (bool) ($this->flags & self::MODIFIER_FINAL);
|
return (bool) ($this->flags & self::MODIFIER_FINAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isReadonly() : bool {
|
||||||
|
return (bool) ($this->flags & self::MODIFIER_READONLY);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the class is anonymous.
|
* Whether the class is anonymous.
|
||||||
*
|
*
|
||||||
@@ -74,6 +81,27 @@ class Class_ extends ClassLike
|
|||||||
return null === $this->name;
|
return null === $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public static function verifyClassModifier($a, $b) {
|
||||||
|
if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
|
||||||
|
throw new Error('Multiple abstract modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
|
||||||
|
throw new Error('Multiple final modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
|
||||||
|
throw new Error('Multiple readonly modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & 48 && $b & 48) {
|
||||||
|
throw new Error('Cannot use the final modifier on an abstract class');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@@ -94,6 +122,10 @@ class Class_ extends ClassLike
|
|||||||
throw new Error('Multiple final modifiers are not allowed');
|
throw new Error('Multiple final modifiers are not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
|
||||||
|
throw new Error('Multiple readonly modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
if ($a & 48 && $b & 48) {
|
if ($a & 48 && $b & 48) {
|
||||||
throw new Error('Cannot use the final modifier on an abstract class member');
|
throw new Error('Cannot use the final modifier on an abstract class member');
|
||||||
}
|
}
|
||||||
|
37
lib/PhpParser/Node/Stmt/EnumCase.php
Normal file
37
lib/PhpParser/Node/Stmt/EnumCase.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node\Stmt;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\AttributeGroup;
|
||||||
|
|
||||||
|
class EnumCase extends Node\Stmt
|
||||||
|
{
|
||||||
|
/** @var Node\Identifier Enum case name */
|
||||||
|
public $name;
|
||||||
|
/** @var Node\Expr|null Enum case expression */
|
||||||
|
public $expr;
|
||||||
|
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|Node\Identifier $name Enum case name
|
||||||
|
* @param Node\Expr|null $expr Enum case expression
|
||||||
|
* @param AttributeGroup[] $attrGroups PHP attribute groups
|
||||||
|
* @param array $attributes Additional attributes
|
||||||
|
*/
|
||||||
|
public function __construct($name, Node\Expr $expr = null, array $attrGroups = [], array $attributes = []) {
|
||||||
|
parent::__construct($attributes);
|
||||||
|
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
|
||||||
|
$this->expr = $expr;
|
||||||
|
$this->attrGroups = $attrGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames() : array {
|
||||||
|
return ['attrGroups', 'name', 'expr'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType() : string {
|
||||||
|
return 'Stmt_EnumCase';
|
||||||
|
}
|
||||||
|
}
|
40
lib/PhpParser/Node/Stmt/Enum_.php
Normal file
40
lib/PhpParser/Node/Stmt/Enum_.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node\Stmt;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
|
||||||
|
class Enum_ extends ClassLike
|
||||||
|
{
|
||||||
|
/** @var null|Node\Identifier Scalar Type */
|
||||||
|
public $scalarType;
|
||||||
|
/** @var Node\Name[] Names of implemented interfaces */
|
||||||
|
public $implements;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|Node\Identifier|null $name Name
|
||||||
|
* @param array $subNodes Array of the following optional subnodes:
|
||||||
|
* 'scalarType' => null : Scalar type
|
||||||
|
* 'implements' => array() : Names of implemented interfaces
|
||||||
|
* 'stmts' => array() : Statements
|
||||||
|
* 'attrGroups' => array() : PHP attribute groups
|
||||||
|
* @param array $attributes Additional attributes
|
||||||
|
*/
|
||||||
|
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
||||||
|
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
|
||||||
|
$this->scalarType = $subNodes['scalarType'] ?? null;
|
||||||
|
$this->implements = $subNodes['implements'] ?? [];
|
||||||
|
$this->stmts = $subNodes['stmts'] ?? [];
|
||||||
|
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||||
|
|
||||||
|
parent::__construct($attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames() : array {
|
||||||
|
return ['attrGroups', 'name', 'scalarType', 'implements', 'stmts'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType() : string {
|
||||||
|
return 'Stmt_Enum';
|
||||||
|
}
|
||||||
|
}
|
@@ -5,9 +5,6 @@ namespace PhpParser\Node\Stmt;
|
|||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
use PhpParser\Node\FunctionLike;
|
use PhpParser\Node\FunctionLike;
|
||||||
|
|
||||||
/**
|
|
||||||
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
|
|
||||||
*/
|
|
||||||
class Function_ extends Node\Stmt implements FunctionLike
|
class Function_ extends Node\Stmt implements FunctionLike
|
||||||
{
|
{
|
||||||
/** @var bool Whether function returns by reference */
|
/** @var bool Whether function returns by reference */
|
||||||
@@ -16,10 +13,15 @@ class Function_ extends Node\Stmt implements FunctionLike
|
|||||||
public $name;
|
public $name;
|
||||||
/** @var Node\Param[] Parameters */
|
/** @var Node\Param[] Parameters */
|
||||||
public $params;
|
public $params;
|
||||||
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
|
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
|
||||||
public $returnType;
|
public $returnType;
|
||||||
/** @var Node\Stmt[] Statements */
|
/** @var Node\Stmt[] Statements */
|
||||||
public $stmts;
|
public $stmts;
|
||||||
|
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
|
/** @var Node\Name|null Namespaced name (if using NameResolver) */
|
||||||
|
public $namespacedName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a function node.
|
* Constructs a function node.
|
||||||
@@ -30,6 +32,7 @@ class Function_ extends Node\Stmt implements FunctionLike
|
|||||||
* 'params' => array(): Parameters
|
* 'params' => array(): Parameters
|
||||||
* 'returnType' => null : Return type
|
* 'returnType' => null : Return type
|
||||||
* 'stmts' => array(): Statements
|
* 'stmts' => array(): Statements
|
||||||
|
* 'attrGroups' => array(): PHP attribute groups
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
||||||
@@ -40,10 +43,11 @@ class Function_ extends Node\Stmt implements FunctionLike
|
|||||||
$returnType = $subNodes['returnType'] ?? null;
|
$returnType = $subNodes['returnType'] ?? null;
|
||||||
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
|
||||||
$this->stmts = $subNodes['stmts'] ?? [];
|
$this->stmts = $subNodes['stmts'] ?? [];
|
||||||
|
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['byRef', 'name', 'params', 'returnType', 'stmts'];
|
return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function returnsByRef() : bool {
|
public function returnsByRef() : bool {
|
||||||
@@ -58,6 +62,10 @@ class Function_ extends Node\Stmt implements FunctionLike
|
|||||||
return $this->returnType;
|
return $this->returnType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAttrGroups() : array {
|
||||||
|
return $this->attrGroups;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return Node\Stmt[] */
|
/** @return Node\Stmt[] */
|
||||||
public function getStmts() : array {
|
public function getStmts() : array {
|
||||||
return $this->stmts;
|
return $this->stmts;
|
||||||
|
@@ -16,6 +16,7 @@ class Interface_ extends ClassLike
|
|||||||
* @param array $subNodes Array of the following optional subnodes:
|
* @param array $subNodes Array of the following optional subnodes:
|
||||||
* 'extends' => array(): Name of extended interfaces
|
* 'extends' => array(): Name of extended interfaces
|
||||||
* 'stmts' => array(): Statements
|
* 'stmts' => array(): Statements
|
||||||
|
* 'attrGroups' => array(): PHP attribute groups
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
||||||
@@ -23,10 +24,11 @@ class Interface_ extends ClassLike
|
|||||||
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
|
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
|
||||||
$this->extends = $subNodes['extends'] ?? [];
|
$this->extends = $subNodes['extends'] ?? [];
|
||||||
$this->stmts = $subNodes['stmts'] ?? [];
|
$this->stmts = $subNodes['stmts'] ?? [];
|
||||||
|
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['name', 'extends', 'stmts'];
|
return ['attrGroups', 'name', 'extends', 'stmts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
|
@@ -3,10 +3,9 @@
|
|||||||
namespace PhpParser\Node\Stmt;
|
namespace PhpParser\Node\Stmt;
|
||||||
|
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
|
use PhpParser\Node\ComplexType;
|
||||||
use PhpParser\Node\Identifier;
|
use PhpParser\Node\Identifier;
|
||||||
use PhpParser\Node\Name;
|
use PhpParser\Node\Name;
|
||||||
use PhpParser\Node\NullableType;
|
|
||||||
use PhpParser\Node\UnionType;
|
|
||||||
|
|
||||||
class Property extends Node\Stmt
|
class Property extends Node\Stmt
|
||||||
{
|
{
|
||||||
@@ -14,8 +13,10 @@ class Property extends Node\Stmt
|
|||||||
public $flags;
|
public $flags;
|
||||||
/** @var PropertyProperty[] Properties */
|
/** @var PropertyProperty[] Properties */
|
||||||
public $props;
|
public $props;
|
||||||
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
|
/** @var null|Identifier|Name|ComplexType Type declaration */
|
||||||
public $type;
|
public $type;
|
||||||
|
/** @var Node\AttributeGroup[] PHP attribute groups */
|
||||||
|
public $attrGroups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a class property list node.
|
* Constructs a class property list node.
|
||||||
@@ -23,17 +24,19 @@ class Property extends Node\Stmt
|
|||||||
* @param int $flags Modifiers
|
* @param int $flags Modifiers
|
||||||
* @param PropertyProperty[] $props Properties
|
* @param PropertyProperty[] $props Properties
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
|
* @param null|string|Identifier|Name|ComplexType $type Type declaration
|
||||||
|
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
|
||||||
*/
|
*/
|
||||||
public function __construct(int $flags, array $props, array $attributes = [], $type = null) {
|
public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) {
|
||||||
$this->attributes = $attributes;
|
$this->attributes = $attributes;
|
||||||
$this->flags = $flags;
|
$this->flags = $flags;
|
||||||
$this->props = $props;
|
$this->props = $props;
|
||||||
$this->type = \is_string($type) ? new Identifier($type) : $type;
|
$this->type = \is_string($type) ? new Identifier($type) : $type;
|
||||||
|
$this->attrGroups = $attrGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['flags', 'type', 'props'];
|
return ['attrGroups', 'flags', 'type', 'props'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,6 +76,15 @@ class Property extends Node\Stmt
|
|||||||
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
|
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the property is readonly.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadonly() : bool {
|
||||||
|
return (bool) ($this->flags & Class_::MODIFIER_READONLY);
|
||||||
|
}
|
||||||
|
|
||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
return 'Stmt_Property';
|
return 'Stmt_Property';
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@ class Throw_ extends Node\Stmt
|
|||||||
public $expr;
|
public $expr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a throw node.
|
* Constructs a legacy throw statement node.
|
||||||
*
|
*
|
||||||
* @param Node\Expr $expr Expression
|
* @param Node\Expr $expr Expression
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
|
@@ -12,16 +12,18 @@ class Trait_ extends ClassLike
|
|||||||
* @param string|Node\Identifier $name Name
|
* @param string|Node\Identifier $name Name
|
||||||
* @param array $subNodes Array of the following optional subnodes:
|
* @param array $subNodes Array of the following optional subnodes:
|
||||||
* 'stmts' => array(): Statements
|
* 'stmts' => array(): Statements
|
||||||
|
* 'attrGroups' => array(): PHP attribute groups
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
public function __construct($name, array $subNodes = [], array $attributes = []) {
|
||||||
$this->attributes = $attributes;
|
$this->attributes = $attributes;
|
||||||
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
|
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
|
||||||
$this->stmts = $subNodes['stmts'] ?? [];
|
$this->stmts = $subNodes['stmts'] ?? [];
|
||||||
|
$this->attrGroups = $subNodes['attrGroups'] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
public function getSubNodeNames() : array {
|
||||||
return ['name', 'stmts'];
|
return ['attrGroups', 'name', 'stmts'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getType() : string {
|
public function getType() : string {
|
||||||
|
@@ -2,17 +2,15 @@
|
|||||||
|
|
||||||
namespace PhpParser\Node;
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
use PhpParser\NodeAbstract;
|
class UnionType extends ComplexType
|
||||||
|
|
||||||
class UnionType extends NodeAbstract
|
|
||||||
{
|
{
|
||||||
/** @var (Identifier|Name)[] Types */
|
/** @var (Identifier|Name|IntersectionType)[] Types */
|
||||||
public $types;
|
public $types;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a union type.
|
* Constructs a union type.
|
||||||
*
|
*
|
||||||
* @param (Identifier|Name)[] $types Types
|
* @param (Identifier|Name|IntersectionType)[] $types Types
|
||||||
* @param array $attributes Additional attributes
|
* @param array $attributes Additional attributes
|
||||||
*/
|
*/
|
||||||
public function __construct(array $types, array $attributes = []) {
|
public function __construct(array $types, array $attributes = []) {
|
||||||
|
27
lib/PhpParser/Node/VariadicPlaceholder.php
Normal file
27
lib/PhpParser/Node/VariadicPlaceholder.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the "..." in "foo(...)" of the first-class callable syntax.
|
||||||
|
*/
|
||||||
|
class VariadicPlaceholder extends NodeAbstract {
|
||||||
|
/**
|
||||||
|
* Create a variadic argument placeholder (first-class callable syntax).
|
||||||
|
*
|
||||||
|
* @param array $attributes Additional attributes
|
||||||
|
*/
|
||||||
|
public function __construct(array $attributes = []) {
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string {
|
||||||
|
return 'VariadicPlaceholder';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames(): array {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
@@ -128,6 +128,9 @@ class NodeDumper
|
|||||||
if ($flags & Class_::MODIFIER_FINAL) {
|
if ($flags & Class_::MODIFIER_FINAL) {
|
||||||
$strs[] = 'MODIFIER_FINAL';
|
$strs[] = 'MODIFIER_FINAL';
|
||||||
}
|
}
|
||||||
|
if ($flags & Class_::MODIFIER_READONLY) {
|
||||||
|
$strs[] = 'MODIFIER_READONLY';
|
||||||
|
}
|
||||||
|
|
||||||
if ($strs) {
|
if ($strs) {
|
||||||
return implode(' | ', $strs) . ' (' . $flags . ')';
|
return implode(' | ', $strs) . ' (' . $flags . ')';
|
||||||
|
@@ -75,6 +75,7 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
$interface = $this->resolveClassName($interface);
|
$interface = $this->resolveClassName($interface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
if (null !== $node->name) {
|
if (null !== $node->name) {
|
||||||
$this->addNamespacedName($node);
|
$this->addNamespacedName($node);
|
||||||
}
|
}
|
||||||
@@ -83,25 +84,43 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
$interface = $this->resolveClassName($interface);
|
$interface = $this->resolveClassName($interface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
$this->addNamespacedName($node);
|
$this->addNamespacedName($node);
|
||||||
|
} elseif ($node instanceof Stmt\Enum_) {
|
||||||
|
foreach ($node->implements as &$interface) {
|
||||||
|
$interface = $this->resolveClassName($interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
|
if (null !== $node->name) {
|
||||||
|
$this->addNamespacedName($node);
|
||||||
|
}
|
||||||
} elseif ($node instanceof Stmt\Trait_) {
|
} elseif ($node instanceof Stmt\Trait_) {
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
$this->addNamespacedName($node);
|
$this->addNamespacedName($node);
|
||||||
} elseif ($node instanceof Stmt\Function_) {
|
} elseif ($node instanceof Stmt\Function_) {
|
||||||
$this->addNamespacedName($node);
|
|
||||||
$this->resolveSignature($node);
|
$this->resolveSignature($node);
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
|
$this->addNamespacedName($node);
|
||||||
} elseif ($node instanceof Stmt\ClassMethod
|
} elseif ($node instanceof Stmt\ClassMethod
|
||||||
|| $node instanceof Expr\Closure
|
|| $node instanceof Expr\Closure
|
||||||
|| $node instanceof Expr\ArrowFunction
|
|| $node instanceof Expr\ArrowFunction
|
||||||
) {
|
) {
|
||||||
$this->resolveSignature($node);
|
$this->resolveSignature($node);
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
} elseif ($node instanceof Stmt\Property) {
|
} elseif ($node instanceof Stmt\Property) {
|
||||||
if (null !== $node->type) {
|
if (null !== $node->type) {
|
||||||
$node->type = $this->resolveType($node->type);
|
$node->type = $this->resolveType($node->type);
|
||||||
}
|
}
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
} elseif ($node instanceof Stmt\Const_) {
|
} elseif ($node instanceof Stmt\Const_) {
|
||||||
foreach ($node->consts as $const) {
|
foreach ($node->consts as $const) {
|
||||||
$this->addNamespacedName($const);
|
$this->addNamespacedName($const);
|
||||||
}
|
}
|
||||||
|
} else if ($node instanceof Stmt\ClassConst) {
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
|
} else if ($node instanceof Stmt\EnumCase) {
|
||||||
|
$this->resolveAttrGroups($node);
|
||||||
} elseif ($node instanceof Expr\StaticCall
|
} elseif ($node instanceof Expr\StaticCall
|
||||||
|| $node instanceof Expr\StaticPropertyFetch
|
|| $node instanceof Expr\StaticPropertyFetch
|
||||||
|| $node instanceof Expr\ClassConstFetch
|
|| $node instanceof Expr\ClassConstFetch
|
||||||
@@ -142,7 +161,7 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
|
private function addAlias(Stmt\UseUse $use, int $type, Name $prefix = null) {
|
||||||
// Add prefix for group uses
|
// Add prefix for group uses
|
||||||
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
|
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
|
||||||
// Type is determined either by individual element or whole use declaration
|
// Type is determined either by individual element or whole use declaration
|
||||||
@@ -157,6 +176,7 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
private function resolveSignature($node) {
|
private function resolveSignature($node) {
|
||||||
foreach ($node->params as $param) {
|
foreach ($node->params as $param) {
|
||||||
$param->type = $this->resolveType($param->type);
|
$param->type = $this->resolveType($param->type);
|
||||||
|
$this->resolveAttrGroups($param);
|
||||||
}
|
}
|
||||||
$node->returnType = $this->resolveType($node->returnType);
|
$node->returnType = $this->resolveType($node->returnType);
|
||||||
}
|
}
|
||||||
@@ -169,7 +189,7 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
$node->type = $this->resolveType($node->type);
|
$node->type = $this->resolveType($node->type);
|
||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
if ($node instanceof Node\UnionType) {
|
if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) {
|
||||||
foreach ($node->types as &$type) {
|
foreach ($node->types as &$type) {
|
||||||
$type = $this->resolveType($type);
|
$type = $this->resolveType($type);
|
||||||
}
|
}
|
||||||
@@ -225,4 +245,13 @@ class NameResolver extends NodeVisitorAbstract
|
|||||||
$node->namespacedName = Name::concat(
|
$node->namespacedName = Name::concat(
|
||||||
$this->nameContext->getNamespace(), (string) $node->name);
|
$this->nameContext->getNamespace(), (string) $node->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function resolveAttrGroups(Node $node)
|
||||||
|
{
|
||||||
|
foreach ($node->attrGroups as $attrGroup) {
|
||||||
|
foreach ($attrGroup->attrs as $attr) {
|
||||||
|
$attr->name = $this->resolveClassName($attr->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
52
lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
Normal file
52
lib/PhpParser/NodeVisitor/NodeConnectingVisitor.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\NodeVisitor;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeVisitorAbstract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visitor that connects a child node to its parent node
|
||||||
|
* as well as its sibling nodes.
|
||||||
|
*
|
||||||
|
* On the child node, the parent node can be accessed through
|
||||||
|
* <code>$node->getAttribute('parent')</code>, the previous
|
||||||
|
* node can be accessed through <code>$node->getAttribute('previous')</code>,
|
||||||
|
* and the next node can be accessed through <code>$node->getAttribute('next')</code>.
|
||||||
|
*/
|
||||||
|
final class NodeConnectingVisitor extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Node[]
|
||||||
|
*/
|
||||||
|
private $stack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ?Node
|
||||||
|
*/
|
||||||
|
private $previous;
|
||||||
|
|
||||||
|
public function beforeTraverse(array $nodes) {
|
||||||
|
$this->stack = [];
|
||||||
|
$this->previous = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enterNode(Node $node) {
|
||||||
|
if (!empty($this->stack)) {
|
||||||
|
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
|
||||||
|
$node->setAttribute('previous', $this->previous);
|
||||||
|
$this->previous->setAttribute('next', $node);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stack[] = $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function leaveNode(Node $node) {
|
||||||
|
$this->previous = $node;
|
||||||
|
|
||||||
|
array_pop($this->stack);
|
||||||
|
}
|
||||||
|
}
|
41
lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
Normal file
41
lib/PhpParser/NodeVisitor/ParentConnectingVisitor.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\NodeVisitor;
|
||||||
|
|
||||||
|
use function array_pop;
|
||||||
|
use function count;
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeVisitorAbstract;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visitor that connects a child node to its parent node.
|
||||||
|
*
|
||||||
|
* On the child node, the parent node can be accessed through
|
||||||
|
* <code>$node->getAttribute('parent')</code>.
|
||||||
|
*/
|
||||||
|
final class ParentConnectingVisitor extends NodeVisitorAbstract
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Node[]
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user