Compare commits

..

155 Commits

Author SHA1 Message Date
715f4d25e2 Release PHP-Parser 4.19.4 2024-09-29 17:01:53 +02:00
80e6f9b243 Remove accidentally committed Php8 parser
Looks like I pulled this in by accident when backporting a change
from the master branch.
2024-09-29 16:58:55 +02:00
220f5b3661 Release PHP-Parser 4.19.3 2024-09-29 16:01:32 +02:00
ad38d47247 Avoid negative indendation in formatting-preserving printer
Fixes #1015.

(cherry-picked from 961f158f6d)
2024-09-29 15:36:28 +02:00
6de6ede1cb Include trailing semicolon in GroupUse
Fixes https://github.com/nikic/PHP-Parser/issues/1026.

(cherry-picked from aedfcc23cd)
2024-09-21 15:58:16 +02:00
20412455fc Avoid use of assertObjectHasAttribute()
Use isset() to avoid the PHPUnit deprecation warnings. This is
the same as on the master branch.
2024-09-21 15:49:26 +02:00
103f95d560 Add PHP 8.4 to CI matrix
We don't support parsing PHP 8.4 on 4.x, but everything else
should work.
2024-09-21 15:47:51 +02:00
78f289c51f Use explicit nullability in test
Avoid a deprecation warning on PHP 8.4.
2024-09-21 15:46:59 +02:00
0f585f0e95 Drop PHPUnit 6
As we no longer support PHP 7.0, we don't need PHPUnit 6 anymore.
2024-09-21 15:45:18 +02:00
0ed4c8949a Release PHP-Parser 4.19.2 2024-09-17 21:36:00 +02:00
8a21ec3182 Normalize enum value to ClassConstFetch
Fixes #930
2024-08-11 16:55:47 +02:00
4d36e9c16f Make phpunit fail on deprecation warnings (#989) 2024-03-17 10:03:35 +01:00
4e1b88d21c Release PHP-Parser 4.19.1 2024-03-17 09:10:35 +01:00
d4d4e3e155 Fix deprecated Optional parameter before required parameter on PrintableNewAnonClassNode (#987)
Fixes https://github.com/nikic/PHP-Parser/issues/986.
2024-03-17 09:05:57 +01:00
32cdab9a03 Release PHP-Parser 4.19.0 2024-03-16 15:49:31 +01:00
c2403aa729 Update minimum PHP version in docs 2024-03-02 18:21:59 +01:00
051ad218f8 Do not use implicitly nullable parameters (#984)
Implicitly nullable parameters will be deprecated in PHP 8.4
(see https://wiki.php.net/rfc/deprecate-implicitly-nullable-types).
To avoid deprecation warnings, replace all implicitly nullable
parameters with explicit ones. Unfortunately, this also means
that we have to drop support for PHP 7.0.
2024-03-02 18:19:50 +01:00
1bcbb2179f Release PHP-Parser 4.18.0 2023-12-10 22:03:43 +01:00
05c01865ea Add PHP 8.3 to CI 2023-12-10 21:57:59 +01:00
2a5e81f7ca Fix NameResolver for class constant native type 2023-11-28 20:10:38 +01:00
e453389866 Add forward-compatibility ParserFactory methods
Add ParserFactory::createForNewestSupportedVersion() and
ParserFactory::createForHostVersion() for forward-compatibility
with PHP-Parser 5. These methods do not accept an externally
constructed lexer and always enable all attributes.
2023-11-12 16:49:53 +01:00
402b6cf345 build: Exclude grammar from export git artifact 2023-11-01 21:31:02 +01:00
54103d8387 Don't drop class statements before error (#952)
When encountering a null statement (indicating that an error occurred),
retain the preceding statements. These were accidentally dropped
previously.
2023-10-03 23:00:18 +02:00
a6303e50c9 Release PHP-Parser 4.17.1 2023-08-13 21:53:39 +02:00
44fc92194b Fix ClassConst::$type phpdoc
The property should be nullable, and on 4.x we should convert
string to Identifier.

Fixes #939.
2023-08-13 21:49:54 +02:00
844c228bf2 Release PHP-Parser 4.17.0 2023-08-13 16:53:08 +02:00
6d2584bdf1 Support fixup for dynamic class const name
This is new in PHP 8.3.

(cherry picked from commit e9416a0eae)
2023-08-13 16:46:31 +02:00
21a61ece15 Properly handle static deref LHS
The rules for static and array/object deref are slightly different:
The former does not allow constants.

(cherry picked from commit 7b4a8c1ebd)
2023-08-13 16:46:25 +02:00
8f8e47b6c1 Support new variables in fixup
(cherry picked from commit 6a88bdb05a)
2023-08-13 16:41:25 +02:00
0aad06bce3 Fix logic for new operand parentheses requirement
We need to perform this check recursively.

(cherry picked from commit cc34c2450c)
2023-08-13 16:36:00 +02:00
80a680bf59 Properly handle new/instanceof operand restrictions
Fixes #912.

(cherry picked from commit 1eb6b5653e)
2023-08-13 16:35:51 +02:00
cfc54e30a4 [PHP 8.3] Support dynamic class const fetch
RFC: https://wiki.php.net/rfc/dynamic_class_constant_fetch
2023-07-30 23:38:32 +02:00
05e84f7201 Support readonly anonymous classes 2023-07-30 23:38:32 +02:00
73ccbabbe7 Add support for typed constants
RFC: https://wiki.php.net/rfc/typed_class_constants
2023-07-30 23:38:32 +02:00
19526a33fb Release PHP-Parser 4.16.0 2023-06-25 16:52:30 +02:00
1d0748ad35 Update main.yml to use GitHub Actions V3
Updates the GitHub Actions from V2 to V3
2023-06-24 16:59:08 +01:00
c9e5a13d68 Add Name::getParts(), deprecate Name::$parts
In preparation for switching this to a plain string in
PHP-Parser 5, deprecate direct access to the property and
provide an API that will work on both versions.
2023-05-21 21:22:47 +02:00
ba788aa98b Remove redundant parameter
Closes #920.
2023-05-19 22:26:12 +02:00
11e2663a5b Release PHP-Parser 4.15.5 2023-05-19 22:20:00 +02:00
11e2dcd96c Add makeReadonly() to param builder 2023-05-19 22:17:09 +02:00
0ffddce52d [4.x] Add constructor property promotion
By making flags on the Param builder configurable by providing make(Public|Protected|Private) methods we can promote parameters to properties from the constructor
2023-03-06 23:12:36 +01:00
6bb5176bc4 Release PHP-Parser 4.15.4 2023-03-05 20:49:14 +01:00
cad49f8ed3 Fix formatting preservation for alternative elseif/else syntax
Test taken from PR #797.

(cherry picked from commit 9b46dffb12)
2023-03-05 20:44:50 +01:00
570e980a20 Release PHP-Parser 4.15.3 2023-01-16 23:05:37 +01:00
a50b4310f7 Test PHP 8.2 in CI 2023-01-16 22:27:18 +01:00
8863f92b58 Support readonly before DNF type
This makes us match the PHP 8.2 handling of readonly. Handling of
"readonly" functions is moved to the parser to allow distinguishing
them from readonly properties with DNF types. We have to uglify the
grammar to avoid some shift/reduce conflicts. Thank you WordPress.

(cherry-picked from 0dd85ebd34)
2023-01-16 22:19:17 +01:00
3182d12b55 Fix attrGroups/attributes confusion in EnumCase builder
Found by staabm in #907.

(cherry picked from commit 21a3e8cac5)
2022-12-14 21:51:15 +01:00
1df465cd90 added type int to argument of addAlias function 2022-11-13 16:33:17 +01:00
f59bbe44bf Release PHP-Parser 4.15.2 2022-11-12 16:38:23 +01:00
2e11deec46 Bail out on PHP tags in removed code
If dropping a node would drop PHP tags, bail out of formatting
preservation. This will lose formatting, but at least produce
legal code.

Closes GH-884.

(cherry picked from commit b0edd4c411)
2022-11-12 16:33:34 +01:00
a4fe65bf60 Add more tests for formatting preservation with InlineHTML
It's all broken...

(cherry picked from commit bad10e1618)
2022-11-12 16:33:21 +01:00
e072fd2c30 Adjust tests to work on 32-bit
Fixes #662.

(cherry picked from commit 950bf8f1d1)
2022-11-12 16:24:01 +01:00
7027899d7f Fix parsing of large hex floats containing "e"
These ended up taking the code path for normal floats and being
cast to zero.

(cherry picked from commit 4ce9781260)
2022-11-12 16:24:01 +01:00
2f1fd784fe Fixed type in UnionType 2022-09-10 22:41:13 +02:00
0ef6c55a3f Release PHP-Parser 4.15.1 2022-09-04 09:30:47 +02:00
8216e878be Fix empty list insertion of multiple attributes
(cherry picked from commit 44c6a97705)
2022-09-04 09:29:05 +02:00
617d0220b9 Release PHP-Parser 4.15.0 2022-09-03 21:54:32 +02:00
a951e9e24d Add support for DNF types (#862)
(cherry picked from commit 9b2a01aa0c)
2022-09-03 21:50:20 +02:00
b30e7e73d5 Support empty list insertion for attributes
(cherry picked from commit a2608f0b74)
2022-09-03 21:48:21 +02:00
ff24d1d61a Fix length bounds check in Name::slice()
The length check did not take into account that there may be a
non-zero offset at this point.

Fixes #875.

(cherry picked from commit 53b907d405)
2022-09-03 21:47:28 +02:00
e55f8c6b30 Add __serialize/__unserialize to ClassMethod::$magicNames
(cherry picked from commit a3f2bb634d)
2022-09-03 21:46:15 +02:00
3ee592b6aa Handle true/false/null types in builder APIs
(cherry picked from commit 71ed641cd4)
2022-09-03 21:45:46 +02:00
3fe2422e34 Add support for true type
The null/false types were alread accepted previously, even though
they are only legal as standalone types since PHP 8.2.

Non-trivial backport from 1eba33d4124a248dd5c970fa7d6064d2e31b8635.
2022-09-03 21:36:40 +02:00
2d589921f2 Fix incorrect doc type 2022-06-04 12:44:36 +02:00
cdb731fa8b Update integration test target to 8.1 2022-05-31 23:18:52 +02:00
e727475d08 Support readonly as function name
This special case was added after the PHP 8.1 release.
2022-05-31 23:18:52 +02:00
34bea19b6e Release PHP-Parser 4.14.0 2022-05-31 22:59:12 +02:00
678ccbe072 [PHP 8.2] Add readonly class support (#834)
RFC: https://wiki.php.net/rfc/readonly_classes

PHP implementation: php/php-src#7305
2022-05-15 23:19:31 +02:00
5d83adcc0e [String_] Add rawValue attribute (#831) 2022-05-15 23:04:59 +02:00
3bf0082455 [DNumber] Add rawValue attribute to hold the original value (#833) 2022-05-15 18:12:28 +02:00
d3eb10aca1 [LNumber] Add rawValue attribute to LNumber to allow numeric separator etc. (#832) 2022-05-15 18:09:05 +02:00
a6e34665fd Reflect support for PHP 8.1 in the README 2022-01-03 21:20:19 +01:00
f4b835f7d8 Fix PHPDoc type of namespacedName properties 2021-12-06 21:33:02 +01:00
210577fe3c Release PHP-Parser 4.13.2 2021-11-30 20:35:32 +01:00
f09f22760e Declare namespacedName property
For historical reasons, this property is used by the NameResolver
(in default mode). Declare it explicitly, rather than using a doc
comment.
2021-11-27 21:02:58 +01:00
d4cb98ae38 Fix typo in property name
This test was working on a dynamic "subNode" property, rather
than an actual subnode (either subNode1 or subNode2).

This test is generally not very valuable, I think it dates back
to a time where __get()/__set() were used.
2021-11-27 20:57:41 +01:00
68d2a52b42 Avoid creation of dynamic property in test
This test requires a property that is not a subnode -- but it does
not need to be dynamic, a declared property works just as well.
2021-11-27 20:53:55 +01:00
63f8699143 Add CallLike test for NullsafeMethodCall 2021-11-14 17:44:47 +01:00
4122ff3a91 Make NullsafeMethodCall extend from CallLike 2021-11-14 17:43:29 +01:00
6f1f206862 Fix minor typo 2021-11-13 22:39:16 +01:00
99a24b6a55 Added builders for enum and enum case 2021-11-05 22:26:04 +01:00
63a79e8daa Release PHP-Parser 4.13.1 2021-11-03 21:52:16 +01:00
6a21234e58 Code highlighting 2021-10-31 09:11:00 +01:00
7064539974 Rename identifier/identifier_ex productions
The names were easy to get wrong, because the corresponding names
in zend_language_parser.y are T_STRING/identifier, so just copying
identifier from upstream gives the wrong behavior.
2021-10-17 20:26:06 +02:00
4bfc4595ed Support reserved keywords as enum cases
Fixes #807.
2021-10-17 20:20:35 +02:00
54f19a0a66 Fixed array value evaluation with unpacked array 2021-10-11 21:35:21 +02:00
8da6d7ac62 Fixed ArrowFunction::$expr 2021-10-09 11:26:27 +02:00
f6e1fbf3a2 Update .gitattributes 2021-10-08 21:23:55 +02:00
50953a2691 Release PHP-Parser 4.13.0 2021-09-20 14:20:58 +02:00
0a20979a62 Unified builder methods for setting types 2021-09-14 18:09:07 +02:00
a45fb2a621 Add CallLike parent class
This provides a helper to determine whether a call is a first-class
callable, and a way to strip the args type to Arg[] if it isn't.
2021-09-12 22:29:08 +02:00
08501991d4 Don't make VariadicPlaceholder an expression
And don't store it in an Arg.
2021-09-12 21:59:26 +02:00
b5234eacd0 Tweak coding style 2021-09-12 21:54:03 +02:00
632ead3a82 Print comma before comments for new array item (#805)
Print comma before rather than after comments. Also switch to multiline mode if inserting an item with comments.

Fixes #804.
2021-09-12 21:51:25 +02:00
13549aa794 Add support for first-class callables
I'm somewhat unsure about the AST structure here.
VariadicPlaceholder is not a general expression. Maybe Arg->expr
should be Expr|VariadicPlaceholder? Or possibly the call arguments
should be an array of Arg|VariadicPlaceholder?
2021-09-03 17:18:40 +02:00
d2c645f163 Adjust token count in octal emulator 2021-09-03 16:35:10 +02:00
def24f2224 Add support for explicit octal literals 2021-09-03 16:29:30 +02:00
cfeb195205 Add test for new in initializer
This already works because we don't validate initializer contents,
everything is accepted from a parser perspective.
2021-09-03 15:10:29 +02:00
ace6c67a8a Add support for intersection types 2021-09-03 15:06:33 +02:00
0483391aca Introduce ComplexType base class
With the upcoming addition of intersection types, a type can
be Identifier|Name|NullableType|UnionType|IntersectionType, which
is quite the mouthful. Give NullableType and UnionType a common
base class ComplexType, which does not have any behavior, but
allows to write these types (and check them in instanceof) more
easily.
2021-09-02 18:35:05 +02:00
9aebf377fc Allow multiple modifiers for property promotion
Fixes issue #800.
2021-08-08 19:12:44 +02:00
5a43015499 Simplify BuilderHelpers::normalizeName() implementation
In order to get rid of the flag in `BuilderHelpers::normalizeNameCommon()` I have moved all the logic related to the normalization of the name to the `BuilderHelpers::normalizeName()` method and expr-related stuff to the `BuilderHelpers::normalizeNameOrExpr()` method which later calls the basic `normalizeName()` as well
2021-07-21 12:51:18 +02:00
6608f01670 Release PHP-Parser 4.12.0 2021-07-21 12:44:31 +02:00
55c4269232 Add support for new PHP 8.1 modifiers (#796)
Implement support for readonly properties (https://wiki.php.net/rfc/readonly_properties_v2) and
final class contstants (https://wiki.php.net/rfc/final_class_const).
2021-07-21 12:43:29 +02:00
c4304c76bd Try to pass --ignore-platform-req=php on 8.1 only 2021-07-21 12:11:59 +02:00
b099e8fc76 Pass --ignore-platform-req=php 2021-07-21 12:08:11 +02:00
acf16edc8e Add PHP 8.1 to GH actions 2021-07-21 11:58:38 +02:00
a8b5ed4306 Fix JSON encoding test on PHP 8.1
Duplicate the test expectation for a different order.
2021-07-21 11:49:28 +02:00
c758510a37 Add support for PHP 8.1
With the introduction of intersection types, PHP now lexes the
token '&' either as T_AMPERSAND_(NOT_)FOLLOWED_BY_VAR_OR_VARARG.
This completely breaks parsing of any code containing '&'.

Fix this by canonicalizing to the new token format (unconditionally,
independent of emulation) and adjusting the parser to use the two
new tokens.

This doesn't add actual support for intersection types yet.
2021-07-09 16:52:58 +02:00
feed91cf0f Avoid ctype_alnum() on integer
This is deprecated in PHP 8.1
2021-07-09 15:46:50 +02:00
fe14cf3672 Release PHP-Parser 4.11.0 2021-07-03 15:36:55 +02:00
3fb73520c1 Add handling for Enum(Case)s in NameResolver 2021-07-03 15:09:11 +02:00
c35cc4b2cb Add support for "never" type in the BuilderHelpers::normalizeType() 2021-06-18 17:25:28 +02:00
e69ebbbfd9 chore: use the word Xdebug instead of XDebug 2021-06-17 18:00:12 +02:00
0b258d9a9e Add missing tests for methods of BuilderHelpers 2021-06-17 17:55:59 +02:00
2d193bb0e4 Add attributes to integration Builder test 2021-06-17 09:18:53 +02:00
49e9951f2c Add addAttribute() method to Builders with normalizer (#782)
Adds addAttribute() method to Builders of all nodes supporting attributes with BuilderHelpers::normalizeAttribute() usage inside so we can pass both Node\Attribute and Node\AttributeGroup instances.
2021-06-16 17:47:22 +02:00
eccf1bf464 Add Attribute builder and support for named args (#781)
Add BuilderFactory::attribute() and support named args in BuilderFactory::args().
2021-06-14 23:52:57 +02:00
b67560d388 Update CHANGELOG.md
very minor for the year.
2021-05-04 22:15:00 +02:00
4432ba399e Release PHP-Parser 4.10.5 2021-05-03 21:11:20 +02:00
37ac4ea9c2 Document that namespaced names containing whitespace are not supported 2021-04-25 22:47:15 +02:00
4848a0d734 Check for \r as newline in closing tag
Fixes #761.
2021-04-25 22:42:18 +02:00
8eb194ea1f Add never type
This should be recognized as Identifier instead of Name now.
2021-04-25 22:35:28 +02:00
e03d63cffb Fix precedence of arrow functions
Arrow functions should have lowest precedence.

Fixes #769.
2021-04-25 22:19:49 +02:00
ce91d139b5 Make sure match is one character long 2021-04-25 21:47:07 +02:00
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
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
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
f767b9fd9f ParserAbstract: add missing '*' to the phpdoc
Otherwise, it's somewhat incompatible with the phpdoc definition.
2021-04-25 12:38:52 +02:00
2a4bb7ef2c Moved phpy pseudo lang functions to separate file 2021-04-10 13:40:52 +02:00
4abdcde5f1 Fix missing argument in documentation (#767)
Missing argument in the getMessageWithColumnInfo call
2021-03-25 18:37:06 +01:00
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
38aa0920c9 Add test for UTF-8 in string
These should not get escaped.
2021-03-21 17:03:15 +01:00
a8223f228a Add emulation for enum keyword 2021-03-20 17:49:44 +01:00
8165cf69fa CS fix 2021-02-02 09:57:47 +01:00
46221a0914 Update Use_ builder to return specific type 2021-01-06 14:30:19 +01:00
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
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
c6d052fc58 Release PHP-Parser 4.10.4 2020-12-20 11:01:03 +01:00
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
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
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
893a5bce3f Fix #741 incorrect varvar positions 2020-12-19 22:03:43 +01:00
bec74aceda Remove .travis.yml 2020-12-08 23:22:57 +01:00
7c09e096c9 Try to re-enable coveralls 2020-12-08 23:17:41 +01:00
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
5e36ef732e Add GitHub Actions workflow (#740) 2020-12-08 23:00:10 +01:00
dbe56d23de Release PHP-Parser 4.10.3 2020-12-03 18:45:45 +01:00
c64986fa55 Allow both '{' and T_CURLY_OPEN as curly bracket (#732) 2020-12-03 18:42:00 +01:00
bc7a9bf9c2 Remove useless code left from old implementation 2020-12-02 12:54:15 +01:00
2816485126 Updated README to indicate that parsing PHP 8.0 code is supported 2020-12-01 15:15:26 +01:00
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
d520bc9e1d Don't allow failures on PHP 8.0 integration tests
These are passing now.
2020-09-30 21:15:05 +02:00
51e0b30843 Test against 8.0.0rc1
This should fix the remaining test failures.
2020-09-30 20:51:01 +02:00
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
192 changed files with 9557 additions and 4070 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

18
.gitattributes vendored
View File

@ -1,9 +1,11 @@
/doc export-ignore
/test export-ignore
/test_old export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.travis.yml export-ignore
CHANGELOG.md export-ignore
/.github export-ignore
/doc export-ignore
/grammar export-ignore
/test export-ignore
/test_old export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
CHANGELOG.md export-ignore
phpunit.xml.dist export-ignore
UPGRADE-*.md export-ignore
UPGRADE-*.md export-ignore

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

@ -0,0 +1,91 @@
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
name: Main
on:
push:
pull_request:
jobs:
tests_71:
runs-on: "ubuntu-latest"
name: "PHP 7.1 Unit Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "xdebug"
php-version: "7.1"
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.2"
- "7.3"
- "7.4"
- "8.0"
- "8.1"
- "8.2"
- "8.3"
- "8.4"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
ini-file: "development"
tools: composer:v2
- name: "Install dependencies"
run: "composer update --no-progress --prefer-dist ${{ matrix.flags }}"
- name: "PHPUnit"
run: "php vendor/bin/phpunit"
test_old_73_80:
runs-on: "ubuntu-latest"
name: "PHP 7.3 Code on PHP 8.0 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.0"
ini-file: "development"
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_71:
runs-on: "ubuntu-latest"
name: "PHP 8.1 Code on PHP 7.1 Integration Tests"
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "7.1"
tools: composer:v2
- name: "Install PHP 8 dependencies"
run: "composer update --no-progress --prefer-dist"
- name: "Tests"
run: "test_old/run-php-src.sh 8.1.6"

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.0beta4
allow_failures:
- name: PHP 8.0 Code on PHP 7.0 Integration Tests
fast_finish: true
script: vendor/bin/phpunit

View File

@ -1,7 +1,269 @@
Version 4.10.3-dev
------------------
Version 4.19.4 (2024-09-29)
---------------------------
### Fixed
* Remove the `Parser\Php8` class from PHP-Parser 5.x that was mistakenly added.
Version 4.19.3 (2024-09-29)
---------------------------
### Fixed
* Include the trailing semicolon inside `Stmt\GroupUse` nodes, making them consistent with
`Stmt\Use_` nodes.
* Fixed indentation sometimes becoming negative in formatting-preserving pretty printer, resulting
in `ValueError`s.
Version 4.19.2 (2024-09-17)
---------------------------
### Added
* Added support for passing enum values to various builder methods, like `BuilderFactory::val()`.
Version 4.19.1 (2024-03-17)
---------------------------
### Fixed
* Fixed "Optional parameter before required parameter" deprecation warning introduced in
previous version.
Version 4.19.0 (2024-03-16)
---------------------------
### Changed
* Do not use implicitly nullable parameters, which are deprecated in PHP 8.4.
* Remove support for running on PHP 7.0, which does not support explicitly nullable parameters.
Version 4.18.0 (2023-12-10)
---------------------------
### Added
* Added methods `ParserFactory::createForNewestSupportedVersion()` and
`ParserFactory::createForHostVersion()` for forward-compatibility with PHP-Parser 5.0.
### Fixed
* Fixed missing name resolution of class constant types.
* Fixed class members being dropped if an error is encountered while parsing a later class member
(when error recovery is enabeld).
### Changed
* The `grammar/` directory has been excluded from exported git archives.
Version 4.17.1 (2023-08-13)
---------------------------
### Fixed
* Fixed phpdoc mismatches for `ClassConst::$type` introduced in previous release.
Version 4.17.0 (2023-08-13)
---------------------------
### Added
* [PHP 8.3] Added support for typed class constants.
* [PHP 8.3] Added supprot for dynamic class constant fetch.
* [PHP 8.3] Added support for readonly anonymous classes.
### Fixed
* Fixed missing required parentheses when pretty printing new with an expression class name.
* Fixed missing required parentheses when pretty printing `(CONST)::$x` and similar.
Version 4.16.0 (2023-06-25)
---------------------------
### Added
* Added `Name::getParts()` method for forward-compatibility with PHP-Parser 5.
### Deprecated
* Deprecated direct access to `Name::$parts`, which will be removed in PHP-Parser 5.
Version 4.15.5 (2023-05-19)
---------------------------
### Added
* Added `makePrivate()`, `makeProtected()`, `makePublic()` and `makeReadonly()` methods to
`Builder\Param` to allow the creation of promoted parameters.
Version 4.15.4 (2023-03-05)
---------------------------
### Fixed
* Fixed formatting-preservation for alternative if syntax with trailing comments.
Version 4.15.3 (2023-01-16)
---------------------------
### Fixed
* Support readonly property with PHP 8.2 DNF type.
* Fixed PHP attribute group and PHP-Parser attribute mixup in EnumCase builder.
Version 4.15.2 (2022-11-12)
---------------------------
### Fixed
* Fixed parsing of large hex float literals that contain an "e" character.
* Fixed tests to pass on 32-bit.
* Fixed generation of invalid code when using formatting-preserving pretty printer with code that
uses inline HTML.
Version 4.15.1 (2022-09-04)
---------------------------
### Fixed
* Fixed formatting preservation when adding *multiple* attributes to a class/method/etc that
previously had none. This fixes a regression in the 4.15.0 release.
Version 4.15.0 (2022-09-03)
---------------------------
### Added
* PHP 8.2: Added support for `true` type.
* PHP 8.2: Added support for DNF types.
### Fixed
* Support `readonly` as a function name.
* Added `__serialize` and `__unserialize` to magic method list.
* Fixed bounds check in `Name::slice()`.
* Fixed formatting preservation when adding attributes to a class/method/etc that previously had none.
Version 4.14.0 (2022-05-31)
---------------------------
### Added
* Added support for readonly classes.
* Added `rawValue` attribute to `LNumber`, `DNumber` and `String_` nodes, which stores the unparsed
value of the literal (e.g. `"1_000"` rather than `1000`).
Version 4.13.2 (2021-11-30)
---------------------------
### Added
* Added builders for enums and enum cases.
### Fixed
* NullsafeMethodCall now extends from CallLike.
* The `namespacedName` property populated by the `NameResolver` is now declared on relevant nodes,
to avoid a dynamic property deprecation warning with PHP 8.2.
Version 4.13.1 (2021-11-03)
---------------------------
### Fixed
* Support reserved keywords as enum cases.
* Support array unpacking in constant expression evaluator.
Version 4.13.0 (2021-09-20)
---------------------------
### Added
* [PHP 8.1] Added support for intersection types using a new `IntersectionType` node. Additionally
a `ComplexType` parent class for `NullableType`, `UnionType` and `IntersectionType` has been
added.
* [PHP 8.1] Added support for explicit octal literals.
* [PHP 8.1] Added support for first-class callables. These are represented using a call whose first
argument is a `VariadicPlaceholder`. The representation is intended to be forward-compatible with
partial function application, just like the PHP feature itself. Call nodes now extend from
`Expr\CallLike`, which provides an `isFirstClassCallable()` method to determine whether a
placeholder id present. `getArgs()` can be used to assert that the call is not a first-class
callable and returns `Arg[]` rather than `array<Arg|VariadicPlaceholder>`.
### Fixed
* Multiple modifiers for promoted properties are now accepted. In particular this allows something
like `public readonly` for promoted properties.
* Formatting-preserving pretty printing for comments in array literals has been fixed.
Version 4.12.0 (2021-07-21)
---------------------------
### Added
* [PHP 8.1] Added support for readonly properties (through a new `MODIFIER_READONLY`).
* [PHP 8.1] Added support for final class constants.
### Fixed
* Fixed compatibility with PHP 8.1. `&` tokens are now canonicalized to the
`T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG` and `T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG` tokens
used in PHP 8.1. This happens unconditionally, regardless of whether the emulative lexer is used.
Version 4.11.0 (2021-07-03)
---------------------------
### Added
* `BuilderFactory::args()` now accepts named arguments.
* `BuilderFactory::attribute()` has been added.
* An `addAttribute()` method accepting an `Attribute` or `AttributeGroup` has been adde to all
builders that accept attributes, such as `Builder\Class_`.
### Fixed
* `NameResolver` now handles enums.
* `PrettyPrinter` now prints backing enum type.
* Builder methods for types now property handle `never` type.
Version 4.10.5 (2021-05-03)
---------------------------
### Added
* [PHP 8.1] Added support for enums. These are represented using the `Stmt\Enum_` and
`Stmt\EnumCase` nodes.
* [PHP 8.1] Added support for never type. This type will now be returned as an `Identifier` rather
than `Name`.
* Added `ClassConst` builder.
### Changed
* Non-UTF-8 code units in strings will now be hex-encoded.
### Fixed
* Fixed precedence of arrow functions.
Version 4.10.4 (2020-12-20)
---------------------------
### Fixed
* Fixed position information for variable-variables (#741).
* Fixed position information for traits/interfaces preceded by if statement (#738).
Version 4.10.3 (2020-12-03)
---------------------------
### Fixed
* Fixed formatting-preserving pretty printing for `"{$x}"`.
* Ternary expressions are now treated as non-associative in the pretty printer, in order to
generate code that is compatible with the parentheses requirement introduced in PHP 8.
* Removed no longer necessary `error_clear_last()` call in lexer, which may interfere with fatal
error handlers if invoked during shutdown.
Nothing yet.
Version 4.10.2 (2020-09-26)
------------------

View File

@ -1,12 +1,12 @@
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.2 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 4.x**][doc_master] (stable; for running on PHP >= 7.0; for parsing PHP 5.2 to PHP 7.4).
[**Documentation for version 4.x**][doc_4_x] (stable; for running on PHP >= 7.1; for parsing PHP 5.2 to PHP 8.2).
[Documentation for version 3.x][doc_3_x] (unsupported; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.2).
@ -15,7 +15,7 @@ Features
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.
* The AST contains accurate location information.
* Dumping the AST in human-readable form.
@ -215,11 +215,11 @@ Component documentation:
* [JSON representation](doc/component/JSON_representation.markdown)
* JSON encoding and decoding of ASTs
* [Performance](doc/component/Performance.markdown)
* Disabling XDebug
* Disabling Xdebug
* Reusing objects
* Garbage collection impact
* [Frequently asked questions](doc/component/FAQ.markdown)
* Parent and sibling references
[doc_3_x]: https://github.com/nikic/PHP-Parser/tree/3.x/doc
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc
[doc_4_x]: https://github.com/nikic/PHP-Parser/tree/4.x/doc

View File

@ -10,7 +10,7 @@ foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php
ini_set('xdebug.max_nesting_level', 3000);
// Disable XDebug var_dump() output truncation
// Disable Xdebug var_dump() output truncation
ini_set('xdebug.var_display_max_children', -1);
ini_set('xdebug.var_display_max_data', -1);
ini_set('xdebug.var_display_max_depth', -1);

View File

@ -13,11 +13,11 @@
}
],
"require": {
"php": ">=7.0",
"php": ">=7.1",
"ext-tokenizer": "*"
},
"require-dev": {
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"ircmaxell/php-yacc": "^0.0.7"
},
"extra": {

View File

@ -1,7 +1,7 @@
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?
-----------------
@ -26,11 +26,15 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
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
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
This allows to parse PHP 7.4 source code running on PHP 7.0, for example. This emulation is somewhat
This allows to parse PHP 7.4 source code running on PHP 7.1, for example. This emulation is somewhat
hacky and not perfect, but it should work well on any sane code.
What output does it produce?

View File

@ -19,7 +19,7 @@ ini_set('xdebug.max_nesting_level', 3000);
```
This ensures that there will be no errors when traversing highly nested node trees. However, it is
preferable to disable XDebug completely, as it can easily make this library more than five times
preferable to disable Xdebug completely, as it can easily make this library more than five times
slower.
Parsing

View File

@ -39,7 +39,7 @@ Component documentation
* [JSON representation](component/JSON_representation.markdown)
* JSON encoding and decoding of ASTs
* [Performance](component/Performance.markdown)
* Disabling XDebug
* Disabling Xdebug
* Reusing objects
* Garbage collection impact
* [Frequently asked questions](component/FAQ.markdown)

View File

@ -36,7 +36,7 @@ if ($e->hasColumnInfo()) {
echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
// or:
echo $e->getMessageWithColumnInfo();
echo $e->getMessageWithColumnInfo($code);
} else {
echo $e->getMessage();
}
@ -72,4 +72,4 @@ if (null !== $stmts) {
}
```
The `NameResolver` visitor also accepts an `ErrorHandler` as a constructor argument.
The `NameResolver` visitor also accepts an `ErrorHandler` as a constructor argument.

View File

@ -8,20 +8,20 @@ described in the following.
Xdebug
------
Running PHP with XDebug adds a lot of overhead, especially for code that performs many method calls.
Just by loading XDebug (without enabling profiling or other more intrusive XDebug features), you
Running PHP with Xdebug adds a lot of overhead, especially for code that performs many method calls.
Just by loading Xdebug (without enabling profiling or other more intrusive Xdebug features), you
can expect that code using PHP-Parser will be approximately *five times slower*.
As such, you should make sure that XDebug is not loaded when using this library. Note that setting
the `xdebug.default_enable=0` ini option does *not* disable XDebug. The *only* way to disable
XDebug is to not load the extension in the first place.
As such, you should make sure that Xdebug is not loaded when using this library. Note that setting
the `xdebug.default_enable=0` ini option does *not* disable Xdebug. The *only* way to disable
Xdebug is to not load the extension in the first place.
If you are building a command-line utility for use by developers (who often have XDebug enabled),
you may want to consider automatically restarting PHP with XDebug unloaded. The
If you are building a command-line utility for use by developers (who often have Xdebug enabled),
you may want to consider automatically restarting PHP with Xdebug unloaded. The
[composer/xdebug-handler](https://github.com/composer/xdebug-handler) package can be used to do
this.
If you do run with XDebug, you may need to increase the `xdebug.max_nesting_level` option to a
If you do run with Xdebug, you may need to increase the `xdebug.max_nesting_level` option to a
higher level, such as 3000. While the parser itself is recursion free, most other code working on
the AST uses recursion and will generate an error if the value of this option is too low.
@ -62,4 +62,4 @@ disproportionally (and are usually generated anyway).
Of course, you can also try to (temporarily) disable GC. By design the AST generated by PHP-Parser
is cycle-free, so the AST itself will never cause leaks with GC disabled. However, other code
(including for example the parser object itself) may hold cycles, so disabling of GC should be
approached with care.
approached with care.

View File

@ -193,7 +193,7 @@ anonymous classes), you know that once you've seen a class declaration, there is
checking all it's child nodes, because PHP does not allow nesting classes. In this case, you can
instruct the traverser to not recurse into the class node:
```
```php
private $classes = [];
public function enterNode(Node $node) {
if ($node instanceof Node\Stmt\Class_) {
@ -211,7 +211,7 @@ after finding it. For example, if you are looking for the node of a class with a
discounting exotic cases like conditionally defining a class two times), you can stop traversal
once you found it:
```
```php
private $class = null;
public function enterNode(Node $node) {
if ($node instanceof Node\Stmt\Class_ &&
@ -251,7 +251,7 @@ Stmt_Return(
the following method calls will be performed:
```
```php
$visitorA->enterNode(Stmt_Return)
$visitorB->enterNode(Stmt_Return)
$visitorA->enterNode(Expr_Variable)

View File

@ -20,6 +20,11 @@ top_statement_list:
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
ampersand:
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
;
reserved_non_modifiers:
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
@ -83,7 +88,7 @@ top_statement:
$this->checkNamespace($$); }
| T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration ';' { $$ = $1; }
| group_use_declaration { $$ = $1; }
| T_CONST constant_declaration_list ';' { $$ = Stmt\Const_[$2]; }
;
@ -93,9 +98,9 @@ use_type:
;
group_use_declaration:
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' ';'
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' ';'
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
;
@ -246,7 +251,12 @@ variables_list:
optional_ref:
/* empty */ { $$ = false; }
| '&' { $$ = true; }
| ampersand { $$ = true; }
;
optional_arg_ref:
/* empty */ { $$ = false; }
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; }
;
optional_ellipsis:
@ -254,8 +264,13 @@ optional_ellipsis:
| T_ELLIPSIS { $$ = true; }
;
identifier_maybe_readonly:
identifier { $$ = $1; }
| T_READONLY { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
;
@ -378,7 +393,7 @@ new_else_single:
foreach_variable:
variable { $$ = array($1, false); }
| '&' variable { $$ = array($2, true); }
| ampersand variable { $$ = array($2, true); }
| list_expr { $$ = array($1, false); }
;
@ -393,9 +408,9 @@ non_empty_parameter_list:
;
parameter:
optional_param_type optional_ref optional_ellipsis plain_variable
optional_param_type optional_arg_ref optional_ellipsis plain_variable
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
| optional_param_type optional_ref optional_ellipsis plain_variable '=' static_scalar
| optional_param_type optional_arg_ref optional_ellipsis plain_variable '=' static_scalar
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
;
@ -428,7 +443,7 @@ non_empty_argument_list:
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| '&' variable { $$ = Node\Arg[$2, true, false]; }
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
;
@ -454,7 +469,7 @@ static_var:
;
class_statement_list_ex:
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } }
| /* empty */ { init(); }
;
@ -562,8 +577,8 @@ expr:
variable { $$ = $1; }
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' '&' new_expr { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' ampersand new_expr { $$ = Expr\AssignRef[$1, $4]; }
| new_expr { $$ = $1; }
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
@ -589,7 +604,8 @@ expr:
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
@ -678,9 +694,7 @@ array_expr:
scalar_dereference:
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[Scalar\String_::fromString($1, attributes()), $3]; }
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */
@ -712,8 +726,13 @@ lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
;
name_readonly:
T_READONLY { $$ = Name[$1]; }
;
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
@ -782,10 +801,8 @@ ctor_arguments:
common_scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes(), false); }
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
@ -816,7 +833,10 @@ static_operation:
| static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| static_scalar '&' static_scalar { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG static_scalar
{ $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG static_scalar
{ $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; }
@ -986,9 +1006,9 @@ non_empty_array_pair_list:
array_pair:
expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
| T_ELLIPSIS expr { $$ = new Expr\ArrayItem($2, null, false, attributes(), true); }
;
encaps_list:

View File

@ -20,6 +20,11 @@ top_statement_list:
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
ampersand:
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
;
reserved_non_modifiers:
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
@ -28,20 +33,20 @@ reserved_non_modifiers:
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
| T_MATCH
| T_MATCH | T_ENUM
;
semi_reserved:
reserved_non_modifiers
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY
;
identifier_ex:
identifier_maybe_reserved:
T_STRING { $$ = Node\Identifier[$1]; }
| semi_reserved { $$ = Node\Identifier[$1]; }
;
identifier:
identifier_not_reserved:
T_STRING { $$ = Node\Identifier[$1]; }
;
@ -128,7 +133,7 @@ top_statement:
$this->checkNamespace($$); }
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration semi { $$ = $1; }
| group_use_declaration { $$ = $1; }
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; }
;
@ -138,9 +143,9 @@ use_type:
;
group_use_declaration:
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}' semi
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}' semi
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
;
@ -176,14 +181,14 @@ non_empty_inline_use_declarations:
unprefixed_use_declaration:
namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| namespace_name T_AS identifier
| namespace_name T_AS identifier_not_reserved
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
use_declaration:
legacy_namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| legacy_namespace_name T_AS identifier
| legacy_namespace_name T_AS identifier_not_reserved
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
@ -203,7 +208,7 @@ non_empty_constant_declaration_list:
;
constant_declaration:
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
identifier_not_reserved '=' expr { $$ = Node\Const_[$1, $3]; }
;
class_const_list:
@ -216,7 +221,10 @@ non_empty_class_const_list:
;
class_const:
identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; }
T_STRING '=' expr
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
| semi_reserved '=' expr
{ $$ = Node\Const_[new Node\Identifier($1, stackAttributes(#1)), $3]; }
;
inner_statement_list_ex:
@ -284,8 +292,8 @@ non_empty_statement:
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
| T_TRY '{' inner_statement_list '}' catches optional_finally
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_GOTO identifier semi { $$ = Stmt\Goto_[$2]; }
| identifier ':' { $$ = Stmt\Label[$1]; }
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
| error { $$ = array(); /* means: no statement */ }
;
@ -327,7 +335,12 @@ non_empty_variables_list:
optional_ref:
/* empty */ { $$ = false; }
| '&' { $$ = true; }
| ampersand { $$ = true; }
;
optional_arg_ref:
/* empty */ { $$ = false; }
| T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = true; }
;
optional_ellipsis:
@ -340,31 +353,58 @@ block_or_error:
| error { $$ = []; }
;
identifier_maybe_readonly:
identifier_not_reserved { $$ = $1; }
| T_READONLY { $$ = Node\Identifier[$1]; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
| attributes T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
| attributes T_FUNCTION optional_ref identifier_maybe_readonly '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
;
class_declaration_statement:
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6, 'attrGroups' => []]];
$this->checkClass($$, #2); }
| attributes class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
| attributes class_entry_type identifier_not_reserved extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkClass($$, #3); }
| optional_attributes T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
| optional_attributes T_INTERFACE identifier_not_reserved interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]];
$this->checkInterface($$, #3); }
| optional_attributes T_TRAIT identifier '{' class_statement_list '}'
| optional_attributes T_TRAIT identifier_not_reserved '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
| optional_attributes T_ENUM identifier_not_reserved enum_scalar_type implements_list '{' class_statement_list '}'
{ $$ = Stmt\Enum_[$3, ['scalarType' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkEnum($$, #3); }
;
enum_scalar_type:
/* empty */ { $$ = null; }
| ':' type { $$ = $2; }
enum_case_expr:
/* empty */ { $$ = null; }
| '=' expr { $$ = $2; }
;
class_entry_type:
T_CLASS { $$ = 0; }
| T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; }
| class_modifiers T_CLASS { $$ = $1; }
;
class_modifiers:
class_modifier { $$ = $1; }
| class_modifiers class_modifier { $this->checkClassModifier($1, $2, #2); $$ = $1 | $2; }
;
class_modifier:
T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
;
extends_from:
@ -417,7 +457,7 @@ non_empty_declare_list:
;
declare_list_element:
identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
identifier_not_reserved '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
;
switch_case_list:
@ -481,7 +521,8 @@ new_elseif_list:
;
new_elseif:
T_ELSEIF '(' expr ')' ':' inner_statement_list { $$ = Stmt\ElseIf_[$3, $6]; }
T_ELSEIF '(' expr ')' ':' inner_statement_list
{ $$ = Stmt\ElseIf_[$3, $6]; $this->fixupAlternativeElse($$); }
;
else_single:
@ -491,12 +532,13 @@ else_single:
new_else_single:
/* empty */ { $$ = null; }
| T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; }
| T_ELSE ':' inner_statement_list
{ $$ = Stmt\Else_[$3]; $this->fixupAlternativeElse($$); }
;
foreach_variable:
variable { $$ = array($1, false); }
| '&' variable { $$ = array($2, true); }
| ampersand variable { $$ = array($2, true); }
| list_expr { $$ = array($1, false); }
| array_short_syntax { $$ = array($1, false); }
;
@ -511,21 +553,30 @@ non_empty_parameter_list:
| non_empty_parameter_list ',' parameter { push($1, $3); }
;
optional_visibility_modifier:
optional_property_modifiers:
/* empty */ { $$ = 0; }
| T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
| optional_property_modifiers property_modifier
{ $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
;
property_modifier:
T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
;
parameter:
optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
| optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
| optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis plain_variable '=' expr
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
| optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
| optional_attributes optional_property_modifiers optional_type_without_static
optional_arg_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
;
@ -533,6 +584,7 @@ type_expr:
type { $$ = $1; }
| '?' type { $$ = Node\NullableType[$2]; }
| union_type { $$ = Node\UnionType[$1]; }
| intersection_type { $$ = $1; }
;
type:
@ -546,20 +598,52 @@ type_without_static:
| T_CALLABLE { $$ = Node\Identifier['callable']; }
;
union_type_element:
type { $$ = $1; }
| '(' intersection_type ')' { $$ = $2; }
;
union_type:
type '|' type { init($1, $3); }
| union_type '|' type { push($1, $3); }
union_type_element '|' union_type_element { init($1, $3); }
| union_type '|' union_type_element { push($1, $3); }
;
union_type_without_static_element:
type_without_static { $$ = $1; }
| '(' intersection_type_without_static ')' { $$ = $2; }
;
union_type_without_static:
type_without_static '|' type_without_static { init($1, $3); }
| union_type_without_static '|' type_without_static { push($1, $3); }
union_type_without_static_element '|' union_type_without_static_element { init($1, $3); }
| union_type_without_static '|' union_type_without_static_element { push($1, $3); }
;
intersection_type_list:
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { init($1, $3); }
| intersection_type_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
{ push($1, $3); }
;
intersection_type:
intersection_type_list { $$ = Node\IntersectionType[$1]; }
;
intersection_type_without_static_list:
type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ init($1, $3); }
| intersection_type_without_static_list T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static
{ push($1, $3); }
;
intersection_type_without_static:
intersection_type_without_static_list { $$ = Node\IntersectionType[$1]; }
;
type_expr_without_static:
type_without_static { $$ = $1; }
| '?' type_without_static { $$ = Node\NullableType[$2]; }
| union_type_without_static { $$ = Node\UnionType[$1]; }
| intersection_type_without_static { $$ = $1; }
;
optional_type_without_static:
@ -576,6 +660,11 @@ optional_return_type:
argument_list:
'(' ')' { $$ = array(); }
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
| '(' variadic_placeholder ')' { init($2); }
;
variadic_placeholder:
T_ELLIPSIS { $$ = Node\VariadicPlaceholder[]; }
;
non_empty_argument_list:
@ -585,9 +674,9 @@ non_empty_argument_list:
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| '&' variable { $$ = Node\Arg[$2, true, false]; }
| ampersand variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
| identifier_ex ':' expr
| identifier_maybe_reserved ':' expr
{ $$ = new Node\Arg($3, false, false, attributes(), $1); }
;
@ -601,7 +690,7 @@ non_empty_global_var_list:
;
global_var:
simple_variable { $$ = Expr\Variable[$1]; }
simple_variable { $$ = $1; }
;
static_var_list:
@ -619,7 +708,7 @@ static_var:
;
class_statement_list_ex:
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } else { $$ = $1; } }
| /* empty */ { init(); }
;
@ -636,10 +725,16 @@ class_statement:
| optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
$this->checkClassConst($$, #2); }
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
| optional_attributes method_modifiers T_CONST type_expr class_const_list semi
{ $$ = new Stmt\ClassConst($5, $2, attributes(), $1, $4);
$this->checkClassConst($$, #2); }
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_maybe_reserved '(' parameter_list ')'
optional_return_type method_body
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
$this->checkClassMethod($$, #2); }
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
| optional_attributes T_CASE identifier_maybe_reserved enum_case_expr semi
{ $$ = Stmt\EnumCase[$3, $4, $1]; }
| error { $$ = null; /* will be skipped */ }
;
@ -656,22 +751,22 @@ trait_adaptation_list:
trait_adaptation:
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
| trait_method_reference T_AS member_modifier identifier_ex ';'
| trait_method_reference T_AS member_modifier identifier_maybe_reserved ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
| trait_method_reference T_AS member_modifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
| trait_method_reference T_AS identifier ';'
| trait_method_reference T_AS identifier_not_reserved ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
;
trait_method_reference_fully_qualified:
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
name T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved { $$ = array($1, $3); }
;
trait_method_reference:
trait_method_reference_fully_qualified { $$ = $1; }
| identifier_ex { $$ = array(null, $1); }
| identifier_maybe_reserved { $$ = array(null, $1); }
;
method_body:
@ -701,6 +796,7 @@ member_modifier:
| T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; }
| T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
| T_READONLY { $$ = Stmt\Class_::MODIFIER_READONLY; }
;
property_declaration_list:
@ -745,7 +841,7 @@ expr:
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
| array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' ampersand variable { $$ = Expr\AssignRef[$1, $4]; }
| new_expr { $$ = $1; }
| match { $$ = $1; }
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
@ -772,7 +868,8 @@ expr:
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
@ -832,18 +929,18 @@ expr:
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
| T_THROW expr { $$ = Expr\Throw_[$2]; }
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8, '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, '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' => []]]; }
| attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
| attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => $1]]; }
| attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
| attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr %prec T_THROW
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $4, 'params' => $6, 'returnType' => $8, 'expr' => $10, 'attrGroups' => $1]]; }
| attributes T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => false, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
@ -852,8 +949,8 @@ expr:
;
anonymous_class:
optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
optional_attributes class_entry_type ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
$this->checkClass($$[0], -1); }
;
@ -881,8 +978,13 @@ lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
;
name_readonly:
T_READONLY { $$ = Name[$1]; }
;
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| name_readonly argument_list { $$ = Expr\FuncCall[$1, $2]; }
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }
@ -942,8 +1044,10 @@ constant:
;
class_constant:
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_maybe_reserved
{ $$ = Expr\ClassConstFetch[$1, $3]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}'
{ $$ = Expr\ClassConstFetch[$1, $4]; }
/* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be
an unfinished static property fetch or unfinished scoped call. */
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error
@ -961,9 +1065,7 @@ dereferencable_scalar:
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs); }
| array_short_syntax { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_::fromString($1, attributes()); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
@ -971,7 +1073,7 @@ dereferencable_scalar:
scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_DNUMBER { $$ = Scalar\DNumber::fromString($1, attributes()); }
| dereferencable_scalar { $$ = $1; }
| constant { $$ = $1; }
| class_constant { $$ = $1; }
@ -1007,7 +1109,7 @@ callable_expr:
;
callable_variable:
simple_variable { $$ = Expr\Variable[$1]; }
simple_variable { $$ = $1; }
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| function_call { $$ = $1; }
@ -1032,15 +1134,15 @@ variable:
;
simple_variable:
T_VARIABLE { $$ = parseVar($1); }
| '$' '{' expr '}' { $$ = $3; }
plain_variable { $$ = $1; }
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
| '$' simple_variable { $$ = Expr\Variable[$2]; }
| '$' error { $$ = Expr\Error[]; $this->errorState = 2; }
| '$' error { $$ = Expr\Variable[Expr\Error[]]; $this->errorState = 2; }
;
static_member_prop_name:
simple_variable
{ $var = $1; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
{ $var = $1->name; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
;
static_member:
@ -1049,7 +1151,7 @@ static_member:
;
new_variable:
simple_variable { $$ = Expr\Variable[$1]; }
simple_variable { $$ = $1; }
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
@ -1061,15 +1163,15 @@ new_variable:
;
member_name:
identifier_ex { $$ = $1; }
identifier_maybe_reserved { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| simple_variable { $$ = Expr\Variable[$1]; }
| simple_variable { $$ = $1; }
;
property_name:
identifier { $$ = $1; }
identifier_not_reserved { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| simple_variable { $$ = Expr\Variable[$1]; }
| simple_variable { $$ = $1; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
;
@ -1095,12 +1197,12 @@ inner_array_pair_list:
array_pair:
expr { $$ = Expr\ArrayItem[$1, null, false]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| ampersand variable { $$ = Expr\ArrayItem[$2, null, true]; }
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW ampersand variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
| T_ELLIPSIS expr { $$ = new Expr\ArrayItem($2, null, false, attributes(), true); }
| /* empty */ { $$ = null; }
;
@ -1122,8 +1224,10 @@ encaps_str_varname:
encaps_var:
plain_variable { $$ = $1; }
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| plain_variable T_OBJECT_OPERATOR identifier_not_reserved
{ $$ = Expr\PropertyFetch[$1, $3]; }
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier_not_reserved
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'

184
grammar/phpyLang.php Normal file
View File

@ -0,0 +1,184 @@
<?php
///////////////////////////////
/// Utility regex constants ///
///////////////////////////////
const LIB = '(?(DEFINE)
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
)';
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?&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 ('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
require __DIR__ . '/phpyLang.php';
$grammarFileToName = [
__DIR__ . '/php5.y' => 'Php5',
__DIR__ . '/php7.y' => 'Php7',
@ -23,21 +25,6 @@ $options = array_flip($argv);
$optionDebug = isset($options['--debug']);
$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 ///
///////////////////
@ -49,10 +36,7 @@ foreach ($grammarFileToName as $grammarFile => $name) {
$grammarCode = file_get_contents($grammarFile);
$grammarCode = str_replace('%tokens', $tokens, $grammarCode);
$grammarCode = resolveNodes($grammarCode);
$grammarCode = resolveMacros($grammarCode);
$grammarCode = resolveStackAccess($grammarCode);
$grammarCode = preprocessGrammar($grammarCode);
file_put_contents($tmpGrammarFile, $grammarCode);
@ -77,151 +61,9 @@ foreach ($grammarFileToName as $grammarFile => $name) {
}
}
///////////////////////////////
/// Preprocessing 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);
}
////////////////////////////////
/// Utility helper functions ///
////////////////////////////////
function ensureDirExists($dir) {
if (!is_dir($dir)) {
@ -237,25 +79,3 @@ function execCmd($cmd) {
}
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

@ -18,7 +18,7 @@
%left T_BOOLEAN_AND
%left '|'
%left '^'
%left '&'
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left T_SL T_SR
@ -74,7 +74,7 @@
%token T_USE
%token T_INSTEADOF
%token T_GLOBAL
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
%token T_VAR
%token T_UNSET
%token T_ISSET
@ -83,6 +83,7 @@
%token T_CLASS
%token T_TRAIT
%token T_INTERFACE
%token T_ENUM
%token T_EXTENDS
%token T_IMPLEMENTS
%token T_OBJECT_OPERATOR
@ -110,4 +111,5 @@
%token T_NAME_FULLY_QUALIFIED
%token T_NAME_QUALIFIED
%token T_NAME_RELATIVE
%token T_ATTRIBUTE
%token T_ATTRIBUTE
%token T_ENUM

View File

@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Const_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
class ClassConst implements PhpParser\Builder
{
protected $flags = 0;
protected $attributes = [];
protected $constants = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/** @var Identifier|Node\Name|Node\ComplexType */
protected $type;
/**
* Creates a class constant builder
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $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|\UnitEnum $value Value
*
* @return $this The builder instance (for fluid interface)
*/
public function addConst($name, $value) {
$this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value));
return $this;
}
/**
* Makes the constant public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Makes the constant protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Makes the constant private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Makes the constant final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
/**
* Sets doc comment for the constant.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Sets the constant type.
*
* @param string|Node\Name|Identifier|Node\ComplexType $type
*
* @return $this
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\ClassConst The built constant node
*/
public function getNode(): PhpParser\Node {
return new Stmt\ClassConst(
$this->constants,
$this->flags,
$this->attributes,
$this->attributeGroups,
$this->type
);
}
}

View File

@ -4,6 +4,7 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
@ -20,6 +21,9 @@ class Class_ extends Declaration
protected $properties = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a class builder.
*
@ -63,7 +67,7 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
return $this;
}
@ -74,7 +78,13 @@ class Class_ extends Declaration
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
public function makeReadonly() {
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}
@ -106,6 +116,19 @@ class Class_ extends Declaration
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
@ -117,6 +140,7 @@ class Class_ extends Declaration
'extends' => $this->extends,
'implements' => $this->implements,
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
class EnumCase implements PhpParser\Builder
{
protected $name;
protected $value = null;
protected $attributes = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an enum case builder.
*
* @param string|Identifier $name Name
*/
public function __construct($name) {
$this->name = $name;
}
/**
* Sets the value.
*
* @param Node\Expr|string|int $value
*
* @return $this
*/
public function setValue($value) {
$this->value = BuilderHelpers::normalizeValue($value);
return $this;
}
/**
* Sets doc comment for the constant.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built enum case node.
*
* @return Stmt\EnumCase The built constant node
*/
public function getNode(): PhpParser\Node {
return new Stmt\EnumCase(
$this->name,
$this->value,
$this->attributeGroups,
$this->attributes
);
}
}

View File

@ -0,0 +1,117 @@
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class Enum_ extends Declaration
{
protected $name;
protected $scalarType = null;
protected $implements = [];
protected $uses = [];
protected $enumCases = [];
protected $constants = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an enum builder.
*
* @param string $name Name of the enum
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Sets the scalar type.
*
* @param string|Identifier $type
*
* @return $this
*/
public function setScalarType($scalarType) {
$this->scalarType = BuilderHelpers::normalizeType($scalarType);
return $this;
}
/**
* Implements one or more interfaces.
*
* @param Name|string ...$interfaces Names of interfaces to implement
*
* @return $this The builder instance (for fluid interface)
*/
public function implement(...$interfaces) {
foreach ($interfaces as $interface) {
$this->implements[] = BuilderHelpers::normalizeName($interface);
}
return $this;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
$targets = [
Stmt\TraitUse::class => &$this->uses,
Stmt\EnumCase::class => &$this->enumCases,
Stmt\ClassConst::class => &$this->constants,
Stmt\ClassMethod::class => &$this->methods,
];
$class = \get_class($stmt);
if (!isset($targets[$class])) {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
$targets[$class][] = $stmt;
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Enum_ The built enum node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Enum_($this->name, [
'scalarType' => $this->scalarType,
'implements' => $this->implements,
'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@ -61,8 +61,7 @@ abstract class FunctionLike extends Declaration
/**
* Sets the return type for PHP 7.
*
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float,
* bool, iterable, or a class/interface name.
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type
*
* @return $this The builder instance (for fluid interface)
*/

View File

@ -12,6 +12,9 @@ class Function_ extends FunctionLike
protected $name;
protected $stmts = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a function builder.
*
@ -34,6 +37,19 @@ class Function_ extends FunctionLike
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built function node.
*
@ -45,6 +61,7 @@ class Function_ extends FunctionLike
'params' => $this->params,
'returnType' => $this->returnType,
'stmts' => $this->stmts,
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@ -4,6 +4,7 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
@ -14,6 +15,9 @@ class Interface_ extends Declaration
protected $constants = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an interface builder.
*
@ -61,6 +65,19 @@ class Interface_ extends Declaration
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built interface node.
*
@ -70,6 +87,7 @@ class Interface_ extends Declaration
return new Stmt\Interface_($this->name, [
'extends' => $this->extends,
'stmts' => array_merge($this->constants, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@ -15,6 +15,9 @@ class Method extends FunctionLike
/** @var array|null */
protected $stmts = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a method builder.
*
@ -112,6 +115,19 @@ class Method extends FunctionLike
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built method node.
*
@ -124,6 +140,7 @@ class Method extends FunctionLike
'params' => $this->params,
'returnType' => $this->returnType,
'stmts' => $this->stmts,
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View File

@ -37,7 +37,7 @@ class Namespace_ extends Declaration
/**
* Returns the built node.
*
* @return Node The built node
* @return Stmt\Namespace_ The built node
*/
public function getNode() : Node {
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);

View File

@ -19,6 +19,11 @@ class Param implements PhpParser\Builder
protected $variadic = false;
protected $flags = 0;
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a parameter builder.
*
@ -44,7 +49,7 @@ class Param implements PhpParser\Builder
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*/
@ -60,7 +65,7 @@ class Param implements PhpParser\Builder
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*
@ -92,6 +97,63 @@ class Param implements PhpParser\Builder
return $this;
}
/**
* Makes the (promoted) parameter public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Makes the (promoted) parameter protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Makes the (promoted) parameter private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Makes the (promoted) parameter readonly.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeReadonly() {
$this->flags = BuilderHelpers::addModifier($this->flags, Node\Stmt\Class_::MODIFIER_READONLY);
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built parameter node.
*
@ -100,7 +162,7 @@ class Param implements PhpParser\Builder
public function getNode() : Node {
return new Node\Param(
new Node\Expr\Variable($this->name),
$this->default, $this->type, $this->byRef, $this->variadic
$this->default, $this->type, $this->byRef, $this->variadic, [], $this->flags, $this->attributeGroups
);
}
}

View File

@ -4,10 +4,11 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt;
use PhpParser\Node\ComplexType;
class Property implements PhpParser\Builder
{
@ -20,6 +21,9 @@ class Property implements PhpParser\Builder
/** @var null|Identifier|Name|NullableType */
protected $type;
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a property builder.
*
@ -73,6 +77,17 @@ class Property implements PhpParser\Builder
return $this;
}
/**
* Makes the property readonly.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeReadonly() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}
/**
* Sets default value for the property.
*
@ -104,7 +119,7 @@ class Property implements PhpParser\Builder
/**
* Sets the property type for PHP 7.4+.
*
* @param string|Name|NullableType|Identifier $type
* @param string|Name|Identifier|ComplexType $type
*
* @return $this
*/
@ -114,6 +129,19 @@ class Property implements PhpParser\Builder
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
@ -126,7 +154,8 @@ class Property implements PhpParser\Builder
new Stmt\PropertyProperty($this->name, $this->default)
],
$this->attributes,
$this->type
$this->type,
$this->attributeGroups
);
}
}

View File

@ -4,6 +4,7 @@ namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class Trait_ extends Declaration
@ -13,6 +14,9 @@ class Trait_ extends Declaration
protected $properties = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an interface builder.
*
@ -45,6 +49,19 @@ class Trait_ extends Declaration
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built trait node.
*
@ -53,7 +70,8 @@ class Trait_ extends Declaration
public function getNode() : PhpParser\Node {
return new Stmt\Trait_(
$this->name, [
'stmts' => array_merge($this->uses, $this->properties, $this->methods)
'stmts' => array_merge($this->uses, $this->properties, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes
);
}

View File

@ -39,7 +39,7 @@ class Use_ implements Builder
/**
* Returns the built node.
*
* @return Node The built node
* @return Stmt\Use_ The built node
*/
public function getNode() : Node {
return new Stmt\Use_([

View File

@ -12,6 +12,21 @@ use PhpParser\Node\Stmt\Use_;
class BuilderFactory
{
/**
* Creates an attribute node.
*
* @param string|Name $name Name of the attribute
* @param array $args Attribute named arguments
*
* @return Node\Attribute
*/
public function attribute($name, array $args = []) : Node\Attribute {
return new Node\Attribute(
BuilderHelpers::normalizeName($name),
$this->args($args)
);
}
/**
* Creates a namespace builder.
*
@ -56,6 +71,17 @@ class BuilderFactory
return new Builder\Trait_($name);
}
/**
* Creates an enum builder.
*
* @param string $name Name of the enum
*
* @return Builder\Enum_ The created enum builder
*/
public function enum(string $name) : Builder\Enum_ {
return new Builder\Enum_($name);
}
/**
* Creates a trait use builder.
*
@ -161,10 +187,33 @@ class BuilderFactory
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
}
/**
* Creates a class constant builder.
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
*
* @return Builder\ClassConst The created use const builder
*/
public function classConst($name, $value) : Builder\ClassConst {
return new Builder\ClassConst($name, $value);
}
/**
* Creates an enum case builder.
*
* @param string|Identifier $name Name
*
* @return Builder\EnumCase The created use const builder
*/
public function enumCase($name) : Builder\EnumCase {
return new Builder\EnumCase($name);
}
/**
* Creates node a for a literal value.
*
* @param Expr|bool|null|int|float|string|array $value $value
* @param Expr|bool|null|int|float|string|array|\UnitEnum $value $value
*
* @return Expr
*/
@ -198,12 +247,14 @@ class BuilderFactory
*/
public function args(array $args) : array {
$normalizedArgs = [];
foreach ($args as $arg) {
if ($arg instanceof Arg) {
$normalizedArgs[] = $arg;
} else {
$normalizedArgs[] = new Arg(BuilderHelpers::normalizeValue($arg));
foreach ($args as $key => $arg) {
if (!($arg instanceof Arg)) {
$arg = new Arg(BuilderHelpers::normalizeValue($arg));
}
if (\is_string($key)) {
$arg->name = BuilderHelpers::normalizeIdentifier($key);
}
$normalizedArgs[] = $arg;
}
return $normalizedArgs;
}
@ -282,7 +333,7 @@ class BuilderFactory
public function constFetch($name) : Expr\ConstFetch {
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
}
/**
* Creates a property fetch node.
*
@ -298,15 +349,15 @@ class BuilderFactory
/**
* Creates a class constant fetch node.
*
* @param string|Name|Expr $class Class name
* @param string|Identifier $name Constant name
* @param string|Name|Expr $class Class name
* @param string|Identifier|Expr $name Constant name
*
* @return Expr\ClassConstFetch
*/
public function classConstFetch($class, $name): Expr\ClassConstFetch {
return new Expr\ClassConstFetch(
BuilderHelpers::normalizeNameOrExpr($class),
BuilderHelpers::normalizeIdentifier($name)
BuilderHelpers::normalizeIdentifierOrExpr($name)
);
}

View File

@ -2,13 +2,14 @@
namespace PhpParser;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\UnionType;
/**
* This class defines helpers used in the implementation of builders. Don't use it directly.
@ -27,7 +28,9 @@ final class BuilderHelpers
public static function normalizeNode($node) : Node {
if ($node instanceof Builder) {
return $node->getNode();
} elseif ($node instanceof Node) {
}
if ($node instanceof Node) {
return $node;
}
@ -102,7 +105,27 @@ final class BuilderHelpers
* @return Name The normalized name
*/
public static function normalizeName($name) : Name {
return self::normalizeNameCommon($name, false);
if ($name instanceof Name) {
return $name;
}
if (is_string($name)) {
if (!$name) {
throw new \LogicException('Name cannot be empty');
}
if ($name[0] === '\\') {
return new Name\FullyQualified(substr($name, 1));
}
if (0 === strpos($name, 'namespace\\')) {
return new Name\Relative(substr($name, strlen('namespace\\')));
}
return new Name($name);
}
throw new \LogicException('Name must be a string or an instance of Node\Name');
}
/**
@ -113,44 +136,17 @@ final class BuilderHelpers
* @return Name|Expr The normalized name or expression
*/
public static function normalizeNameOrExpr($name) {
return self::normalizeNameCommon($name, true);
}
/**
* Normalizes a name: Converts string names to Name nodes, optionally allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
* @param bool $allowExpr Whether to also allow expressions
*
* @return Name|Expr The normalized name, or expression (if allowed)
*/
private static function normalizeNameCommon($name, bool $allowExpr) {
if ($name instanceof Name) {
if ($name instanceof Expr) {
return $name;
} elseif (is_string($name)) {
if (!$name) {
throw new \LogicException('Name cannot be empty');
}
if ($name[0] === '\\') {
return new Name\FullyQualified(substr($name, 1));
} elseif (0 === strpos($name, 'namespace\\')) {
return new Name\Relative(substr($name, strlen('namespace\\')));
} else {
return new Name($name);
}
}
if ($allowExpr) {
if ($name instanceof Expr) {
return $name;
}
if (!is_string($name) && !($name instanceof Name)) {
throw new \LogicException(
'Name must be a string or an instance of Node\Name or Node\Expr'
);
} else {
throw new \LogicException('Name must be a string or an instance of Node\Name');
}
return self::normalizeName($name);
}
/**
@ -159,18 +155,18 @@ final class BuilderHelpers
* In particular, builtin types become Identifiers, custom types become Names and nullables
* are wrapped in NullableType nodes.
*
* @param string|Name|Identifier|NullableType|UnionType $type The type to normalize
* @param string|Name|Identifier|ComplexType $type The type to normalize
*
* @return Name|Identifier|NullableType|UnionType The normalized type
* @return Name|Identifier|ComplexType The normalized type
*/
public static function normalizeType($type) {
if (!is_string($type)) {
if (
!$type instanceof Name && !$type instanceof Identifier &&
!$type instanceof NullableType && !$type instanceof UnionType
!$type instanceof ComplexType
) {
throw new \LogicException(
'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType'
'Type must be a string, or an instance of Name, Identifier or ComplexType'
);
}
return $type;
@ -183,7 +179,20 @@ final class BuilderHelpers
}
$builtinTypes = [
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed'
'array',
'callable',
'bool',
'int',
'float',
'string',
'iterable',
'void',
'object',
'null',
'false',
'mixed',
'never',
'true',
];
$lowerType = strtolower($type);
@ -193,12 +202,11 @@ final class BuilderHelpers
$type = self::normalizeName($type);
}
if ($nullable && (string) $type === 'void') {
throw new \LogicException('void type cannot be nullable');
}
if ($nullable && (string) $type === 'mixed') {
throw new \LogicException('mixed type cannot be nullable');
$notNullableTypes = [
'void', 'mixed', 'never',
];
if ($nullable && in_array((string) $type, $notNullableTypes)) {
throw new \LogicException(sprintf('%s type cannot be nullable', $type));
}
return $nullable ? new NullableType($type) : $type;
@ -208,28 +216,40 @@ final class BuilderHelpers
* Normalizes a value: Converts nulls, booleans, integers,
* floats, strings and arrays into their respective nodes
*
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
* @param Node\Expr|bool|null|int|float|string|array|\UnitEnum $value The value to normalize
*
* @return Expr The normalized value
*/
public static function normalizeValue($value) : Expr {
if ($value instanceof Node\Expr) {
return $value;
} elseif (is_null($value)) {
}
if (is_null($value)) {
return new Expr\ConstFetch(
new Name('null')
);
} elseif (is_bool($value)) {
}
if (is_bool($value)) {
return new Expr\ConstFetch(
new Name($value ? 'true' : 'false')
);
} elseif (is_int($value)) {
}
if (is_int($value)) {
return new Scalar\LNumber($value);
} elseif (is_float($value)) {
}
if (is_float($value)) {
return new Scalar\DNumber($value);
} elseif (is_string($value)) {
}
if (is_string($value)) {
return new Scalar\String_($value);
} elseif (is_array($value)) {
}
if (is_array($value)) {
$items = [];
$lastKey = -1;
foreach ($value as $itemKey => $itemValue) {
@ -248,9 +268,13 @@ final class BuilderHelpers
}
return new Expr\Array_($items);
} else {
throw new \LogicException('Invalid value');
}
if ($value instanceof \UnitEnum) {
return new Expr\ClassConstFetch(new FullyQualified(\get_class($value)), new Identifier($value->name));
}
throw new \LogicException('Invalid value');
}
/**
@ -263,11 +287,33 @@ final class BuilderHelpers
public static function normalizeDocComment($docComment) : Comment\Doc {
if ($docComment instanceof Comment\Doc) {
return $docComment;
} elseif (is_string($docComment)) {
return new Comment\Doc($docComment);
} else {
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
}
if (is_string($docComment)) {
return new Comment\Doc($docComment);
}
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
}
/**
* Normalizes a attribute: Converts attribute to the Attribute Group if needed.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return Node\AttributeGroup The Attribute Group
*/
public static function normalizeAttribute($attribute) : Node\AttributeGroup
{
if ($attribute instanceof Node\AttributeGroup) {
return $attribute;
}
if (!($attribute instanceof Node\Attribute)) {
throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
}
return new Node\AttributeGroup([$attribute]);
}
/**
@ -282,4 +328,13 @@ final class BuilderHelpers
Stmt\Class_::verifyModifier($modifiers, $modifier);
return $modifiers | $modifier;
}
/**
* Adds a modifier and returns new modifier bitmask.
* @return int New modifiers
*/
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
return $existingModifiers | $modifierToSet;
}
}

View File

@ -2,6 +2,7 @@
namespace PhpParser;
use function array_merge;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
@ -36,7 +37,7 @@ class ConstExprEvaluator
*
* @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
*/
public function __construct(callable $fallbackEvaluator = null) {
public function __construct(?callable $fallbackEvaluator = null) {
$this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
throw new ConstExprEvaluationException(
"Expression of type {$expr->getType()} cannot be evaluated"
@ -150,6 +151,8 @@ class ConstExprEvaluator
foreach ($expr->items as $item) {
if (null !== $item->key) {
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
} elseif ($item->unpack) {
$array = array_merge($array, $this->evaluate($item->value));
} else {
$array[] = $this->evaluate($item->value);
}

View File

@ -19,6 +19,8 @@ class PrintableNewAnonClassNode extends Expr
{
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var int Modifiers */
public $flags;
/** @var Node\Arg[] Arguments */
public $args;
/** @var null|Node\Name Name of extended class */
@ -29,11 +31,12 @@ class PrintableNewAnonClassNode extends Expr
public $stmts;
public function __construct(
array $attrGroups, array $args, Node\Name $extends = null, array $implements,
array $attrGroups, int $flags, array $args, ?Node\Name $extends, array $implements,
array $stmts, array $attributes
) {
parent::__construct($attributes);
$this->attrGroups = $attrGroups;
$this->flags = $flags;
$this->args = $args;
$this->extends = $extends;
$this->implements = $implements;
@ -46,7 +49,7 @@ class PrintableNewAnonClassNode extends Expr
// We don't assert that $class->name is null here, to allow consumers to assign unique names
// to anonymous classes for their own purposes. We simplify ignore the name here.
return new self(
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
$class->attrGroups, $class->flags, $newNode->args, $class->extends, $class->implements,
$class->stmts, $newNode->getAttributes()
);
}
@ -56,6 +59,6 @@ class PrintableNewAnonClassNode extends Expr
}
public function getSubNodeNames() : array {
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
return ['attrGroups', 'flags', 'args', 'extends', 'implements', 'stmts'];
}
}

View File

@ -46,7 +46,8 @@ class TokenStream
* @return 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, '}');
}
@ -201,9 +202,15 @@ class TokenStream
public function haveBracesInRange(int $startPos, int $endPos) {
return $this->haveTokenInRange($startPos, $endPos, '{')
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|| $this->haveTokenInRange($startPos, $endPos, '}');
}
public function haveTagInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);
}
/**
* Get indentation before token position.
*

View File

@ -69,7 +69,7 @@ class Lexer
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
* ErrorHandler\Throwing
*/
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
public function startLexing(string $code, ?ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing();
}
@ -85,7 +85,6 @@ class Lexer
$scream = ini_set('xdebug.scream', '0');
error_clear_last();
$this->tokens = @token_get_all($code);
$this->postprocessTokens($errorHandler);
@ -135,10 +134,11 @@ class Lexer
// detected by finding "gaps" in the token array. Unterminated comments are detected
// by checking if a trailing comment has a "*/" at the end.
//
// Additionally, we canonicalize to the PHP 8 comment format here, which does not include
// the trailing whitespace anymore.
//
// We also canonicalize to the PHP 8 T_NAME_* tokens.
// Additionally, we perform a number of canonicalizations here:
// * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore.
// * Use PHP 8.0 T_NAME_* tokens.
// * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and
// T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types.
$filePos = 0;
$line = 1;
@ -209,6 +209,22 @@ class Lexer
}
}
if ($token === '&') {
$next = $i + 1;
while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) {
$next++;
}
$followedByVarOrVarArg = isset($this->tokens[$next]) &&
($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS);
$this->tokens[$i] = $token = [
$followedByVarOrVarArg
? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
: \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
'&',
$line,
];
}
$tokenValue = \is_string($token) ? $token : $token[1];
$tokenLen = \strlen($tokenValue);
@ -323,7 +339,8 @@ class Lexer
$value = $token[1];
$id = $this->tokenMap[$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]) {
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
}
@ -422,6 +439,11 @@ class Lexer
'T_MATCH',
'T_NULLSAFE_OBJECT_OPERATOR',
'T_ATTRIBUTE',
// PHP 8.1
'T_ENUM',
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
'T_READONLY',
];
// PHP-Parser might be used together with another library that also emulates some or all
@ -512,6 +534,10 @@ class Lexer
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_ENUM] = Tokens::T_ENUM;
$tokenMap[\T_READONLY] = Tokens::T_READONLY;
return $tokenMap;
}
@ -520,7 +546,7 @@ class Lexer
// Based on semi_reserved production.
return array_fill_keys([
\T_STRING,
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC,
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY,
\T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND,
\T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE,
\T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH,

View File

@ -6,21 +6,26 @@ use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyFunctionTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
use PhpParser\Parser\Tokens;
class Emulative extends Lexer
{
const PHP_7_3 = '7.3dev';
const PHP_7_4 = '7.4dev';
const PHP_8_0 = '8.0dev';
const PHP_8_1 = '8.1dev';
const PHP_8_2 = '8.2dev';
/** @var mixed[] Patches used to reverse changes introduced in the code */
private $patches = [];
@ -34,11 +39,11 @@ class Emulative extends Lexer
/**
* @param mixed[] $options Lexer options. In addition to the usual options,
* accepts a 'phpVersion' string that specifies the
* version to emulated. Defaults to newest supported.
* version to emulate. Defaults to newest supported.
*/
public function __construct(array $options = [])
{
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_0;
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_2;
unset($options['phpVersion']);
parent::__construct($options);
@ -51,6 +56,10 @@ class Emulative extends Lexer
new NumericLiteralSeparatorEmulator(),
new NullsafeTokenEmulator(),
new AttributeEmulator(),
new EnumTokenEmulator(),
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
new ReadonlyFunctionTokenEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running
@ -65,7 +74,7 @@ class Emulative extends Lexer
}
}
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
public function startLexing(string $code, ?ErrorHandler $errorHandler = null) {
$emulators = array_filter($this->emulators, function($emulator) use($code) {
return $emulator->isEmulationNeeded($code);
});

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,44 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
class ExplicitOctalEmulator extends TokenEmulator {
public function getPhpVersion(): string {
return Emulative::PHP_8_1;
}
public function isEmulationNeeded(string $code): bool {
return strpos($code, '0o') !== false || strpos($code, '0O') !== false;
}
public function emulate(string $code, array $tokens): array {
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' &&
isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING &&
preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])
) {
$tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]);
array_splice($tokens, $i, 2, [
[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]],
]);
$c--;
}
}
return $tokens;
}
private function resolveIntegerOrFloatToken(string $str): int
{
$str = substr($str, 1);
$str = str_replace('_', '', $str);
$num = octdec($str);
return is_float($num) ? \T_DNUMBER : \T_LNUMBER;
}
public function reverseEmulate(string $code, array $tokens): array {
// Explicit octals were not legal code previously, don't bother.
return $tokens;
}
}

View File

@ -12,16 +12,18 @@ abstract class KeywordEmulator extends TokenEmulator
return strpos(strtolower($code), $this->getKeywordString()) !== false;
}
protected function isKeywordContext(array $tokens, int $pos): bool
{
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR;
}
public function emulate(string $code, array $tokens): array
{
$keywordString = $this->getKeywordString();
foreach ($tokens as $i => $token) {
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString) {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i);
if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === \T_OBJECT_OPERATOR) {
continue;
}
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString
&& $this->isKeywordContext($tokens, $i)) {
$tokens[$i][0] = $this->getKeywordToken();
}
}
@ -31,7 +33,7 @@ abstract class KeywordEmulator extends TokenEmulator
/**
* @param mixed[] $tokens
* @return mixed[]|null
* @return array|string|null
*/
private function getPreviousNonSpaceToken(array $tokens, int $start)
{

View File

@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
/*
* In PHP 8.1, "readonly(" was special cased in the lexer in order to support functions with
* name readonly. In PHP 8.2, this may conflict with readonly properties having a DNF type. For
* this reason, PHP 8.2 instead treats this as T_READONLY and then handles it specially in the
* parser. This emulator only exists to handle this special case, which is skipped by the
* PHP 8.1 ReadonlyTokenEmulator.
*/
class ReadonlyFunctionTokenEmulator extends KeywordEmulator {
public function getKeywordString(): string {
return 'readonly';
}
public function getKeywordToken(): int {
return \T_READONLY;
}
public function getPhpVersion(): string {
return Emulative::PHP_8_2;
}
public function reverseEmulate(string $code, array $tokens): array {
// Don't bother
return $tokens;
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class ReadonlyTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_1;
}
public function getKeywordString(): string
{
return 'readonly';
}
public function getKeywordToken(): int
{
return \T_READONLY;
}
protected function isKeywordContext(array $tokens, int $pos): bool
{
if (!parent::isKeywordContext($tokens, $pos)) {
return false;
}
// Support "function readonly("
return !(isset($tokens[$pos + 1]) &&
($tokens[$pos + 1][0] === '(' ||
($tokens[$pos + 1][0] === \T_WHITESPACE &&
isset($tokens[$pos + 2]) &&
$tokens[$pos + 2][0] === '(')));
}
}

View File

@ -36,7 +36,7 @@ class NameContext
*
* @param Name|null $namespace Null is the global namespace
*/
public function startNamespace(Name $namespace = null) {
public function startNamespace(?Name $namespace = null) {
$this->namespace = $namespace;
$this->origAliases = $this->aliases = [
Stmt\Use_::TYPE_NORMAL => [],

View File

@ -2,6 +2,7 @@
namespace PhpParser\Node;
use PhpParser\Node\VariadicPlaceholder;
use PhpParser\NodeAbstract;
class Arg extends NodeAbstract
@ -26,7 +27,7 @@ class Arg extends NodeAbstract
*/
public function __construct(
Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [],
Identifier $name = null
?Identifier $name = null
) {
$this->attributes = $attributes;
$this->name = $name;

View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* This is a base class for complex types, including nullable types and union types.
*
* It does not provide any shared behavior and exists only for type-checking purposes.
*/
abstract class ComplexType extends NodeAbstract
{
}

View File

@ -4,9 +4,6 @@ namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* @property Name $namespacedName Namespaced name (for global constants, if using NameResolver)
*/
class Const_ extends NodeAbstract
{
/** @var Identifier Name */
@ -14,6 +11,9 @@ class Const_ extends NodeAbstract
/** @var Expr Value */
public $value;
/** @var Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**
* Constructs a const node for use in class const and const statements.
*
@ -30,7 +30,7 @@ class Const_ extends NodeAbstract
public function getSubNodeNames() : array {
return ['name', 'value'];
}
public function getType() : string {
return 'Const';
}

View File

@ -18,7 +18,7 @@ class ArrayDimFetch extends Expr
* @param null|Expr $dim Array index / dim
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $dim = null, array $attributes = []) {
public function __construct(Expr $var, ?Expr $dim = null, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->dim = $dim;

View File

@ -23,7 +23,7 @@ class ArrayItem extends Expr
* @param bool $byRef Whether to assign by reference
* @param array $attributes Additional attributes
*/
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
public function __construct(Expr $value, ?Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
$this->attributes = $attributes;
$this->key = $key;
$this->value = $value;

View File

@ -17,7 +17,7 @@ class ArrowFunction extends Expr implements FunctionLike
/** @var Node\Param[] */
public $params = [];
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType */
public $returnType;
/** @var Expr */
@ -42,7 +42,7 @@ class ArrowFunction extends Expr implements FunctionLike
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->expr = $subNodes['expr'] ?? null;
$this->expr = $subNodes['expr'];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}

View File

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
abstract class CallLike extends Expr {
/**
* Return raw arguments, which may be actual Args, or VariadicPlaceholders for first-class
* callables.
*
* @return array<Arg|VariadicPlaceholder>
*/
abstract public function getRawArgs(): array;
/**
* Returns whether this call expression is actually a first class callable.
*/
public function isFirstClassCallable(): bool {
foreach ($this->getRawArgs() as $arg) {
if ($arg instanceof VariadicPlaceholder) {
return true;
}
}
return false;
}
/**
* Assert that this is not a first-class callable and return only ordinary Args.
*
* @return Arg[]
*/
public function getArgs(): array {
assert(!$this->isFirstClassCallable());
return $this->getRawArgs();
}
}

View File

@ -10,15 +10,15 @@ class ClassConstFetch extends Expr
{
/** @var Name|Expr Class name */
public $class;
/** @var Identifier|Error Constant name */
/** @var Identifier|Expr|Error Constant name */
public $name;
/**
* Constructs a class const fetch node.
*
* @param Name|Expr $class Class name
* @param string|Identifier|Error $name Constant name
* @param array $attributes Additional attributes
* @param Name|Expr $class Class name
* @param string|Identifier|Expr|Error $name Constant name
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $attributes = []) {
$this->attributes = $attributes;
@ -29,7 +29,7 @@ class ClassConstFetch extends Expr
public function getSubNodeNames() : array {
return ['class', 'name'];
}
public function getType() : string {
return 'Expr_ClassConstFetch';
}

View File

@ -16,7 +16,7 @@ class Closure extends Expr implements FunctionLike
public $params;
/** @var ClosureUse[] use()s */
public $uses;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;

View File

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

View File

@ -5,19 +5,19 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
class FuncCall extends Expr
class FuncCall extends CallLike
{
/** @var Node\Name|Expr Function name */
public $name;
/** @var Node\Arg[] Arguments */
/** @var array<Node\Arg|Node\VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr $name Function name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Node\Name|Expr $name Function name
* @param array<Node\Arg|Node\VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
@ -32,4 +32,8 @@ class FuncCall extends Expr
public function getType() : string {
return 'Expr_FuncCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -5,23 +5,24 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class MethodCall extends Expr
class MethodCall extends CallLike
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
@ -37,4 +38,8 @@ class MethodCall extends Expr
public function getType() : string {
return 'Expr_MethodCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -3,20 +3,22 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\VariadicPlaceholder;
class New_ extends Expr
class New_ extends CallLike
{
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
public $class;
/** @var Node\Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
* @param Node\Arg[] $args Arguments
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, array $args = [], array $attributes = []) {
@ -32,4 +34,8 @@ class New_ extends Expr
public function getType() : string {
return 'Expr_New';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -5,23 +5,24 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class NullsafeMethodCall extends Expr
class NullsafeMethodCall extends CallLike
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a nullsafe method call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
@ -37,4 +38,8 @@ class NullsafeMethodCall extends Expr
public function getType() : string {
return 'Expr_NullsafeMethodCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -3,25 +3,27 @@
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\VariadicPlaceholder;
class StaticCall extends Expr
class StaticCall extends CallLike
{
/** @var Node\Name|Expr Class name */
public $class;
/** @var Identifier|Expr Method name */
public $name;
/** @var Node\Arg[] Arguments */
/** @var array<Arg|VariadicPlaceholder> Arguments */
public $args;
/**
* Constructs a static method call node.
*
* @param Node\Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Node\Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param array<Arg|VariadicPlaceholder> $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
@ -37,4 +39,8 @@ class StaticCall extends Expr
public function getType() : string {
return 'Expr_StaticCall';
}
public function getRawArgs(): array {
return $this->args;
}
}

View File

@ -13,7 +13,7 @@ class Variable extends Expr
* Constructs a variable node.
*
* @param string|Expr $name Name
* @param array $attributes Additional attributes
* @param array $attributes Additional attributes
*/
public function __construct($name, array $attributes = []) {
$this->attributes = $attributes;

View File

@ -18,7 +18,7 @@ class Yield_ extends Expr
* @param null|Expr $key Key expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $value = null, Expr $key = null, array $attributes = []) {
public function __construct(?Expr $value = null, ?Expr $key = null, array $attributes = []) {
$this->attributes = $attributes;
$this->key = $key;
$this->value = $value;

View File

@ -23,7 +23,7 @@ interface FunctionLike extends Node
/**
* Get the declared return type or null
*
* @return null|Identifier|Name|NullableType|UnionType
* @return null|Identifier|Name|ComplexType
*/
public function getReturnType();

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class IntersectionType extends ComplexType
{
/** @var (Identifier|Name)[] Types */
public $types;
/**
* Constructs an intersection type.
*
* @param (Identifier|Name)[] $types Types
* @param array $attributes Additional attributes
*/
public function __construct(array $types, array $attributes = []) {
$this->attributes = $attributes;
$this->types = $types;
}
public function getSubNodeNames() : array {
return ['types'];
}
public function getType() : string {
return 'IntersectionType';
}
}

View File

@ -6,7 +6,10 @@ use PhpParser\NodeAbstract;
class Name extends NodeAbstract
{
/** @var string[] Parts of the name */
/**
* @var string[] Parts of the name
* @deprecated Use getParts() instead
*/
public $parts;
private static $specialClassNames = [
@ -30,6 +33,15 @@ class Name extends NodeAbstract
return ['parts'];
}
/**
* Get parts of name (split by the namespace separator).
*
* @return string[] Parts of name
*/
public function getParts(): array {
return $this->parts;
}
/**
* Gets the first part of the name, i.e. everything before the first namespace separator.
*
@ -150,7 +162,7 @@ class Name extends NodeAbstract
*
* @return static|null Sliced name
*/
public function slice(int $offset, int $length = null) {
public function slice(int $offset, ?int $length = null) {
$numParts = count($this->parts);
$realOffset = $offset < 0 ? $offset + $numParts : $offset;
@ -162,7 +174,7 @@ class Name extends NodeAbstract
$realLength = $numParts - $realOffset;
} else {
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
if ($realLength < 0 || $realLength > $numParts) {
if ($realLength < 0 || $realLength > $numParts - $realOffset) {
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
}
}

View File

@ -2,9 +2,7 @@
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class NullableType extends NodeAbstract
class NullableType extends ComplexType
{
/** @var Identifier|Name Type */
public $type;

View File

@ -6,7 +6,7 @@ use PhpParser\NodeAbstract;
class Param extends NodeAbstract
{
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
/** @var null|Identifier|Name|ComplexType Type declaration */
public $type;
/** @var bool Whether parameter is passed by reference */
public $byRef;
@ -24,17 +24,17 @@ class Param extends NodeAbstract
/**
* Constructs a parameter node.
*
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param AttributeGroup[] $attrGroups PHP attribute groups
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Identifier|Name|ComplexType $type Type declaration
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(
$var, Expr $default = null, $type = null,
$var, ?Expr $default = null, $type = null,
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0,

View File

@ -24,6 +24,17 @@ class DNumber extends Scalar
return ['value'];
}
/**
* @param mixed[] $attributes
*/
public static function fromString(string $str, array $attributes = []): DNumber
{
$attributes['rawValue'] = $str;
$float = self::parse($str);
return new DNumber($float, $attributes);
}
/**
* @internal
*
@ -36,13 +47,7 @@ class DNumber extends Scalar
public static function parse(string $str) : float {
$str = str_replace('_', '', $str);
// if string contains any of .eE just cast it to float
if (false !== strpbrk($str, '.eE')) {
return (float) $str;
}
// otherwise it's an integer notation that overflowed into a float
// if it starts with 0 it's one of the special integer notations
// Check whether this is one of the special integer notations.
if ('0' === $str[0]) {
// hex
if ('x' === $str[1] || 'X' === $str[1]) {
@ -54,16 +59,18 @@ class DNumber extends Scalar
return bindec($str);
}
// oct
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
// so that only the digits before that are used
return octdec(substr($str, 0, strcspn($str, '89')));
// oct, but only if the string does not contain any of '.eE'.
if (false === strpbrk($str, '.eE')) {
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit
// (8 or 9) so that only the digits before that are used.
return octdec(substr($str, 0, strcspn($str, '89')));
}
}
// dec
return (float) $str;
}
public function getType() : string {
return 'Scalar_DNumber';
}

View File

@ -41,6 +41,8 @@ class LNumber extends Scalar
* @return LNumber The constructed LNumber, including kind attribute
*/
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
$attributes['rawValue'] = $str;
$str = str_replace('_', '', $str);
if ('0' !== $str[0] || '0' === $str) {
@ -62,11 +64,16 @@ class LNumber extends Scalar
throw new Error('Invalid numeric literal', $attributes);
}
// Strip optional explicit octal prefix.
if ('o' === $str[1] || 'O' === $str[1]) {
$str = substr($str, 2);
}
// use intval instead of octdec to get proper cutting behavior with malformed numbers
$attributes['kind'] = LNumber::KIND_OCT;
return new LNumber(intval($str, 8), $attributes);
}
public function getType() : string {
return 'Scalar_LNumber';
}

View File

@ -42,6 +42,22 @@ class String_ extends Scalar
return ['value'];
}
/**
* @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
*/
public static function fromString(string $str, array $attributes = [], bool $parseUnicodeEscape = true): self
{
$attributes['kind'] = ($str[0] === "'" || ($str[1] === "'" && ($str[0] === 'b' || $str[0] === 'B')))
? Scalar\String_::KIND_SINGLE_QUOTED
: Scalar\String_::KIND_DOUBLE_QUOTED;
$attributes['rawValue'] = $str;
$string = self::parse($str, $parseUnicodeEscape);
return new self($string, $attributes);
}
/**
* @internal
*

View File

@ -15,7 +15,7 @@ class Break_ extends Node\Stmt
* @param null|Node\Expr $num Number of loops to break
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $num = null, array $attributes = []) {
public function __construct(?Node\Expr $num = null, array $attributes = []) {
$this->attributes = $attributes;
$this->num = $num;
}

View File

@ -23,7 +23,7 @@ class Catch_ extends Node\Stmt
* @param array $attributes Additional attributes
*/
public function __construct(
array $types, Expr\Variable $var = null, array $stmts = [], array $attributes = []
array $types, ?Expr\Variable $var = null, array $stmts = [], array $attributes = []
) {
$this->attributes = $attributes;
$this->types = $types;

View File

@ -10,31 +10,36 @@ class ClassConst extends Node\Stmt
public $flags;
/** @var Node\Const_[] Constant declarations */
public $consts;
/** @var Node\AttributeGroup[] */
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Identifier|Node\Name|Node\ComplexType|null Type declaration */
public $type;
/**
* Constructs a class const list node.
*
* @param Node\Const_[] $consts Constant declarations
* @param int $flags Modifiers
* @param array $attributes Additional attributes
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param Node\Const_[] $consts Constant declarations
* @param int $flags Modifiers
* @param array $attributes Additional attributes
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param null|string|Node\Identifier|Node\Name|Node\ComplexType $type Type declaration
*/
public function __construct(
array $consts,
int $flags = 0,
array $attributes = [],
array $attrGroups = []
array $attrGroups = [],
$type = null
) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->consts = $consts;
$this->attrGroups = $attrGroups;
$this->type = \is_string($type) ? new Node\Identifier($type) : $type;
}
public function getSubNodeNames() : array {
return ['attrGroups', 'flags', 'consts'];
return ['attrGroups', 'flags', 'type', 'consts'];
}
/**
@ -65,6 +70,15 @@ class ClassConst extends Node\Stmt
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
}
/**
* Whether constant is final.
*
* @return bool
*/
public function isFinal() : bool {
return (bool) ($this->flags & Class_::MODIFIER_FINAL);
}
public function getType() : string {
return 'Stmt_ClassConst';
}

View File

@ -4,9 +4,6 @@ namespace PhpParser\Node\Stmt;
use PhpParser\Node;
/**
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
*/
abstract class ClassLike extends Node\Stmt
{
/** @var Node\Identifier|null Name */
@ -16,6 +13,9 @@ abstract class ClassLike extends Node\Stmt
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**
* @return TraitUse[]
*/

View File

@ -15,7 +15,7 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
public $returnType;
/** @var Node\Stmt[]|null Statements */
public $stmts;
@ -23,21 +23,23 @@ class ClassMethod extends Node\Stmt implements FunctionLike
public $attrGroups;
private static $magicNames = [
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
'__serialize' => true,
'__unserialize' => true,
];
/**

View File

@ -13,6 +13,7 @@ class Class_ extends ClassLike
const MODIFIER_STATIC = 8;
const MODIFIER_ABSTRACT = 16;
const MODIFIER_FINAL = 32;
const MODIFIER_READONLY = 64;
const VISIBILITY_MODIFIER_MASK = 7; // 1 | 2 | 4
@ -32,7 +33,7 @@ class Class_ extends ClassLike
* 'extends' => null : Name of extended class
* 'implements' => array(): Names of implemented interfaces
* 'stmts' => array(): Statements
* '$attrGroups' => array(): PHP attribute groups
* 'attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
@ -67,6 +68,10 @@ class Class_ extends ClassLike
return (bool) ($this->flags & self::MODIFIER_FINAL);
}
public function isReadonly() : bool {
return (bool) ($this->flags & self::MODIFIER_READONLY);
}
/**
* Whether the class is anonymous.
*
@ -76,6 +81,27 @@ class Class_ extends ClassLike
return null === $this->name;
}
/**
* @internal
*/
public static function verifyClassModifier($a, $b) {
if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
throw new Error('Cannot use the final modifier on an abstract class');
}
}
/**
* @internal
*/
@ -96,6 +122,10 @@ class Class_ extends ClassLike
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & self::MODIFIER_READONLY && $b & self::MODIFIER_READONLY) {
throw new Error('Multiple readonly modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
throw new Error('Cannot use the final modifier on an abstract class member');
}

View File

@ -15,7 +15,7 @@ class Continue_ extends Node\Stmt
* @param null|Node\Expr $num Number of loops to continue
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $num = null, array $attributes = []) {
public function __construct(?Node\Expr $num = null, array $attributes = []) {
$this->attributes = $attributes;
$this->num = $num;
}

View File

@ -18,7 +18,7 @@ class Declare_ extends Node\Stmt
* @param Node\Stmt[]|null $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(array $declares, array $stmts = null, array $attributes = []) {
public function __construct(array $declares, ?array $stmts = null, array $attributes = []) {
$this->attributes = $attributes;
$this->declares = $declares;
$this->stmts = $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

@ -5,9 +5,6 @@ namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
/**
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
*/
class Function_ extends Node\Stmt implements FunctionLike
{
/** @var bool Whether function returns by reference */
@ -16,13 +13,16 @@ class Function_ extends Node\Stmt implements FunctionLike
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
/** @var null|Node\Identifier|Node\Name|Node\ComplexType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Name|null Namespaced name (if using NameResolver) */
public $namespacedName;
/**
* Constructs a function node.
*

View File

@ -22,7 +22,7 @@ class Namespace_ extends Node\Stmt
* @param null|Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Name $name = null, $stmts = [], array $attributes = []) {
public function __construct(?Node\Name $name = null, $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
$this->stmts = $stmts;

View File

@ -3,10 +3,9 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\ComplexType;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
class Property extends Node\Stmt
{
@ -14,7 +13,7 @@ class Property extends Node\Stmt
public $flags;
/** @var PropertyProperty[] Properties */
public $props;
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
/** @var null|Identifier|Name|ComplexType Type declaration */
public $type;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
@ -22,11 +21,11 @@ class Property extends Node\Stmt
/**
* Constructs a class property list node.
*
* @param int $flags Modifiers
* @param PropertyProperty[] $props Properties
* @param array $attributes Additional attributes
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
* @param int $flags Modifiers
* @param PropertyProperty[] $props Properties
* @param array $attributes Additional attributes
* @param null|string|Identifier|Name|ComplexType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) {
$this->attributes = $attributes;
@ -77,6 +76,15 @@ class Property extends Node\Stmt
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
}
/**
* Whether the property is readonly.
*
* @return bool
*/
public function isReadonly() : bool {
return (bool) ($this->flags & Class_::MODIFIER_READONLY);
}
public function getType() : string {
return 'Stmt_Property';
}

View File

@ -18,7 +18,7 @@ class PropertyProperty extends Node\Stmt
* @param null|Node\Expr $default Default value
* @param array $attributes Additional attributes
*/
public function __construct($name, Node\Expr $default = null, array $attributes = []) {
public function __construct($name, ?Node\Expr $default = null, array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name;
$this->default = $default;

View File

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

View File

@ -20,7 +20,7 @@ class StaticVar extends Node\Stmt
* @param array $attributes Additional attributes
*/
public function __construct(
Expr\Variable $var, Node\Expr $default = null, array $attributes = []
Expr\Variable $var, ?Node\Expr $default = null, array $attributes = []
) {
$this->attributes = $attributes;
$this->var = $var;

View File

@ -21,7 +21,7 @@ class TryCatch extends Node\Stmt
* @param null|Finally_ $finally Optional finally node
* @param array $attributes Additional attributes
*/
public function __construct(array $stmts, array $catches, Finally_ $finally = null, array $attributes = []) {
public function __construct(array $stmts, array $catches, ?Finally_ $finally = null, array $attributes = []) {
$this->attributes = $attributes;
$this->stmts = $stmts;
$this->catches = $catches;

View File

@ -2,17 +2,15 @@
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class UnionType extends NodeAbstract
class UnionType extends ComplexType
{
/** @var (Identifier|Name)[] Types */
/** @var (Identifier|Name|IntersectionType)[] Types */
public $types;
/**
* Constructs a union type.
*
* @param (Identifier|Name)[] $types Types
* @param (Identifier|Name|IntersectionType)[] $types Types
* @param array $attributes Additional attributes
*/
public function __construct(array $types, array $attributes = []) {

View File

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* Represents the "..." in "foo(...)" of the first-class callable syntax.
*/
class VariadicPlaceholder extends NodeAbstract {
/**
* Create a variadic argument placeholder (first-class callable syntax).
*
* @param array $attributes Additional attributes
*/
public function __construct(array $attributes = []) {
$this->attributes = $attributes;
}
public function getType(): string {
return 'VariadicPlaceholder';
}
public function getSubNodeNames(): array {
return [];
}
}

View File

@ -39,7 +39,7 @@ class NodeDumper
*
* @return string Dumped value
*/
public function dump($node, string $code = null) : string {
public function dump($node, ?string $code = null) : string {
$this->code = $code;
return $this->dumpRecursive($node);
}
@ -128,6 +128,9 @@ class NodeDumper
if ($flags & Class_::MODIFIER_FINAL) {
$strs[] = 'MODIFIER_FINAL';
}
if ($flags & Class_::MODIFIER_READONLY) {
$strs[] = 'MODIFIER_READONLY';
}
if ($strs) {
return implode(' | ', $strs) . ' (' . $flags . ')';

View File

@ -35,7 +35,7 @@ class NameResolver extends NodeVisitorAbstract
* @param ErrorHandler|null $errorHandler Error handler
* @param array $options Options
*/
public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
public function __construct(?ErrorHandler $errorHandler = null, array $options = []) {
$this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
$this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false;
$this->replaceNodes = $options['replaceNodes'] ?? true;
@ -86,6 +86,15 @@ class NameResolver extends NodeVisitorAbstract
$this->resolveAttrGroups($node);
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\Enum_) {
foreach ($node->implements as &$interface) {
$interface = $this->resolveClassName($interface);
}
$this->resolveAttrGroups($node);
if (null !== $node->name) {
$this->addNamespacedName($node);
}
} elseif ($node instanceof Stmt\Trait_) {
$this->resolveAttrGroups($node);
$this->addNamespacedName($node);
@ -109,6 +118,11 @@ class NameResolver extends NodeVisitorAbstract
$this->addNamespacedName($const);
}
} else if ($node instanceof Stmt\ClassConst) {
if (null !== $node->type) {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} else if ($node instanceof Stmt\EnumCase) {
$this->resolveAttrGroups($node);
} elseif ($node instanceof Expr\StaticCall
|| $node instanceof Expr\StaticPropertyFetch
@ -150,7 +164,7 @@ class NameResolver extends NodeVisitorAbstract
return null;
}
private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
private function addAlias(Stmt\UseUse $use, int $type, ?Name $prefix = null) {
// Add prefix for group uses
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
// Type is determined either by individual element or whole use declaration
@ -178,7 +192,7 @@ class NameResolver extends NodeVisitorAbstract
$node->type = $this->resolveType($node->type);
return $node;
}
if ($node instanceof Node\UnionType) {
if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) {
foreach ($node->types as &$type) {
$type = $this->resolveType($type);
}

View File

@ -14,5 +14,5 @@ interface Parser
* @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and
* the parser was unable to recover from an error).
*/
public function parse(string $code, ErrorHandler $errorHandler = null);
public function parse(string $code, ?ErrorHandler $errorHandler = null);
}

View File

@ -24,7 +24,7 @@ class Multiple implements Parser
$this->parsers = $parsers;
}
public function parse(string $code, ErrorHandler $errorHandler = null) {
public function parse(string $code, ?ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -35,110 +35,114 @@ final class Tokens
const T_COALESCE = 283;
const T_BOOLEAN_OR = 284;
const T_BOOLEAN_AND = 285;
const T_IS_EQUAL = 286;
const T_IS_NOT_EQUAL = 287;
const T_IS_IDENTICAL = 288;
const T_IS_NOT_IDENTICAL = 289;
const T_SPACESHIP = 290;
const T_IS_SMALLER_OR_EQUAL = 291;
const T_IS_GREATER_OR_EQUAL = 292;
const T_SL = 293;
const T_SR = 294;
const T_INSTANCEOF = 295;
const T_INC = 296;
const T_DEC = 297;
const T_INT_CAST = 298;
const T_DOUBLE_CAST = 299;
const T_STRING_CAST = 300;
const T_ARRAY_CAST = 301;
const T_OBJECT_CAST = 302;
const T_BOOL_CAST = 303;
const T_UNSET_CAST = 304;
const T_POW = 305;
const T_NEW = 306;
const T_CLONE = 307;
const T_EXIT = 308;
const T_IF = 309;
const T_ELSEIF = 310;
const T_ELSE = 311;
const T_ENDIF = 312;
const T_LNUMBER = 313;
const T_DNUMBER = 314;
const T_STRING = 315;
const T_STRING_VARNAME = 316;
const T_VARIABLE = 317;
const T_NUM_STRING = 318;
const T_INLINE_HTML = 319;
const T_ENCAPSED_AND_WHITESPACE = 320;
const T_CONSTANT_ENCAPSED_STRING = 321;
const T_ECHO = 322;
const T_DO = 323;
const T_WHILE = 324;
const T_ENDWHILE = 325;
const T_FOR = 326;
const T_ENDFOR = 327;
const T_FOREACH = 328;
const T_ENDFOREACH = 329;
const T_DECLARE = 330;
const T_ENDDECLARE = 331;
const T_AS = 332;
const T_SWITCH = 333;
const T_MATCH = 334;
const T_ENDSWITCH = 335;
const T_CASE = 336;
const T_DEFAULT = 337;
const T_BREAK = 338;
const T_CONTINUE = 339;
const T_GOTO = 340;
const T_FUNCTION = 341;
const T_FN = 342;
const T_CONST = 343;
const T_RETURN = 344;
const T_TRY = 345;
const T_CATCH = 346;
const T_FINALLY = 347;
const T_USE = 348;
const T_INSTEADOF = 349;
const T_GLOBAL = 350;
const T_STATIC = 351;
const T_ABSTRACT = 352;
const T_FINAL = 353;
const T_PRIVATE = 354;
const T_PROTECTED = 355;
const T_PUBLIC = 356;
const T_VAR = 357;
const T_UNSET = 358;
const T_ISSET = 359;
const T_EMPTY = 360;
const T_HALT_COMPILER = 361;
const T_CLASS = 362;
const T_TRAIT = 363;
const T_INTERFACE = 364;
const T_EXTENDS = 365;
const T_IMPLEMENTS = 366;
const T_OBJECT_OPERATOR = 367;
const T_NULLSAFE_OBJECT_OPERATOR = 368;
const T_LIST = 369;
const T_ARRAY = 370;
const T_CALLABLE = 371;
const T_CLASS_C = 372;
const T_TRAIT_C = 373;
const T_METHOD_C = 374;
const T_FUNC_C = 375;
const T_LINE = 376;
const T_FILE = 377;
const T_START_HEREDOC = 378;
const T_END_HEREDOC = 379;
const T_DOLLAR_OPEN_CURLY_BRACES = 380;
const T_CURLY_OPEN = 381;
const T_PAAMAYIM_NEKUDOTAYIM = 382;
const T_NAMESPACE = 383;
const T_NS_C = 384;
const T_DIR = 385;
const T_NS_SEPARATOR = 386;
const T_ELLIPSIS = 387;
const T_NAME_FULLY_QUALIFIED = 388;
const T_NAME_QUALIFIED = 389;
const T_NAME_RELATIVE = 390;
const T_ATTRIBUTE = 391;
const T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG = 286;
const T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG = 287;
const T_IS_EQUAL = 288;
const T_IS_NOT_EQUAL = 289;
const T_IS_IDENTICAL = 290;
const T_IS_NOT_IDENTICAL = 291;
const T_SPACESHIP = 292;
const T_IS_SMALLER_OR_EQUAL = 293;
const T_IS_GREATER_OR_EQUAL = 294;
const T_SL = 295;
const T_SR = 296;
const T_INSTANCEOF = 297;
const T_INC = 298;
const T_DEC = 299;
const T_INT_CAST = 300;
const T_DOUBLE_CAST = 301;
const T_STRING_CAST = 302;
const T_ARRAY_CAST = 303;
const T_OBJECT_CAST = 304;
const T_BOOL_CAST = 305;
const T_UNSET_CAST = 306;
const T_POW = 307;
const T_NEW = 308;
const T_CLONE = 309;
const T_EXIT = 310;
const T_IF = 311;
const T_ELSEIF = 312;
const T_ELSE = 313;
const T_ENDIF = 314;
const T_LNUMBER = 315;
const T_DNUMBER = 316;
const T_STRING = 317;
const T_STRING_VARNAME = 318;
const T_VARIABLE = 319;
const T_NUM_STRING = 320;
const T_INLINE_HTML = 321;
const T_ENCAPSED_AND_WHITESPACE = 322;
const T_CONSTANT_ENCAPSED_STRING = 323;
const T_ECHO = 324;
const T_DO = 325;
const T_WHILE = 326;
const T_ENDWHILE = 327;
const T_FOR = 328;
const T_ENDFOR = 329;
const T_FOREACH = 330;
const T_ENDFOREACH = 331;
const T_DECLARE = 332;
const T_ENDDECLARE = 333;
const T_AS = 334;
const T_SWITCH = 335;
const T_MATCH = 336;
const T_ENDSWITCH = 337;
const T_CASE = 338;
const T_DEFAULT = 339;
const T_BREAK = 340;
const T_CONTINUE = 341;
const T_GOTO = 342;
const T_FUNCTION = 343;
const T_FN = 344;
const T_CONST = 345;
const T_RETURN = 346;
const T_TRY = 347;
const T_CATCH = 348;
const T_FINALLY = 349;
const T_USE = 350;
const T_INSTEADOF = 351;
const T_GLOBAL = 352;
const T_STATIC = 353;
const T_ABSTRACT = 354;
const T_FINAL = 355;
const T_PRIVATE = 356;
const T_PROTECTED = 357;
const T_PUBLIC = 358;
const T_READONLY = 359;
const T_VAR = 360;
const T_UNSET = 361;
const T_ISSET = 362;
const T_EMPTY = 363;
const T_HALT_COMPILER = 364;
const T_CLASS = 365;
const T_TRAIT = 366;
const T_INTERFACE = 367;
const T_ENUM = 368;
const T_EXTENDS = 369;
const T_IMPLEMENTS = 370;
const T_OBJECT_OPERATOR = 371;
const T_NULLSAFE_OBJECT_OPERATOR = 372;
const T_LIST = 373;
const T_ARRAY = 374;
const T_CALLABLE = 375;
const T_CLASS_C = 376;
const T_TRAIT_C = 377;
const T_METHOD_C = 378;
const T_FUNC_C = 379;
const T_LINE = 380;
const T_FILE = 381;
const T_START_HEREDOC = 382;
const T_END_HEREDOC = 383;
const T_DOLLAR_OPEN_CURLY_BRACES = 384;
const T_CURLY_OPEN = 385;
const T_PAAMAYIM_NEKUDOTAYIM = 386;
const T_NAMESPACE = 387;
const T_NS_C = 388;
const T_DIR = 389;
const T_NS_SEPARATOR = 390;
const T_ELLIPSIS = 391;
const T_NAME_FULLY_QUALIFIED = 392;
const T_NAME_QUALIFIED = 393;
const T_NAME_RELATIVE = 394;
const T_ATTRIBUTE = 395;
}

View File

@ -16,8 +16,12 @@ use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Nop;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\UseUse;
@ -60,7 +64,7 @@ abstract class ParserAbstract implements Parser
/** @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
action is defaulted, i.e. $actionDefault[$state] should be used instead. */
* action is defaulted, i.e. $actionDefault[$state] should be used instead. */
protected $actionBase;
/** @var int[] Table of actions. Indexed according to $actionBase comment. */
protected $action;
@ -151,7 +155,7 @@ abstract class ParserAbstract implements Parser
* @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and
* the parser was unable to recover from an error).
*/
public function parse(string $code, ErrorHandler $errorHandler = null) {
public function parse(string $code, ?ErrorHandler $errorHandler = null) {
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
$this->lexer->startLexing($code, $this->errorHandler);
@ -219,10 +223,7 @@ abstract class ParserAbstract implements Parser
));
}
// This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
// the attributes of the next token, even though they don't contain it themselves.
$this->startAttributeStack[$stackPos+1] = $startAttributes;
$this->endAttributeStack[$stackPos+1] = $endAttributes;
// Allow productions to access the start attributes of the lookahead token.
$this->lookaheadStartAttributes = $startAttributes;
//$this->traceRead($symbol);
@ -294,7 +295,8 @@ abstract class ParserAbstract implements Parser
/* Goto - shift nonterminal */
$lastEndAttributes = $this->endAttributeStack[$stackPos];
$stackPos -= $this->ruleToLength[$rule];
$ruleLength = $this->ruleToLength[$rule];
$stackPos -= $ruleLength;
$nonTerminal = $this->ruleToNonTerminal[$rule];
$idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos];
if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) {
@ -307,6 +309,10 @@ abstract class ParserAbstract implements Parser
$stateStack[$stackPos] = $state;
$this->semStack[$stackPos] = $this->semValue;
$this->endAttributeStack[$stackPos] = $lastEndAttributes;
if ($ruleLength === 0) {
// Empty productions use the start attributes of the lookahead token.
$this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes;
}
} else {
/* error */
switch ($this->errorState) {
@ -340,6 +346,7 @@ abstract class ParserAbstract implements Parser
// We treat the error symbol as being empty, so we reset the end attributes
// to the end attributes of the last non-error symbol
$this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes;
$this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1];
$this->endAttributes = $this->endAttributeStack[$stackPos - 1];
break;
@ -659,6 +666,8 @@ abstract class ParserAbstract implements Parser
'null' => true,
'false' => true,
'mixed' => true,
'never' => true,
'true' => true,
];
if (!$name->isUnqualified()) {
@ -870,6 +879,33 @@ abstract class ParserAbstract implements Parser
return $attributes;
}
/** @param ElseIf_|Else_ $node */
protected function fixupAlternativeElse($node) {
// Make sure a trailing nop statement carrying comments is part of the node.
$numStmts = \count($node->stmts);
if ($numStmts !== 0 && $node->stmts[$numStmts - 1] instanceof Nop) {
$nopAttrs = $node->stmts[$numStmts - 1]->getAttributes();
if (isset($nopAttrs['endLine'])) {
$node->setAttribute('endLine', $nopAttrs['endLine']);
}
if (isset($nopAttrs['endFilePos'])) {
$node->setAttribute('endFilePos', $nopAttrs['endFilePos']);
}
if (isset($nopAttrs['endTokenPos'])) {
$node->setAttribute('endTokenPos', $nopAttrs['endTokenPos']);
}
}
}
protected function checkClassModifier($a, $b, $modifierPos) {
try {
Class_::verifyClassModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
}
protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere
try {
@ -909,22 +945,17 @@ abstract class ParserAbstract implements Parser
}
}
protected function checkClass(Class_ $node, $namePos) {
if (null !== $node->name && $node->name->isSpecialClassName()) {
private function checkClassName($name, $namePos) {
if (null !== $name && $name->isSpecialClassName()) {
$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)
));
}
}
if ($node->extends && $node->extends->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
$node->extends->getAttributes()
));
}
foreach ($node->implements as $interface) {
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),
@ -934,22 +965,27 @@ abstract class ParserAbstract implements Parser
}
}
protected function checkInterface(Interface_ $node, $namePos) {
if (null !== $node->name && $node->name->isSpecialClassName()) {
protected function checkClass(Class_ $node, $namePos) {
$this->checkClassName($node->name, $namePos);
if ($node->extends && $node->extends->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
$this->getAttributesAt($namePos)
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
$node->extends->getAttributes()
));
}
foreach ($node->extends as $interface) {
if ($interface->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
$this->checkImplementedInterfaces($node->implements);
}
protected function checkInterface(Interface_ $node, $namePos) {
$this->checkClassName($node->name, $namePos);
$this->checkImplementedInterfaces($node->extends);
}
protected function checkEnum(Enum_ $node, $namePos) {
$this->checkClassName($node->name, $namePos);
$this->checkImplementedInterfaces($node->implements);
}
protected function checkClassMethod(ClassMethod $node, $modifierPos) {
@ -972,6 +1008,12 @@ abstract class ParserAbstract implements Parser
break;
}
}
if ($node->flags & Class_::MODIFIER_READONLY) {
$this->emitError(new Error(
sprintf('Method %s() cannot be readonly', $node->name),
$this->getAttributesAt($modifierPos)));
}
}
protected function checkClassConst(ClassConst $node, $modifierPos) {
@ -985,9 +1027,9 @@ abstract class ParserAbstract implements Parser
"Cannot use 'abstract' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Class_::MODIFIER_FINAL) {
if ($node->flags & Class_::MODIFIER_READONLY) {
$this->emitError(new Error(
"Cannot use 'final' as constant modifier",
"Cannot use 'readonly' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}

View File

@ -2,6 +2,9 @@
namespace PhpParser;
use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Php7;
class ParserFactory
{
const PREFER_PHP7 = 1;
@ -18,7 +21,7 @@ class ParserFactory
*
* @return Parser The parser instance
*/
public function create(int $kind, Lexer $lexer = null, array $parserOptions = []) : Parser {
public function create(int $kind, ?Lexer $lexer = null, array $parserOptions = []) : Parser {
if (null === $lexer) {
$lexer = new Lexer\Emulative();
}
@ -41,4 +44,33 @@ class ParserFactory
);
}
}
/**
* Create a parser targeting the newest version supported by this library. Code for older
* versions will be accepted if there have been no relevant backwards-compatibility breaks in
* PHP.
*
* All supported lexer attributes (comments, startLine, endLine, startTokenPos, endTokenPos,
* startFilePos, endFilePos) will be enabled.
*/
public function createForNewestSupportedVersion(): Parser {
return new Php7(new Emulative($this->getLexerOptions()));
}
/**
* Create a parser targeting the host PHP version, that is the PHP version we're currently
* running on. This parser will not use any token emulation.
*
* All supported lexer attributes (comments, startLine, endLine, startTokenPos, endTokenPos,
* startFilePos, endFilePos) will be enabled.
*/
public function createForHostVersion(): Parser {
return new Php7(new Lexer($this->getLexerOptions()));
}
private function getLexerOptions(): array {
return ['usedAttributes' => [
'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos',
]];
}
}

View File

@ -33,6 +33,10 @@ class Standard extends PrettyPrinterAbstract
. $this->p($node->value);
}
protected function pVariadicPlaceholder(Node\VariadicPlaceholder $node) {
return '...';
}
protected function pConst(Node\Const_ $node) {
return $node->name . ' = ' . $this->p($node->value);
}
@ -42,7 +46,19 @@ class Standard extends PrettyPrinterAbstract
}
protected function pUnionType(Node\UnionType $node) {
return $this->pImplode($node->types, '|');
$types = [];
foreach ($node->types as $typeNode) {
if ($typeNode instanceof Node\IntersectionType) {
$types[] = '('. $this->p($typeNode) . ')';
continue;
}
$types[] = $this->p($typeNode);
}
return implode('|', $types);
}
protected function pIntersectionType(Node\IntersectionType $node) {
return $this->pImplode($node->types, '&');
}
protected function pIdentifier(Node\Identifier $node) {
@ -513,7 +529,7 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_StaticCall(Expr\StaticCall $node) {
return $this->pDereferenceLhs($node->class) . '::'
return $this->pStaticDereferenceLhs($node->class) . '::'
. ($node->name instanceof Expr
? ($node->name instanceof Expr\Variable
? $this->p($node->name)
@ -590,7 +606,7 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) {
return $this->pDereferenceLhs($node->class) . '::' . $this->p($node->name);
return $this->pStaticDereferenceLhs($node->class) . '::' . $this->pObjectProperty($node->name);
}
protected function pExpr_PropertyFetch(Expr\PropertyFetch $node) {
@ -602,7 +618,7 @@ class Standard extends PrettyPrinterAbstract
}
protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) {
return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name);
return $this->pStaticDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name);
}
protected function pExpr_ShellExec(Expr\ShellExec $node) {
@ -727,6 +743,14 @@ class Standard extends PrettyPrinterAbstract
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Enum(Stmt\Enum_ $node) {
return $this->pAttrGroups($node->attrGroups)
. 'enum ' . $node->name
. ($node->scalarType ? " : $node->scalarType" : '')
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Class(Stmt\Class_ $node) {
return $this->pClassCommon($node, ' ' . $node->name);
}
@ -737,6 +761,13 @@ class Standard extends PrettyPrinterAbstract
. $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) {
return 'use ' . $this->pCommaSeparated($node->traits)
. (empty($node->adaptations)
@ -783,7 +814,9 @@ class Standard extends PrettyPrinterAbstract
protected function pStmt_ClassConst(Stmt\ClassConst $node) {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'const ' . $this->pCommaSeparated($node->consts) . ';';
. 'const '
. (null !== $node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->consts) . ';';
}
protected function pStmt_Function(Stmt\Function_ $node) {
@ -984,14 +1017,27 @@ class Standard extends PrettyPrinterAbstract
$escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\");
}
// Escape other control characters
return preg_replace_callback('/([\0-\10\16-\37])(?=([0-7]?))/', function ($matches) {
$oct = decoct(ord($matches[1]));
if ($matches[2] !== '') {
// If there is a trailing digit, use the full three character form
return '\\' . str_pad($oct, 3, '0', \STR_PAD_LEFT);
}
return '\\' . $oct;
// Escape control characters and non-UTF-8 characters.
// Regex based on https://stackoverflow.com/a/11709412/385378.
$regex = '/(
[\x00-\x08\x0E-\x1F] # Control characters
| [\xC0-\xC1] # Invalid UTF-8 Bytes
| [\xF5-\xFF] # Invalid UTF-8 Bytes
| \xE0(?=[\x80-\x9F]) # Overlong encoding of prior code point
| \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);
}
@ -1023,6 +1069,14 @@ class Standard extends PrettyPrinterAbstract
}
}
protected function pStaticDereferenceLhs(Node $node) {
if (!$this->staticDereferenceLhsRequiresParens($node)) {
return $this->p($node);
} else {
return '(' . $this->p($node) . ')';
}
}
protected function pCallLhs(Node $node) {
if (!$this->callLhsRequiresParens($node)) {
return $this->p($node);
@ -1031,16 +1085,19 @@ class Standard extends PrettyPrinterAbstract
}
}
protected function pNewVariable(Node $node) {
// TODO: This is not fully accurate.
return $this->pDereferenceLhs($node);
protected function pNewVariable(Node $node): string {
if (!$this->newOperandRequiresParens($node)) {
return $this->p($node);
} else {
return '(' . $this->p($node) . ')';
}
}
/**
* @param Node[] $nodes
* @return bool
*/
private function hasNodeWithComments(array $nodes) {
protected function hasNodeWithComments(array $nodes) {
foreach ($nodes as $node) {
if ($node && $node->getComments()) {
return true;
@ -1049,7 +1106,7 @@ class Standard extends PrettyPrinterAbstract
return false;
}
private function pMaybeMultiline(array $nodes, bool $trailingComma = false) {
protected function pMaybeMultiline(array $nodes, bool $trailingComma = false) {
if (!$this->hasNodeWithComments($nodes)) {
return $this->pCommaSeparated($nodes);
} else {
@ -1057,7 +1114,7 @@ class Standard extends PrettyPrinterAbstract
}
}
private function pAttrGroups(array $nodes, bool $inline = false): string {
protected function pAttrGroups(array $nodes, bool $inline = false): string {
$result = '';
$sep = $inline ? ' ' : $this->nl;
foreach ($nodes as $node) {

View File

@ -21,6 +21,8 @@ abstract class PrettyPrinterAbstract
const FIXUP_BRACED_NAME = 4; // Name operand that may require bracing
const FIXUP_VAR_BRACED_NAME = 5; // Name operand that may require ${} bracing
const FIXUP_ENCAPSED = 6; // Encapsed string part
const FIXUP_NEW = 7; // New/instanceof operand
const FIXUP_STATIC_DEREF_LHS = 8; // LHS of static dereferencing operation
protected $precedenceMap = [
// [precedence, associativity]
@ -66,7 +68,7 @@ abstract class PrettyPrinterAbstract
BinaryOp\BooleanAnd::class => [120, -1],
BinaryOp\BooleanOr::class => [130, -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
Expr\Assign::class => [160, 1],
Expr\AssignRef::class => [160, 1],
@ -656,7 +658,7 @@ abstract class PrettyPrinterAbstract
$result .= $extraLeft;
$origIndentLevel = $this->indentLevel;
$this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment);
$this->setIndentLevel(max($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment, 0));
// If it's the same node that was previously in this position, it certainly doesn't
// need fixup. It's important to check this here, because our fixup checks are more
@ -759,7 +761,7 @@ abstract class PrettyPrinterAbstract
\assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
$origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
$lastElemIndentLevel = max($this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment, 0);
$this->setIndentLevel($lastElemIndentLevel);
$comments = $arrItem->getComments();
@ -774,7 +776,8 @@ abstract class PrettyPrinterAbstract
}
if ($skipRemovedNode) {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
@ -824,7 +827,11 @@ abstract class PrettyPrinterAbstract
return null;
}
if ($insertStr === ', ' && $this->isMultiline($origNodes)) {
// We go multiline if the original code was multiline,
// or if it's an array item with a comment above it.
if ($insertStr === ', ' &&
($this->isMultiline($origNodes) || $arrItem->getComments())
) {
$insertStr = ',';
$insertNewline = true;
}
@ -842,11 +849,11 @@ abstract class PrettyPrinterAbstract
$this->setIndentLevel($lastElemIndentLevel);
if ($insertNewline) {
$result .= $insertStr . $this->nl;
$comments = $arrItem->getComments();
if ($comments) {
$result .= $this->nl . $this->pComments($comments);
$result .= $this->pComments($comments) . $this->nl;
}
$result .= $insertStr . $this->nl;
} else {
$result .= $insertStr;
}
@ -873,7 +880,8 @@ abstract class PrettyPrinterAbstract
$pos, $itemStartPos, $indentAdjustment);
$skipRemovedNode = true;
} else {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
return null;
@ -919,11 +927,14 @@ abstract class PrettyPrinterAbstract
foreach ($delayedAdd as $delayedAddNode) {
if (!$first) {
$result .= $insertStr;
if ($insertNewline) {
$result .= $this->nl;
}
}
$result .= $this->p($delayedAddNode, true);
$first = false;
}
$result .= $extraRight;
$result .= $extraRight === "\n" ? $this->nl : $extraRight;
}
return $result;
@ -968,6 +979,19 @@ abstract class PrettyPrinterAbstract
return '(' . $this->p($subNode) . ')';
}
break;
case self::FIXUP_STATIC_DEREF_LHS:
if ($this->staticDereferenceLhsRequiresParens($subNode)
&& !$this->origTokens->haveParens($subStartPos, $subEndPos)
) {
return '(' . $this->p($subNode) . ')';
}
break;
case self::FIXUP_NEW:
if ($this->newOperandRequiresParens($subNode)
&& !$this->origTokens->haveParens($subStartPos, $subEndPos)) {
return '(' . $this->p($subNode) . ')';
}
break;
case self::FIXUP_BRACED_NAME:
case self::FIXUP_VAR_BRACED_NAME:
if ($subNode instanceof Expr
@ -1038,13 +1062,26 @@ abstract class PrettyPrinterAbstract
}
/**
* Determines whether the LHS of a dereferencing operation must be wrapped in parenthesis.
* Determines whether the LHS of an array/object operation must be wrapped in parentheses.
*
* @param Node $node LHS of dereferencing operation
*
* @return bool Whether parentheses are required
*/
protected function dereferenceLhsRequiresParens(Node $node) : bool {
// A constant can occur on the LHS of an array/object deref, but not a static deref.
return $this->staticDereferenceLhsRequiresParens($node)
&& !$node instanceof Expr\ConstFetch;
}
/**
* Determines whether the LHS of a static operation must be wrapped in parentheses.
*
* @param Node $node LHS of dereferencing operation
*
* @return bool Whether parentheses are required
*/
protected function staticDereferenceLhsRequiresParens(Node $node): bool {
return !($node instanceof Expr\Variable
|| $node instanceof Node\Name
|| $node instanceof Expr\ArrayDimFetch
@ -1057,10 +1094,31 @@ abstract class PrettyPrinterAbstract
|| $node instanceof Expr\StaticCall
|| $node instanceof Expr\Array_
|| $node instanceof Scalar\String_
|| $node instanceof Expr\ConstFetch
|| $node instanceof Expr\ClassConstFetch);
}
/**
* Determines whether an expression used in "new" or "instanceof" requires parentheses.
*
* @param Node $node New or instanceof operand
*
* @return bool Whether parentheses are required
*/
protected function newOperandRequiresParens(Node $node): bool {
if ($node instanceof Node\Name || $node instanceof Expr\Variable) {
return false;
}
if ($node instanceof Expr\ArrayDimFetch || $node instanceof Expr\PropertyFetch ||
$node instanceof Expr\NullsafePropertyFetch
) {
return $this->newOperandRequiresParens($node->var);
}
if ($node instanceof Expr\StaticPropertyFetch) {
return $this->newOperandRequiresParens($node->class);
}
return true;
}
/**
* Print modifiers, including trailing whitespace.
*
@ -1074,7 +1132,8 @@ abstract class PrettyPrinterAbstract
. ($modifiers & Stmt\Class_::MODIFIER_PRIVATE ? 'private ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_STATIC ? 'static ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT ? 'abstract ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '');
. ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_READONLY ? 'readonly ' : '');
}
/**
@ -1123,7 +1182,8 @@ abstract class PrettyPrinterAbstract
for ($i = 0; $i < 256; $i++) {
// Since PHP 7.1 The lower range is 0x80. However, we also want to support code for
// older versions.
$this->labelCharMap[chr($i)] = $i >= 0x7f || ctype_alnum($i);
$chr = chr($i);
$this->labelCharMap[$chr] = $i >= 0x7f || ctype_alnum($chr);
}
}
@ -1160,7 +1220,7 @@ abstract class PrettyPrinterAbstract
Expr\PostDec::class => ['var' => self::FIXUP_PREC_LEFT],
Expr\Instanceof_::class => [
'expr' => self::FIXUP_PREC_LEFT,
'class' => self::FIXUP_PREC_RIGHT, // TODO: FIXUP_NEW_VARIABLE
'class' => self::FIXUP_NEW,
],
Expr\Ternary::class => [
'cond' => self::FIXUP_PREC_LEFT,
@ -1168,10 +1228,13 @@ abstract class PrettyPrinterAbstract
],
Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS],
Expr\StaticCall::class => ['class' => self::FIXUP_DEREF_LHS],
Expr\StaticCall::class => ['class' => self::FIXUP_STATIC_DEREF_LHS],
Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS],
Expr\ClassConstFetch::class => ['var' => self::FIXUP_DEREF_LHS],
Expr\New_::class => ['class' => self::FIXUP_DEREF_LHS], // TODO: FIXUP_NEW_VARIABLE
Expr\ClassConstFetch::class => [
'class' => self::FIXUP_STATIC_DEREF_LHS,
'name' => self::FIXUP_BRACED_NAME,
],
Expr\New_::class => ['class' => self::FIXUP_NEW],
Expr\MethodCall::class => [
'var' => self::FIXUP_DEREF_LHS,
'name' => self::FIXUP_BRACED_NAME,
@ -1181,7 +1244,7 @@ abstract class PrettyPrinterAbstract
'name' => self::FIXUP_BRACED_NAME,
],
Expr\StaticPropertyFetch::class => [
'class' => self::FIXUP_DEREF_LHS,
'class' => self::FIXUP_STATIC_DEREF_LHS,
'name' => self::FIXUP_VAR_BRACED_NAME,
],
Expr\PropertyFetch::class => [
@ -1242,7 +1305,7 @@ abstract class PrettyPrinterAbstract
/**
* 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.
*/
protected function initializeRemovalMap() {
@ -1267,8 +1330,11 @@ abstract class PrettyPrinterAbstract
'Param->default' => $stripEquals,
'Stmt_Break->num' => $stripBoth,
'Stmt_Catch->var' => $stripLeft,
'Stmt_ClassConst->type' => $stripRight,
'Stmt_ClassMethod->returnType' => $stripColon,
'Stmt_Class->extends' => ['left' => \T_EXTENDS],
'Stmt_Enum->scalarType' => $stripColon,
'Stmt_EnumCase->expr' => $stripEquals,
'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS],
'Stmt_Continue->num' => $stripBoth,
'Stmt_Foreach->keyVar' => $stripDoubleArrow,
@ -1306,7 +1372,10 @@ abstract class PrettyPrinterAbstract
'Stmt_Break->num' => [\T_BREAK, false, ' ', null],
'Stmt_Catch->var' => [null, false, ' ', null],
'Stmt_ClassMethod->returnType' => [')', false, ' : ', null],
'Stmt_ClassConst->type' => [\T_CONST, false, ' ', 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],
'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null],
'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '],
@ -1337,6 +1406,7 @@ abstract class PrettyPrinterAbstract
//'Scalar_Encapsed->parts' => '',
'Stmt_Catch->types' => '|',
'UnionType->types' => '|',
'IntersectionType->types' => '&',
'Stmt_If->elseifs' => ' ',
'Stmt_TryCatch->catches' => ' ',
@ -1356,6 +1426,7 @@ abstract class PrettyPrinterAbstract
'Stmt_ClassConst->consts' => ', ',
'Stmt_ClassMethod->params' => ', ',
'Stmt_Class->implements' => ', ',
'Stmt_Enum->implements' => ', ',
'Expr_PrintableNewAnonClass->implements' => ', ',
'Stmt_Const->consts' => ', ',
'Stmt_Declare->declares' => ', ',
@ -1382,6 +1453,7 @@ abstract class PrettyPrinterAbstract
'Stmt_Case->stmts' => "\n",
'Stmt_Catch->stmts' => "\n",
'Stmt_Class->stmts' => "\n",
'Stmt_Enum->stmts' => "\n",
'Expr_PrintableNewAnonClass->stmts' => "\n",
'Stmt_Interface->stmts' => "\n",
'Stmt_Trait->stmts' => "\n",
@ -1397,6 +1469,8 @@ abstract class PrettyPrinterAbstract
'Stmt_If->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",
@ -1435,9 +1509,20 @@ abstract class PrettyPrinterAbstract
'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''],
'Expr_StaticCall->args' => ['(', '', ''],
'Stmt_Class->implements' => [null, ' implements ', ''],
'Stmt_Enum->implements' => [null, ' implements ', ''],
'Stmt_ClassMethod->params' => ['(', '', ''],
'Stmt_Interface->extends' => [null, ' extends ', ''],
'Stmt_Function->params' => ['(', '', ''],
'Stmt_Interface->attrGroups' => [null, '', "\n"],
'Stmt_Class->attrGroups' => [null, '', "\n"],
'Stmt_ClassConst->attrGroups' => [null, '', "\n"],
'Stmt_ClassMethod->attrGroups' => [null, '', "\n"],
'Stmt_Function->attrGroups' => [null, '', "\n"],
'Stmt_Property->attrGroups' => [null, '', "\n"],
'Stmt_Trait->attrGroups' => [null, '', "\n"],
'Expr_ArrowFunction->attrGroups' => [null, '', ' '],
'Expr_Closure->attrGroups' => [null, '', ' '],
'Expr_PrintableNewAnonClass->attrGroups' => [\T_NEW, ' ', ''],
/* These cannot be empty to start with:
* Expr_Isset->vars
@ -1477,6 +1562,7 @@ abstract class PrettyPrinterAbstract
'Stmt_ClassMethod->flags' => \T_FUNCTION,
'Stmt_Class->flags' => \T_CLASS,
'Stmt_Property->flags' => \T_VARIABLE,
'Expr_PrintableNewAnonClass->flags' => \T_CLASS,
'Param->flags' => \T_VARIABLE,
//'Stmt_TraitUseAdaptation_Alias->newModifier' => 0, // TODO
];

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