Compare commits

...

54 Commits

Author SHA1 Message Date
Nikita Popov
4432ba399e Release PHP-Parser 4.10.5 2021-05-03 21:11:20 +02:00
Nikita Popov
37ac4ea9c2 Document that namespaced names containing whitespace are not supported 2021-04-25 22:47:15 +02:00
Nikita Popov
4848a0d734 Check for \r as newline in closing tag
Fixes #761.
2021-04-25 22:42:18 +02:00
Nikita Popov
8eb194ea1f Add never type
This should be recognized as Identifier instead of Name now.
2021-04-25 22:35:28 +02:00
Nikita Popov
e03d63cffb Fix precedence of arrow functions
Arrow functions should have lowest precedence.

Fixes #769.
2021-04-25 22:19:49 +02:00
Nikita Popov
ce91d139b5 Make sure match is one character long 2021-04-25 21:47:07 +02:00
Nikita Popov
33d7c8d3d8 Escape invalid UTF-8 in strings
To be friendlier to tooling that expects PHP files to be UTF-8
encoded, escape any sequences that are not legal under UTF-8.
2021-04-25 21:37:01 +02:00
Nikita Popov
6b409b96bb Use hex escaping for special characters in strings
Apart from \0, using the \xHH notation is more typical.
2021-04-25 21:22:15 +02:00
Tomas Votruba
f68e1a43ff [PHP 8.1] Add support for enums (#758)
RFC: https://wiki.php.net/rfc/enumerations

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
2021-04-25 21:11:36 +02:00
Iskander (Alex) Sharipov
f767b9fd9f ParserAbstract: add missing '*' to the phpdoc
Otherwise, it's somewhat incompatible with the phpdoc definition.
2021-04-25 12:38:52 +02:00
Miguel
2a4bb7ef2c Moved phpy pseudo lang functions to separate file 2021-04-10 13:40:52 +02:00
Maxim L
4abdcde5f1 Fix missing argument in documentation (#767)
Missing argument in the getMessageWithColumnInfo call
2021-03-25 18:37:06 +01:00
WhizSid
d46f261ef9 Adding class constant to builder factory (#765)
* Adding class constant to builder factory

* Fix formatting,doc issues and Tweak returning node from ClassConst

* Fix PHPUnit
2021-03-21 20:58:21 +01:00
Nikita Popov
38aa0920c9 Add test for UTF-8 in string
These should not get escaped.
2021-03-21 17:03:15 +01:00
Nikita Popov
a8223f228a Add emulation for enum keyword 2021-03-20 17:49:44 +01:00
Oskar Stark
8165cf69fa CS fix 2021-02-02 09:57:47 +01:00
Tomas Votruba
46221a0914 Update Use_ builder to return specific type 2021-01-06 14:30:19 +01:00
Tomas Votruba
3b87eb721c make Namespace_ builder return Namespace_
Hi, just a little detail. This should fix invalid return type of docs
2020-12-30 10:13:54 +01:00
Ayesh Karunaratne
e3471d94d3 Update .gitattributes export-ignore rules
- Adds `.github` directory to `.gitattributes` file with an `export-ignore` rule.
 - This also aligns all `export-ignore` rules for more readability.
 - Removes `.travis.yml` file that is no longer used.
2020-12-22 22:58:39 +01:00
Nikita Popov
c6d052fc58 Release PHP-Parser 4.10.4 2020-12-20 11:01:03 +01:00
Nikita Popov
8008d07bef Remove Travis build badge
We no longer use Travis, so this isn't meaningful... And GitHub
already displays the build status, so there's no value in having
this kind of badge at all.
2020-12-20 10:58:16 +01:00
Nikita Popov
7284a4d019 Remove no longer necessary class parsing workaround
This was split to work around the attribute assignment bug fixed
in the previous commit, and as such is no longer necessary.
2020-12-20 10:53:45 +01:00
Nikita Popov
d3d1ee470a Fix #738 incorrect start line for traits
Empty productions are supposed to be assigned the start attributes
of the lookahead token. Currently, this happens by assigning above
the current stack position when the token it read.

This fails in a situation where we first reduce an empty production
higher up in the stack, and then again reduce an empty production
lower in the stack, without consuming the lookahead token in the
meantime.

Fix this by moving the assignment into the reduction phase. We
also need to do this for error productions, which are effectively
empty.
2020-12-20 10:52:47 +01:00
Nikita Popov
893a5bce3f Fix #741 incorrect varvar positions 2020-12-19 22:03:43 +01:00
Nikita Popov
bec74aceda Remove .travis.yml 2020-12-08 23:22:57 +01:00
Nikita Popov
7c09e096c9 Try to re-enable coveralls 2020-12-08 23:17:41 +01:00
Nikita Popov
0f64504317 Drop -n flag from test_old
In the hope of fixing the failure on GH actions.
2020-12-08 23:05:19 +01:00
Matt Glaman
5e36ef732e Add GitHub Actions workflow (#740) 2020-12-08 23:00:10 +01:00
Nikita Popov
dbe56d23de Release PHP-Parser 4.10.3 2020-12-03 18:45:45 +01:00
Tommy Quissens
c64986fa55 Allow both '{' and T_CURLY_OPEN as curly bracket (#732) 2020-12-03 18:42:00 +01:00
Stefano Arlandini
bc7a9bf9c2 Remove useless code left from old implementation 2020-12-02 12:54:15 +01:00
James Titcumb
2816485126 Updated README to indicate that parsing PHP 8.0 code is supported 2020-12-01 15:15:26 +01:00
Anders Jürisoo
1d1bc8a364 Upgrade some PrettyPrinter methods to protected
I noticed some methods in the Standard PrettyPrinter is private, can they be upgraded to `protected` to ease when extending it?
2020-10-17 22:04:40 +02:00
Nikita Popov
d520bc9e1d Don't allow failures on PHP 8.0 integration tests
These are passing now.
2020-09-30 21:15:05 +02:00
Nikita Popov
51e0b30843 Test against 8.0.0rc1
This should fix the remaining test failures.
2020-09-30 20:51:01 +02:00
Nikita Popov
eff72eeffa Fix #718: PrettyPrinter breaks nested ternaries
Mark ternary as non-associative operator, as left-associative use
is deprecated in PHP 7.4 and removed in PHP 8.0.
2020-09-30 20:42:58 +02:00
Nikita Popov
658f1be311 Release PHP-Parser 4.10.2 2020-09-26 12:30:38 +02:00
Nikita Popov
b9b65a2996 Fix check for token emulation conflicts 2020-09-26 12:29:16 +02:00
Nikita Popov
1b479e7592 Release PHP-Parser 4.10.1 2020-09-23 20:23:49 +02:00
Nikita Popov
b5351f883a Make keyword emulation check case-insensitive 2020-09-23 20:19:40 +02:00
Nikita Popov
88be6127fa FPPP: Fix remove + add at start of list 2020-09-22 22:41:02 +02:00
Nikita Popov
8a97fa157f Recover from missing semicolon after property or class const
Fixes #712.
2020-09-19 23:11:36 +02:00
Nikita Popov
1c13d05035 Release PHP-Parser 4.10.0 2020-09-19 16:52:48 +02:00
Nikita Popov
c7dc3ce552 Add basic FPPP support for attributes 2020-09-19 16:43:45 +02:00
Nikita Popov
9f6ad686a7 Adjust skip list for moved test
The two remaining test_old failures are due to a bug on the PHP
side.
2020-09-19 15:55:07 +02:00
Nikita Popov
1899471f80 Update test for PHP 8 error behavior
Replace division by zero with a different error condition that
still warns, and adjust the expected message based on PHP version.
2020-09-19 15:52:05 +02:00
Nikita Popov
8505acd151 Correctly handle ?-> in encapsed strings
Followup upstream change.
2020-09-19 15:47:14 +02:00
Nikita Popov
c3e20d9970 Use 8.0.0beta4 to tests against 2020-09-19 15:27:27 +02:00
Tomas Votruba
4c22c62783 [PHP 8.0] Add attributes support (#661)
Adds support for PHP 8 attributes, represented using `AttrGroup` nodes
containing `Attribute` nodes. The `attrGroup` subnode is added to all
nodes that can have attributes.

This is still missing FPPP support.

Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
2020-09-13 21:01:17 +02:00
Nikita Popov
f66a32e2df Emulate PHP 8 attribute syntax
Perform emulation by replacing #[ with %[, then patching % back
to # and coalescing #[ into T_ATTRIBUTE if it is a freestanding
token.
2020-09-06 17:42:38 +02:00
Nikita Popov
75abbbd2d4 Handle flexible heredoc via TokenEmulator
Extend the interface to support preprocessing.
2020-09-06 17:05:23 +02:00
Nikita Popov
39b046007d Refactor token emulator registration
Only determine needed emulators based on PHP version once, and
add an adaptor that allows treating forward and reverse emulation
the same.

Previously the isEmulationNeeded() check was too conservative,
as it also considered emulators that are not relevant for the
version. Though possibly that check should just be dropped
altogether.
2020-09-06 15:50:52 +02:00
Nikita Popov
e3872b8906 Improve compatibility with other libs defining compat tokens 2020-09-06 15:00:28 +02:00
Ondřej Mirtes
4a40a84cf6 Fixed PHPDoc typo 2020-09-02 12:33:46 +02:00
121 changed files with 5237 additions and 2200 deletions

9
.editorconfig Normal file
View 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

2
.gitattributes vendored
View File

@@ -1,9 +1,9 @@
/.github export-ignore
/doc export-ignore /doc export-ignore
/test export-ignore /test export-ignore
/test_old export-ignore /test_old 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

86
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,86 @@
# 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"
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"
- 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.0 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.0.0"

View File

@@ -1,43 +0,0 @@
language: php
dist: xenial
before_install: composer self-update --2
install: composer update --no-progress --prefer-dist
matrix:
include:
- name: PHP 7.0 Unit Tests
php: 7.0
install:
- composer require php-coveralls/php-coveralls:^2.2 --dev --no-update
- composer update --no-progress --prefer-dist
script: vendor/bin/phpunit --coverage-clover build/logs/clover.xml
after_success: php vendor/bin/coveralls
- name: PHP 7.1 Unit Tests
php: 7.1
- name: PHP 7.2 Unit Tests
php: 7.2
- name: PHP 7.3 Unit Tests
php: 7.3
- name: PHP 7.4 Unit Tests
php: 7.4
- name: PHP 8.0 Unit Tests
php: nightly
install:
- composer update --ignore-platform-req=php --no-progress --prefer-dist
- name: PHP 7.3 Code on PHP 8.0 Integration Tests
php: nightly
install:
- composer update --ignore-platform-req=php --no-progress --prefer-dist
script:
- test_old/run-php-src.sh 7.3.21
- name: PHP 8.0 Code on PHP 7.0 Integration Tests
php: 7.0
script:
- test_old/run-php-src.sh 8.0.0beta1
allow_failures:
- name: PHP 8.0 Code on PHP 7.0 Integration Tests
fast_finish: true
script: vendor/bin/phpunit

View File

@@ -1,8 +1,85 @@
Version 4.9.2-dev Version 4.10.6-dev
----------------- ------------------
Nothing yet. Nothing yet.
Version 4.10.5 (2020-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) Version 4.9.1 (2020-08-30)
-------------------------- --------------------------

View File

@@ -1,12 +1,12 @@
PHP Parser PHP Parser
========== ==========
[![Build Status](https://travis-ci.org/nikic/PHP-Parser.svg?branch=master)](https://travis-ci.org/nikic/PHP-Parser) [![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master) [![Coverage Status](https://coveralls.io/repos/github/nikic/PHP-Parser/badge.svg?branch=master)](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
This is a PHP 5.2 to PHP 7.4 parser written in PHP. Its purpose is to simplify static code analysis and This is a PHP 5.2 to PHP 8.0 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_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 8.0).
[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.

View File

@@ -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.

View File

@@ -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();
} }

View File

@@ -28,7 +28,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 | T_MATCH | T_ENUM
; ;
semi_reserved: semi_reserved:
@@ -82,6 +82,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; }
@@ -317,18 +342,32 @@ block_or_error:
function_declaration_statement: function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error T_FUNCTION optional_ref identifier '(' 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 '(' 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 '}' optional_attributes class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; { $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkClass($$, #2); } $this->checkClass($$, #3); }
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}' | optional_attributes T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; { $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]];
$this->checkInterface($$, #2); } $this->checkInterface($$, #3); }
| T_TRAIT identifier '{' class_statement_list '}' | optional_attributes T_TRAIT identifier '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; } { $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
| optional_attributes T_ENUM identifier 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:
@@ -489,14 +528,14 @@ optional_visibility_modifier:
; ;
parameter: parameter:
optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
{ $$ = new Node\Param($5, null, $2, $3, $4, attributes(), $1); { $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); } $this->checkParam($$); }
| optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr | optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
{ $$ = new Node\Param($5, $7, $2, $3, $4, attributes(), $1); { $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); } $this->checkParam($$); }
| optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error | optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $2, $3, $4, attributes(), $1); } { $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
; ;
type_expr: type_expr:
@@ -571,7 +610,7 @@ non_empty_global_var_list:
; ;
global_var: global_var:
simple_variable { $$ = Expr\Variable[$1]; } simple_variable { $$ = $1; }
; ;
static_var_list: static_var_list:
@@ -600,15 +639,18 @@ class_statement_list:
; ;
class_statement: class_statement:
variable_modifiers optional_type_without_static 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_ex '(' parameter_list ')' optional_return_type method_body
$this->checkClassMethod($$, #1); } { $$ = 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 enum_case_expr semi
{ $$ = Stmt\EnumCase[$3, $4, $1]; }
| error { $$ = null; /* will be skipped */ } | error { $$ = null; /* will be skipped */ }
; ;
@@ -801,22 +843,28 @@ expr:
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; } | T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
| T_THROW expr { $$ = Expr\Throw_[$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); }
; ;
@@ -970,7 +1018,7 @@ 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; }
@@ -995,15 +1043,15 @@ variable:
; ;
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:
@@ -1012,7 +1060,7 @@ 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]; }
@@ -1026,13 +1074,13 @@ new_variable:
member_name: member_name:
identifier_ex { $$ = $1; } identifier_ex { $$ = $1; }
| '{' expr '}' { $$ = $2; } | '{' expr '}' { $$ = $2; }
| simple_variable { $$ = Expr\Variable[$1]; } | simple_variable { $$ = $1; }
; ;
property_name: property_name:
identifier { $$ = $1; } identifier { $$ = $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; }
; ;

192
grammar/phpyLang.php Normal file
View File

@@ -0,0 +1,192 @@
<?php
///////////////////////////////
/// Utility regex constants ///
///////////////////////////////
const LIB = '(?(DEFINE)
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
)';
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?&params)\][^[\]]*+)*+)\]';
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 ('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);
}
//////////////////////////////
/// 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;
}

View File

@@ -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',
@@ -23,21 +25,6 @@ $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>[^[\]]*+(?:\[(?&params)\][^[\]]*+)*+)\]';
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;
}

View File

@@ -83,6 +83,7 @@
%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
@@ -110,3 +111,5 @@
%token T_NAME_FULLY_QUALIFIED %token T_NAME_FULLY_QUALIFIED
%token T_NAME_QUALIFIED %token T_NAME_QUALIFIED
%token T_NAME_RELATIVE %token T_NAME_RELATIVE
%token T_ATTRIBUTE
%token T_ENUM

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node\Const_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
class ClassConst implements PhpParser\Builder
{
protected $flags = 0;
protected $attributes = [];
protected $constants = [];
/**
* 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;
}
/**
* 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;
}
/**
* 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
);
}
}

View File

@@ -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);

View File

@@ -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_([

View File

@@ -161,6 +161,18 @@ 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 node a for a literal value. * Creates node a for a literal value.
* *

View File

@@ -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'];
} }
} }

View File

@@ -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->haveTokenImmediatelyBefore($startPos, '{') return ($this->haveTokenImmediatelyBefore($startPos, '{')
|| $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN))
&& $this->haveTokenImmediatelyAfter($endPos, '}'); && $this->haveTokenImmediatelyAfter($endPos, '}');
} }
@@ -201,6 +202,7 @@ class TokenStream
public function haveBracesInRange(int $startPos, int $endPos) { public function haveBracesInRange(int $startPos, int $endPos) {
return $this->haveTokenInRange($startPos, $endPos, '{') return $this->haveTokenInRange($startPos, $endPos, '{')
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|| $this->haveTokenInRange($startPos, $endPos, '}'); || $this->haveTokenInRange($startPos, $endPos, '}');
} }

View File

@@ -85,7 +85,6 @@ 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->postprocessTokens($errorHandler); $this->postprocessTokens($errorHandler);
@@ -323,7 +322,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;
} }
@@ -405,35 +405,62 @@ class Lexer
} }
private function defineCompatibilityTokens() { private function defineCompatibilityTokens() {
// PHP 7.4 static $compatTokensDefined = false;
if (!defined('T_BAD_CHARACTER')) { if ($compatTokensDefined) {
\define('T_BAD_CHARACTER', -1); return;
}
if (!defined('T_FN')) {
\define('T_FN', -2);
}
if (!defined('T_COALESCE_EQUAL')) {
\define('T_COALESCE_EQUAL', -3);
} }
$compatTokens = [
// PHP 7.4
'T_BAD_CHARACTER',
'T_FN',
'T_COALESCE_EQUAL',
// PHP 8.0 // PHP 8.0
if (!defined('T_NAME_QUALIFIED')) { 'T_NAME_QUALIFIED',
\define('T_NAME_QUALIFIED', -4); 'T_NAME_FULLY_QUALIFIED',
'T_NAME_RELATIVE',
'T_MATCH',
'T_NULLSAFE_OBJECT_OPERATOR',
'T_ATTRIBUTE',
// PHP 8.1
'T_ENUM',
];
// 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
));
} }
if (!defined('T_NAME_FULLY_QUALIFIED')) { $usedTokenIds[$tokenId] = $token;
\define('T_NAME_FULLY_QUALIFIED', -5);
} }
if (!defined('T_NAME_RELATIVE')) {
\define('T_NAME_RELATIVE', -6);
} }
if (!defined('T_MATCH')) {
\define('T_MATCH', -7); // 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--;
} }
if (!defined('T_NULLSAFE_OBJECT_OPERATOR')) { \define($token, $newTokenId);
\define('T_NULLSAFE_OBJECT_OPERATOR', -8); $newTokenId--;
} }
} }
$compatTokensDefined = true;
}
/** /**
* Creates the token map. * Creates the token map.
* *
@@ -486,6 +513,8 @@ class Lexer
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE; $tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = Tokens::T_MATCH; $tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR; $tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
$tokenMap[\T_ENUM] = Tokens::T_ENUM;
return $tokenMap; return $tokenMap;
} }

View File

@@ -5,31 +5,29 @@ 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\FlexibleDocStringEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator; use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator; use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator; use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator; use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface; use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Parser\Tokens; use PhpParser\Lexer\TokenEmulator\TokenEmulator;
class Emulative extends Lexer class Emulative extends Lexer
{ {
const PHP_7_3 = '7.3dev'; const PHP_7_3 = '7.3dev';
const PHP_7_4 = '7.4dev'; const PHP_7_4 = '7.4dev';
const PHP_8_0 = '8.0dev'; const PHP_8_0 = '8.0dev';
const PHP_8_1 = '8.1dev';
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 */ /** @var string */
private $targetPhpVersion; private $targetPhpVersion;
@@ -41,32 +39,53 @@ REGEX;
*/ */
public function __construct(array $options = []) public function __construct(array $options = [])
{ {
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_0; $this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
unset($options['phpVersion']); unset($options['phpVersion']);
parent::__construct($options); parent::__construct($options);
$this->tokenEmulators[] = new FnTokenEmulator(); $emulators = [
$this->tokenEmulators[] = new MatchTokenEmulator(); new FlexibleDocStringEmulator(),
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator(); new FnTokenEmulator(),
$this->tokenEmulators[] = new NumericLiteralSeparatorEmulator(); new MatchTokenEmulator(),
$this->tokenEmulators[] = new NullsafeTokenEmulator(); new CoaleseEqualTokenEmulator(),
new NumericLiteralSeparatorEmulator(),
new NullsafeTokenEmulator(),
new AttributeEmulator(),
new EnumTokenEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running
// 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();
@@ -77,84 +96,28 @@ REGEX;
} }
} }
foreach ($this->tokenEmulators as $tokenEmulator) { foreach ($emulators as $emulator) {
$emulatorPhpVersion = $tokenEmulator->getPhpVersion(); $this->tokens = $emulator->emulate($code, $this->tokens);
if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=')
&& $tokenEmulator->isEmulationNeeded($code)) {
$this->tokens = $tokenEmulator->emulate($code, $this->tokens);
} else if (version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<')
&& $tokenEmulator->isEmulationNeeded($code)) {
$this->tokens = $tokenEmulator->reverseEmulate($code, $this->tokens);
}
} }
} }
private function isHeredocNowdocEmulationNeeded(string $code): bool 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()
@@ -173,7 +136,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;
} }
@@ -201,6 +177,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);
} }
@@ -247,7 +228,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");
} }

View 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;
}
}

View File

@@ -4,7 +4,7 @@ namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative; use PhpParser\Lexer\Emulative;
final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface final class CoaleseEqualTokenEmulator extends TokenEmulator
{ {
public function getPhpVersion(): string public function getPhpVersion(): string
{ {

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -2,26 +2,28 @@
namespace PhpParser\Lexer\TokenEmulator; namespace PhpParser\Lexer\TokenEmulator;
abstract class KeywordEmulator implements TokenEmulatorInterface abstract class KeywordEmulator extends TokenEmulator
{ {
abstract function getKeywordString(): string; abstract function getKeywordString(): string;
abstract function getKeywordToken(): int; abstract function getKeywordToken(): int;
public function isEmulationNeeded(string $code): bool public function isEmulationNeeded(string $code): bool
{ {
return strpos($code, $this->getKeywordString()) !== false; 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 public function emulate(string $code, array $tokens): array
{ {
$keywordString = $this->getKeywordString(); $keywordString = $this->getKeywordString();
foreach ($tokens as $i => $token) { foreach ($tokens as $i => $token) {
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString) { if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i); && $this->isKeywordContext($tokens, $i)) {
if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === \T_OBJECT_OPERATOR) {
continue;
}
$tokens[$i][0] = $this->getKeywordToken(); $tokens[$i][0] = $this->getKeywordToken();
} }
} }

View File

@@ -4,7 +4,7 @@ namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative; use PhpParser\Lexer\Emulative;
final class NullsafeTokenEmulator implements TokenEmulatorInterface final class NullsafeTokenEmulator extends TokenEmulator
{ {
public function getPhpVersion(): string public function getPhpVersion(): string
{ {
@@ -22,15 +22,35 @@ final class NullsafeTokenEmulator implements TokenEmulatorInterface
// the tokens array on the way // the tokens array on the way
$line = 1; $line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) { for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if (isset($tokens[$i + 1])) { if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
if ($tokens[$i] === '?' && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
array_splice($tokens, $i, 2, [ array_splice($tokens, $i, 2, [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line] [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
]); ]);
$c--; $c--;
continue; 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])) { if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n"); $line += substr_count($tokens[$i][1], "\n");
} }

View File

@@ -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]+)*)';

View 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;
}
}

View 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;
}
}

View File

@@ -1,21 +0,0 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/** @internal */
interface TokenEmulatorInterface
{
public function getPhpVersion(): string;
public function isEmulationNeeded(string $code): bool;
/**
* @return array Modified Tokens
*/
public function emulate(string $code, array $tokens): array;
/**
* @return array Modified Tokens
*/
public function reverseEmulate(string $code, array $tokens): array;
}

View 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';
}
}

View 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';
}
}

View File

@@ -5,7 +5,7 @@ namespace PhpParser\Node;
use PhpParser\NodeAbstract; use PhpParser\NodeAbstract;
/** /**
* @property Name $namespacedName Namespaced name (for class constants, if using NameResolver) * @property Name $namespacedName Namespaced name (for global constants, if using NameResolver)
*/ */
class Const_ extends NodeAbstract class Const_ extends NodeAbstract
{ {

View File

@@ -22,6 +22,8 @@ class ArrowFunction extends Expr implements FunctionLike
/** @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 = []) {
@@ -40,10 +43,11 @@ class ArrowFunction 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->expr = $subNodes['expr'] ?? null; $this->expr = $subNodes['expr'] ?? null;
$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_[]
*/ */

View File

@@ -20,6 +20,8 @@ class Closure extends Expr implements FunctionLike
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';
} }

View File

@@ -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|NullableType|UnionType
*/ */
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;
} }

View File

@@ -18,6 +18,8 @@ class Param extends NodeAbstract
public $default; public $default;
/** @var int */ /** @var int */
public $flags; public $flags;
/** @var AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** /**
* Constructs a parameter node. * Constructs a parameter node.
@@ -27,14 +29,16 @@ class Param extends NodeAbstract
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration * @param null|string|Identifier|Name|NullableType|UnionType $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 $flags Optional visibility flags
* @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, bool $byRef = false, bool $variadic = false,
array $attributes = [], array $attributes = [],
int $flags = 0 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;
@@ -43,10 +47,11 @@ class Param extends NodeAbstract
$this->var = $var; $this->var = $var;
$this->default = $default; $this->default = $default;
$this->flags = $flags; $this->flags = $flags;
$this->attrGroups = $attrGroups;
} }
public function getSubNodeNames() : array { public function getSubNodeNames() : array {
return ['flags', 'type', 'byRef', 'variadic', 'var', 'default']; return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
} }
public function getType() : string { public function getType() : string {

View File

@@ -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'];
} }
/** /**

View File

@@ -13,6 +13,8 @@ abstract class ClassLike extends Node\Stmt
public $name; public $name;
/** @var Node\Stmt[] Statements */ /** @var Node\Stmt[] Statements */
public $stmts; public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** /**
* @return TraitUse[] * @return TraitUse[]

View File

@@ -19,6 +19,8 @@ class ClassMethod extends Node\Stmt implements FunctionLike
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,
@@ -48,6 +50,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 +62,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 +85,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.
* *

View File

@@ -32,6 +32,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 +42,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'];
} }
/** /**

View 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';
}
}

View 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';
}
}

View File

@@ -20,6 +20,8 @@ class Function_ extends Node\Stmt implements FunctionLike
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 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;

View File

@@ -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 {

View File

@@ -16,6 +16,8 @@ class Property extends Node\Stmt
public $props; public $props;
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */ /** @var null|Identifier|Name|NullableType|UnionType 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.
@@ -24,16 +26,18 @@ class Property extends Node\Stmt
* @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|NullableType|UnionType $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'];
} }
/** /**

View File

@@ -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 {

View File

@@ -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,32 @@ 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\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);
} 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
@@ -157,6 +165,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);
} }
@@ -225,4 +234,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);
}
}
}
} }

View File

@@ -17,11 +17,11 @@ use PhpParser\Node\Stmt;
*/ */
class Php5 extends \PhpParser\ParserAbstract class Php5 extends \PhpParser\ParserAbstract
{ {
protected $tokenToSymbolMapSize = 391; protected $tokenToSymbolMapSize = 393;
protected $actionTableSize = 1069; protected $actionTableSize = 1069;
protected $gotoTableSize = 580; protected $gotoTableSize = 580;
protected $invalidSymbol = 164; protected $invalidSymbol = 166;
protected $errorSymbol = 1; protected $errorSymbol = 1;
protected $defaultAction = -32766; protected $defaultAction = -32766;
protected $unexpectedTokenRule = 32767; protected $unexpectedTokenRule = 32767;
@@ -193,36 +193,38 @@ class Php5 extends \PhpParser\ParserAbstract
"'`'", "'`'",
"']'", "']'",
"'\"'", "'\"'",
"T_NULLSAFE_OBJECT_OPERATOR" "T_ENUM",
"T_NULLSAFE_OBJECT_OPERATOR",
"T_ATTRIBUTE"
); );
protected $tokenToSymbol = array( protected $tokenToSymbol = array(
0, 164, 164, 164, 164, 164, 164, 164, 164, 164, 0, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 55, 162, 164, 159, 54, 37, 164, 166, 166, 166, 55, 162, 166, 159, 54, 37, 166,
157, 158, 52, 49, 8, 50, 51, 53, 164, 164, 157, 158, 52, 49, 8, 50, 51, 53, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 31, 154, 166, 166, 166, 166, 166, 166, 166, 166, 31, 154,
43, 16, 45, 30, 67, 164, 164, 164, 164, 164, 43, 16, 45, 30, 67, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 69, 164, 161, 36, 164, 160, 164, 164, 164, 166, 69, 166, 161, 36, 166, 160, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 155, 35, 156, 57, 164, 164, 164, 166, 166, 166, 155, 35, 156, 57, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166,
164, 164, 164, 164, 164, 164, 1, 2, 3, 4, 166, 166, 166, 166, 166, 166, 1, 2, 3, 4,
5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 32, 33, 34, 38, 39, 40, 41, 27, 28, 29, 32, 33, 34, 38, 39, 40, 41,
@@ -233,10 +235,10 @@ class Php5 extends \PhpParser\ParserAbstract
94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
124, 125, 126, 127, 128, 129, 130, 131, 163, 132, 124, 125, 126, 127, 128, 163, 129, 130, 131, 164,
133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151,
153 152, 153, 165
); );
protected $action = array( protected $action = array(

File diff suppressed because it is too large Load Diff

View File

@@ -114,30 +114,32 @@ final class Tokens
const T_CLASS = 362; const T_CLASS = 362;
const T_TRAIT = 363; const T_TRAIT = 363;
const T_INTERFACE = 364; const T_INTERFACE = 364;
const T_EXTENDS = 365; const T_ENUM = 365;
const T_IMPLEMENTS = 366; const T_EXTENDS = 366;
const T_OBJECT_OPERATOR = 367; const T_IMPLEMENTS = 367;
const T_NULLSAFE_OBJECT_OPERATOR = 368; const T_OBJECT_OPERATOR = 368;
const T_LIST = 369; const T_NULLSAFE_OBJECT_OPERATOR = 369;
const T_ARRAY = 370; const T_LIST = 370;
const T_CALLABLE = 371; const T_ARRAY = 371;
const T_CLASS_C = 372; const T_CALLABLE = 372;
const T_TRAIT_C = 373; const T_CLASS_C = 373;
const T_METHOD_C = 374; const T_TRAIT_C = 374;
const T_FUNC_C = 375; const T_METHOD_C = 375;
const T_LINE = 376; const T_FUNC_C = 376;
const T_FILE = 377; const T_LINE = 377;
const T_START_HEREDOC = 378; const T_FILE = 378;
const T_END_HEREDOC = 379; const T_START_HEREDOC = 379;
const T_DOLLAR_OPEN_CURLY_BRACES = 380; const T_END_HEREDOC = 380;
const T_CURLY_OPEN = 381; const T_DOLLAR_OPEN_CURLY_BRACES = 381;
const T_PAAMAYIM_NEKUDOTAYIM = 382; const T_CURLY_OPEN = 382;
const T_NAMESPACE = 383; const T_PAAMAYIM_NEKUDOTAYIM = 383;
const T_NS_C = 384; const T_NAMESPACE = 384;
const T_DIR = 385; const T_NS_C = 385;
const T_NS_SEPARATOR = 386; const T_DIR = 386;
const T_ELLIPSIS = 387; const T_NS_SEPARATOR = 387;
const T_NAME_FULLY_QUALIFIED = 388; const T_ELLIPSIS = 388;
const T_NAME_QUALIFIED = 389; const T_NAME_FULLY_QUALIFIED = 389;
const T_NAME_RELATIVE = 390; const T_NAME_QUALIFIED = 390;
const T_NAME_RELATIVE = 391;
const T_ATTRIBUTE = 392;
} }

View File

@@ -16,6 +16,7 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst; use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\Interface_; use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_; use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property; use PhpParser\Node\Stmt\Property;
@@ -60,7 +61,7 @@ abstract class ParserAbstract implements Parser
/** @var int[] Map of states to a displacement into the $action table. The corresponding action for this /** @var int[] Map of states to a displacement into the $action table. The corresponding action for this
* state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the * state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
action is defaulted, i.e. $actionDefault[$state] should be used instead. */ * action is defaulted, i.e. $actionDefault[$state] should be used instead. */
protected $actionBase; protected $actionBase;
/** @var int[] Table of actions. Indexed according to $actionBase comment. */ /** @var int[] Table of actions. Indexed according to $actionBase comment. */
protected $action; protected $action;
@@ -219,10 +220,7 @@ abstract class ParserAbstract implements Parser
)); ));
} }
// This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get // Allow productions to access the start attributes of the lookahead token.
// the attributes of the next token, even though they don't contain it themselves.
$this->startAttributeStack[$stackPos+1] = $startAttributes;
$this->endAttributeStack[$stackPos+1] = $endAttributes;
$this->lookaheadStartAttributes = $startAttributes; $this->lookaheadStartAttributes = $startAttributes;
//$this->traceRead($symbol); //$this->traceRead($symbol);
@@ -294,7 +292,8 @@ abstract class ParserAbstract implements Parser
/* Goto - shift nonterminal */ /* Goto - shift nonterminal */
$lastEndAttributes = $this->endAttributeStack[$stackPos]; $lastEndAttributes = $this->endAttributeStack[$stackPos];
$stackPos -= $this->ruleToLength[$rule]; $ruleLength = $this->ruleToLength[$rule];
$stackPos -= $ruleLength;
$nonTerminal = $this->ruleToNonTerminal[$rule]; $nonTerminal = $this->ruleToNonTerminal[$rule];
$idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos]; $idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos];
if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) { if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) {
@@ -307,6 +306,10 @@ abstract class ParserAbstract implements Parser
$stateStack[$stackPos] = $state; $stateStack[$stackPos] = $state;
$this->semStack[$stackPos] = $this->semValue; $this->semStack[$stackPos] = $this->semValue;
$this->endAttributeStack[$stackPos] = $lastEndAttributes; $this->endAttributeStack[$stackPos] = $lastEndAttributes;
if ($ruleLength === 0) {
// Empty productions use the start attributes of the lookahead token.
$this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes;
}
} else { } else {
/* error */ /* error */
switch ($this->errorState) { switch ($this->errorState) {
@@ -340,6 +343,7 @@ abstract class ParserAbstract implements Parser
// We treat the error symbol as being empty, so we reset the end attributes // We treat the error symbol as being empty, so we reset the end attributes
// to the end attributes of the last non-error symbol // to the end attributes of the last non-error symbol
$this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes;
$this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1]; $this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1];
$this->endAttributes = $this->endAttributeStack[$stackPos - 1]; $this->endAttributes = $this->endAttributeStack[$stackPos - 1];
break; break;
@@ -659,6 +663,7 @@ abstract class ParserAbstract implements Parser
'null' => true, 'null' => true,
'false' => true, 'false' => true,
'mixed' => true, 'mixed' => true,
'never' => true,
]; ];
if (!$name->isUnqualified()) { if (!$name->isUnqualified()) {
@@ -909,13 +914,28 @@ abstract class ParserAbstract implements Parser
} }
} }
protected function checkClass(Class_ $node, $namePos) { private function checkClassName($name, $namePos) {
if (null !== $node->name && $node->name->isSpecialClassName()) { if (null !== $name && $name->isSpecialClassName()) {
$this->emitError(new Error( $this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name), sprintf('Cannot use \'%s\' as class name as it is reserved', $name),
$this->getAttributesAt($namePos) $this->getAttributesAt($namePos)
)); ));
} }
}
private function checkImplementedInterfaces(array $interfaces) {
foreach ($interfaces as $interface) {
if ($interface->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
}
protected function checkClass(Class_ $node, $namePos) {
$this->checkClassName($node->name, $namePos);
if ($node->extends && $node->extends->isSpecialClassName()) { if ($node->extends && $node->extends->isSpecialClassName()) {
$this->emitError(new Error( $this->emitError(new Error(
@@ -924,32 +944,17 @@ abstract class ParserAbstract implements Parser
)); ));
} }
foreach ($node->implements as $interface) { $this->checkImplementedInterfaces($node->implements);
if ($interface->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
} }
protected function checkInterface(Interface_ $node, $namePos) { protected function checkInterface(Interface_ $node, $namePos) {
if (null !== $node->name && $node->name->isSpecialClassName()) { $this->checkClassName($node->name, $namePos);
$this->emitError(new Error( $this->checkImplementedInterfaces($node->extends);
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
$this->getAttributesAt($namePos)
));
} }
foreach ($node->extends as $interface) { protected function checkEnum(Enum_ $node, $namePos) {
if ($interface->isSpecialClassName()) { $this->checkClassName($node->name, $namePos);
$this->emitError(new Error( $this->checkImplementedInterfaces($node->implements);
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
} }
protected function checkClassMethod(ClassMethod $node, $modifierPos) { protected function checkClassMethod(ClassMethod $node, $modifierPos) {

View File

@@ -18,7 +18,8 @@ class Standard extends PrettyPrinterAbstract
// Special nodes // Special nodes
protected function pParam(Node\Param $node) { protected function pParam(Node\Param $node) {
return ($this->pModifiers($node->flags)) return $this->pAttrGroups($node->attrGroups, true)
. $this->pModifiers($node->flags)
. ($node->type ? $this->p($node->type) . ' ' : '') . ($node->type ? $this->p($node->type) . ' ' : '')
. ($node->byRef ? '&' : '') . ($node->byRef ? '&' : '')
. ($node->variadic ? '...' : '') . ($node->variadic ? '...' : '')
@@ -52,6 +53,15 @@ class Standard extends PrettyPrinterAbstract
return '$' . $node->name; return '$' . $node->name;
} }
protected function pAttribute(Node\Attribute $node) {
return $this->p($node->name)
. ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : '');
}
protected function pAttributeGroup(Node\AttributeGroup $node) {
return '#[' . $this->pCommaSeparated($node->attrs) . ']';
}
// Names // Names
protected function pName(Name $node) { protected function pName(Name $node) {
@@ -600,7 +610,8 @@ class Standard extends PrettyPrinterAbstract
} }
protected function pExpr_Closure(Expr\Closure $node) { protected function pExpr_Closure(Expr\Closure $node) {
return ($node->static ? 'static ' : '') return $this->pAttrGroups($node->attrGroups, true)
. ($node->static ? 'static ' : '')
. 'function ' . ($node->byRef ? '&' : '') . 'function ' . ($node->byRef ? '&' : '')
. '(' . $this->pCommaSeparated($node->params) . ')' . '(' . $this->pCommaSeparated($node->params) . ')'
. (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')' : '') . (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')' : '')
@@ -621,7 +632,8 @@ class Standard extends PrettyPrinterAbstract
} }
protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) { protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) {
return ($node->static ? 'static ' : '') return $this->pAttrGroups($node->attrGroups, true)
. ($node->static ? 'static ' : '')
. 'fn' . ($node->byRef ? '&' : '') . 'fn' . ($node->byRef ? '&' : '')
. '(' . $this->pCommaSeparated($node->params) . ')' . '(' . $this->pCommaSeparated($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '') . (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
@@ -709,20 +721,36 @@ class Standard extends PrettyPrinterAbstract
} }
protected function pStmt_Interface(Stmt\Interface_ $node) { protected function pStmt_Interface(Stmt\Interface_ $node) {
return 'interface ' . $node->name return $this->pAttrGroups($node->attrGroups)
. 'interface ' . $node->name
. (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '') . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
} }
protected function pStmt_Enum(Stmt\Enum_ $node) {
return $this->pAttrGroups($node->attrGroups)
. 'enum ' . $node->name
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Class(Stmt\Class_ $node) { protected function pStmt_Class(Stmt\Class_ $node) {
return $this->pClassCommon($node, ' ' . $node->name); return $this->pClassCommon($node, ' ' . $node->name);
} }
protected function pStmt_Trait(Stmt\Trait_ $node) { protected function pStmt_Trait(Stmt\Trait_ $node) {
return 'trait ' . $node->name return $this->pAttrGroups($node->attrGroups)
. 'trait ' . $node->name
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
} }
protected function pStmt_EnumCase(Stmt\EnumCase $node) {
return $this->pAttrGroups($node->attrGroups)
. 'case ' . $node->name
. ($node->expr ? ' = ' . $this->p($node->expr) : '')
. ';';
}
protected function pStmt_TraitUse(Stmt\TraitUse $node) { protected function pStmt_TraitUse(Stmt\TraitUse $node) {
return 'use ' . $this->pCommaSeparated($node->traits) return 'use ' . $this->pCommaSeparated($node->traits)
. (empty($node->adaptations) . (empty($node->adaptations)
@@ -744,7 +772,8 @@ class Standard extends PrettyPrinterAbstract
} }
protected function pStmt_Property(Stmt\Property $node) { protected function pStmt_Property(Stmt\Property $node) {
return (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags)) return $this->pAttrGroups($node->attrGroups)
. (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
. ($node->type ? $this->p($node->type) . ' ' : '') . ($node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->props) . ';'; . $this->pCommaSeparated($node->props) . ';';
} }
@@ -755,7 +784,8 @@ class Standard extends PrettyPrinterAbstract
} }
protected function pStmt_ClassMethod(Stmt\ClassMethod $node) { protected function pStmt_ClassMethod(Stmt\ClassMethod $node) {
return $this->pModifiers($node->flags) return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'function ' . ($node->byRef ? '&' : '') . $node->name . 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pMaybeMultiline($node->params) . ')' . '(' . $this->pMaybeMultiline($node->params) . ')'
. (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '')
@@ -765,12 +795,14 @@ class Standard extends PrettyPrinterAbstract
} }
protected function pStmt_ClassConst(Stmt\ClassConst $node) { protected function pStmt_ClassConst(Stmt\ClassConst $node) {
return $this->pModifiers($node->flags) return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'const ' . $this->pCommaSeparated($node->consts) . ';'; . 'const ' . $this->pCommaSeparated($node->consts) . ';';
} }
protected function pStmt_Function(Stmt\Function_ $node) { protected function pStmt_Function(Stmt\Function_ $node) {
return 'function ' . ($node->byRef ? '&' : '') . $node->name return $this->pAttrGroups($node->attrGroups)
. 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pCommaSeparated($node->params) . ')' . '(' . $this->pCommaSeparated($node->params) . ')'
. (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '') . (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'; . $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
@@ -925,7 +957,8 @@ class Standard extends PrettyPrinterAbstract
// Helpers // Helpers
protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) { protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) {
return $this->pModifiers($node->flags) return $this->pAttrGroups($node->attrGroups, $node->name === null)
. $this->pModifiers($node->flags)
. 'class' . $afterClassToken . 'class' . $afterClassToken
. (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '') . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '') . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
@@ -965,14 +998,27 @@ class Standard extends PrettyPrinterAbstract
$escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\"); $escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\");
} }
// Escape other control characters // Escape control characters and non-UTF-8 characters.
return preg_replace_callback('/([\0-\10\16-\37])(?=([0-7]?))/', function ($matches) { // Regex based on https://stackoverflow.com/a/11709412/385378.
$oct = decoct(ord($matches[1])); $regex = '/(
if ($matches[2] !== '') { [\x00-\x08\x0E-\x1F] # Control characters
// If there is a trailing digit, use the full three character form | [\xC0-\xC1] # Invalid UTF-8 Bytes
return '\\' . str_pad($oct, 3, '0', \STR_PAD_LEFT); | [\xF5-\xFF] # Invalid UTF-8 Bytes
} | \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point
return '\\' . $oct; | \xF0(?=[\x80-\x8F]) # Overlong encoding of prior code point
| [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
| [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
| [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
| (?<=[\x00-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
| (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
| (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
| (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
| (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
)/x';
return preg_replace_callback($regex, function ($matches) {
assert(strlen($matches[0]) === 1);
$hex = dechex(ord($matches[0]));;
return '\\x' . str_pad($hex, 2, '0', \STR_PAD_LEFT);
}, $escaped); }, $escaped);
} }
@@ -1021,7 +1067,7 @@ class Standard extends PrettyPrinterAbstract
* @param Node[] $nodes * @param Node[] $nodes
* @return bool * @return bool
*/ */
private function hasNodeWithComments(array $nodes) { protected function hasNodeWithComments(array $nodes) {
foreach ($nodes as $node) { foreach ($nodes as $node) {
if ($node && $node->getComments()) { if ($node && $node->getComments()) {
return true; return true;
@@ -1030,11 +1076,21 @@ class Standard extends PrettyPrinterAbstract
return false; return false;
} }
private function pMaybeMultiline(array $nodes, $trailingComma = false) { protected function pMaybeMultiline(array $nodes, bool $trailingComma = false) {
if (!$this->hasNodeWithComments($nodes)) { if (!$this->hasNodeWithComments($nodes)) {
return $this->pCommaSeparated($nodes); return $this->pCommaSeparated($nodes);
} else { } else {
return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl; return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl;
} }
} }
protected function pAttrGroups(array $nodes, bool $inline = false): string {
$result = '';
$sep = $inline ? ' ' : $this->nl;
foreach ($nodes as $node) {
$result .= $this->p($node) . $sep;
}
return $result;
}
} }

View File

@@ -66,7 +66,7 @@ abstract class PrettyPrinterAbstract
BinaryOp\BooleanAnd::class => [120, -1], BinaryOp\BooleanAnd::class => [120, -1],
BinaryOp\BooleanOr::class => [130, -1], BinaryOp\BooleanOr::class => [130, -1],
BinaryOp\Coalesce::class => [140, 1], BinaryOp\Coalesce::class => [140, 1],
Expr\Ternary::class => [150, -1], Expr\Ternary::class => [150, 0],
// parser uses %left for assignments, but they really behave as %right // parser uses %left for assignments, but they really behave as %right
Expr\Assign::class => [160, 1], Expr\Assign::class => [160, 1],
Expr\AssignRef::class => [160, 1], Expr\AssignRef::class => [160, 1],
@@ -756,7 +756,7 @@ abstract class PrettyPrinterAbstract
$itemStartPos = $origArrItem->getStartTokenPos(); $itemStartPos = $origArrItem->getStartTokenPos();
$itemEndPos = $origArrItem->getEndTokenPos(); $itemEndPos = $origArrItem->getEndTokenPos();
\assert($itemStartPos >= 0 && $itemEndPos >= 0); \assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
$origIndentLevel = $this->indentLevel; $origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment; $lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
@@ -767,16 +767,25 @@ abstract class PrettyPrinterAbstract
$commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos; $commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos;
\assert($commentStartPos >= 0); \assert($commentStartPos >= 0);
$commentsChanged = $comments !== $origComments; if ($commentStartPos < $pos) {
if ($commentsChanged) { // Comments may be assigned to multiple nodes if they start at the same position.
// Remove old comments // Make sure we don't try to print them multiple times.
$itemStartPos = $commentStartPos; $commentStartPos = $itemStartPos;
}
if ($skipRemovedNode) {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
return null;
}
} else {
$result .= $this->origTokens->getTokenCode(
$pos, $commentStartPos, $indentAdjustment);
} }
if (!empty($delayedAdd)) { if (!empty($delayedAdd)) {
$result .= $this->origTokens->getTokenCode(
$pos, $commentStartPos, $indentAdjustment);
/** @var Node $delayedAddNode */ /** @var Node $delayedAddNode */
foreach ($delayedAdd as $delayedAddNode) { foreach ($delayedAdd as $delayedAddNode) {
if ($insertNewline) { if ($insertNewline) {
@@ -795,25 +804,16 @@ abstract class PrettyPrinterAbstract
} }
} }
$delayedAdd = [];
}
if ($comments !== $origComments) {
if ($comments) {
$result .= $this->pComments($comments) . $this->nl;
}
} else {
$result .= $this->origTokens->getTokenCode( $result .= $this->origTokens->getTokenCode(
$commentStartPos, $itemStartPos, $indentAdjustment); $commentStartPos, $itemStartPos, $indentAdjustment);
$delayedAdd = [];
} else if (!$skipRemovedNode) {
$result .= $this->origTokens->getTokenCode(
$pos, $itemStartPos, $indentAdjustment);
} else {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
return null;
}
}
if ($commentsChanged && $comments) {
// Add new comments
$result .= $this->pComments($comments) . $this->nl;
} }
// If we had to remove anything, we have done so now. // If we had to remove anything, we have done so now.
@@ -1242,7 +1242,7 @@ abstract class PrettyPrinterAbstract
/** /**
* Lazily initializes the removal map. * Lazily initializes the removal map.
* *
* The removal map is used to determine which additional tokens should be returned when a * The removal map is used to determine which additional tokens should be removed when a
* certain node is replaced by null. * certain node is replaced by null.
*/ */
protected function initializeRemovalMap() { protected function initializeRemovalMap() {
@@ -1269,6 +1269,8 @@ abstract class PrettyPrinterAbstract
'Stmt_Catch->var' => $stripLeft, 'Stmt_Catch->var' => $stripLeft,
'Stmt_ClassMethod->returnType' => $stripColon, 'Stmt_ClassMethod->returnType' => $stripColon,
'Stmt_Class->extends' => ['left' => \T_EXTENDS], 'Stmt_Class->extends' => ['left' => \T_EXTENDS],
'Stmt_Enum->scalarType' => $stripColon,
'Stmt_EnumCase->expr' => $stripEquals,
'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS], 'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS],
'Stmt_Continue->num' => $stripBoth, 'Stmt_Continue->num' => $stripBoth,
'Stmt_Foreach->keyVar' => $stripDoubleArrow, 'Stmt_Foreach->keyVar' => $stripDoubleArrow,
@@ -1307,6 +1309,8 @@ abstract class PrettyPrinterAbstract
'Stmt_Catch->var' => [null, false, ' ', null], 'Stmt_Catch->var' => [null, false, ' ', null],
'Stmt_ClassMethod->returnType' => [')', false, ' : ', null], 'Stmt_ClassMethod->returnType' => [')', false, ' : ', null],
'Stmt_Class->extends' => [null, false, ' extends ', null], 'Stmt_Class->extends' => [null, false, ' extends ', null],
'Stmt_Enum->scalarType' => [null, false, ' : ', null],
'Stmt_EnumCase->expr' => [null, false, ' = ', null],
'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null], 'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null],
'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null], 'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null],
'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '], 'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '],
@@ -1356,6 +1360,7 @@ abstract class PrettyPrinterAbstract
'Stmt_ClassConst->consts' => ', ', 'Stmt_ClassConst->consts' => ', ',
'Stmt_ClassMethod->params' => ', ', 'Stmt_ClassMethod->params' => ', ',
'Stmt_Class->implements' => ', ', 'Stmt_Class->implements' => ', ',
'Stmt_Enum->implements' => ', ',
'Expr_PrintableNewAnonClass->implements' => ', ', 'Expr_PrintableNewAnonClass->implements' => ', ',
'Stmt_Const->consts' => ', ', 'Stmt_Const->consts' => ', ',
'Stmt_Declare->declares' => ', ', 'Stmt_Declare->declares' => ', ',
@@ -1375,12 +1380,14 @@ abstract class PrettyPrinterAbstract
'Stmt_Unset->vars' => ', ', 'Stmt_Unset->vars' => ', ',
'Stmt_Use->uses' => ', ', 'Stmt_Use->uses' => ', ',
'MatchArm->conds' => ', ', 'MatchArm->conds' => ', ',
'AttributeGroup->attrs' => ', ',
// statement lists // statement lists
'Expr_Closure->stmts' => "\n", 'Expr_Closure->stmts' => "\n",
'Stmt_Case->stmts' => "\n", 'Stmt_Case->stmts' => "\n",
'Stmt_Catch->stmts' => "\n", 'Stmt_Catch->stmts' => "\n",
'Stmt_Class->stmts' => "\n", 'Stmt_Class->stmts' => "\n",
'Stmt_Enum->stmts' => "\n",
'Expr_PrintableNewAnonClass->stmts' => "\n", 'Expr_PrintableNewAnonClass->stmts' => "\n",
'Stmt_Interface->stmts' => "\n", 'Stmt_Interface->stmts' => "\n",
'Stmt_Trait->stmts' => "\n", 'Stmt_Trait->stmts' => "\n",
@@ -1395,6 +1402,19 @@ abstract class PrettyPrinterAbstract
'Stmt_Function->stmts' => "\n", 'Stmt_Function->stmts' => "\n",
'Stmt_If->stmts' => "\n", 'Stmt_If->stmts' => "\n",
'Stmt_Namespace->stmts' => "\n", 'Stmt_Namespace->stmts' => "\n",
'Stmt_Class->attrGroups' => "\n",
'Stmt_Enum->attrGroups' => "\n",
'Stmt_EnumCase->attrGroups' => "\n",
'Stmt_Interface->attrGroups' => "\n",
'Stmt_Trait->attrGroups' => "\n",
'Stmt_Function->attrGroups' => "\n",
'Stmt_ClassMethod->attrGroups' => "\n",
'Stmt_ClassConst->attrGroups' => "\n",
'Stmt_Property->attrGroups' => "\n",
'Expr_PrintableNewAnonClass->attrGroups' => ' ',
'Expr_Closure->attrGroups' => ' ',
'Expr_ArrowFunction->attrGroups' => ' ',
'Param->attrGroups' => ' ',
'Stmt_Switch->cases' => "\n", 'Stmt_Switch->cases' => "\n",
'Stmt_TraitUse->adaptations' => "\n", 'Stmt_TraitUse->adaptations' => "\n",
'Stmt_TryCatch->stmts' => "\n", 'Stmt_TryCatch->stmts' => "\n",
@@ -1423,6 +1443,7 @@ abstract class PrettyPrinterAbstract
'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''], 'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''],
'Expr_StaticCall->args' => ['(', '', ''], 'Expr_StaticCall->args' => ['(', '', ''],
'Stmt_Class->implements' => [null, ' implements ', ''], 'Stmt_Class->implements' => [null, ' implements ', ''],
'Stmt_Enum->implements' => [null, ' implements ', ''],
'Stmt_ClassMethod->params' => ['(', '', ''], 'Stmt_ClassMethod->params' => ['(', '', ''],
'Stmt_Interface->extends' => [null, ' extends ', ''], 'Stmt_Interface->extends' => [null, ' extends ', ''],
'Stmt_Function->params' => ['(', '', ''], 'Stmt_Function->params' => ['(', '', ''],

View File

@@ -0,0 +1,166 @@
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\Comment;
use PhpParser\Node\Const_;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt;
class ClassConstTest extends \PHPUnit\Framework\TestCase
{
public function createClassConstBuilder($name, $value) {
return new ClassConst($name, $value);
}
public function testModifiers() {
$node = $this->createClassConstBuilder("TEST", 1)
->makePrivate()
->getNode()
;
$this->assertEquals(
new Stmt\ClassConst(
[
new Const_("TEST", new LNumber(1))
],
Stmt\Class_::MODIFIER_PRIVATE
),
$node
);
$node = $this->createClassConstBuilder("TEST", 1)
->makeProtected()
->getNode()
;
$this->assertEquals(
new Stmt\ClassConst(
[
new Const_("TEST", new LNumber(1) )
],
Stmt\Class_::MODIFIER_PROTECTED
),
$node
);
$node = $this->createClassConstBuilder("TEST", 1)
->makePublic()
->getNode()
;
$this->assertEquals(
new Stmt\ClassConst(
[
new Const_("TEST", new LNumber(1) )
],
Stmt\Class_::MODIFIER_PUBLIC
),
$node
);
}
public function testDocComment() {
$node = $this->createClassConstBuilder('TEST',1)
->setDocComment('/** Test */')
->makePublic()
->getNode();
$this->assertEquals(
new Stmt\ClassConst(
[
new Const_("TEST", new LNumber(1) )
],
Stmt\Class_::MODIFIER_PUBLIC,
[
'comments' => [new Comment\Doc('/** Test */')]
]
),
$node
);
}
public function testAddConst() {
$node = $this->createClassConstBuilder('FIRST_TEST',1)
->addConst("SECOND_TEST",2)
->getNode();
$this->assertEquals(
new Stmt\ClassConst(
[
new Const_("FIRST_TEST", new LNumber(1)),
new Const_("SECOND_TEST", new LNumber(2))
]
),
$node
);
}
/**
* @dataProvider provideTestDefaultValues
*/
public function testValues($value, $expectedValueNode) {
$node = $this->createClassConstBuilder('TEST', $value)
->getNode()
;
$this->assertEquals($expectedValueNode, $node->consts[0]->value);
}
public function provideTestDefaultValues() {
return [
[
null,
new Expr\ConstFetch(new Name('null'))
],
[
true,
new Expr\ConstFetch(new Name('true'))
],
[
false,
new Expr\ConstFetch(new Name('false'))
],
[
31415,
new Scalar\LNumber(31415)
],
[
3.1415,
new Scalar\DNumber(3.1415)
],
[
'Hallo World',
new Scalar\String_('Hallo World')
],
[
[1, 2, 3],
new Expr\Array_([
new Expr\ArrayItem(new Scalar\LNumber(1)),
new Expr\ArrayItem(new Scalar\LNumber(2)),
new Expr\ArrayItem(new Scalar\LNumber(3)),
])
],
[
['foo' => 'bar', 'bar' => 'foo'],
new Expr\Array_([
new Expr\ArrayItem(
new Scalar\String_('bar'),
new Scalar\String_('foo')
),
new Expr\ArrayItem(
new Scalar\String_('foo'),
new Scalar\String_('bar')
),
])
],
[
new Scalar\MagicConst\Dir,
new Scalar\MagicConst\Dir
]
];
}
}

View File

@@ -36,6 +36,11 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
]; ];
} }
public function testFactoryClassConst() {
$factory = new BuilderFactory;
$this->assertInstanceOf(Builder\ClassConst::class, $factory->classConst('TEST',1));
}
public function testVal() { public function testVal() {
// This method is a wrapper around BuilderHelpers::normalizeValue(), // This method is a wrapper around BuilderHelpers::normalizeValue(),
// which is already tested elsewhere // which is already tested elsewhere
@@ -276,7 +281,11 @@ class BuilderFactoryTest extends \PHPUnit\Framework\TestCase
->addStmt($factory->property('someProperty')->makeProtected()) ->addStmt($factory->property('someProperty')->makeProtected())
->addStmt($factory->property('anotherProperty') ->addStmt($factory->property('anotherProperty')
->makePrivate() ->makePrivate()
->setDefault([1, 2, 3]))) ->setDefault([1, 2, 3]))
->addStmt($factory->classConst("FIRST_CLASS_CONST", 1)
->addConst("SECOND_CLASS_CONST",2)
->makePrivate()))
->getNode() ->getNode()
; ;
@@ -297,6 +306,7 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
AnotherTrait::baz as test; AnotherTrait::baz as test;
AnotherTrait::func insteadof SecondTrait; AnotherTrait::func insteadof SecondTrait;
} }
private const FIRST_CLASS_CONST = 1, SECOND_CLASS_CONST = 2;
protected $someProperty; protected $someProperty;
private $anotherProperty = array(1, 2, 3); private $anotherProperty = array(1, 2, 3);
function firstMethod() function firstMethod()

View File

@@ -121,9 +121,11 @@ class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase
'Modulo by zero' 'Modulo by zero'
], ],
[ [
new Expr\BinaryOp\Div(new Scalar\LNumber(42), new Scalar\LNumber(0)), new Expr\BinaryOp\Plus(new Scalar\LNumber(42), new Scalar\String_("1foo")),
\ErrorException::class, \ErrorException::class,
'Division by zero' \PHP_VERSION_ID >= 80000
? 'A non-numeric value encountered'
: 'A non well formed numeric value encountered'
], ],
]; ];
} }

View File

@@ -24,6 +24,17 @@ class EmulativeTest extends LexerTest
$this->assertSame(0, $lexer->getNextToken()); $this->assertSame(0, $lexer->getNextToken());
} }
/**
* @dataProvider provideTestReplaceKeywords
*/
public function testReplaceKeywordsUppercase($keyword, $expectedToken) {
$lexer = $this->getLexer();
$lexer->startLexing('<?php ' . strtoupper($keyword));
$this->assertSame($expectedToken, $lexer->getNextToken());
$this->assertSame(0, $lexer->getNextToken());
}
/** /**
* @dataProvider provideTestReplaceKeywords * @dataProvider provideTestReplaceKeywords
*/ */
@@ -275,6 +286,62 @@ class EmulativeTest extends LexerTest
['?->', [ ['?->', [
[Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'], [Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'],
]], ]],
['#[Attr]', [
[Tokens::T_ATTRIBUTE, '#['],
[Tokens::T_STRING, 'Attr'],
[ord(']'), ']'],
]],
["#[\nAttr\n]", [
[Tokens::T_ATTRIBUTE, '#['],
[Tokens::T_STRING, 'Attr'],
[ord(']'), ']'],
]],
// Test interaction of two patch-based emulators
["<<<LABEL\n LABEL, #[Attr]", [
[Tokens::T_START_HEREDOC, "<<<LABEL\n"],
[Tokens::T_END_HEREDOC, " LABEL"],
[ord(','), ','],
[Tokens::T_ATTRIBUTE, '#['],
[Tokens::T_STRING, 'Attr'],
[ord(']'), ']'],
]],
["#[Attr] <<<LABEL\n LABEL,", [
[Tokens::T_ATTRIBUTE, '#['],
[Tokens::T_STRING, 'Attr'],
[ord(']'), ']'],
[Tokens::T_START_HEREDOC, "<<<LABEL\n"],
[Tokens::T_END_HEREDOC, " LABEL"],
[ord(','), ','],
]],
// Enums use a contextual keyword
['enum Foo {}', [
[Tokens::T_ENUM, 'enum'],
[Tokens::T_STRING, 'Foo'],
[ord('{'), '{'],
[ord('}'), '}'],
]],
['class Enum {}', [
[Tokens::T_CLASS, 'class'],
[Tokens::T_STRING, 'Enum'],
[ord('{'), '{'],
[ord('}'), '}'],
]],
['class Enum extends X {}', [
[Tokens::T_CLASS, 'class'],
[Tokens::T_STRING, 'Enum'],
[Tokens::T_EXTENDS, 'extends'],
[Tokens::T_STRING, 'X'],
[ord('{'), '{'],
[ord('}'), '}'],
]],
['class Enum implements X {}', [
[Tokens::T_CLASS, 'class'],
[Tokens::T_STRING, 'Enum'],
[Tokens::T_IMPLEMENTS, 'implements'],
[Tokens::T_STRING, 'X'],
[ord('{'), '{'],
[ord('}'), '}'],
]],
]; ];
} }
@@ -291,8 +358,27 @@ class EmulativeTest extends LexerTest
return [ return [
['8.0', 'match', [[Tokens::T_MATCH, 'match']]], ['8.0', 'match', [[Tokens::T_MATCH, 'match']]],
['7.4', 'match', [[Tokens::T_STRING, 'match']]], ['7.4', 'match', [[Tokens::T_STRING, 'match']]],
// Keywords are not case-sensitive.
['7.4', 'fn', [[Tokens::T_FN, 'fn']]], ['7.4', 'fn', [[Tokens::T_FN, 'fn']]],
['7.4', 'FN', [[Tokens::T_FN, 'FN']]],
['7.3', 'fn', [[Tokens::T_STRING, 'fn']]], ['7.3', 'fn', [[Tokens::T_STRING, 'fn']]],
['7.3', 'FN', [[Tokens::T_STRING, 'FN']]],
// Tested here to skip testLeaveStuffAloneInStrings.
['8.0', '"$foo?->bar"', [
[ord('"'), '"'],
[Tokens::T_VARIABLE, '$foo'],
[Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'],
[Tokens::T_STRING, 'bar'],
[ord('"'), '"'],
]],
['8.0', '"$foo?->bar baz"', [
[ord('"'), '"'],
[Tokens::T_VARIABLE, '$foo'],
[Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'],
[Tokens::T_STRING, 'bar'],
[Tokens::T_ENCAPSED_AND_WHITESPACE, ' baz'],
[ord('"'), '"'],
]],
]; ];
} }
} }

View File

@@ -248,6 +248,7 @@ PHP;
} }
}, },
"flags": 0, "flags": 0,
"attrGroups": [],
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4 "endLine": 4
@@ -275,6 +276,7 @@ PHP;
} }
}, },
"flags": 0, "flags": 0,
"attrGroups": [],
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"endLine": 4 "endLine": 4
@@ -302,6 +304,7 @@ PHP;
} }
} }
], ],
"attrGroups": [],
"attributes": { "attributes": {
"startLine": 4, "startLine": 4,
"comments": [ "comments": [

View File

@@ -165,13 +165,8 @@ namespace Baz {
} }
EOC; EOC;
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard; $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser = new PhpParser\NodeTraverser; $stmts = $this->parseAndResolve($code);
$traverser->addVisitor(new NameResolver);
$stmts = $parser->parse($code);
$stmts = $traverser->traverse($stmts);
$this->assertSame( $this->assertSame(
$this->canonicalize($expectedCode), $this->canonicalize($expectedCode),
@@ -187,33 +182,43 @@ EOC;
<?php <?php
namespace NS; namespace NS;
#[X]
class A extends B implements C, D { class A extends B implements C, D {
use E, F, G { use E, F, G {
f as private g; f as private g;
E::h as i; E::h as i;
E::j insteadof F, G; E::j insteadof F, G;
} }
#[X]
public float $php = 7.4;
public ?Foo $person;
protected static ?bool $probability;
public A|B|int $prop;
#[X]
const C = 1;
} }
#[X]
interface A extends C, D { interface A extends C, D {
public function a(A $a) : A; public function a(A $a) : A;
public function b(A|B|int $a): A|B|int; public function b(A|B|int $a): A|B|int;
} }
class ClassWithTypeProperties { #[X]
public float $php = 7.4; trait A {}
public ?Foo $person;
protected static ?bool $probability;
public A|B|int $prop;
}
function f(A $a) : A {} #[X]
function f(#[X] A $a) : A {}
function f2(array $a) : array {} function f2(array $a) : array {}
function(A $a) : A {};
function fn3(?A $a) : ?A {} function fn3(?A $a) : ?A {}
function fn4(?array $a) : ?array {} function fn4(?array $a) : ?array {}
#[X]
function(A $a) : A {};
#[X]
fn(array $a): array => $a; fn(array $a): array => $a;
fn(A $a): A => $a; fn(A $a): A => $a;
fn(?A $a): ?A => $a; fn(?A $a): ?A => $a;
@@ -236,6 +241,7 @@ EOC;
$expectedCode = <<<'EOC' $expectedCode = <<<'EOC'
namespace NS; namespace NS;
#[\NS\X]
class A extends \NS\B implements \NS\C, \NS\D class A extends \NS\B implements \NS\C, \NS\D
{ {
use \NS\E, \NS\F, \NS\G { use \NS\E, \NS\F, \NS\G {
@@ -243,34 +249,40 @@ class A extends \NS\B implements \NS\C, \NS\D
\NS\E::h as i; \NS\E::h as i;
\NS\E::j insteadof \NS\F, \NS\G; \NS\E::j insteadof \NS\F, \NS\G;
} }
#[\NS\X]
public float $php = 7.4;
public ?\NS\Foo $person;
protected static ?bool $probability;
public \NS\A|\NS\B|int $prop;
#[\NS\X]
const C = 1;
} }
#[\NS\X]
interface A extends \NS\C, \NS\D interface A extends \NS\C, \NS\D
{ {
public function a(\NS\A $a) : \NS\A; public function a(\NS\A $a) : \NS\A;
public function b(\NS\A|\NS\B|int $a) : \NS\A|\NS\B|int; public function b(\NS\A|\NS\B|int $a) : \NS\A|\NS\B|int;
} }
class ClassWithTypeProperties #[\NS\X]
trait A
{ {
public float $php = 7.4;
public ?\NS\Foo $person;
protected static ?bool $probability;
public \NS\A|\NS\B|int $prop;
} }
function f(\NS\A $a) : \NS\A #[\NS\X]
function f(#[\NS\X] \NS\A $a) : \NS\A
{ {
} }
function f2(array $a) : array function f2(array $a) : array
{ {
} }
function (\NS\A $a) : \NS\A {
};
function fn3(?\NS\A $a) : ?\NS\A function fn3(?\NS\A $a) : ?\NS\A
{ {
} }
function fn4(?array $a) : ?array function fn4(?array $a) : ?array
{ {
} }
fn(array $a): array => $a; #[\NS\X] function (\NS\A $a) : \NS\A {
};
#[\NS\X] fn(array $a): array => $a;
fn(\NS\A $a): \NS\A => $a; fn(\NS\A $a): \NS\A => $a;
fn(?\NS\A $a): ?\NS\A => $a; fn(?\NS\A $a): ?\NS\A => $a;
\NS\A::b(); \NS\A::b();
@@ -287,13 +299,8 @@ try {
} }
EOC; EOC;
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard; $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser = new PhpParser\NodeTraverser; $stmts = $this->parseAndResolve($code);
$traverser->addVisitor(new NameResolver);
$stmts = $parser->parse($code);
$stmts = $traverser->traverse($stmts);
$this->assertSame( $this->assertSame(
$this->canonicalize($expectedCode), $this->canonicalize($expectedCode),
@@ -512,4 +519,14 @@ EOC;
$this->assertEquals( $this->assertEquals(
new Name\FullyQualified('Foo\bar'), $n2->getAttribute('namespacedName')); new Name\FullyQualified('Foo\bar'), $n2->getAttribute('namespacedName'));
} }
private function parseAndResolve(string $code): array
{
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$stmts = $parser->parse($code);
return $traverser->traverse($stmts);
}
} }

View File

@@ -0,0 +1,179 @@
Attributes
-----
<?php
#[A]
class X {
#[A]
public function m(#[A] & $p) {}
#[A]
public
$prop;
#[A]
const
X = 42;
}
#[A]
trait X {}
#[A]
interface X {}
#[A]
function f() {}
new #[A] class {};
#[A] function() {};
#[A] fn()
=> 42;
-----
$attrGroup = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('B'), []),
]);
$stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[0]->params[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[1]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[2]->attrGroups[] = $attrGroup;
$stmts[1]->attrGroups[] = $attrGroup;
$stmts[2]->attrGroups[] = $attrGroup;
$stmts[3]->attrGroups[] = $attrGroup;
$stmts[4]->expr->class->attrGroups[] = $attrGroup;
$stmts[5]->expr->attrGroups[] = $attrGroup;
$stmts[6]->expr->attrGroups[] = $attrGroup;
-----
<?php
#[A]
#[B]
class X {
#[A]
#[B]
public function m(#[A] #[B] & $p) {}
#[A]
#[B]
public
$prop;
#[A]
#[B]
const
X = 42;
}
#[A]
#[B]
trait X {}
#[A]
#[B]
interface X {}
#[A]
#[B]
function f() {}
new #[A] #[B] class {};
#[A] #[B] function() {};
#[A] #[B] fn()
=> 42;
-----
<?php
class X {
public function m() {}
public
$prop;
const
X = 42;
}
trait X {}
interface X {}
function f() {}
new class {};
function() {};
fn()
=> 42;
-----
// TODO: Currently we lose formatting for this case.
$attrGroup = new Node\AttributeGroup([
new Node\Attribute(new Node\Name('A'), []),
]);
$stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[0]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[1]->attrGroups[] = $attrGroup;
$stmts[0]->stmts[2]->attrGroups[] = $attrGroup;
$stmts[1]->attrGroups[] = $attrGroup;
$stmts[2]->attrGroups[] = $attrGroup;
$stmts[3]->attrGroups[] = $attrGroup;
$stmts[4]->expr->class->attrGroups[] = $attrGroup;
$stmts[5]->expr->attrGroups[] = $attrGroup;
$stmts[6]->expr->attrGroups[] = $attrGroup;
-----
<?php
#[A]
class X
{
#[A]
public function m()
{
}
#[A]
public $prop;
#[A]
const X = 42;
}
#[A]
trait X
{
}
#[A]
interface X
{
}
#[A]
function f()
{
}
new #[A] class
{
};
#[A] function () {
};
#[A] fn() => 42;
-----
<?php
#[ A, B]
class X {};
#[
A,
B,
]
class X {};
-----
$attr = new Node\Attribute(new Node\Name('C'), []);
$stmts[0]->attrGroups[0]->attrs[] = $attr;
$stmts[1]->attrGroups[0]->attrs[] = $attr;
-----
<?php
#[ A, B, C]
class X {};
#[
A,
B,
C,
]
class X {};

View File

@@ -0,0 +1,100 @@
Enum formatting preservation
-----
<?php
enum X: int
{}
-----
$stmts[0]->scalarType = null;
-----
<?php
enum X
{}
-----
<?php
enum X {
case
Y = 1;
}
-----
$stmts[0]->stmts[0]->expr = null;
-----
<?php
enum X {
case
Y;
}
-----
<?php
enum X
{}
-----
$stmts[0]->scalarType = new Node\Identifier('int');
-----
<?php
enum X : int
{}
-----
<?php
enum X
implements Y
{}
-----
$stmts[0]->scalarType = new Node\Identifier('int');
-----
<?php
enum X : int
implements Y
{}
-----
<?php
enum X {
case
Y;
}
-----
$stmts[0]->stmts[0]->expr = new Scalar\LNumber(1);
-----
<?php
enum X {
case
Y = 1;
}
-----
<?php
enum X {
case A;
case B;
}
-----
$stmts[0]->stmts[] = new Node\Stmt\EnumCase('C');
-----
<?php
enum X {
case A;
case B;
case C;
}
-----
<?php
enum X
implements Y
{}
-----
$stmts[0]->implements[] = new Node\Name('Z');
-----
<?php
enum X
implements Y, Z
{}
-----
<?php
enum X
{}
-----
$stmts[0]->implements[] = new Node\Name('Y');
-----
<?php
enum X implements Y
{}

View File

@@ -317,3 +317,22 @@ $stmts[0]->returnType->types[] = new Node\Name('C');
<?php <?php
function test(): A function test(): A
|B|C {} |B|C {}
-----
<?php
function test() {
if ($x) {
$a;
$b;
}
$z;
}
-----
$fnStmts =& $stmts[0]->stmts;
array_splice($fnStmts, 0, 1, $fnStmts[0]->stmts);
-----
<?php
function test() {
$a;
$b;
$z;
}

View File

@@ -0,0 +1,9 @@
Rewrite string with variable interpolation
-----
<?php
"{$e}";
-----
$stmts[0]->expr->parts[0]->setAttribute('origNode', null);
-----
<?php
"{$e}";

View File

@@ -8,6 +8,8 @@ class MyClass {
----- -----
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: MyClass name: MyClass
@@ -17,6 +19,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2) flags: MODIFIER_PROTECTED (2)
type: null type: null
props: array( props: array(

View File

@@ -161,6 +161,8 @@ function test() {
Syntax error, unexpected '}' from 4:1 to 4:1 Syntax error, unexpected '}' from 4:1 to 4:1
array( array(
0: Stmt_Function( 0: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: test name: test
@@ -306,6 +308,8 @@ function foo() {
Syntax error, unexpected '}', expecting T_STRING or T_VARIABLE or '{' or '$' from 4:1 to 4:1 Syntax error, unexpected '}', expecting T_STRING or T_VARIABLE or '{' or '$' from 4:1 to 4:1
array( array(
0: Stmt_Function[2:1 - 4:1]( 0: Stmt_Function[2:1 - 4:1](
attrGroups: array(
)
byRef: false byRef: false
name: Identifier[2:10 - 2:12]( name: Identifier[2:10 - 2:12](
name: foo name: foo
@@ -683,6 +687,8 @@ array(
) )
) )
4: Stmt_Class( 4: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: X name: X
@@ -736,6 +742,8 @@ array(
) )
) )
2: Stmt_ClassConst( 2: Stmt_ClassConst(
attrGroups: array(
)
flags: 0 flags: 0
consts: array( consts: array(
0: Const( 0: Const(
@@ -749,6 +757,8 @@ array(
) )
) )
3: Stmt_Property( 3: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: null type: null
props: array( props: array(
@@ -763,6 +773,8 @@ array(
) )
) )
5: Stmt_Interface( 5: Stmt_Interface(
attrGroups: array(
)
name: Identifier( name: Identifier(
name: I name: I
) )
@@ -895,6 +907,8 @@ class Foo {
Syntax error, unexpected T_STRING from 4:5 to 4:9 Syntax error, unexpected T_STRING from 4:5 to 4:9
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: Foo name: Foo
@@ -904,6 +918,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: null type: null
props: array( props: array(
@@ -998,12 +1014,16 @@ Syntax error, unexpected ')', expecting T_VARIABLE from 22:21 to 22:21
Syntax error, unexpected ')', expecting T_VARIABLE from 25:13 to 25:13 Syntax error, unexpected ')', expecting T_VARIABLE from 25:13 to 25:13
array( array(
0: Stmt_Function( 0: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: foo name: foo
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Name( type: Name(
parts: array( parts: array(
@@ -1027,12 +1047,16 @@ array(
) )
) )
1: Stmt_Function( 1: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: foo name: foo
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Name( type: Name(
parts: array( parts: array(
@@ -1047,6 +1071,8 @@ array(
default: null default: null
) )
1: Param( 1: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Name( type: Name(
parts: array( parts: array(
@@ -1070,12 +1096,16 @@ array(
) )
) )
2: Stmt_Function( 2: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: foo name: foo
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -1095,12 +1125,16 @@ array(
) )
) )
3: Stmt_Function( 3: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: foo name: foo
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: true byRef: true
@@ -1120,12 +1154,16 @@ array(
) )
) )
4: Stmt_Function( 4: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: foo name: foo
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Name( type: Name(
parts: array( parts: array(
@@ -1144,6 +1182,8 @@ array(
) )
) )
5: Stmt_Class( 5: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: Bar name: Bar
@@ -1153,6 +1193,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0 flags: 0
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -1160,6 +1202,8 @@ array(
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Name( type: Name(
parts: array( parts: array(
@@ -1181,10 +1225,14 @@ array(
) )
6: Stmt_Expression( 6: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Name( type: Name(
parts: array( parts: array(
@@ -1219,9 +1267,9 @@ $array = [
]; ];
----- -----
!!php7 !!php7
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 3:18 to 3:34 Syntax error, unexpected T_VARIABLE, expecting ',' or ']' or ')' from 3:18 to 3:34
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 6:12 to 6:28 Syntax error, unexpected T_VARIABLE, expecting ',' or ']' or ')' from 6:12 to 6:28
Syntax error, unexpected T_VARIABLE, expecting ',' or ')' or ']' from 9:21 to 9:37 Syntax error, unexpected T_VARIABLE, expecting ',' or ']' or ')' from 9:21 to 9:37
array( array(
0: Stmt_Expression( 0: Stmt_Expression(
expr: Expr_Assign( expr: Expr_Assign(
@@ -1330,6 +1378,8 @@ function foo() :
Syntax error, unexpected '{' from 3:1 to 3:1 Syntax error, unexpected '{' from 3:1 to 3:1
array( array(
0: Stmt_Function( 0: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: foo name: foo
@@ -1351,4 +1401,124 @@ array(
$a = ["a "thing"]; $a = ["a "thing"];
----- -----
!!php7 !!php7
Syntax error, unexpected T_STRING, expecting ',' or ')' or ']' from 2:11 to 2:15 Syntax error, unexpected T_STRING, expecting ',' or ']' or ')' from 2:11 to 2:15
-----
<?php
class A {
/** @var ?string */
private $foo
public function __construct(string $s) {
$this->foo = $s;
}
}
class B {
const X = 1
}
-----
!!php7
Syntax error, unexpected T_PUBLIC, expecting ';' from 6:5 to 6:10
Syntax error, unexpected '}', expecting ';' from 12:1 to 12:1
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: A
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4)
type: null
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: foo
)
default: null
)
)
comments: array(
0: /** @var ?string */
)
)
1: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
name: __construct
)
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: Identifier(
name: string
)
byRef: false
variadic: false
var: Expr_Variable(
name: s
)
default: null
)
)
returnType: null
stmts: array(
0: Stmt_Expression(
expr: Expr_Assign(
var: Expr_PropertyFetch(
var: Expr_Variable(
name: this
)
name: Identifier(
name: foo
)
)
expr: Expr_Variable(
name: s
)
)
)
)
)
)
)
1: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: B
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassConst(
attrGroups: array(
)
flags: 0
consts: array(
0: Const(
name: Identifier(
name: X
)
value: Scalar_LNumber(
value: 1
)
)
)
)
)
)
)

View File

@@ -59,6 +59,8 @@ array(
) )
) )
1: Stmt_Function( 1: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: getArr name: getArr
@@ -92,6 +94,8 @@ array(
) )
) )
2: Stmt_Function( 2: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: arrGen name: arrGen

View File

@@ -7,15 +7,22 @@ static fn(&$x) => $x;
fn&($x) => $x; fn&($x) => $x;
fn($x, ...$rest) => $rest; fn($x, ...$rest) => $rest;
fn(): int => $x; fn(): int => $x;
fn($a, $b) => $a and $b;
fn($a, $b) => $a && $b;
----- -----
!!php7 !!php7
array( array(
0: Stmt_Expression( 0: Stmt_Expression(
expr: Expr_ArrowFunction( expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Identifier( type: Identifier(
name: bool name: bool
@@ -36,10 +43,14 @@ array(
) )
1: Stmt_Expression( 1: Stmt_Expression(
expr: Expr_ArrowFunction( expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -60,10 +71,14 @@ array(
) )
2: Stmt_Expression( 2: Stmt_Expression(
expr: Expr_ArrowFunction( expr: Expr_ArrowFunction(
attrGroups: array(
)
static: true static: true
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: true byRef: true
@@ -82,10 +97,14 @@ array(
) )
3: Stmt_Expression( 3: Stmt_Expression(
expr: Expr_ArrowFunction( expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false static: false
byRef: true byRef: true
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -104,10 +123,14 @@ array(
) )
4: Stmt_Expression( 4: Stmt_Expression(
expr: Expr_ArrowFunction( expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -118,6 +141,8 @@ array(
default: null default: null
) )
1: Param( 1: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -136,6 +161,8 @@ array(
) )
5: Stmt_Expression( 5: Stmt_Expression(
expr: Expr_ArrowFunction( expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
@@ -148,4 +175,90 @@ array(
) )
) )
) )
6: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: a
)
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: b
)
default: null
)
)
returnType: null
expr: Expr_BinaryOp_LogicalAnd(
left: Expr_Variable(
name: a
)
right: Expr_Variable(
name: b
)
)
)
)
7: Stmt_Expression(
expr: Expr_ArrowFunction(
attrGroups: array(
)
static: false
byRef: false
params: array(
0: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: a
)
default: null
)
1: Param(
attrGroups: array(
)
flags: 0
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: b
)
default: null
)
)
returnType: null
expr: Expr_BinaryOp_BooleanAnd(
left: Expr_Variable(
name: a
)
right: Expr_Variable(
name: b
)
)
)
)
) )

View File

@@ -12,10 +12,14 @@ function() use($a) : \Foo\Bar {};
array( array(
0: Stmt_Expression( 0: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -40,10 +44,14 @@ array(
) )
1: Stmt_Expression( 1: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -69,6 +77,8 @@ array(
) )
2: Stmt_Expression( 2: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
@@ -94,10 +104,14 @@ array(
) )
3: Stmt_Expression( 3: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: true byRef: true
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -117,6 +131,8 @@ array(
) )
4: Stmt_Expression( 4: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: true static: true
byRef: false byRef: false
params: array( params: array(
@@ -130,10 +146,14 @@ array(
) )
5: Stmt_Expression( 5: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -155,6 +175,8 @@ array(
) )
6: Stmt_Expression( 6: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(

View File

@@ -7,6 +7,8 @@ function() use($a,) {};
array( array(
0: Stmt_Expression( 0: Stmt_Expression(
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(

View File

@@ -71,11 +71,13 @@ array(
4: Stmt_Expression( 4: Stmt_Expression(
expr: Scalar_Encapsed( expr: Scalar_Encapsed(
parts: array( parts: array(
0: Expr_Variable( 0: Expr_NullsafePropertyFetch(
var: Expr_Variable(
name: a name: a
) )
1: Scalar_EncapsedStringPart( name: Identifier(
value: ?->b name: b
)
) )
) )
) )

View File

@@ -204,10 +204,14 @@ array(
name: Expr_FuncCall( name: Expr_FuncCall(
name: Expr_FuncCall( name: Expr_FuncCall(
name: Expr_Closure( name: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -274,10 +278,14 @@ array(
name: f name: f
) )
expr: Expr_Closure( expr: Expr_Closure(
attrGroups: array(
)
static: false static: false
byRef: false byRef: false
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false

View File

@@ -0,0 +1,17 @@
Variable variable positions
-----
<?php
$$$x;
-----
!!positions
array(
0: Stmt_Expression[2:1 - 2:5](
expr: Expr_Variable[2:1 - 2:4](
name: Expr_Variable[2:2 - 2:4](
name: Expr_Variable[2:3 - 2:4](
name: x
)
)
)
)
)

View File

@@ -54,6 +54,8 @@ class Foo {
----- -----
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: Test name: Test
@@ -63,6 +65,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0 flags: 0
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -75,6 +79,8 @@ array(
) )
) )
1: Stmt_ClassMethod( 1: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0 flags: 0
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -87,6 +93,8 @@ array(
) )
) )
2: Stmt_ClassMethod( 2: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -99,6 +107,8 @@ array(
) )
) )
3: Stmt_ClassMethod( 3: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -111,6 +121,8 @@ array(
) )
) )
4: Stmt_Property( 4: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: null type: null
props: array( props: array(
@@ -123,6 +135,8 @@ array(
) )
) )
5: Stmt_Property( 5: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: null type: null
props: array( props: array(
@@ -135,6 +149,8 @@ array(
) )
) )
6: Stmt_ClassConst( 6: Stmt_ClassConst(
attrGroups: array(
)
flags: 0 flags: 0
consts: array( consts: array(
0: Const( 0: Const(
@@ -156,6 +172,8 @@ array(
) )
) )
7: Stmt_ClassConst( 7: Stmt_ClassConst(
attrGroups: array(
)
flags: 0 flags: 0
consts: array( consts: array(
0: Const( 0: Const(
@@ -344,6 +362,8 @@ array(
) )
) )
10: Stmt_Class( 10: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: Foo name: Foo

View File

@@ -0,0 +1,388 @@
Attributes
-----
<?php
#[
A1,
A2(),
A3(0),
A4(x: 1),
]
function a() {
}
#[A5]
class C {
#[A6]
public function m(
#[A7] $param,
) {}
#[A14]
public $prop;
}
#[A8]
interface I {}
#[A9]
trait T {}
$x = #[A10] function() {};
$y = #[A11] fn() => 0;
$a = #[A12] static function() {};
$b = #[A13] static fn() => 0;
-----
!!php7
array(
0: Stmt_Function(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A1
)
)
args: array(
)
)
1: Attribute(
name: Name(
parts: array(
0: A2
)
)
args: array(
)
)
2: Attribute(
name: Name(
parts: array(
0: A3
)
)
args: array(
0: Arg(
name: null
value: Scalar_LNumber(
value: 0
)
byRef: false
unpack: false
)
)
)
3: Attribute(
name: Name(
parts: array(
0: A4
)
)
args: array(
0: Arg(
name: Identifier(
name: x
)
value: Scalar_LNumber(
value: 1
)
byRef: false
unpack: false
)
)
)
)
)
)
byRef: false
name: Identifier(
name: a
)
params: array(
)
returnType: null
stmts: array(
)
)
1: Stmt_Class(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A5
)
)
args: array(
)
)
)
)
)
flags: 0
name: Identifier(
name: C
)
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A6
)
)
args: array(
)
)
)
)
)
flags: MODIFIER_PUBLIC (1)
byRef: false
name: Identifier(
name: m
)
params: array(
0: Param(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A7
)
)
args: array(
)
)
)
)
)
flags: 0
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: param
)
default: null
)
)
returnType: null
stmts: array(
)
)
1: Stmt_Property(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A14
)
)
args: array(
)
)
)
)
)
flags: MODIFIER_PUBLIC (1)
type: null
props: array(
0: Stmt_PropertyProperty(
name: VarLikeIdentifier(
name: prop
)
default: null
)
)
)
)
)
2: Stmt_Interface(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A8
)
)
args: array(
)
)
)
)
)
name: Identifier(
name: I
)
extends: array(
)
stmts: array(
)
)
3: Stmt_Trait(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A9
)
)
args: array(
)
)
)
)
)
name: Identifier(
name: T
)
stmts: array(
)
)
4: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: x
)
expr: Expr_Closure(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A10
)
)
args: array(
)
)
)
)
)
static: false
byRef: false
params: array(
)
uses: array(
)
returnType: null
stmts: array(
)
)
)
)
5: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: y
)
expr: Expr_ArrowFunction(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A11
)
)
args: array(
)
)
)
)
)
static: false
byRef: false
params: array(
)
returnType: null
expr: Scalar_LNumber(
value: 0
)
)
)
)
6: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: a
)
expr: Expr_Closure(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A12
)
)
args: array(
)
)
)
)
)
static: true
byRef: false
params: array(
)
uses: array(
)
returnType: null
stmts: array(
)
)
)
)
7: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: b
)
expr: Expr_ArrowFunction(
attrGroups: array(
0: AttributeGroup(
attrs: array(
0: Attribute(
name: Name(
parts: array(
0: A13
)
)
args: array(
)
)
)
)
)
static: true
byRef: false
params: array(
)
returnType: null
expr: Scalar_LNumber(
value: 0
)
)
)
)
)

View File

@@ -9,6 +9,8 @@ abstract class A {
----- -----
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16) flags: MODIFIER_ABSTRACT (16)
name: Identifier( name: Identifier(
name: A name: A
@@ -18,6 +20,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -30,6 +34,8 @@ array(
) )
) )
1: Stmt_ClassMethod( 1: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_ABSTRACT (17) flags: MODIFIER_PUBLIC | MODIFIER_ABSTRACT (17)
byRef: false byRef: false
name: Identifier( name: Identifier(

View File

@@ -25,6 +25,8 @@ array(
0: Stmt_Expression( 0: Stmt_Expression(
expr: Expr_New( expr: Expr_New(
class: Stmt_Class( class: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: null name: null
extends: null extends: null
@@ -32,6 +34,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -52,6 +56,8 @@ array(
1: Stmt_Expression( 1: Stmt_Expression(
expr: Expr_New( expr: Expr_New(
class: Stmt_Class( class: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: null name: null
extends: Name( extends: Name(
@@ -81,6 +87,8 @@ array(
2: Stmt_Expression( 2: Stmt_Expression(
expr: Expr_New( expr: Expr_New(
class: Stmt_Class( class: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: null name: null
extends: null extends: null
@@ -88,6 +96,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: null type: null
props: array( props: array(
@@ -108,6 +118,8 @@ array(
3: Stmt_Expression( 3: Stmt_Expression(
expr: Expr_New( expr: Expr_New(
class: Stmt_Class( class: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: null name: null
extends: Name( extends: Name(
@@ -152,6 +164,8 @@ array(
) )
) )
4: Stmt_Class( 4: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -161,6 +175,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -173,6 +189,8 @@ array(
0: Stmt_Return( 0: Stmt_Return(
expr: Expr_New( expr: Expr_New(
class: Stmt_Class( class: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: null name: null
extends: Name( extends: Name(
@@ -184,6 +202,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassConst( 0: Stmt_ClassConst(
attrGroups: array(
)
flags: 0 flags: 0
consts: array( consts: array(
0: Const( 0: Const(

View File

@@ -0,0 +1,94 @@
Class position
-----
<?php
if (1);
class C {}
-----
!!positions
array(
0: Stmt_If[3:1 - 3:7](
cond: Scalar_LNumber[3:5 - 3:5](
value: 1
)
stmts: array(
)
elseifs: array(
)
else: null
)
1: Stmt_Class[5:1 - 5:10](
attrGroups: array(
)
flags: 0
name: Identifier[5:7 - 5:7](
name: C
)
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php
if (1);
trait X {}
-----
!!positions
array(
0: Stmt_If[3:1 - 3:7](
cond: Scalar_LNumber[3:5 - 3:5](
value: 1
)
stmts: array(
)
elseifs: array(
)
else: null
)
1: Stmt_Trait[5:1 - 5:10](
attrGroups: array(
)
name: Identifier[5:7 - 5:7](
name: X
)
stmts: array(
)
)
)
-----
<?php
if (1);
interface X {}
-----
!!positions
array(
0: Stmt_If[3:1 - 3:7](
cond: Scalar_LNumber[3:5 - 3:5](
value: 1
)
stmts: array(
)
elseifs: array(
)
else: null
)
1: Stmt_Interface[5:1 - 5:14](
attrGroups: array(
)
name: Identifier[5:11 - 5:11](
name: X
)
extends: array(
)
stmts: array(
)
)
)

View File

@@ -17,6 +17,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A

View File

@@ -9,6 +9,8 @@ class A {
Cannot use 'static' as constant modifier from 3:5 to 3:10 Cannot use 'static' as constant modifier from 3:5 to 3:10
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -18,6 +20,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassConst( 0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
consts: array( consts: array(
0: Const( 0: Const(
@@ -43,6 +47,8 @@ class A {
Cannot use 'abstract' as constant modifier from 3:5 to 3:12 Cannot use 'abstract' as constant modifier from 3:5 to 3:12
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -52,6 +58,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassConst( 0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16) flags: MODIFIER_ABSTRACT (16)
consts: array( consts: array(
0: Const( 0: Const(
@@ -77,6 +85,8 @@ class A {
Cannot use 'final' as constant modifier from 3:5 to 3:9 Cannot use 'final' as constant modifier from 3:5 to 3:9
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -86,6 +96,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassConst( 0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_FINAL (32) flags: MODIFIER_FINAL (32)
consts: array( consts: array(
0: Const( 0: Const(
@@ -111,6 +123,8 @@ class A {
Multiple access type modifiers are not allowed from 3:12 to 3:17 Multiple access type modifiers are not allowed from 3:12 to 3:17
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -120,6 +134,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassConst( 0: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
consts: array( consts: array(
0: Const( 0: Const(

View File

@@ -12,6 +12,8 @@ class Foo {
!!php7 !!php7
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: Foo name: Foo
@@ -21,6 +23,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassConst( 0: Stmt_ClassConst(
attrGroups: array(
)
flags: 0 flags: 0
consts: array( consts: array(
0: Const( 0: Const(
@@ -34,6 +38,8 @@ array(
) )
) )
1: Stmt_ClassConst( 1: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
consts: array( consts: array(
0: Const( 0: Const(
@@ -47,6 +53,8 @@ array(
) )
) )
2: Stmt_ClassConst( 2: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2) flags: MODIFIER_PROTECTED (2)
consts: array( consts: array(
0: Const( 0: Const(
@@ -60,6 +68,8 @@ array(
) )
) )
3: Stmt_ClassConst( 3: Stmt_ClassConst(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4) flags: MODIFIER_PRIVATE (4)
consts: array( consts: array(
0: Const( 0: Const(

View File

@@ -0,0 +1,65 @@
Enum
-----
<?php
enum A {}
enum B implements Bar, Baz {
}
enum C: int implements Bar {}
-----
!!php7
array(
0: Stmt_Enum(
attrGroups: array(
)
name: Identifier(
name: A
)
scalarType: null
implements: array(
)
stmts: array(
)
)
1: Stmt_Enum(
attrGroups: array(
)
name: Identifier(
name: B
)
scalarType: null
implements: array(
0: Name(
parts: array(
0: Bar
)
)
1: Name(
parts: array(
0: Baz
)
)
)
stmts: array(
)
)
2: Stmt_Enum(
attrGroups: array(
)
name: Identifier(
name: C
)
scalarType: Identifier(
name: int
)
implements: array(
0: Name(
parts: array(
0: Bar
)
)
)
stmts: array(
)
)
)

View File

@@ -0,0 +1,67 @@
Enum
-----
<?php
enum Suit: string
{
case Hearts = 'H';
case Diamonds;
case Clubs = 'C';
case Spades = 'S';
}
-----
!!php7
array(
0: Stmt_Enum(
attrGroups: array(
)
name: Identifier(
name: Suit
)
scalarType: Identifier(
name: string
)
implements: array(
)
stmts: array(
0: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Hearts
)
expr: Scalar_String(
value: H
)
)
1: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Diamonds
)
expr: null
)
2: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Clubs
)
expr: Scalar_String(
value: C
)
)
3: Stmt_EnumCase(
attrGroups: array(
)
name: Identifier(
name: Spades
)
expr: Scalar_String(
value: S
)
)
)
)
)

View File

@@ -6,6 +6,8 @@ final class A {}
----- -----
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_FINAL (32) flags: MODIFIER_FINAL (32)
name: Identifier( name: Identifier(
name: A name: A

View File

@@ -14,6 +14,8 @@ abstract class A {
----- -----
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16) flags: MODIFIER_ABSTRACT (16)
name: Identifier( name: Identifier(
name: A name: A
@@ -23,6 +25,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
props: array( props: array(
@@ -35,6 +39,8 @@ array(
) )
) )
1: Stmt_Property( 1: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
type: null type: null
props: array( props: array(
@@ -47,6 +53,8 @@ array(
) )
) )
2: Stmt_ClassMethod( 2: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16) flags: MODIFIER_ABSTRACT (16)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -58,6 +66,8 @@ array(
stmts: null stmts: null
) )
3: Stmt_ClassMethod( 3: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_FINAL (32) flags: MODIFIER_FINAL (32)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -70,6 +80,8 @@ array(
) )
) )
4: Stmt_ClassMethod( 4: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -82,6 +94,8 @@ array(
) )
) )
5: Stmt_ClassMethod( 5: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC | MODIFIER_FINAL (40) flags: MODIFIER_STATIC | MODIFIER_FINAL (40)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -94,6 +108,8 @@ array(
) )
) )
6: Stmt_ClassMethod( 6: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0 flags: 0
byRef: false byRef: false
name: Identifier( name: Identifier(

View File

@@ -8,6 +8,8 @@ interface A extends C, D {
----- -----
array( array(
0: Stmt_Interface( 0: Stmt_Interface(
attrGroups: array(
)
name: Identifier( name: Identifier(
name: A name: A
) )
@@ -25,6 +27,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
byRef: false byRef: false
name: Identifier( name: Identifier(

View File

@@ -5,6 +5,8 @@ Invalid modifier combination
Multiple access type modifiers are not allowed from 1:24 to 1:29 Multiple access type modifiers are not allowed from 1:24 to 1:29
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -14,6 +16,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: null type: null
props: array( props: array(
@@ -34,6 +38,8 @@ array(
Multiple access type modifiers are not allowed from 1:24 to 1:32 Multiple access type modifiers are not allowed from 1:24 to 1:32
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -43,6 +49,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_PROTECTED (3) flags: MODIFIER_PUBLIC | MODIFIER_PROTECTED (3)
type: null type: null
props: array( props: array(
@@ -63,6 +71,8 @@ array(
Multiple abstract modifiers are not allowed from 1:26 to 1:33 Multiple abstract modifiers are not allowed from 1:26 to 1:33
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -72,6 +82,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16) flags: MODIFIER_ABSTRACT (16)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -91,6 +103,8 @@ array(
Multiple static modifiers are not allowed from 1:24 to 1:29 Multiple static modifiers are not allowed from 1:24 to 1:29
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -100,6 +114,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
type: null type: null
props: array( props: array(
@@ -120,6 +136,8 @@ array(
Multiple final modifiers are not allowed from 1:23 to 1:27 Multiple final modifiers are not allowed from 1:23 to 1:27
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -129,6 +147,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_FINAL (32) flags: MODIFIER_FINAL (32)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -149,6 +169,8 @@ array(
Cannot use the final modifier on an abstract class member from 1:26 to 1:30 Cannot use the final modifier on an abstract class member from 1:26 to 1:30
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -158,6 +180,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT | MODIFIER_FINAL (48) flags: MODIFIER_ABSTRACT | MODIFIER_FINAL (48)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -178,6 +202,8 @@ array(
Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20 Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: MODIFIER_FINAL (32) flags: MODIFIER_FINAL (32)
name: Identifier( name: Identifier(
name: A name: A
@@ -200,6 +226,8 @@ array(
Properties cannot be declared abstract from 1:17 to 1:24 Properties cannot be declared abstract from 1:17 to 1:24
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -209,6 +237,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT (16) flags: MODIFIER_ABSTRACT (16)
type: null type: null
props: array( props: array(
@@ -229,6 +259,8 @@ array(
Properties cannot be declared final from 1:17 to 1:21 Properties cannot be declared final from 1:17 to 1:21
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -238,6 +270,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_FINAL (32) flags: MODIFIER_FINAL (32)
type: null type: null
props: array( props: array(

View File

@@ -5,6 +5,8 @@ Invalid class name
Cannot use 'self' as class name as it is reserved from 1:13 to 1:16 Cannot use 'self' as class name as it is reserved from 1:13 to 1:16
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: self name: self
@@ -22,6 +24,8 @@ array(
Cannot use 'PARENT' as class name as it is reserved from 1:13 to 1:18 Cannot use 'PARENT' as class name as it is reserved from 1:13 to 1:18
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: PARENT name: PARENT
@@ -45,6 +49,8 @@ array(
Cannot use 'self' as class name as it is reserved from 1:23 to 1:26 Cannot use 'self' as class name as it is reserved from 1:23 to 1:26
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -66,6 +72,8 @@ array(
Cannot use 'PARENT' as class name as it is reserved from 1:23 to 1:28 Cannot use 'PARENT' as class name as it is reserved from 1:23 to 1:28
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -87,6 +95,8 @@ array(
Cannot use 'static' as class name as it is reserved from 1:23 to 1:28 Cannot use 'static' as class name as it is reserved from 1:23 to 1:28
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -108,6 +118,8 @@ array(
Cannot use 'self' as interface name as it is reserved from 1:26 to 1:29 Cannot use 'self' as interface name as it is reserved from 1:26 to 1:29
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -130,6 +142,8 @@ array(
Cannot use 'PARENT' as interface name as it is reserved from 1:26 to 1:31 Cannot use 'PARENT' as interface name as it is reserved from 1:26 to 1:31
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -152,6 +166,8 @@ array(
Cannot use 'static' as interface name as it is reserved from 1:26 to 1:31 Cannot use 'static' as interface name as it is reserved from 1:26 to 1:31
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -174,6 +190,8 @@ array(
Cannot use 'self' as class name as it is reserved from 1:17 to 1:20 Cannot use 'self' as class name as it is reserved from 1:17 to 1:20
array( array(
0: Stmt_Interface( 0: Stmt_Interface(
attrGroups: array(
)
name: Identifier( name: Identifier(
name: self name: self
) )
@@ -189,6 +207,8 @@ array(
Cannot use 'PARENT' as class name as it is reserved from 1:17 to 1:22 Cannot use 'PARENT' as class name as it is reserved from 1:17 to 1:22
array( array(
0: Stmt_Interface( 0: Stmt_Interface(
attrGroups: array(
)
name: Identifier( name: Identifier(
name: PARENT name: PARENT
) )
@@ -210,6 +230,8 @@ array(
Cannot use 'self' as interface name as it is reserved from 1:27 to 1:30 Cannot use 'self' as interface name as it is reserved from 1:27 to 1:30
array( array(
0: Stmt_Interface( 0: Stmt_Interface(
attrGroups: array(
)
name: Identifier( name: Identifier(
name: A name: A
) )
@@ -230,6 +252,8 @@ array(
Cannot use 'PARENT' as interface name as it is reserved from 1:27 to 1:32 Cannot use 'PARENT' as interface name as it is reserved from 1:27 to 1:32
array( array(
0: Stmt_Interface( 0: Stmt_Interface(
attrGroups: array(
)
name: Identifier( name: Identifier(
name: A name: A
) )
@@ -250,6 +274,8 @@ array(
Cannot use 'static' as interface name as it is reserved from 1:27 to 1:32 Cannot use 'static' as interface name as it is reserved from 1:27 to 1:32
array( array(
0: Stmt_Interface( 0: Stmt_Interface(
attrGroups: array(
)
name: Identifier( name: Identifier(
name: A name: A
) )

View File

@@ -10,6 +10,8 @@ class A {
----- -----
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -19,6 +21,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
props: array( props: array(
@@ -31,6 +35,8 @@ array(
) )
) )
1: Stmt_ClassMethod( 1: Stmt_ClassMethod(
attrGroups: array(
)
flags: 0 flags: 0
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -43,6 +49,8 @@ array(
) )
) )
2: Stmt_ClassMethod( 2: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_ABSTRACT | MODIFIER_STATIC (24) flags: MODIFIER_ABSTRACT | MODIFIER_STATIC (24)
byRef: false byRef: false
name: Identifier( name: Identifier(

View File

@@ -11,6 +11,8 @@ class A {
!!php7 !!php7
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -20,6 +22,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Property( 0: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: Identifier( type: Identifier(
name: string name: string
@@ -34,6 +38,8 @@ array(
) )
) )
1: Stmt_Property( 1: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PROTECTED | MODIFIER_STATIC (10) flags: MODIFIER_PROTECTED | MODIFIER_STATIC (10)
type: Name( type: Name(
parts: array( parts: array(
@@ -50,6 +56,8 @@ array(
) )
) )
2: Stmt_Property( 2: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4) flags: MODIFIER_PRIVATE (4)
type: NullableType( type: NullableType(
type: Identifier( type: Identifier(

View File

@@ -13,6 +13,8 @@ class Point {
!!php7 !!php7
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: Point name: Point
@@ -22,6 +24,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -29,6 +33,8 @@ array(
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: Identifier( type: Identifier(
name: float name: float
@@ -43,6 +49,8 @@ array(
) )
) )
1: Param( 1: Param(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2) flags: MODIFIER_PROTECTED (2)
type: Identifier( type: Identifier(
name: array name: array
@@ -58,6 +66,8 @@ array(
) )
) )
2: Param( 2: Param(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4) flags: MODIFIER_PRIVATE (4)
type: Identifier( type: Identifier(
name: string name: string

View File

@@ -18,6 +18,8 @@ class A extends B implements C, D {
----- -----
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -41,6 +43,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassConst( 0: Stmt_ClassConst(
attrGroups: array(
)
flags: 0 flags: 0
consts: array( consts: array(
0: Const( 0: Const(
@@ -62,6 +66,8 @@ array(
) )
) )
1: Stmt_Property( 1: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
type: null type: null
props: array( props: array(
@@ -84,6 +90,8 @@ array(
) )
) )
2: Stmt_Property( 2: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2) flags: MODIFIER_PROTECTED (2)
type: null type: null
props: array( props: array(
@@ -96,6 +104,8 @@ array(
) )
) )
3: Stmt_Property( 3: Stmt_Property(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4) flags: MODIFIER_PRIVATE (4)
type: null type: null
props: array( props: array(
@@ -108,6 +118,8 @@ array(
) )
) )
4: Stmt_ClassMethod( 4: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -120,6 +132,8 @@ array(
) )
) )
5: Stmt_ClassMethod( 5: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_STATIC (9) flags: MODIFIER_PUBLIC | MODIFIER_STATIC (9)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -127,6 +141,8 @@ array(
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -142,6 +158,8 @@ array(
) )
) )
6: Stmt_ClassMethod( 6: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_FINAL (33) flags: MODIFIER_PUBLIC | MODIFIER_FINAL (33)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -158,6 +176,8 @@ array(
) )
) )
7: Stmt_ClassMethod( 7: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PROTECTED (2) flags: MODIFIER_PROTECTED (2)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -170,6 +190,8 @@ array(
) )
) )
8: Stmt_ClassMethod( 8: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PRIVATE (4) flags: MODIFIER_PRIVATE (4)
byRef: false byRef: false
name: Identifier( name: Identifier(

View File

@@ -5,6 +5,8 @@ Some special methods cannot be static
Constructor __construct() cannot be static from 1:17 to 1:22 Constructor __construct() cannot be static from 1:17 to 1:22
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -14,6 +16,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -34,6 +38,8 @@ array(
Destructor __destruct() cannot be static from 1:17 to 1:22 Destructor __destruct() cannot be static from 1:17 to 1:22
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -43,6 +49,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -63,6 +71,8 @@ array(
Clone method __clone() cannot be static from 1:17 to 1:22 Clone method __clone() cannot be static from 1:17 to 1:22
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -72,6 +82,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -92,6 +104,8 @@ array(
Constructor __CONSTRUCT() cannot be static from 1:17 to 1:22 Constructor __CONSTRUCT() cannot be static from 1:17 to 1:22
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -101,6 +115,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -121,6 +137,8 @@ array(
Destructor __Destruct() cannot be static from 1:17 to 1:22 Destructor __Destruct() cannot be static from 1:17 to 1:22
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -130,6 +148,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -150,6 +170,8 @@ array(
Clone method __cLoNe() cannot be static from 1:17 to 1:22 Clone method __cLoNe() cannot be static from 1:17 to 1:22
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: A name: A
@@ -159,6 +181,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_STATIC (8) flags: MODIFIER_STATIC (8)
byRef: false byRef: false
name: Identifier( name: Identifier(

View File

@@ -8,6 +8,8 @@ class Test {
!!php7 !!php7
array( array(
0: Stmt_Class( 0: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: Test name: Test
@@ -17,6 +19,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC | MODIFIER_STATIC (9) flags: MODIFIER_PUBLIC | MODIFIER_STATIC (9)
byRef: false byRef: false
name: Identifier( name: Identifier(

View File

@@ -23,11 +23,15 @@ class B {
----- -----
array( array(
0: Stmt_Trait( 0: Stmt_Trait(
attrGroups: array(
)
name: Identifier( name: Identifier(
name: A name: A
) )
stmts: array( stmts: array(
0: Stmt_ClassMethod( 0: Stmt_ClassMethod(
attrGroups: array(
)
flags: MODIFIER_PUBLIC (1) flags: MODIFIER_PUBLIC (1)
byRef: false byRef: false
name: Identifier( name: Identifier(
@@ -42,6 +46,8 @@ array(
) )
) )
1: Stmt_Class( 1: Stmt_Class(
attrGroups: array(
)
flags: 0 flags: 0
name: Identifier( name: Identifier(
name: B name: B

View File

@@ -6,12 +6,16 @@ function test(bool $a, Int $b, FLOAT $c, StRiNg $d, iterable $e, object $f, mixe
!!php7 !!php7
array( array(
0: Stmt_Function( 0: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: test name: test
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Identifier( type: Identifier(
name: bool name: bool
@@ -24,6 +28,8 @@ array(
default: null default: null
) )
1: Param( 1: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Identifier( type: Identifier(
name: int name: int
@@ -36,6 +42,8 @@ array(
default: null default: null
) )
2: Param( 2: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Identifier( type: Identifier(
name: float name: float
@@ -48,6 +56,8 @@ array(
default: null default: null
) )
3: Param( 3: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Identifier( type: Identifier(
name: string name: string
@@ -60,6 +70,8 @@ array(
default: null default: null
) )
4: Param( 4: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Identifier( type: Identifier(
name: iterable name: iterable
@@ -72,6 +84,8 @@ array(
default: null default: null
) )
5: Param( 5: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Identifier( type: Identifier(
name: object name: object
@@ -84,6 +98,8 @@ array(
default: null default: null
) )
6: Param( 6: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: Identifier( type: Identifier(
name: mixed name: mixed

View File

@@ -7,12 +7,16 @@ function &a($b) {}
----- -----
array( array(
0: Stmt_Function( 0: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: a name: a
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: true byRef: true
@@ -28,12 +32,16 @@ array(
) )
) )
1: Stmt_Function( 1: Stmt_Function(
attrGroups: array(
)
byRef: true byRef: true
name: Identifier( name: Identifier(
name: a name: a
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false

View File

@@ -17,6 +17,8 @@ array(
) )
stmts: array( stmts: array(
0: Stmt_Function( 0: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: A name: A

View File

@@ -16,12 +16,16 @@ function a(
----- -----
array( array(
0: Stmt_Function( 0: Stmt_Function(
attrGroups: array(
)
byRef: false byRef: false
name: Identifier( name: Identifier(
name: a name: a
) )
params: array( params: array(
0: Param( 0: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -38,6 +42,8 @@ array(
) )
) )
1: Param( 1: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -50,6 +56,8 @@ array(
) )
) )
2: Param( 2: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -69,6 +77,8 @@ array(
) )
) )
3: Param( 3: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -83,6 +93,8 @@ array(
) )
) )
4: Param( 4: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -97,6 +109,8 @@ array(
) )
) )
5: Param( 5: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -110,6 +124,8 @@ array(
) )
) )
6: Param( 6: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -123,6 +139,8 @@ array(
) )
) )
7: Param( 7: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false
@@ -144,6 +162,8 @@ array(
) )
) )
8: Param( 8: Param(
attrGroups: array(
)
flags: 0 flags: 0
type: null type: null
byRef: false byRef: false

View File

@@ -0,0 +1,23 @@
Never type
-----
<?php
function test(): never {}
-----
!!php7
array(
0: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: test
)
params: array(
)
returnType: Identifier(
name: never
)
stmts: array(
)
)
)

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