mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-07-09 16:36:31 +02:00
Compare commits
172 Commits
v2.0.0alph
...
v3.0.2
Author | SHA1 | Date | |
---|---|---|---|
adf44419c0 | |||
5219f75719 | |||
a485ecd7ba | |||
030de805e1 | |||
aa6aec90e1 | |||
3e158a2313 | |||
68973aed1e | |||
bcdfb703d5 | |||
27281e9130 | |||
098294beec | |||
b02f8ac07d | |||
e52ffc4447 | |||
c5cdd5ad73 | |||
c0630f8169 | |||
6db8d9d5a5 | |||
71438559ae | |||
c0f0edf044 | |||
fa7357b483 | |||
91cb82d3d2 | |||
7672b974ff | |||
8489364528 | |||
0d0accfa9f | |||
623bad2c8b | |||
f66cf8f0dd | |||
4e25f51581 | |||
a46b309975 | |||
f99a96e0a2 | |||
90834bff8e | |||
a910f6a247 | |||
caa5c0cc76 | |||
5e5cb86e83 | |||
2be7838fc6 | |||
f6eb341b15 | |||
b2fe43cf7a | |||
648a246be0 | |||
2e5ae28c39 | |||
5025d75160 | |||
c79ea6d1d3 | |||
e926efd62e | |||
17d1e738fa | |||
9e5d3bbe25 | |||
f3c7dc9d89 | |||
c5e0c3d7e2 | |||
ea47b6e0d6 | |||
9e1c535b1d | |||
cfd207cae5 | |||
f5d334d9bf | |||
f03823cde5 | |||
4dd659edad | |||
1ab24d26ee | |||
a7120116b0 | |||
83f34e7fa4 | |||
d0cfb98133 | |||
13f7321def | |||
46495abb49 | |||
f7cb00d6d3 | |||
7dae6c7a6b | |||
2b209aaaf0 | |||
977cbab8e7 | |||
09086fbe0a | |||
ec614c95dd | |||
21b18eb294 | |||
faa09884db | |||
b740076ab1 | |||
c9fea2ef67 | |||
818ef2e692 | |||
5f97b12576 | |||
1dea9111a2 | |||
174e6c3cab | |||
eefcfeed23 | |||
18129480ae | |||
1b1ff8995b | |||
867ae5148d | |||
72e91845e4 | |||
537b59d4d1 | |||
7ff12b8fcb | |||
574665b45b | |||
437890d386 | |||
1edf72c040 | |||
7a54aca468 | |||
81f7da3b23 | |||
5ea2a76d80 | |||
5044fce1ff | |||
038e11da4b | |||
225804c147 | |||
96cbd48df6 | |||
e45e31c218 | |||
371c783344 | |||
47b254ea51 | |||
912c0bb9c9 | |||
9897fa8819 | |||
5a6e7dd452 | |||
90eb1165d1 | |||
cf9b9e2afa | |||
e7869b9f14 | |||
4c7ad7e194 | |||
f8a40b3f24 | |||
82bb6627c9 | |||
d1dd9f5aec | |||
e7f0860d85 | |||
954051f240 | |||
39f93f09f9 | |||
2d0c3b70f8 | |||
4252ffa43e | |||
8cacc85913 | |||
588e6a4d4c | |||
fa6a17755a | |||
52cb5ecec7 | |||
1565a2815d | |||
14de71898e | |||
5fa8493675 | |||
b31a973fa7 | |||
9ac3592190 | |||
35011d2e4d | |||
60f01bdd86 | |||
15a2388d75 | |||
68b4c0388a | |||
eb4bfe1366 | |||
e7ca4b7b04 | |||
fc36239be5 | |||
f493219c7d | |||
aa199120c7 | |||
ae30f97af6 | |||
47c342a3e4 | |||
7eac2cfd8b | |||
a0c216bf4b | |||
06d9ba42de | |||
573c7c20c4 | |||
d5cbf79f2f | |||
ce5be709d5 | |||
9829bf69cd | |||
c8282e6e76 | |||
a73aa7eec1 | |||
1fe8f09caa | |||
47509cf927 | |||
5b96a11a1f | |||
7faa1dcab9 | |||
65af37f7b0 | |||
d6361136e1 | |||
e05ef23743 | |||
73a9d494fb | |||
94f10d3c50 | |||
c4bbc8e236 | |||
6dffb72ce0 | |||
eb73441032 | |||
9a6a147369 | |||
5c2cc50455 | |||
58eb1ea7c3 | |||
719ca71d4a | |||
c542e5d86a | |||
a9074c7444 | |||
b2f26d30ee | |||
75cd4ab7a5 | |||
33602889c1 | |||
98d28d7aa0 | |||
e4b837e0c4 | |||
2b9c5a62cb | |||
99e89743bd | |||
39a039fa42 | |||
fcf23101dd | |||
c8898df3dd | |||
b2961915a6 | |||
6f3fd7834a | |||
f78af2c9c8 | |||
eecaf1e93b | |||
950ada4cba | |||
0d4239ef56 | |||
e3a9356178 | |||
5118e21c6e | |||
40455b5c18 | |||
fe6755ff4c | |||
f57d217e91 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
grammar/kmyacc.exe
|
||||
grammar/y.output
|
22
.travis.yml
22
.travis.yml
@ -1,13 +1,31 @@
|
||||
language: php
|
||||
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- nightly
|
||||
- hhvm
|
||||
|
||||
install:
|
||||
- if [ $TRAVIS_PHP_VERSION = '5.6' ]; then composer require satooshi/php-coveralls '~1.0'; fi
|
||||
- composer install --prefer-dist
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: 7.0
|
||||
- php: nightly
|
||||
fast_finish: true
|
||||
|
||||
script:
|
||||
- if [ $TRAVIS_PHP_VERSION = '5.6' ]; then vendor/bin/phpunit --coverage-clover build/logs/clover.xml; else vendor/bin/phpunit; fi
|
||||
- if [ $TRAVIS_PHP_VERSION = '7.0' ]; then test_old/run-php-src.sh; fi
|
||||
|
||||
after_success:
|
||||
if [ $TRAVIS_PHP_VERSION = '5.6' ]; then php vendor/bin/coveralls; fi
|
||||
|
||||
|
489
CHANGELOG.md
489
CHANGELOG.md
@ -1,8 +1,251 @@
|
||||
Version 2.0.0-dev-dev
|
||||
---------------------
|
||||
Version 3.0.3-dev
|
||||
-----------------
|
||||
|
||||
Nothing yet.
|
||||
|
||||
Version 3.0.2 (2016-12-06)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed name resolution of nullable types.
|
||||
* Fixed pretty-printing of nullable types.
|
||||
|
||||
Version 3.0.1 (2016-12-01)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed handling of nested `list()`s: If the nested list was unkeyed, it was directly included in
|
||||
the list items. If it was keyed, it was wrapped in `ArrayItem`. Now nested `List_` nodes are
|
||||
always wrapped in `ArrayItem`s.
|
||||
|
||||
Version 3.0.0 (2016-11-30)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for dumping node positions in the NodeDumper through the `dumpPositions` option.
|
||||
* Added error recovery support for `$`, `new`, `Foo::`.
|
||||
|
||||
Version 3.0.0-beta2 (2016-10-29)
|
||||
--------------------------------
|
||||
|
||||
This release primarily improves our support for error recovery.
|
||||
|
||||
### Added
|
||||
|
||||
* Added `Node::setDocComment()` method.
|
||||
* Added `Error::getMessageWithColumnInfo()` method.
|
||||
* Added support for recovery from lexer errors.
|
||||
* Added support for recovering from "special" errors (i.e. non-syntax parse errors).
|
||||
* Added precise location information for lexer errors.
|
||||
* Added `ErrorHandler` interface, and `ErrorHandler\Throwing` and `ErrorHandler\Collecting` as
|
||||
specific implementations. These provide a general mechanism for handling error recovery.
|
||||
* Added optional `ErrorHandler` argument to `Parser::parse()`, `Lexer::startLexing()` and
|
||||
`NameResolver::__construct()`.
|
||||
* The `NameResolver` now adds a `namespacedName` attribute on name nodes that cannot be statically
|
||||
resolved (unqualified unaliased function or constant names in namespaces).
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed attribute assignment for `GroupUse` prefix and variables in interpolated strings.
|
||||
|
||||
### Changed
|
||||
|
||||
* The constants on `NameTraverserInterface` have been moved into the `NameTraverser` class.
|
||||
* Due to the error handling changes, the `Parser` interface and `Lexer` API have changed.
|
||||
* The emulative lexer now directly postprocesses tokens, instead of using `~__EMU__~` sequences.
|
||||
This changes the protected API of the lexer.
|
||||
* The `Name::slice()` method now returns `null` for empty slices, previously `new Name([])` was
|
||||
used. `Name::concat()` now also supports concatenation with `null`.
|
||||
|
||||
### Removed
|
||||
|
||||
* Removed `Name::append()` and `Name::prepend()`. These mutable methods have been superseded by
|
||||
the immutable `Name::concat()`.
|
||||
* Removed `Error::getRawLine()` and `Error::setRawLine()`. These methods have been superseded by
|
||||
`Error::getStartLine()` and `Error::setStartLine()`.
|
||||
* Removed support for node cloning in the `NodeTraverser`.
|
||||
* Removed `$separator` argument from `Name::toString()`.
|
||||
* Removed `throw_on_error` parser option and `Parser::getErrors()` method. Use the `ErrorHandler`
|
||||
mechanism instead.
|
||||
|
||||
Version 3.0.0-beta1 (2016-09-16)
|
||||
--------------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [7.1] Function/method and parameter builders now support PHP 7.1 type hints (void, iterable and
|
||||
nullable types).
|
||||
* Nodes and Comments now implement `JsonSerializable`. The node kind is stored in a `nodeType`
|
||||
property.
|
||||
* The `InlineHTML` node now has an `hasLeadingNewline` attribute, that specifies whether the
|
||||
preceding closing tag contained a newline. The pretty printer honors this attribute.
|
||||
* Partial parsing of `$obj->` (with missing property name) is now supported in error recovery mode.
|
||||
* The error recovery mode is now exposed in the `php-parse` script through the `--with-recovery`
|
||||
or `-r` flags.
|
||||
|
||||
The following changes are also part of PHP-Parser 2.1.1:
|
||||
|
||||
* The PHP 7 parser will now generate a parse error for `$var =& new Obj` assignments.
|
||||
* Comments on free-standing code blocks will now be retained as comments on the first statement in
|
||||
the code block.
|
||||
|
||||
Version 3.0.0-alpha1 (2016-07-25)
|
||||
---------------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* [7.1] Added support for `void` and `iterable` types. These will now be represented as strings
|
||||
(instead of `Name` instances) similar to other builtin types.
|
||||
* [7.1] Added support for class constant visibility. The `ClassConst` node now has a `flags` subnode
|
||||
holding the visibility modifier, as well as `isPublic()`, `isProtected()` and `isPrivate()`
|
||||
methods. The constructor changed to accept the additional subnode.
|
||||
* [7.1] Added support for nullable types. These are represented using a new `NullableType` node
|
||||
with a single `type` subnode.
|
||||
* [7.1] Added support for short array destructuring syntax. This means that `Array` nodes may now
|
||||
appear as the left-hand-side of assignments and foreach value targets. Additionally the array
|
||||
items may now contain `null` values if elements are skipped.
|
||||
* [7.1] Added support for keys in list() destructuring. The `List` subnode `vars` has been renamed
|
||||
to `items` and now contains `ArrayItem`s instead of plain variables.
|
||||
* [7.1] Added support for multi-catch. The `Catch` subnode `type` has been renamed to `types` and
|
||||
is now an array of `Name`s.
|
||||
* `Name::slice()` now supports lengths and negative offsets. This brings it in line with
|
||||
`array_slice()` functionality.
|
||||
|
||||
### Changed
|
||||
|
||||
Due to PHP 7.1 support additions described above, the node structure changed as follows:
|
||||
|
||||
* `void` and `iterable` types are now stored as strings if the PHP 7 parser is used.
|
||||
* The `ClassConst` constructor changed to accept an additional `flags` subnode.
|
||||
* The `Array` subnode `items` may now contain `null` elements (destructuring).
|
||||
* The `List` subnode `vars` has been renamed to `items` and now contains `ArrayItem`s instead of
|
||||
plain variables.
|
||||
* The `Catch` subnode `type` has been renamed to `types` and is now an array of `Name`s.
|
||||
|
||||
Additionally the following changes were made:
|
||||
|
||||
* The `type` subnode on `Class`, `ClassMethod` and `Property` has been renamed to `flags`. The
|
||||
`type` subnode has retained for backwards compatibility and is populated to the same value as
|
||||
`flags`. However, writes to `type` will not update `flags`.
|
||||
* The `TryCatch` subnode `finallyStmts` has been replaced with a `finally` subnode that holds an
|
||||
explicit `Finally` node. This allows for more accurate attribute assignment.
|
||||
* The `Trait` constructor now has the same form as the `Class` and `Interface` constructors: It
|
||||
takes an array of subnodes. Unlike classes/interfaces, traits can only have a `stmts` subnode.
|
||||
* The `NodeDumper` now prints class/method/property/constant modifiers, as well as the include and
|
||||
use type in a textual representation, instead of only showing the number.
|
||||
* All methods on `PrettyPrinter\Standard` are now protected. Previoulsy most of them were public.
|
||||
|
||||
### Removed
|
||||
|
||||
* Removed support for running on PHP 5.4. It is however still possible to parse PHP 5.2-5.4 code
|
||||
while running on a newer version.
|
||||
* The deprecated `Comment::setLine()` and `Comment::setText()` methods have been removed.
|
||||
* The deprecated `Name::set()`, `Name::setFirst()` and `Name::setLast()` methods have been removed.
|
||||
|
||||
Version 2.1.1 (2016-09-16)
|
||||
--------------------------
|
||||
|
||||
### Changed
|
||||
|
||||
* The pretty printer will now escape all control characters in the range `\x00-\x1F` inside double
|
||||
quoted strings. If no special escape sequence is available, an octal escape will be used.
|
||||
* The quality of the error recovery has been improved. In particular unterminated expressions should
|
||||
be handled more gracefully.
|
||||
* The PHP 7 parser will now generate a parse error for `$var =& new Obj` assignments.
|
||||
* Comments on free-standing code blocks will no be retained as comments on the first statement in
|
||||
the code block.
|
||||
|
||||
Version 2.1.0 (2016-04-19)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Properly support `B""` strings (with uppercase `B`) in a number of places.
|
||||
* Fixed reformatting of indented parts in a certain non-standard comment style.
|
||||
|
||||
### Added
|
||||
|
||||
* Added `dumpComments` option to node dumper, to enable dumping of comments associated with nodes.
|
||||
* Added `Stmt\Nop` node, that is used to collect comments located at the end of a block or at the
|
||||
end of a file (without a following node with which they could otherwise be associated).
|
||||
* Added `kind` attribute to `Expr\Exit` to distinguish between `exit` and `die`.
|
||||
* Added `kind` attribute to `Scalar\LNumber` to distinguish between decimal, binary, octal and
|
||||
hexadecimal numbers.
|
||||
* Added `kind` attribtue to `Expr\Array` to distinguish between `array()` and `[]`.
|
||||
* Added `kind` attribute to `Scalar\String` and `Scalar\Encapsed` to distinguish between
|
||||
single-quoted, double-quoted, heredoc and nowdoc string.
|
||||
* Added `docLabel` attribute to `Scalar\String` and `Scalar\Encapsed`, if it is a heredoc or
|
||||
nowdoc string.
|
||||
* Added start file offset information to `Comment` nodes.
|
||||
* Added `setReturnType()` method to function and method builders.
|
||||
* Added `-h` and `--help` options to `php-parse` script.
|
||||
|
||||
### Changed
|
||||
|
||||
* Invalid octal literals now throw a parse error in PHP 7 mode.
|
||||
* The pretty printer takes all the new attributes mentioned in the previous section into account.
|
||||
* The protected `AbstractPrettyPrinter::pComments()` method no longer returns a trailing newline.
|
||||
* The bundled autoloader supports library files being stored in a different directory than
|
||||
`PhpParser` for easier downstream distribution.
|
||||
|
||||
### Deprecated
|
||||
|
||||
* The `Comment::setLine()` and `Comment::setText()` methods have been deprecated. Construct new
|
||||
objects instead.
|
||||
|
||||
### Removed
|
||||
|
||||
* The internal (but public) method `Scalar\LNumber::parse()` has been removed. A non-internal
|
||||
`LNumber::fromString()` method has been added instead.
|
||||
|
||||
Version 2.0.1 (2016-02-28)
|
||||
--------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* `declare() {}` and `declare();` are not semantically equivalent and will now result in different
|
||||
ASTs. The format case will have an empty `stmts` array, while the latter will set `stmts` to
|
||||
`null`.
|
||||
* Magic constants are now supported as semi-reserved keywords.
|
||||
* A shebang line like `#!/usr/bin/env php` is now allowed at the start of a namespaced file.
|
||||
Previously this generated an exception.
|
||||
* The `prettyPrintFile()` method will not strip a trailing `?>` from the raw data that follows a
|
||||
`__halt_compiler()` statement.
|
||||
* The `prettyPrintFile()` method will not strip an opening `<?php` if the file starts with a
|
||||
comment followed by InlineHTML.
|
||||
|
||||
Version 2.0.0 (2015-12-04)
|
||||
--------------------------
|
||||
|
||||
### Changed
|
||||
|
||||
* String parts of encapsed strings are now represented using `Scalar\EncapsStringPart` nodes.
|
||||
Previously raw strings were used. This affects the `parts` child of `Scalar\Encaps` and
|
||||
`Expr\ShellExec`. The change has been done to allow assignment of attributes to encapsed string
|
||||
parts.
|
||||
|
||||
Version 2.0.0-beta1 (2015-10-21)
|
||||
--------------------------------
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed issue with too many newlines being stripped at the end of heredoc/nowdoc strings in some
|
||||
cases. (#227)
|
||||
|
||||
### Changed
|
||||
|
||||
* Update group use support to be in line with recent PHP 7.0 builds.
|
||||
* Renamed `php-parse.php` to `php-parse` and registered it as a composer bin.
|
||||
* Use composer PSR-4 autoloader instead of custom autoloader.
|
||||
* Specify phpunit as a dev dependency.
|
||||
|
||||
### Added
|
||||
|
||||
* Added `shortArraySyntax` option to pretty printer, to print all arrays using short syntax.
|
||||
|
||||
Version 2.0.0-alpha1 (2015-07-14)
|
||||
---------------------------------
|
||||
|
||||
@ -11,7 +254,7 @@ A more detailed description of backwards incompatible changes can be found in th
|
||||
|
||||
### Removed
|
||||
|
||||
* Removed support for running on PHP 5.4. It is however still possible to parse PHP 5.2 and PHP 5.3
|
||||
* Removed support for running on PHP 5.3. It is however still possible to parse PHP 5.2 and PHP 5.3
|
||||
code while running on a newer version.
|
||||
* Removed legacy class name aliases. This includes the old non-namespaced class names and the old
|
||||
names for classes that were renamed for PHP 7 compatibility.
|
||||
@ -43,242 +286,8 @@ A more detailed description of backwards incompatible changes can be found in th
|
||||
* The constructor for `Scalar` nodes no longer has a default value. E.g. `new LNumber()` should now
|
||||
be written as `new LNumber(0)`.
|
||||
|
||||
Version 1.4.0 (2015-07-14)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Added interface `PhpParser\Node\FunctionLike`, which is implemented by `Stmt\ClassMethod`,
|
||||
`Stmt\Function_` and `Expr\Closure` nodes. This interface provides getters for their common
|
||||
subnodes.
|
||||
* Added `Node\Stmt\ClassLike::getMethod()` to look up a specific method on a class/interface/trait.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed `isPublic()` return value for implicitly public properties and methods that define and
|
||||
additional modifier like `static` or `abstract`.
|
||||
* Properties are now accepted by the trait builder.
|
||||
* Fixed `__HALT_COMPILER_OFFSET__` support on HHVM.
|
||||
|
||||
Version 1.3.0 (2015-05-02)
|
||||
--------------------------
|
||||
|
||||
### Added
|
||||
|
||||
* Errors can now store the attributes of the node/token where the error occurred. Previously only the start line was
|
||||
stored.
|
||||
* If file positions are enabled in the lexer, errors can now provide column information if it is available. See
|
||||
[documentation](https://github.com/nikic/PHP-Parser/blob/master/doc/component/Error.markdown#column-information).
|
||||
* The parser now provides an experimental error recovery mode, which can be enabled by disabling the `throwOnError`
|
||||
parser option. In this mode the parser will try to construct a partial AST even if the code is not valid PHP. See
|
||||
[documentation](https://github.com/nikic/PHP-Parser/blob/master/doc/component/Error.markdown#error-recovery).
|
||||
* Added support for PHP 7 `yield from` expression. It is represented by `Expr\YieldFrom`.
|
||||
* Added support for PHP 7 anonymous classes. These are represented by ordinary `Stmt\Class_` nodes with the name set to
|
||||
`null`. Furthermore this implies that `Expr\New_` can now contain a `Stmt\Class_` in its `class` subnode.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed registration of PHP 7 aliases, for the case where the old name was used before the new name.
|
||||
* Fixed handling of precedence when pretty-printing `print` expressions.
|
||||
* Floating point numbers are now pretty-printed with a higher precision.
|
||||
* Checks for special class names like `self` are now case-insensitive.
|
||||
|
||||
Version 1.2.2 (2015-04-03)
|
||||
--------------------------
|
||||
|
||||
* The `NameResolver` now resolves parameter type hints when entering the function/method/closure node. As such other
|
||||
visitors running after it will be able to make use of the resolved names at that point already.
|
||||
* The autoloader no longer sets the `unserialize_callback_func` ini option on registration - this is not necessary and
|
||||
may cause issues when running PhpUnit tests with process isolation.
|
||||
|
||||
Version 1.2.1 (2015-03-24)
|
||||
--------------------------
|
||||
|
||||
* Fixed registration of the aliases introduced in 1.2.0. Previously the old class names could not be used in
|
||||
`instanceof` checks under some circumstances.
|
||||
|
||||
Version 1.2.0 (2015-03-22)
|
||||
--------------------------
|
||||
|
||||
### Changed
|
||||
|
||||
* To ensure compatibility with PHP 7, the following node classes have been renamed:
|
||||
|
||||
OLD => NEW
|
||||
PhpParser\Node\Expr\Cast\Bool => PhpParser\Node\Expr\Cast\Bool_
|
||||
PhpParser\Node\Expr\Cast\Int => PhpParser\Node\Expr\Cast\Int_
|
||||
PhpParser\Node\Expr\Cast\Object => PhpParser\Node\Expr\Cast\Object_
|
||||
PhpParser\Node\Expr\Cast\String => PhpParser\Node\Expr\Cast\String_
|
||||
PhpParser\Node\Scalar\String => PhpParser\Node\Scalar\String_
|
||||
|
||||
**The previous class names are still supported as aliases.** However it is strongly encouraged to use the new names
|
||||
in order to make your code compatible with PHP 7.
|
||||
|
||||
* Subnodes are now stored using real properties instead of an array. This improves performance and memory usage of the
|
||||
initial parse and subsequent node tree operations. The `NodeAbstract` class still supports the old way of specifying
|
||||
subnodes, however this is *deprecated*. In any case properties that are assigned to a node after creation will no
|
||||
longer be considered as subnodes.
|
||||
|
||||
* Methods and property declarations will no longer set the `Stmt\Class_::MODIFIER_PUBLIC` flag if no visibility is
|
||||
explicitly given. However the `isPublic()` method will continue to return true. This allows you to distinguish whether
|
||||
a method/property is explicitly or implicitly public and control the pretty printer output more precisely.
|
||||
|
||||
* The `Stmt\Class_`, `Stmt\Interface_` and `Stmt\Trait_` nodes now inherit from `Stmt\ClassLike`, which provides a
|
||||
`getMethods()` method. Previously this method was only available on `Stmt\Class_`.
|
||||
|
||||
* Support including the `bootstrap.php` file multiple times.
|
||||
|
||||
* Make documentation and tests part of the release tarball again.
|
||||
|
||||
* Improve support for HHVM and PHP 7.
|
||||
|
||||
### Added
|
||||
|
||||
* Added support for PHP 7 return type declarations. This adds an additional `returnType` subnode to `Stmt\Function_`,
|
||||
`Stmt\ClassMethod` and `Expr\Closure`.
|
||||
|
||||
* Added support for the PHP 7 null coalesce operator `??`. The operator is represented by `Expr\BinaryOp\Coalesce`.
|
||||
|
||||
* Added support for the PHP 7 spaceship operator `<=>`. The operator is represented by `Expr\BinaryOp\Spaceship`.
|
||||
|
||||
* Added use builder.
|
||||
|
||||
* Added global namespace support to the namespace builder.
|
||||
|
||||
* Added a constructor flag to `NodeTraverser`, which disables cloning of nodes.
|
||||
|
||||
Version 1.1.0 (2015-01-18)
|
||||
--------------------------
|
||||
|
||||
* Methods that do not specify an explicit visibility (e.g. `function method()`) will now have the `MODIFIER_PUBLIC`
|
||||
flag set. This also means that their `isPublic()` method will return true.
|
||||
|
||||
* Declaring a property as abstract or final is now an error.
|
||||
|
||||
* The `Lexer` and `Lexer\Emulative` classes now accept an `$options` array in their constructors. Currently only the
|
||||
`usedAttributes` option is supported, which determines which attributes will be added to AST nodes. In particular
|
||||
it is now possible to add information on the token and file positions corresponding to a node. For more details see
|
||||
the [Lexer component](https://github.com/nikic/PHP-Parser/blob/master/doc/component/Lexer.markdown) documentation.
|
||||
|
||||
* Node visitors can now return `NodeTraverser::DONT_TRAVERSE_CHILDREN` from `enterNode()` in order to skip all children
|
||||
of the current node, for all visitors.
|
||||
|
||||
* Added builders for traits and namespaces.
|
||||
|
||||
* The class, interface, trait, function, method and property builders now support adding doc comments using the
|
||||
`setDocComment()` method.
|
||||
|
||||
* Added support for fully-qualified and namespace-relative names in builders. No longer allow use of name component
|
||||
arrays.
|
||||
|
||||
* Do not add documentation and tests to distribution archive files.
|
||||
|
||||
Version 1.0.2 (2014-11-04)
|
||||
--------------------------
|
||||
|
||||
* The `NameResolver` visitor now also resolves names in trait adaptations (aliases and precedence declarations).
|
||||
|
||||
* Remove stray whitespace when pretty-printing trait adaptations that only change visibility.
|
||||
|
||||
Version 1.0.1 (2014-10-14)
|
||||
--------------------------
|
||||
|
||||
* Disallow `new` expressions without a class name. Previously `new;` was accidentally considered to be valid code.
|
||||
|
||||
* Support T_ONUMBER token used by HHVM.
|
||||
|
||||
* Add ability to directly pass code to the `php-parse.php` script.
|
||||
|
||||
* Prevent truncation of `var_dump()` output in the `php-parse.php` script if XDebug is used.
|
||||
|
||||
Version 1.0.0 (2014-09-12)
|
||||
--------------------------
|
||||
|
||||
* [BC] Removed deprecated `Template` and `TemplateLoader` classes.
|
||||
|
||||
* Fixed XML unserializer to properly work with new namespaced node names.
|
||||
|
||||
Version 1.0.0-beta2 (2014-08-31)
|
||||
--------------------------------
|
||||
|
||||
* [PHP 5.6] Updated support for constant scalar expressions to comply with latest changes. This means that arrays
|
||||
and array dimension fetches are now supported as well.
|
||||
|
||||
* [PHP 5.6] Direct array dereferencing of constants is supported now, i.e. both `FOO[0]` and `Foo::BAR[0]` are valid
|
||||
now.
|
||||
|
||||
* Fixed handling of special class names (`self`, `parent` and `static`) in the name resolver to be case insensitive.
|
||||
Additionally the name resolver now enforces that special class names are only used as unqualified names, e.g. `\self`
|
||||
is considered invalid.
|
||||
|
||||
* The case of references to the `static` class name is now preserved. Previously `static` was always lowercased,
|
||||
regardless of the case used in the source code.
|
||||
|
||||
* The autoloader now only requires a file if it exists. This allows usages like
|
||||
`class_exists('PhpParser\NotExistingClass')`.
|
||||
|
||||
* Added experimental `bin/php-parse.php` script, which is intended to help exploring and debugging the node tree.
|
||||
|
||||
* Separated the parser implemention (in `lib/PhpParser/ParserAbstract.php`) and the generated data (in
|
||||
`lib/PhpParser/Parser.php`). Furthermore the parser now uses meaningful variable names and contains comments
|
||||
explaining their usage.
|
||||
|
||||
Version 1.0.0-beta1 (2014-03-27)
|
||||
--------------------------------
|
||||
|
||||
* [BC] PHP-Parser now requires PHP 5.3 or newer to run. It is however still possible to *parse* PHP 5.2 source code,
|
||||
while running on a newer version.
|
||||
|
||||
* [BC] The library has been moved to use namespaces with the `PhpParser` vendor prefix. However, the old names using
|
||||
underscores are still available as aliases, as such most code should continue running on the new version without
|
||||
further changes.
|
||||
|
||||
However, code performing dispatch operations on `Node::getType()` may be affected by some of the name changes. For
|
||||
example a `+` node will now return type `Expr_BinaryOp_Plus` instead of `Expr_Plus`. In particular this may affect
|
||||
custom pretty printers.
|
||||
|
||||
Due to conflicts with reserved keywords, some class names now end with an underscore, e.g. `PHPParser_Node_Stmt_Class`
|
||||
is now `PhpParser\Node\Stmt\Class_`. (But as usual, the old name is still available)
|
||||
|
||||
* [PHP 5.6] Added support for the power operator `**` (node `Expr\BinaryOp\Pow`) and the compound power assignment
|
||||
operator `**=` (node `Expr\AssignOp\Pow`).
|
||||
|
||||
* [PHP 5.6] Added support for variadic functions: `Param` nodes now have `variadic` as a boolean subnode.
|
||||
|
||||
* [PHP 5.6] Added support for argument unpacking: `Arg` nodes now have `unpack` as a boolean subnode.
|
||||
|
||||
* [PHP 5.6] Added support for aliasing of functions and constants. `Stmt\Use_` nodes now have an integral `type`
|
||||
subnode, which is one of `Stmt\Use_::TYPE_NORMAL` (`use`), `Stmt\Use_::TYPE_FUNCTION` (`use function`) or
|
||||
`Stmt\Use_::TYPE_CONSTANT` (`use const`).
|
||||
|
||||
The `NameResolver` now also supports resolution of such aliases.
|
||||
|
||||
* [PHP 5.6] Added support for constant scalar expressions. This means that certain expressions are now allowed as the
|
||||
initializer for constants, properties, parameters, static variables, etc.
|
||||
|
||||
* [BC] Improved pretty printing of empty statements lists, which are now printed as `{\n}` instead of `{\n \n}`.
|
||||
This changes the behavior of the protected `PrettyPrinterAbstract::pStmts()` method, so custom pretty printing code
|
||||
making use it of may need to be adjusted.
|
||||
|
||||
* Changed the order of some subnodes to be consistent with their order in the sour code. For example `Stmt\If->cond`
|
||||
will now appear before `Stmt\If->stmts` etc.
|
||||
|
||||
* Added `Scalar\MagicConstant->getName()`, which returns the name of the magic constant (e.g. `__CLASS__`).
|
||||
|
||||
**The following changes are also included in 0.9.5**:
|
||||
|
||||
* [BC] Deprecated `PHPParser_Template` and `PHPParser_TemplateLoader`. This functionality does not belong in the main project
|
||||
and - as far as I know - nobody is using it.
|
||||
|
||||
* Add `NodeTraverser::removeVisitor()` method, which removes a visitor from the node traverser. This also modifies the
|
||||
corresponding `NodeTraverserInterface`.
|
||||
|
||||
* Fix alias resolution in `NameResolver`: Class names are now correctly handled as case-insensitive.
|
||||
|
||||
* The undefined variable error, which is used to the lexer to reset the error state, will no longer interfere with
|
||||
custom error handlers.
|
||||
|
||||
---
|
||||
|
||||
**This changelog only includes changes from the 1.0 and 2.0 series. For older changes see the
|
||||
[0.9 series changelog][https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md].**
|
||||
**This changelog only includes changes from the 2.0 series. For older changes see the
|
||||
[1.x series changelog](https://github.com/nikic/PHP-Parser/blob/1.x/CHANGELOG.md) and the
|
||||
[0.9 series changelog](https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md).**
|
28
README.md
28
README.md
@ -1,12 +1,16 @@
|
||||
PHP Parser
|
||||
==========
|
||||
|
||||
This is a PHP 5.2 to PHP 5.6 parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
[](https://travis-ci.org/nikic/PHP-Parser) [](https://coveralls.io/github/nikic/PHP-Parser?branch=master)
|
||||
|
||||
This is a PHP 5.2 to PHP 7.1 parser written in PHP. Its purpose is to simplify static code analysis and
|
||||
manipulation.
|
||||
|
||||
[**Documentation for version 1.x**][doc_1_x] (stable; for running on PHP >= 5.3).
|
||||
[**Documentation for version 3.x**][doc_master] (stable; for running on PHP >= 5.5; for parsing PHP 5.2 to PHP 7.1).
|
||||
|
||||
[Documentation for version 2.x-dev][doc_master] (dev; for running on PHP >= 5.4).
|
||||
[Documentation for version 2.x][doc_2_x] (stable; for running on PHP >= 5.4; for parsing PHP 5.2 to PHP 7.0).
|
||||
|
||||
[Documentation for version 1.x][doc_1_x] (unsupported; for running on PHP >= 5.3; for parsing PHP 5.2 to PHP 5.6).
|
||||
|
||||
In a Nutshell
|
||||
-------------
|
||||
@ -70,18 +74,26 @@ programming errors or security issues).
|
||||
Additionally, you can convert a syntax tree back to PHP code. This allows you to do code preprocessing
|
||||
(like automatedly porting code to older PHP versions).
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The preferred installation method is [composer](https://getcomposer.org):
|
||||
|
||||
php composer.phar require nikic/php-parser
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
1. [Introduction](doc/0_Introduction.markdown)
|
||||
2. [Installation](doc/1_Installation.markdown)
|
||||
3. [Usage of basic components](doc/2_Usage_of_basic_components.markdown)
|
||||
4. [Other node tree representations](doc/3_Other_node_tree_representations.markdown)
|
||||
5. [Code generation](doc/4_Code_generation.markdown)
|
||||
2. [Usage of basic components](doc/2_Usage_of_basic_components.markdown)
|
||||
3. [Other node tree representations](doc/3_Other_node_tree_representations.markdown)
|
||||
4. [Code generation](doc/4_Code_generation.markdown)
|
||||
|
||||
Component documentation:
|
||||
|
||||
1. [Lexer](doc/component/Lexer.markdown)
|
||||
1. [Error handling](doc/component/Error_handling.markdown)
|
||||
2. [Lexer](doc/component/Lexer.markdown)
|
||||
|
||||
[doc_1_x]: https://github.com/nikic/PHP-Parser/tree/1.x/doc
|
||||
[doc_2_x]: https://github.com/nikic/PHP-Parser/tree/2.x/doc
|
||||
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc
|
||||
|
@ -68,4 +68,7 @@ deprecated. Instead `Name::concat()` and `Name->slice()` should be used.
|
||||
* The legacy node format has been removed. If you use custom nodes, they are now expected to
|
||||
implement a `getSubNodeNames()` method.
|
||||
* The default value for `Scalar` node constructors was removed. This means that something like
|
||||
`new LNumber()` should be replaced by `new LNumber(0)`.
|
||||
`new LNumber()` should be replaced by `new LNumber(0)`.
|
||||
* String parts of encapsed strings are now represented using `Scalar\EncapsStringPart` nodes, while
|
||||
previously raw strings were used. This affects the `parts` child of `Scalar\Encaps` and
|
||||
`Expr\ShellExec`.
|
156
UPGRADE-3.0.md
Normal file
156
UPGRADE-3.0.md
Normal file
@ -0,0 +1,156 @@
|
||||
Upgrading from PHP-Parser 2.x to 3.0
|
||||
====================================
|
||||
|
||||
The backwards-incompatible changes in this release may be summarized as follows:
|
||||
|
||||
* The specific details of the node representation have changed in some cases, primarily to
|
||||
accomodate new PHP 7.1 features.
|
||||
* There have been significant changes to the error recovery implementation. This may affect you,
|
||||
if you used the error recovery mode or have a custom lexer implementation.
|
||||
* A number of deprecated methods were removed.
|
||||
|
||||
### PHP version requirements
|
||||
|
||||
PHP-Parser now requires PHP 5.5 or newer to run. It is however still possible to *parse* PHP 5.2,
|
||||
5.3 and 5.4 source code, while running on a newer version.
|
||||
|
||||
### Changes to the node structure
|
||||
|
||||
The following changes are likely to require code changes if the respective nodes are used:
|
||||
|
||||
* The `List` subnode `vars` has been renamed to `items` and now contains `ArrayItem`s instead of
|
||||
plain variables.
|
||||
* The `Catch` subnode `type` has been renamed to `types` and is now an array of `Name`s.
|
||||
* The `TryCatch` subnode `finallyStmts` has been replaced with a `finally` subnode that holds an
|
||||
explicit `Finally` node.
|
||||
* The `type` subnode on `Class`, `ClassMethod` and `Property` has been renamed to `flags`. The
|
||||
`type` subnode has retained for backwards compatibility and is populated to the same value as
|
||||
`flags`. However, writes to `type` will not update `flags` and use of `type` is discouraged.
|
||||
|
||||
The following changes are unlikely to require code changes:
|
||||
|
||||
* The `ClassConst` constructor changed to accept an additional `flags` subnode.
|
||||
* The `Trait` constructor now has the same form as the `Class` and `Interface` constructors: It
|
||||
takes an array of subnodes. Unlike classes/interfaces, traits can only have a `stmts` subnode.
|
||||
* The `Array` subnode `items` may now contain `null` elements (due to destructuring).
|
||||
* `void` and `iterable` types are now stored as strings if the PHP 7 parser is used. Previously
|
||||
these would have been represented as `Name` instances.
|
||||
|
||||
### Changes to error recovery mode
|
||||
|
||||
Previously, error recovery mode was enabled by setting the `throwOnError` option to `false` when
|
||||
creating the parser, while collected errors were retrieved using the `getErrors()` method:
|
||||
|
||||
```php
|
||||
$lexer = ...;
|
||||
$parser = (new ParserFactory)->create(ParserFactor::ONLY_PHP7, $lexer, [
|
||||
'throwOnError' => true,
|
||||
]);
|
||||
|
||||
$stmts = $parser->parse($code);
|
||||
$errors = $parser->getErrors();
|
||||
if ($errors) {
|
||||
handleErrors($errors);
|
||||
}
|
||||
processAst($stmts);
|
||||
```
|
||||
|
||||
Both the `throwOnError` option and the `getErrors()` method have been removed in PHP-Parser 3.0.
|
||||
Instead an instance of `ErrorHandler\Collecting` should be passed to the `parse()` method:
|
||||
|
||||
```php
|
||||
$lexer = ...;
|
||||
$parser = (new ParserFactory)->create(ParserFactor::ONLY_PHP7, $lexer);
|
||||
|
||||
$errorHandler = new ErrorHandler\Collecting;
|
||||
$stmts = $parser->parse($code, $errorHandler);
|
||||
if ($errorHandler->hasErrors()) {
|
||||
handleErrors($errorHandler->getErrors());
|
||||
}
|
||||
processAst($stmts);
|
||||
```
|
||||
|
||||
#### Multiple parser fallback in error recovery mode
|
||||
|
||||
As a result of this change, if a `Multiple` parser is used (e.g. through the `ParserFactory` using
|
||||
`PREFER_PHP7` or `PREFER_PHP5`), it will now return the result of the first *non-throwing* parse. As
|
||||
parsing never throws in error recovery mode, the result from the first parser will always be
|
||||
returned.
|
||||
|
||||
The PHP 7 parser is a superset of the PHP 5 parser, with the exceptions that `=& new` and
|
||||
`global $$foo->bar` are not supported (other differences are in representation only). The PHP 7
|
||||
parser will be able to recover from the error in both cases. For this reason, this change will
|
||||
likely pass unnoticed if you do not specifically test for this syntax.
|
||||
|
||||
It is possible to restore the precise previous behavior with the following code:
|
||||
|
||||
```php
|
||||
$lexer = ...;
|
||||
$parser7 = new Parser\Php7($lexer);
|
||||
$parser5 = new Parser\Php5($lexer);
|
||||
|
||||
$errors7 = new ErrorHandler\Collecting();
|
||||
$stmts7 = $parser7->parse($code, $errors7);
|
||||
if ($errors7->hasErrors()) {
|
||||
$errors5 = new ErrorHandler\Collecting();
|
||||
$stmts5 = $parser5->parse($code, $errors5);
|
||||
if (!$errors5->hasErrors()) {
|
||||
// If PHP 7 parse has errors but PHP 5 parse has no errors, use PHP 5 result
|
||||
return [$stmts5, $errors5];
|
||||
}
|
||||
}
|
||||
// If PHP 7 succeeds or both fail use PHP 7 result
|
||||
return [$stmts7, $errors7];
|
||||
```
|
||||
|
||||
#### Error handling in the lexer
|
||||
|
||||
In order to support recovery from lexer errors, the signature of the `startLexing()` method changed
|
||||
to optionally accept an `ErrorHandler`:
|
||||
|
||||
```php
|
||||
// OLD
|
||||
public function startLexing($code);
|
||||
// NEW
|
||||
public function startLexing($code, ErrorHandler $errorHandler = null);
|
||||
```
|
||||
|
||||
If you use a custom lexer with overriden `startLexing()` method, it needs to be changed to accept
|
||||
the extra parameter. The value should be passed on to the parent method.
|
||||
|
||||
#### Error checks in node constructors
|
||||
|
||||
The constructors of certain nodes used to contain additional checks for semantic errors, such as
|
||||
creating a try block without either catch or finally. These checks have been moved from the node
|
||||
constructors into the parser. This allows recovery from such errors, as well as representing the
|
||||
resulting (invalid) AST.
|
||||
|
||||
This means that certain error conditions are no longer checked for manually constructed nodes.
|
||||
|
||||
### Removed methods, arguments, options
|
||||
|
||||
The following methods, arguments or options have been removed:
|
||||
|
||||
* `Comment::setLine()`, `Comment::setText()`: Create new `Comment` instances instead.
|
||||
* `Name::set()`, `Name::setFirst()`, `Name::setLast()`, `Name::append()`, `Name::prepend()`:
|
||||
Use `Name::concat()` in combination with `Name::slice()` instead.
|
||||
* `Error::getRawLine()`, `Error::setRawLine()`. Use `Error::getStartLine()` and
|
||||
`Error::setStartLine()` instead.
|
||||
* `Parser::getErrors()`. Use `ErrorHandler\Collecting` instead.
|
||||
* `$separator` argument of `Name::toString()`. Use `strtr()` instead, if you really need it.
|
||||
* `$cloneNodes` argument of `NodeTraverser::__construct()`. Explicitly clone nodes in the visitor
|
||||
instead.
|
||||
* `throwOnError` parser option. Use `ErrorHandler\Collecting` instead.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
* All methods on `PrettyPrinter\Standard` are now protected. Previoulsy most of them were public.
|
||||
The pretty printer should only be invoked using the `prettyPrint()`, `prettyPrintFile()` and
|
||||
`prettyPrintExpr()` methods.
|
||||
* The node dumper now prints numeric values that act as enums/flags in a string representation.
|
||||
If node dumper results are used in tests, updates may be needed to account for this.
|
||||
* The constants on `NameTraverserInterface` have been moved into the `NameTraverser` class.
|
||||
* The emulative lexer now directly postprocesses tokens, instead of using `~__EMU__~` sequences.
|
||||
This changes the protected API of the emulative lexer.
|
||||
* The `Name::slice()` method now returns `null` for empty slices, previously `new Name([])` was
|
||||
used. `Name::concat()` now also supports concatenation with `null`.
|
@ -1,7 +1,12 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../lib/bootstrap.php';
|
||||
foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ini_set('xdebug.max_nesting_level', 3000);
|
||||
|
||||
@ -22,10 +27,13 @@ if (empty($files)) {
|
||||
}
|
||||
|
||||
$lexer = new PhpParser\Lexer\Emulative(array('usedAttributes' => array(
|
||||
'startLine', 'endLine', 'startFilePos', 'endFilePos'
|
||||
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
|
||||
)));
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer);
|
||||
$dumper = new PhpParser\NodeDumper;
|
||||
$parser = (new PhpParser\ParserFactory)->create(
|
||||
PhpParser\ParserFactory::PREFER_PHP7,
|
||||
$lexer
|
||||
);
|
||||
$dumper = new PhpParser\NodeDumper(['dumpComments' => true]);
|
||||
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
|
||||
$serializer = new PhpParser\Serializer\XML;
|
||||
|
||||
@ -45,20 +53,23 @@ foreach ($files as $file) {
|
||||
echo "====> File $file:\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
} catch (PhpParser\Error $e) {
|
||||
if ($attributes['with-column-info'] && $e->hasColumnInfo()) {
|
||||
$startLine = $e->getStartLine();
|
||||
$endLine = $e->getEndLine();
|
||||
$startColumn = $e->getStartColumn($code);
|
||||
$endColumn = $e->getEndColumn($code);
|
||||
$message .= $e->getRawMessage() . " from $startLine:$startColumn to $endLine:$endColumn";
|
||||
} else {
|
||||
$message = $e->getMessage();
|
||||
if ($attributes['with-recovery']) {
|
||||
$errorHandler = new PhpParser\ErrorHandler\Collecting;
|
||||
$stmts = $parser->parse($code, $errorHandler);
|
||||
foreach ($errorHandler->getErrors() as $error) {
|
||||
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
|
||||
echo $message . "\n";
|
||||
}
|
||||
if (null === $stmts) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
} catch (PhpParser\Error $error) {
|
||||
$message = formatErrorMessage($error, $code, $attributes['with-column-info']);
|
||||
die($message . "\n");
|
||||
}
|
||||
|
||||
die($message . "\n");
|
||||
}
|
||||
|
||||
foreach ($operations as $operation) {
|
||||
@ -81,11 +92,21 @@ foreach ($files as $file) {
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp($error) {
|
||||
die($error . "\n\n" .
|
||||
<<<OUTPUT
|
||||
Usage: php php-parse.php [operations] file1.php [file2.php ...]
|
||||
or: php php-parse.php [operations] "<?php code"
|
||||
function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) {
|
||||
if ($withColumnInfo && $e->hasColumnInfo()) {
|
||||
return $e->getMessageWithColumnInfo($code);
|
||||
} else {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function showHelp($error = '') {
|
||||
if ($error) {
|
||||
echo $error . "\n\n";
|
||||
}
|
||||
die(<<<OUTPUT
|
||||
Usage: php-parse [operations] file1.php [file2.php ...]
|
||||
or: php-parse [operations] "<?php code"
|
||||
Turn PHP source code into an abstract syntax tree.
|
||||
|
||||
Operations is a list of the following options (--dump by default):
|
||||
@ -96,9 +117,11 @@ Operations is a list of the following options (--dump by default):
|
||||
--var-dump var_dump() nodes (for exact structure)
|
||||
-N, --resolve-names Resolve names using NodeVisitor\NameResolver
|
||||
-c, --with-column-info Show column-numbers for errors (if available)
|
||||
-r, --with-recovery Use parsing with error recovery
|
||||
-h, --help Display this page
|
||||
|
||||
Example:
|
||||
php php-parse.php -d -p -N -d file.php
|
||||
php-parse -d -p -N -d file.php
|
||||
|
||||
Dumps nodes, pretty prints them, then resolves names and dumps them again.
|
||||
|
||||
@ -111,7 +134,8 @@ function parseArgs($args) {
|
||||
$operations = array();
|
||||
$files = array();
|
||||
$attributes = array(
|
||||
'with-column-info' => false,
|
||||
'with-column-info' => false,
|
||||
'with-recovery' => false,
|
||||
);
|
||||
|
||||
array_shift($args);
|
||||
@ -145,6 +169,14 @@ function parseArgs($args) {
|
||||
case '-c';
|
||||
$attributes['with-column-info'] = true;
|
||||
break;
|
||||
case '--with-recovery':
|
||||
case '-r':
|
||||
$attributes['with-recovery'] = true;
|
||||
break;
|
||||
case '--help':
|
||||
case '-h';
|
||||
showHelp();
|
||||
break;
|
||||
case '--':
|
||||
$parseOptions = false;
|
||||
break;
|
@ -10,15 +10,21 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4",
|
||||
"php": ">=5.5",
|
||||
"ext-tokenizer": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"files": ["lib/bootstrap.php"]
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0|~5.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpParser\\": "lib/PhpParser"
|
||||
}
|
||||
},
|
||||
"bin": ["bin/php-parse"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This project is a PHP 5.2 to PHP 5.6 parser **written in PHP itself**.
|
||||
This project is a PHP 5.2 to PHP 7.1 parser **written in PHP itself**.
|
||||
|
||||
What is this for?
|
||||
-----------------
|
||||
@ -18,7 +18,7 @@ For example an AST abstracts away the fact that in PHP variables can be written
|
||||
as `$$bar`, `${'foobar'}` or even `${!${''}=barfoo()}`. You don't have to worry about recognizing
|
||||
all the different syntaxes from a stream of tokens.
|
||||
|
||||
Another questions is: Why would I want to have a PHP parser *written in PHP*? Well, PHP might not be
|
||||
Another question is: Why would I want to have a PHP parser *written in PHP*? Well, PHP might not be
|
||||
a language especially suited for fast parsing, but processing the AST is much easier in PHP than it
|
||||
would be in other, faster languages like C. Furthermore the people most probably wanting to do
|
||||
programmatic PHP code analysis are incidentally PHP developers, not C developers.
|
||||
@ -29,9 +29,9 @@ What can it parse?
|
||||
The parser supports parsing PHP 5.2-5.6 and PHP 7.
|
||||
|
||||
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 new tokens from 5.5, 5.6 and 7.0 is
|
||||
provided. This allows to parse PHP 7.0 source code running on PHP 5.4, for example. This emulation
|
||||
is somewhat hacky and not perfect, but it should work well on any sane code.
|
||||
version it runs on), additionally a wrapper for emulating tokens from newer versions is provided.
|
||||
This allows to parse PHP 7.1 source code running on PHP 5.5, for example. This emulation is somewhat
|
||||
hacky and not perfect, but it should work well on any sane code.
|
||||
|
||||
What output does it produce?
|
||||
----------------------------
|
||||
|
@ -1,31 +0,0 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
There are multiple ways to include the PHP parser into your project:
|
||||
|
||||
Installing via Composer
|
||||
-----------------------
|
||||
|
||||
Run the following command inside your project:
|
||||
|
||||
php composer.phar require nikic/php-parser
|
||||
|
||||
If you haven't installed [Composer][1] yet, you can do so using:
|
||||
|
||||
curl -s http://getcomposer.org/installer | php
|
||||
|
||||
Installing as a Git Submodule
|
||||
-----------------------------
|
||||
|
||||
Run the following command to install the parser into the `vendor/PHP-Parser` folder:
|
||||
|
||||
git submodule add git://github.com/nikic/PHP-Parser.git vendor/PHP-Parser
|
||||
|
||||
Installing from the Zip- or Tarball
|
||||
-----------------------------------
|
||||
|
||||
Download the latest version from [the download page][2], unpack it and move the files somewhere into your project.
|
||||
|
||||
|
||||
[1]: https://getcomposer.org/
|
||||
[2]: https://github.com/nikic/PHP-Parser/tags
|
@ -6,8 +6,7 @@ This document explains how to use the parser, the pretty printer and the node tr
|
||||
Bootstrapping
|
||||
-------------
|
||||
|
||||
The library needs to register a class autoloader. When using composer, include the usual
|
||||
`vendor/autoload.php` file (alternatively you can use the bundled `lib/bootstrap.php` file):
|
||||
To bootstrap the library, include the autoloader generated by composer:
|
||||
|
||||
```php
|
||||
require 'path/to/vendor/autoload.php';
|
||||
@ -19,7 +18,9 @@ Additionally you may want to set the `xdebug.max_nesting_level` ini option to a
|
||||
ini_set('xdebug.max_nesting_level', 3000);
|
||||
```
|
||||
|
||||
This ensures that there will be no errors when traversing highly nested node trees.
|
||||
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
|
||||
slower.
|
||||
|
||||
Parsing
|
||||
-------
|
||||
@ -269,7 +270,7 @@ The `enterNode()` method can additionally return the value `NodeTraverser::DONT_
|
||||
which instructs the traverser to skip all children of the current node.
|
||||
|
||||
The `leaveNode()` method can additionally return the value `NodeTraverser::REMOVE_NODE`, in which
|
||||
case the current node will be removed from the parent array. Furthermove it is possible to return
|
||||
case the current node will be removed from the parent array. Furthermore it is possible to return
|
||||
an array of nodes, which will be merged into the parent array at the offset of the current node.
|
||||
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will
|
||||
be `array(A, X, Y, Z, C)`.
|
||||
@ -377,7 +378,7 @@ create a name with backslashes either write `$node->toString()` or `(string) $no
|
||||
a new name from the string and return it. Returning a new node replaces the old node.
|
||||
|
||||
Another thing we need to do is change the class/function/const declarations. Currently they contain
|
||||
only the shortname (i.e. the last part of the name), but they need to contain the complete name inclduing
|
||||
only the shortname (i.e. the last part of the name), but they need to contain the complete name including
|
||||
the namespace prefix:
|
||||
|
||||
```php
|
||||
|
@ -8,7 +8,7 @@ Simple serialization
|
||||
|
||||
It is possible to serialize the node tree using `serialize()` and also unserialize it using
|
||||
`unserialize()`. The output is not human readable and not easily processable from anything
|
||||
but PHP, but it is compact and generates fast. The main application thus is in caching.
|
||||
but PHP, but it is compact and generates quickly. The main application thus is in caching.
|
||||
|
||||
Human readable dumping
|
||||
----------------------
|
||||
@ -86,6 +86,134 @@ array(
|
||||
)
|
||||
```
|
||||
|
||||
JSON encoding
|
||||
-------------
|
||||
|
||||
Nodes (and comments) implement the `JsonSerializable` interface. As such, it is possible to JSON
|
||||
encode the AST directly using `json_encode()`:
|
||||
|
||||
```php
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
printLine('Hello World!!!');
|
||||
CODE;
|
||||
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7);
|
||||
$nodeDumper = new PhpParser\NodeDumper;
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
echo json_encode($stmts, JSON_PRETTY_PRINT), "\n";
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
This will result in the following output (which includes attributes):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"nodeType": "Stmt_Function",
|
||||
"byRef": false,
|
||||
"name": "printLine",
|
||||
"params": [
|
||||
{
|
||||
"nodeType": "Param",
|
||||
"type": null,
|
||||
"byRef": false,
|
||||
"variadic": false,
|
||||
"name": "msg",
|
||||
"default": null,
|
||||
"attributes": {
|
||||
"startLine": 3,
|
||||
"endLine": 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"returnType": null,
|
||||
"stmts": [
|
||||
{
|
||||
"nodeType": "Stmt_Echo",
|
||||
"exprs": [
|
||||
{
|
||||
"nodeType": "Expr_Variable",
|
||||
"name": "msg",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "Scalar_String",
|
||||
"value": "\n",
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4,
|
||||
"kind": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 4,
|
||||
"endLine": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 3,
|
||||
"endLine": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"nodeType": "Expr_FuncCall",
|
||||
"name": {
|
||||
"nodeType": "Name",
|
||||
"parts": [
|
||||
"printLine"
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 7,
|
||||
"endLine": 7
|
||||
}
|
||||
},
|
||||
"args": [
|
||||
{
|
||||
"nodeType": "Arg",
|
||||
"value": {
|
||||
"nodeType": "Scalar_String",
|
||||
"value": "Hello World!!!",
|
||||
"attributes": {
|
||||
"startLine": 7,
|
||||
"endLine": 7,
|
||||
"kind": 1
|
||||
}
|
||||
},
|
||||
"byRef": false,
|
||||
"unpack": false,
|
||||
"attributes": {
|
||||
"startLine": 7,
|
||||
"endLine": 7
|
||||
}
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"startLine": 7,
|
||||
"endLine": 7
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
There is currently no mechanism to convert JSON back into a node tree. Furthermore, not all ASTs
|
||||
can be JSON encoded. In particular, JSON only supports UTF-8 strings.
|
||||
|
||||
Serialization to XML
|
||||
--------------------
|
||||
|
||||
|
@ -29,6 +29,7 @@ $node = $factory->namespace('Name\Space')
|
||||
->addStmt($factory->method('someMethod')
|
||||
->makePublic()
|
||||
->makeAbstract() // ->makeFinal()
|
||||
->setReturnType('bool')
|
||||
->addParam($factory->param('someParam')->setTypeHint('SomeClass'))
|
||||
->setDocComment('/**
|
||||
* This method does something.
|
||||
@ -74,7 +75,7 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, \Interfaces
|
||||
*
|
||||
* @param SomeClass And takes a parameter
|
||||
*/
|
||||
public abstract function someMethod(SomeClass $someParam);
|
||||
public abstract function someMethod(SomeClass $someParam) : bool;
|
||||
protected function anotherMethod($someParam = 'test')
|
||||
{
|
||||
print $someParam;
|
||||
|
@ -35,6 +35,8 @@ the source code of the parsed file. An example for printing an error:
|
||||
if ($e->hasColumnInfo()) {
|
||||
echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
|
||||
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
|
||||
// or:
|
||||
echo $e->getMessageWithColumnInfo();
|
||||
} else {
|
||||
echo $e->getMessage();
|
||||
}
|
||||
@ -46,27 +48,23 @@ file.
|
||||
Error recovery
|
||||
--------------
|
||||
|
||||
> **EXPERIMENTAL**
|
||||
The error behavior of the parser (and other components) is controlled by an `ErrorHandler`. Whenever an error is
|
||||
encountered, `ErrorHandler::handleError()` is invoked. The default error handling strategy is `ErrorHandler\Throwing`,
|
||||
which will immediately throw when an error is encountered.
|
||||
|
||||
By default the parser will throw an exception upon encountering the first error during parsing. An alternative mode is
|
||||
also supported, in which the parser will remember the error, but try to continue parsing the rest of the source code.
|
||||
|
||||
To enable this mode the `throwOnError` parser option needs to be disabled. Any errors that occurred during parsing can
|
||||
then be retrieved using `$parser->getErrors()`. The `$parser->parse()` method will either return a partial syntax tree
|
||||
or `null` if recovery fails.
|
||||
|
||||
A usage example:
|
||||
To instead collect all encountered errors into an array, while trying to continue parsing the rest of the source code,
|
||||
an instance of `ErrorHandler\Collecting` can be passed to the `Parser::parse()` method. A usage example:
|
||||
|
||||
```php
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, null, array(
|
||||
'throwOnError' => false,
|
||||
));
|
||||
$parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::ONLY_PHP7);
|
||||
$errorHandler = new PhpParser\ErrorHandler\Collecting;
|
||||
|
||||
$stmts = $parser->parse($code);
|
||||
$errors = $parser->getErrors();
|
||||
$stmts = $parser->parse($code, $errorHandler);
|
||||
|
||||
foreach ($errors as $error) {
|
||||
// $error is an ordinary PhpParser\Error
|
||||
if ($errorHandler->hasErrors()) {
|
||||
foreach ($errorHandler->getErrors() as $error) {
|
||||
// $error is an ordinary PhpParser\Error
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $stmts) {
|
||||
@ -74,4 +72,4 @@ if (null !== $stmts) {
|
||||
}
|
||||
```
|
||||
|
||||
The error recovery implementation is experimental -- it currently won't be able to recover from many types of errors.
|
||||
The `NameResolver` visitor also accepts an `ErrorHandler` as a constructor argument.
|
@ -95,13 +95,14 @@ Lexer extension
|
||||
|
||||
A lexer has to define the following public interface:
|
||||
|
||||
void startLexing(string $code);
|
||||
void startLexing(string $code, ErrorHandler $errorHandler = null);
|
||||
array getTokens();
|
||||
string handleHaltCompiler();
|
||||
int getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null);
|
||||
|
||||
The `startLexing()` method is invoked with the source code that is to be lexed (including the opening tag) whenever the
|
||||
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens.
|
||||
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens. The
|
||||
passes `ErrorHandler` should be used to report lexing errors.
|
||||
|
||||
The `getTokens()` method returns the current token array, in the usual `token_get_all()` format. This method is not
|
||||
used by the parser (which uses `getNextToken()`), but is useful in combination with the token position attributes.
|
||||
@ -122,9 +123,10 @@ node and the `$endAttributes` from the last token that is part of the node.
|
||||
E.g. if the tokens `T_FUNCTION T_STRING ... '{' ... '}'` constitute a node, then the `$startAttributes` from the
|
||||
`T_FUNCTION` token will be taken and the `$endAttributes` from the `'}'` token.
|
||||
|
||||
An application of custom attributes is storing the original formatting of literals: The parser does not retain
|
||||
information about the formatting of integers (like decimal vs. hexadecimal) or strings (like used quote type or used
|
||||
escape sequences). This can be remedied by storing the original value in an attribute:
|
||||
An application of custom attributes is storing the exact original formatting of literals: While the parser does retain
|
||||
some information about the formatting of integers (like decimal vs. hexadecimal) or strings (like used quote type), it
|
||||
does not preserve the exact original formatting (e.g. leading zeros for integers or escape sequences in strings). This
|
||||
can be remedied by storing the original value in an attribute:
|
||||
|
||||
```php
|
||||
use PhpParser\Lexer;
|
||||
@ -135,9 +137,10 @@ class KeepOriginalValueLexer extends Lexer // or Lexer\Emulative
|
||||
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
|
||||
$tokenId = parent::getNextToken($value, $startAttributes, $endAttributes);
|
||||
|
||||
if ($tokenId == Tokens::T_CONSTANT_ENCAPSED_STRING // non-interpolated string
|
||||
|| $tokenId == Tokens::T_LNUMBER // integer
|
||||
|| $tokenId == Tokens::T_DNUMBER // floating point number
|
||||
if ($tokenId == Tokens::T_CONSTANT_ENCAPSED_STRING // non-interpolated string
|
||||
|| $tokenId == Tokens::T_ENCAPSED_AND_WHITESPACE // interpolated string
|
||||
|| $tokenId == Tokens::T_LNUMBER // integer
|
||||
|| $tokenId == Tokens::T_DNUMBER // floating point number
|
||||
) {
|
||||
// could also use $startAttributes, doesn't really matter here
|
||||
$endAttributes['originalValue'] = $value;
|
||||
|
@ -1,13 +1,12 @@
|
||||
What do all those files mean?
|
||||
=============================
|
||||
|
||||
* `php5.y`: PHP 5 grammar written in a pseudo language
|
||||
* `php7.y`: PHP 7 grammar written in a pseudo language
|
||||
* `tokens.y`: Tokens definition shared between PHP 5 and PHP 7 grammars
|
||||
* `parser.template`: A `kmyacc` parser prototype file for PHP
|
||||
* `tokens.template`: A `kmyacc` prototype file for the `Tokens` class
|
||||
* `analyze.php`: Analyzes the grammer and outputs some info about it
|
||||
* `rebuildParser.php`: Preprocesses the grammar and builds the parser using `kmyacc`
|
||||
* `php5.y`: PHP 5 grammar written in a pseudo language
|
||||
* `php7.y`: PHP 7 grammar written in a pseudo language
|
||||
* `tokens.y`: Tokens definition shared between PHP 5 and PHP 7 grammars
|
||||
* `parser.template`: A `kmyacc` parser prototype file for PHP
|
||||
* `tokens.template`: A `kmyacc` prototype file for the `Tokens` class
|
||||
* `rebuildParsers.php`: Preprocesses the grammar and builds the parser using `kmyacc`
|
||||
|
||||
.phpy pseudo language
|
||||
=====================
|
||||
@ -17,13 +16,13 @@ applied to it:
|
||||
|
||||
* Nodes are created using the syntax `Name[..., ...]`. This is transformed into
|
||||
`new Name(..., ..., attributes())`
|
||||
* Some function-like constructs are resolved (see `rebuildParser.php` for a list)
|
||||
* Some function-like constructs are resolved (see `rebuildParsers.php` for a list)
|
||||
|
||||
Building the parser
|
||||
===================
|
||||
|
||||
In order to rebuild the parser, you need [moriyoshi's fork of kmyacc](https://github.com/moriyoshi/kmyacc-forked).
|
||||
After you compiled/installed it, run the `rebuildParser.php` script.
|
||||
After you compiled/installed it, run the `rebuildParsers.php` script.
|
||||
|
||||
By default only the `Parser.php` is built. If you want to additionally emit debug symbols and create `y.output`, run the
|
||||
script with `--debug`. If you want to retain the preprocessed grammar pass `--keep-tmp-grammar`.
|
||||
|
@ -1,96 +0,0 @@
|
||||
<?php
|
||||
|
||||
const GRAMMAR_FILE = './php5.y';
|
||||
|
||||
const LIB = '(?(DEFINE)
|
||||
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
|
||||
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
|
||||
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
|
||||
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
|
||||
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
|
||||
)';
|
||||
|
||||
const RULE_BLOCK = '(?<name>[a-z_]++):(?<rules>[^\'"/{};]*+(?:(?:(?&string)|(?&comment)|(?&code)|/|})[^\'"/{};]*+)*+);';
|
||||
|
||||
$usedTerminals = array_flip(array(
|
||||
'T_VARIABLE', 'T_STRING', 'T_INLINE_HTML', 'T_ENCAPSED_AND_WHITESPACE',
|
||||
'T_LNUMBER', 'T_DNUMBER', 'T_CONSTANT_ENCAPSED_STRING', 'T_STRING_VARNAME', 'T_NUM_STRING'
|
||||
));
|
||||
$unusedNonterminals = array_flip(array(
|
||||
'case_separator', 'optional_comma'
|
||||
));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return array_filter($pieces);
|
||||
}
|
||||
|
||||
echo '<pre>';
|
||||
|
||||
////////////////////
|
||||
////////////////////
|
||||
////////////////////
|
||||
|
||||
list($defs, $ruleBlocks) = magicSplit('%%', file_get_contents(GRAMMAR_FILE));
|
||||
|
||||
if ('' !== trim(preg_replace(regex(RULE_BLOCK), '', $ruleBlocks))) {
|
||||
die('Not all rule blocks were properly recognized!');
|
||||
}
|
||||
|
||||
preg_match_all(regex(RULE_BLOCK), $ruleBlocks, $ruleBlocksMatches, PREG_SET_ORDER);
|
||||
foreach ($ruleBlocksMatches as $match) {
|
||||
$ruleBlockName = $match['name'];
|
||||
$rules = magicSplit('\|', $match['rules']);
|
||||
|
||||
foreach ($rules as &$rule) {
|
||||
$parts = magicSplit('\s+', $rule);
|
||||
$usedParts = array();
|
||||
|
||||
foreach ($parts as $part) {
|
||||
if ('{' === $part[0]) {
|
||||
preg_match_all('~\$([0-9]+)~', $part, $backReferencesMatches, PREG_SET_ORDER);
|
||||
foreach ($backReferencesMatches as $match) {
|
||||
$usedParts[$match[1]] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$i = 1;
|
||||
foreach ($parts as &$part) {
|
||||
if ('/' === $part[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($usedParts[$i])) {
|
||||
if ('\'' === $part[0] || '{' === $part[0]
|
||||
|| (ctype_upper($part[0]) && !isset($usedTerminals[$part]))
|
||||
|| (ctype_lower($part[0]) && isset($unusedNonterminals[$part]))
|
||||
) {
|
||||
$part = '<span style="background-color: red; color: white;">' . $part . '</span>';
|
||||
} else {
|
||||
$part = '<strong><em>' . $part . '</em></strong>';
|
||||
}
|
||||
} elseif ((ctype_upper($part[0]) && isset($usedTerminals[$part]))
|
||||
|| (ctype_lower($part[0]) && !isset($unusedNonterminals[$part]))
|
||||
|
||||
) {
|
||||
$part = '<span style="background-color: blue; color: white;">' . $part . '</span>';
|
||||
}
|
||||
|
||||
++$i;
|
||||
}
|
||||
|
||||
$rule = implode(' ', $parts);
|
||||
}
|
||||
|
||||
echo $ruleBlockName, ':', "\n", ' ', implode("\n" . ' | ', $rules), "\n", ';', "\n\n";
|
||||
}
|
177
grammar/php5.y
177
grammar/php5.y
@ -9,11 +9,17 @@ start:
|
||||
top_statement_list { $$ = $this->handleNamespaces($1); }
|
||||
;
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list top_statement { pushNormalizing($1, $2); }
|
||||
top_statement_list_ex:
|
||||
top_statement_list_ex top_statement { pushNormalizing($1, $2); }
|
||||
| /* empty */ { init(); }
|
||||
;
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
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
|
||||
@ -21,6 +27,7 @@ reserved_non_modifiers:
|
||||
| T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
|
||||
| T_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
|
||||
;
|
||||
|
||||
semi_reserved:
|
||||
@ -48,12 +55,15 @@ top_statement:
|
||||
| class_declaration_statement { $$ = $1; }
|
||||
| T_HALT_COMPILER
|
||||
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
|
||||
| T_NAMESPACE namespace_name ';' { $$ = Stmt\Namespace_[$2, null]; }
|
||||
| T_NAMESPACE namespace_name '{' top_statement_list '}' { $$ = Stmt\Namespace_[$2, $4]; }
|
||||
| T_NAMESPACE '{' top_statement_list '}' { $$ = Stmt\Namespace_[null, $3]; }
|
||||
| T_NAMESPACE namespace_name ';'
|
||||
{ $$ = Stmt\Namespace_[$2, null]; $this->checkNamespace($$); }
|
||||
| T_NAMESPACE namespace_name '{' top_statement_list '}'
|
||||
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); }
|
||||
| T_NAMESPACE '{' top_statement_list '}'
|
||||
{ $$ = Stmt\Namespace_[null, $3]; $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]; }
|
||||
;
|
||||
|
||||
@ -64,10 +74,20 @@ use_type:
|
||||
|
||||
/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
|
||||
group_use_declaration:
|
||||
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[Name[$3], $6, $2]; }
|
||||
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, $2]; }
|
||||
| T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($4, stackAttributes(#4)), $7, $2]; }
|
||||
| T_USE namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[Name[$2], $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
{ $$ = Stmt\GroupUse[new Name($2, stackAttributes(#2)), $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| T_USE T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
;
|
||||
|
||||
unprefixed_use_declarations:
|
||||
unprefixed_use_declarations ',' unprefixed_use_declaration
|
||||
{ push($1, $3); }
|
||||
| unprefixed_use_declaration { init($1); }
|
||||
;
|
||||
|
||||
use_declarations:
|
||||
@ -75,21 +95,26 @@ use_declarations:
|
||||
| use_declaration { init($1); }
|
||||
;
|
||||
|
||||
use_declaration:
|
||||
namespace_name { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| namespace_name T_AS T_STRING { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| T_NS_SEPARATOR namespace_name { $$ = Stmt\UseUse[$2, null, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| T_NS_SEPARATOR namespace_name T_AS T_STRING { $$ = Stmt\UseUse[$2, $4, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
;
|
||||
|
||||
inline_use_declarations:
|
||||
inline_use_declarations ',' inline_use_declaration { push($1, $3); }
|
||||
| inline_use_declaration { init($1); }
|
||||
;
|
||||
|
||||
unprefixed_use_declaration:
|
||||
namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| namespace_name T_AS T_STRING
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
use_declaration:
|
||||
unprefixed_use_declaration { $$ = $1; }
|
||||
| T_NS_SEPARATOR unprefixed_use_declaration { $$ = $2; }
|
||||
;
|
||||
|
||||
inline_use_declaration:
|
||||
use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; }
|
||||
| use_type use_declaration { $$ = $2; $$->type = $1; }
|
||||
unprefixed_use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; }
|
||||
| use_type unprefixed_use_declaration { $$ = $2; $$->type = $1; }
|
||||
;
|
||||
|
||||
constant_declaration_list:
|
||||
@ -110,11 +135,17 @@ class_const:
|
||||
identifier '=' static_scalar { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list inner_statement { pushNormalizing($1, $2); }
|
||||
inner_statement_list_ex:
|
||||
inner_statement_list_ex inner_statement { pushNormalizing($1, $2); }
|
||||
| /* empty */ { init(); }
|
||||
;
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
inner_statement:
|
||||
statement { $$ = $1; }
|
||||
| function_declaration_statement { $$ = $1; }
|
||||
@ -123,8 +154,8 @@ inner_statement:
|
||||
{ throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
|
||||
;
|
||||
|
||||
statement:
|
||||
'{' inner_statement_list '}' { $$ = $2; }
|
||||
non_empty_statement:
|
||||
'{' inner_statement_list '}' { $$ = $2; prependLeadingComments($$); }
|
||||
| T_IF parentheses_expr statement elseif_list else_single
|
||||
{ $$ = Stmt\If_[$2, ['stmts' => toArray($3), 'elseifs' => $4, 'else' => $5]]; }
|
||||
| T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
|
||||
@ -152,15 +183,22 @@ statement:
|
||||
| T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
|
||||
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
|
||||
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
|
||||
| ';' { $$ = array(); /* means: no statement */ }
|
||||
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; }
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
|
||||
| T_GOTO T_STRING ';' { $$ = Stmt\Goto_[$2]; }
|
||||
| T_STRING ':' { $$ = Stmt\Label[$1]; }
|
||||
| expr error { $$ = $1; }
|
||||
| error { $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
statement:
|
||||
non_empty_statement { $$ = $1; }
|
||||
| ';'
|
||||
{ makeNop($$, $this->startAttributeStack[#1]);
|
||||
if ($$ === null) $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
catches:
|
||||
/* empty */ { init(); }
|
||||
| catches catch { push($1, $2); }
|
||||
@ -168,12 +206,12 @@ catches:
|
||||
|
||||
catch:
|
||||
T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Catch_[$3, parseVar($4), $7]; }
|
||||
{ $$ = Stmt\Catch_[array($3), parseVar($4), $7]; }
|
||||
;
|
||||
|
||||
optional_finally:
|
||||
/* empty */ { $$ = null; }
|
||||
| T_FINALLY '{' inner_statement_list '}' { $$ = $3; }
|
||||
| T_FINALLY '{' inner_statement_list '}' { $$ = Stmt\Finally_[$3]; }
|
||||
;
|
||||
|
||||
variables_list:
|
||||
@ -198,11 +236,13 @@ function_declaration_statement:
|
||||
|
||||
class_declaration_statement:
|
||||
class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; }
|
||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
|
||||
$this->checkClass($$, #2); }
|
||||
| T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; }
|
||||
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
|
||||
$this->checkInterface($$, #2); }
|
||||
| T_TRAIT T_STRING '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Trait_[$2, $4]; }
|
||||
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
|
||||
;
|
||||
|
||||
class_entry_type:
|
||||
@ -242,7 +282,8 @@ foreach_statement:
|
||||
;
|
||||
|
||||
declare_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
non_empty_statement { $$ = toArray($1); }
|
||||
| ';' { $$ = null; }
|
||||
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
@ -328,9 +369,9 @@ non_empty_parameter_list:
|
||||
|
||||
parameter:
|
||||
optional_param_type optional_ref optional_ellipsis T_VARIABLE
|
||||
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; }
|
||||
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; $this->checkParam($$); }
|
||||
| optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' static_scalar
|
||||
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; }
|
||||
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; $this->checkParam($$); }
|
||||
;
|
||||
|
||||
type:
|
||||
@ -393,10 +434,12 @@ class_statement_list:
|
||||
;
|
||||
|
||||
class_statement:
|
||||
variable_modifiers property_declaration_list ';' { $$ = Stmt\Property[$1, $2]; }
|
||||
| T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2]; }
|
||||
variable_modifiers property_declaration_list ';'
|
||||
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
||||
| T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2, 0]; }
|
||||
| method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
|
||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]]; }
|
||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
|
||||
$this->checkClassMethod($$, #1); }
|
||||
| T_USE name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
||||
;
|
||||
|
||||
@ -448,7 +491,7 @@ method_modifiers:
|
||||
|
||||
non_empty_member_modifiers:
|
||||
member_modifier { $$ = $1; }
|
||||
| non_empty_member_modifiers member_modifier { Stmt\Class_::verifyModifier($1, $2); $$ = $1 | $2; }
|
||||
| non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
|
||||
;
|
||||
|
||||
member_modifier:
|
||||
@ -555,7 +598,10 @@ expr:
|
||||
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
|
||||
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
|
||||
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
|
||||
| T_EXIT exit_expr { $$ = Expr\Exit_ [$2]; }
|
||||
| T_EXIT exit_expr
|
||||
{ $attrs = attributes();
|
||||
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
|
||||
$$ = new Expr\Exit_($2, $attrs); }
|
||||
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
|
||||
| scalar { $$ = $1; }
|
||||
| array_expr { $$ = $1; }
|
||||
@ -583,14 +629,19 @@ yield_expr:
|
||||
;
|
||||
|
||||
array_expr:
|
||||
T_ARRAY '(' array_pair_list ')' { $$ = Expr\Array_[$3]; }
|
||||
| '[' array_pair_list ']' { $$ = Expr\Array_[$2]; }
|
||||
T_ARRAY '(' array_pair_list ')'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
|
||||
$$ = new Expr\Array_($3, $attrs); }
|
||||
| '[' array_pair_list ']'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT;
|
||||
$$ = new Expr\Array_($2, $attrs); }
|
||||
;
|
||||
|
||||
scalar_dereference:
|
||||
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
|
||||
{ $$ = Expr\ArrayDimFetch[Scalar\String_[Scalar\String_::parse($1, false)], $3]; }
|
||||
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
|
||||
$$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
|
||||
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
/* alternative array syntax missing intentionally */
|
||||
@ -598,7 +649,8 @@ scalar_dereference:
|
||||
|
||||
anonymous_class:
|
||||
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2); }
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
|
||||
$this->checkClass($$[0], -1); }
|
||||
|
||||
new_expr:
|
||||
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
|
||||
@ -691,7 +743,7 @@ exit_expr:
|
||||
backticks_expr:
|
||||
/* empty */ { $$ = array(); }
|
||||
| T_ENCAPSED_AND_WHITESPACE
|
||||
{ $$ = array(Scalar\String_::parseEscapeSequences($1, '`', false)); }
|
||||
{ $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`', false)]); }
|
||||
| encaps_list { parseEncapsed($1, '`', false); $$ = $1; }
|
||||
;
|
||||
|
||||
@ -701,9 +753,11 @@ ctor_arguments:
|
||||
;
|
||||
|
||||
common_scalar:
|
||||
T_LNUMBER { $$ = Scalar\LNumber[Scalar\LNumber::parse($1)]; }
|
||||
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
|
||||
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
|
||||
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_[Scalar\String_::parse($1, false)]; }
|
||||
| T_CONSTANT_ENCAPSED_STRING
|
||||
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
|
||||
$$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
|
||||
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
|
||||
| T_FILE { $$ = Scalar\MagicConst\File[]; }
|
||||
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
|
||||
@ -713,9 +767,11 @@ common_scalar:
|
||||
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
|
||||
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
|
||||
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
|
||||
{ $$ = Scalar\String_[Scalar\String_::parseDocString($1, $2, false)]; }
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
$$ = new Scalar\String_(Scalar\String_::parseDocString($1, $2, false), $attrs); }
|
||||
| T_START_HEREDOC T_END_HEREDOC
|
||||
{ $$ = Scalar\String_['']; }
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
$$ = new Scalar\String_('', $attrs); }
|
||||
;
|
||||
|
||||
static_scalar:
|
||||
@ -773,9 +829,11 @@ scalar:
|
||||
common_scalar { $$ = $1; }
|
||||
| constant { $$ = $1; }
|
||||
| '"' encaps_list '"'
|
||||
{ parseEncapsed($2, '"', false); $$ = Scalar\Encapsed[$2]; }
|
||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
| T_START_HEREDOC encaps_list T_END_HEREDOC
|
||||
{ parseEncapsedDoc($2, false); $$ = Scalar\Encapsed[$2]; }
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
parseEncapsedDoc($2, true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
;
|
||||
|
||||
static_array_pair_list:
|
||||
@ -867,6 +925,7 @@ object_property:
|
||||
T_STRING { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| variable_without_objects { $$ = $1; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
list_expr:
|
||||
@ -879,8 +938,8 @@ list_expr_elements:
|
||||
;
|
||||
|
||||
list_expr_element:
|
||||
variable { $$ = $1; }
|
||||
| list_expr { $$ = $1; }
|
||||
variable { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| /* empty */ { $$ = null; }
|
||||
;
|
||||
|
||||
@ -903,15 +962,23 @@ array_pair:
|
||||
|
||||
encaps_list:
|
||||
encaps_list encaps_var { push($1, $2); }
|
||||
| encaps_list T_ENCAPSED_AND_WHITESPACE { push($1, $2); }
|
||||
| encaps_list encaps_string_part { push($1, $2); }
|
||||
| encaps_var { init($1); }
|
||||
| T_ENCAPSED_AND_WHITESPACE encaps_var { init($1, $2); }
|
||||
| encaps_string_part encaps_var { init($1, $2); }
|
||||
;
|
||||
|
||||
encaps_string_part:
|
||||
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
|
||||
;
|
||||
|
||||
encaps_base_var:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
;
|
||||
|
||||
encaps_var:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
| T_VARIABLE '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[Expr\Variable[parseVar($1)], $3]; }
|
||||
| T_VARIABLE T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[Expr\Variable[parseVar($1)], $3]; }
|
||||
encaps_base_var { $$ = $1; }
|
||||
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| encaps_base_var T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[$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 T_STRING_VARNAME '[' expr ']' '}'
|
||||
|
221
grammar/php7.y
221
grammar/php7.y
@ -9,11 +9,17 @@ start:
|
||||
top_statement_list { $$ = $this->handleNamespaces($1); }
|
||||
;
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list top_statement { pushNormalizing($1, $2); }
|
||||
top_statement_list_ex:
|
||||
top_statement_list_ex top_statement { pushNormalizing($1, $2); }
|
||||
| /* empty */ { init(); }
|
||||
;
|
||||
|
||||
top_statement_list:
|
||||
top_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
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
|
||||
@ -21,6 +27,7 @@ reserved_non_modifiers:
|
||||
| T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
|
||||
| T_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
|
||||
;
|
||||
|
||||
semi_reserved:
|
||||
@ -48,12 +55,15 @@ top_statement:
|
||||
| class_declaration_statement { $$ = $1; }
|
||||
| T_HALT_COMPILER
|
||||
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
|
||||
| T_NAMESPACE namespace_name ';' { $$ = Stmt\Namespace_[$2, null]; }
|
||||
| T_NAMESPACE namespace_name '{' top_statement_list '}' { $$ = Stmt\Namespace_[$2, $4]; }
|
||||
| T_NAMESPACE '{' top_statement_list '}' { $$ = Stmt\Namespace_[null, $3]; }
|
||||
| T_NAMESPACE namespace_name ';'
|
||||
{ $$ = Stmt\Namespace_[$2, null]; $this->checkNamespace($$); }
|
||||
| T_NAMESPACE namespace_name '{' top_statement_list '}'
|
||||
{ $$ = Stmt\Namespace_[$2, $4]; $this->checkNamespace($$); }
|
||||
| T_NAMESPACE '{' top_statement_list '}'
|
||||
{ $$ = Stmt\Namespace_[null, $3]; $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]; }
|
||||
;
|
||||
|
||||
@ -64,10 +74,20 @@ use_type:
|
||||
|
||||
/* Using namespace_name_parts here to avoid s/r conflict on T_NS_SEPARATOR */
|
||||
group_use_declaration:
|
||||
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[Name[$3], $6, $2]; }
|
||||
T_USE use_type namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, $2]; }
|
||||
| T_USE use_type T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($4, stackAttributes(#4)), $7, $2]; }
|
||||
| T_USE namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[Name[$2], $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
{ $$ = Stmt\GroupUse[new Name($2, stackAttributes(#2)), $5, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| T_USE T_NS_SEPARATOR namespace_name_parts T_NS_SEPARATOR '{' inline_use_declarations '}'
|
||||
{ $$ = Stmt\GroupUse[new Name($3, stackAttributes(#3)), $6, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
;
|
||||
|
||||
unprefixed_use_declarations:
|
||||
unprefixed_use_declarations ',' unprefixed_use_declaration
|
||||
{ push($1, $3); }
|
||||
| unprefixed_use_declaration { init($1); }
|
||||
;
|
||||
|
||||
use_declarations:
|
||||
@ -75,21 +95,26 @@ use_declarations:
|
||||
| use_declaration { init($1); }
|
||||
;
|
||||
|
||||
use_declaration:
|
||||
namespace_name { $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| namespace_name T_AS T_STRING { $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| T_NS_SEPARATOR namespace_name { $$ = Stmt\UseUse[$2, null, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
| T_NS_SEPARATOR namespace_name T_AS T_STRING { $$ = Stmt\UseUse[$2, $4, Stmt\Use_::TYPE_UNKNOWN]; }
|
||||
;
|
||||
|
||||
inline_use_declarations:
|
||||
inline_use_declarations ',' inline_use_declaration { push($1, $3); }
|
||||
| inline_use_declaration { init($1); }
|
||||
;
|
||||
|
||||
unprefixed_use_declaration:
|
||||
namespace_name
|
||||
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
|
||||
| namespace_name T_AS T_STRING
|
||||
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
|
||||
;
|
||||
|
||||
use_declaration:
|
||||
unprefixed_use_declaration { $$ = $1; }
|
||||
| T_NS_SEPARATOR unprefixed_use_declaration { $$ = $2; }
|
||||
;
|
||||
|
||||
inline_use_declaration:
|
||||
use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; }
|
||||
| use_type use_declaration { $$ = $2; $$->type = $1; }
|
||||
unprefixed_use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; }
|
||||
| use_type unprefixed_use_declaration { $$ = $2; $$->type = $1; }
|
||||
;
|
||||
|
||||
constant_declaration_list:
|
||||
@ -110,11 +135,17 @@ class_const:
|
||||
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
|
||||
;
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list inner_statement { pushNormalizing($1, $2); }
|
||||
inner_statement_list_ex:
|
||||
inner_statement_list_ex inner_statement { pushNormalizing($1, $2); }
|
||||
| /* empty */ { init(); }
|
||||
;
|
||||
|
||||
inner_statement_list:
|
||||
inner_statement_list_ex
|
||||
{ makeNop($nop, $this->lookaheadStartAttributes);
|
||||
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
|
||||
;
|
||||
|
||||
inner_statement:
|
||||
statement { $$ = $1; }
|
||||
| function_declaration_statement { $$ = $1; }
|
||||
@ -123,8 +154,8 @@ inner_statement:
|
||||
{ throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
|
||||
;
|
||||
|
||||
statement:
|
||||
'{' inner_statement_list '}' { $$ = $2; }
|
||||
non_empty_statement:
|
||||
'{' inner_statement_list '}' { $$ = $2; prependLeadingComments($$); }
|
||||
| T_IF '(' expr ')' statement elseif_list else_single
|
||||
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
|
||||
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
|
||||
@ -148,28 +179,40 @@ statement:
|
||||
| T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
|
||||
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
|
||||
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
|
||||
| ';' { $$ = array(); /* means: no statement */ }
|
||||
| T_TRY '{' inner_statement_list '}' catches optional_finally
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; }
|
||||
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
|
||||
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
|
||||
| T_GOTO T_STRING ';' { $$ = Stmt\Goto_[$2]; }
|
||||
| T_STRING ':' { $$ = Stmt\Label[$1]; }
|
||||
| expr error { $$ = $1; }
|
||||
| error { $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
statement:
|
||||
non_empty_statement { $$ = $1; }
|
||||
| ';'
|
||||
{ makeNop($$, $this->startAttributeStack[#1]);
|
||||
if ($$ === null) $$ = array(); /* means: no statement */ }
|
||||
;
|
||||
|
||||
catches:
|
||||
/* empty */ { init(); }
|
||||
| catches catch { push($1, $2); }
|
||||
;
|
||||
|
||||
name_union:
|
||||
name { init($1); }
|
||||
| name_union '|' name { push($1, $3); }
|
||||
;
|
||||
|
||||
catch:
|
||||
T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
|
||||
T_CATCH '(' name_union T_VARIABLE ')' '{' inner_statement_list '}'
|
||||
{ $$ = Stmt\Catch_[$3, parseVar($4), $7]; }
|
||||
;
|
||||
|
||||
optional_finally:
|
||||
/* empty */ { $$ = null; }
|
||||
| T_FINALLY '{' inner_statement_list '}' { $$ = $3; }
|
||||
| T_FINALLY '{' inner_statement_list '}' { $$ = Stmt\Finally_[$3]; }
|
||||
;
|
||||
|
||||
variables_list:
|
||||
@ -194,11 +237,13 @@ function_declaration_statement:
|
||||
|
||||
class_declaration_statement:
|
||||
class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]]; }
|
||||
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
|
||||
$this->checkClass($$, #2); }
|
||||
| T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]]; }
|
||||
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
|
||||
$this->checkInterface($$, #2); }
|
||||
| T_TRAIT T_STRING '{' class_statement_list '}'
|
||||
{ $$ = Stmt\Trait_[$2, $4]; }
|
||||
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
|
||||
;
|
||||
|
||||
class_entry_type:
|
||||
@ -238,7 +283,8 @@ foreach_statement:
|
||||
;
|
||||
|
||||
declare_statement:
|
||||
statement { $$ = toArray($1); }
|
||||
non_empty_statement { $$ = toArray($1); }
|
||||
| ';' { $$ = null; }
|
||||
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
|
||||
;
|
||||
|
||||
@ -310,6 +356,7 @@ foreach_variable:
|
||||
variable { $$ = array($1, false); }
|
||||
| '&' variable { $$ = array($2, true); }
|
||||
| list_expr { $$ = array($1, false); }
|
||||
| array_short_syntax { $$ = array($1, false); }
|
||||
;
|
||||
|
||||
parameter_list:
|
||||
@ -324,25 +371,30 @@ non_empty_parameter_list:
|
||||
|
||||
parameter:
|
||||
optional_param_type optional_ref optional_ellipsis T_VARIABLE
|
||||
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; }
|
||||
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; $this->checkParam($$); }
|
||||
| optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' expr
|
||||
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; }
|
||||
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; $this->checkParam($$); }
|
||||
;
|
||||
|
||||
type_expr:
|
||||
type { $$ = $1; }
|
||||
| '?' type { $$ = Node\NullableType[$2]; }
|
||||
;
|
||||
|
||||
type:
|
||||
name { $$ = $this->handleScalarTypes($1); }
|
||||
name { $$ = $this->handleBuiltinTypes($1); }
|
||||
| T_ARRAY { $$ = 'array'; }
|
||||
| T_CALLABLE { $$ = 'callable'; }
|
||||
;
|
||||
|
||||
optional_param_type:
|
||||
/* empty */ { $$ = null; }
|
||||
| type { $$ = $1; }
|
||||
| type_expr { $$ = $1; }
|
||||
;
|
||||
|
||||
optional_return_type:
|
||||
/* empty */ { $$ = null; }
|
||||
| ':' type { $$ = $2; }
|
||||
| ':' type_expr { $$ = $2; }
|
||||
;
|
||||
|
||||
argument_list:
|
||||
@ -386,10 +438,13 @@ class_statement_list:
|
||||
;
|
||||
|
||||
class_statement:
|
||||
variable_modifiers property_declaration_list ';' { $$ = Stmt\Property[$1, $2]; }
|
||||
| T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2]; }
|
||||
variable_modifiers property_declaration_list ';'
|
||||
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
|
||||
| method_modifiers T_CONST class_const_list ';'
|
||||
{ $$ = Stmt\ClassConst[$3, $1]; $this->checkClassConst($$, #1); }
|
||||
| method_modifiers T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type method_body
|
||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]]; }
|
||||
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
|
||||
$this->checkClassMethod($$, #1); }
|
||||
| T_USE name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
|
||||
;
|
||||
|
||||
@ -441,7 +496,7 @@ method_modifiers:
|
||||
|
||||
non_empty_member_modifiers:
|
||||
member_modifier { $$ = $1; }
|
||||
| non_empty_member_modifiers member_modifier { Stmt\Class_::verifyModifier($1, $2); $$ = $1 | $2; }
|
||||
| non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
|
||||
;
|
||||
|
||||
member_modifier:
|
||||
@ -476,9 +531,9 @@ for_expr:
|
||||
expr:
|
||||
variable { $$ = $1; }
|
||||
| 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 '=' '&' 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]; }
|
||||
@ -546,7 +601,10 @@ expr:
|
||||
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
|
||||
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
|
||||
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
|
||||
| T_EXIT exit_expr { $$ = Expr\Exit_ [$2]; }
|
||||
| T_EXIT exit_expr
|
||||
{ $attrs = attributes();
|
||||
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
|
||||
$$ = new Expr\Exit_($2, $attrs); }
|
||||
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
|
||||
| scalar { $$ = $1; }
|
||||
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
|
||||
@ -565,7 +623,8 @@ expr:
|
||||
|
||||
anonymous_class:
|
||||
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2); }
|
||||
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
|
||||
$this->checkClass($$[0], -1); }
|
||||
|
||||
new_expr:
|
||||
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
|
||||
@ -608,6 +667,7 @@ name:
|
||||
class_name_reference:
|
||||
class_name { $$ = $1; }
|
||||
| new_variable { $$ = $1; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
class_name_or_var:
|
||||
@ -622,7 +682,8 @@ exit_expr:
|
||||
|
||||
backticks_expr:
|
||||
/* empty */ { $$ = array(); }
|
||||
| T_ENCAPSED_AND_WHITESPACE { $$ = array(Scalar\String_::parseEscapeSequences($1, '`')); }
|
||||
| T_ENCAPSED_AND_WHITESPACE
|
||||
{ $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
|
||||
| encaps_list { parseEncapsed($1, '`', true); $$ = $1; }
|
||||
;
|
||||
|
||||
@ -635,16 +696,30 @@ constant:
|
||||
name { $$ = Expr\ConstFetch[$1]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier
|
||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
/* We interpret and isolated FOO:: as an unfinished class constant fetch. It could also be
|
||||
an unfinished static property fetch or unfinished scoped call. */
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error
|
||||
{ $$ = Expr\ClassConstFetch[$1, Expr\Error[]]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
array_short_syntax:
|
||||
'[' array_pair_list ']'
|
||||
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT;
|
||||
$$ = new Expr\Array_($2, $attrs); }
|
||||
;
|
||||
|
||||
dereferencable_scalar:
|
||||
T_ARRAY '(' array_pair_list ')' { $$ = Expr\Array_[$3]; }
|
||||
| '[' array_pair_list ']' { $$ = Expr\Array_[$2]; }
|
||||
| T_CONSTANT_ENCAPSED_STRING { $$ = Scalar\String_[Scalar\String_::parse($1)]; }
|
||||
T_ARRAY '(' array_pair_list ')'
|
||||
{ $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); }
|
||||
;
|
||||
|
||||
scalar:
|
||||
T_LNUMBER { $$ = Scalar\LNumber[Scalar\LNumber::parse($1)]; }
|
||||
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
|
||||
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
|
||||
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
|
||||
| T_FILE { $$ = Scalar\MagicConst\File[]; }
|
||||
@ -657,18 +732,17 @@ scalar:
|
||||
| dereferencable_scalar { $$ = $1; }
|
||||
| constant { $$ = $1; }
|
||||
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
|
||||
{ $$ = Scalar\String_[Scalar\String_::parseDocString($1, $2)]; }
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
$$ = new Scalar\String_(Scalar\String_::parseDocString($1, $2), $attrs); }
|
||||
| T_START_HEREDOC T_END_HEREDOC
|
||||
{ $$ = Scalar\String_['']; }
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
$$ = new Scalar\String_('', $attrs); }
|
||||
| '"' encaps_list '"'
|
||||
{ parseEncapsed($2, '"', true); $$ = Scalar\Encapsed[$2]; }
|
||||
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
|
||||
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
| T_START_HEREDOC encaps_list T_END_HEREDOC
|
||||
{ parseEncapsedDoc($2, true); $$ = Scalar\Encapsed[$2]; }
|
||||
;
|
||||
|
||||
optional_comma:
|
||||
/* empty */
|
||||
| ','
|
||||
{ $attrs = attributes(); setDocStringAttrs($attrs, $1);
|
||||
parseEncapsedDoc($2, true); $$ = new Scalar\Encapsed($2, $attrs); }
|
||||
;
|
||||
|
||||
optional_expr:
|
||||
@ -708,6 +782,7 @@ simple_variable:
|
||||
T_VARIABLE { $$ = parseVar($1); }
|
||||
| '$' '{' expr '}' { $$ = $3; }
|
||||
| '$' simple_variable { $$ = Expr\Variable[$2]; }
|
||||
| '$' error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
static_member:
|
||||
@ -734,6 +809,7 @@ property_name:
|
||||
T_STRING { $$ = $1; }
|
||||
| '{' expr '}' { $$ = $2; }
|
||||
| simple_variable { $$ = Expr\Variable[$1]; }
|
||||
| error { $$ = Expr\Error[]; $this->errorState = 2; }
|
||||
;
|
||||
|
||||
list_expr:
|
||||
@ -746,18 +822,20 @@ list_expr_elements:
|
||||
;
|
||||
|
||||
list_expr_element:
|
||||
variable { $$ = $1; }
|
||||
| list_expr { $$ = $1; }
|
||||
variable { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW variable { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
|
||||
| /* empty */ { $$ = null; }
|
||||
;
|
||||
|
||||
array_pair_list:
|
||||
/* empty */ { $$ = array(); }
|
||||
| non_empty_array_pair_list optional_comma { $$ = $1; }
|
||||
inner_array_pair_list
|
||||
{ $$ = $1; $end = count($$)-1; if ($$[$end] === null) unset($$[$end]); }
|
||||
;
|
||||
|
||||
non_empty_array_pair_list:
|
||||
non_empty_array_pair_list ',' array_pair { push($1, $3); }
|
||||
inner_array_pair_list:
|
||||
inner_array_pair_list ',' array_pair { push($1, $3); }
|
||||
| array_pair { init($1); }
|
||||
;
|
||||
|
||||
@ -766,19 +844,28 @@ array_pair:
|
||||
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
|
||||
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
|
||||
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
|
||||
| /* empty */ { $$ = null; }
|
||||
;
|
||||
|
||||
encaps_list:
|
||||
encaps_list encaps_var { push($1, $2); }
|
||||
| encaps_list T_ENCAPSED_AND_WHITESPACE { push($1, $2); }
|
||||
| encaps_list encaps_string_part { push($1, $2); }
|
||||
| encaps_var { init($1); }
|
||||
| T_ENCAPSED_AND_WHITESPACE encaps_var { init($1, $2); }
|
||||
| encaps_string_part encaps_var { init($1, $2); }
|
||||
;
|
||||
|
||||
encaps_string_part:
|
||||
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
|
||||
;
|
||||
|
||||
encaps_base_var:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
;
|
||||
|
||||
encaps_var:
|
||||
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
|
||||
| T_VARIABLE '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[Expr\Variable[parseVar($1)], $3]; }
|
||||
| T_VARIABLE T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[Expr\Variable[parseVar($1)], $3]; }
|
||||
encaps_base_var { $$ = $1; }
|
||||
| encaps_base_var '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| encaps_base_var T_OBJECT_OPERATOR T_STRING { $$ = Expr\PropertyFetch[$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 T_STRING_VARNAME '[' expr ']' '}'
|
||||
|
@ -85,7 +85,7 @@ foreach ($grammarFileToName as $grammarFile => $name) {
|
||||
|
||||
function resolveNodes($code) {
|
||||
return preg_replace_callback(
|
||||
'~(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
||||
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
||||
function($matches) {
|
||||
// recurse
|
||||
$matches['params'] = resolveNodes($matches['params']);
|
||||
@ -124,6 +124,12 @@ function resolveMacros($code) {
|
||||
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) . ')';
|
||||
}
|
||||
@ -156,17 +162,52 @@ function resolveMacros($code) {
|
||||
if ('parseEncapsed' == $name) {
|
||||
assertArgs(3, $args, $name);
|
||||
|
||||
return 'foreach (' . $args[0] . ' as &$s) { if (is_string($s)) {'
|
||||
. ' $s = Node\Scalar\String_::parseEscapeSequences($s, ' . $args[1] . ', ' . $args[2] . '); } }';
|
||||
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 ('parseEncapsedDoc' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return 'foreach (' . $args[0] . ' as &$s) { if (is_string($s)) {'
|
||||
. ' $s = Node\Scalar\String_::parseEscapeSequences($s, null, ' . $args[1] . '); } }'
|
||||
. ' $s = preg_replace(\'~(\r\n|\n|\r)$~\', \'\', $s);'
|
||||
. ' if (\'\' === $s) array_pop(' . $args[0] . ');';
|
||||
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
||||
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, null, ' . $args[1] . '); } }'
|
||||
. ' $s->value = preg_replace(\'~(\r\n|\n|\r)\z~\', \'\', $s->value);'
|
||||
. ' if (\'\' === $s->value) array_pop(' . $args[0] . ');';
|
||||
}
|
||||
|
||||
if ('makeNop' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return '$startAttributes = ' . $args[1] . ';'
|
||||
. ' if (isset($startAttributes[\'comments\']))'
|
||||
. ' { ' . $args[0] . ' = new Stmt\Nop([\'comments\' => $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 ('setDocStringAttrs' == $name) {
|
||||
assertArgs(2, $args, $name);
|
||||
|
||||
return $args[0] . '[\'kind\'] = strpos(' . $args[1] . ', "\'") === false '
|
||||
. '? Scalar\String_::KIND_HEREDOC : Scalar\String_::KIND_NOWDOC; '
|
||||
. 'preg_match(\'/\A[bB]?<<<[ \t]*[\\\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\\\'"]?(?:\r\n|\n|\r)\z/\', ' . $args[1] . ', $matches); '
|
||||
. $args[0] . '[\'docLabel\'] = $matches[1];';
|
||||
}
|
||||
|
||||
if ('prependLeadingComments' == $name) {
|
||||
assertArgs(1, $args, $name);
|
||||
|
||||
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
||||
. 'if (!empty($attrs[\'comments\']) && isset($stmts[0])) {'
|
||||
. '$stmts[0]->setAttribute(\'comments\', '
|
||||
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
||||
}
|
||||
|
||||
return $matches[0];
|
||||
|
@ -31,7 +31,7 @@ class Autoloader
|
||||
*/
|
||||
static public function autoload($class) {
|
||||
if (0 === strpos($class, 'PhpParser\\')) {
|
||||
$fileName = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
|
||||
$fileName = __DIR__ . strtr(substr($class, 9), '\\', '/') . '.php';
|
||||
if (file_exists($fileName)) {
|
||||
require $fileName;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class Class_ extends Declaration
|
||||
|
||||
protected $extends = null;
|
||||
protected $implements = array();
|
||||
protected $type = 0;
|
||||
protected $flags = 0;
|
||||
|
||||
protected $uses = array();
|
||||
protected $constants = array();
|
||||
@ -44,8 +44,7 @@ class Class_ extends Declaration
|
||||
/**
|
||||
* Implements one or more interfaces.
|
||||
*
|
||||
* @param Name|string $interface Name of interface to implement
|
||||
* @param Name|string $... More interfaces to implement
|
||||
* @param Name|string ...$interfaces Names of interfaces to implement
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
@ -113,7 +112,7 @@ class Class_ extends Declaration
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\Class_($this->name, array(
|
||||
'type' => $this->type,
|
||||
'flags' => $this->flags,
|
||||
'extends' => $this->extends,
|
||||
'implements' => $this->implements,
|
||||
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
|
||||
|
@ -3,8 +3,6 @@
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
abstract class Declaration extends PhpParser\BuilderAbstract
|
||||
{
|
||||
|
@ -4,13 +4,15 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
abstract class FunctionLike extends Declaration
|
||||
{
|
||||
protected $returnByRef = false;
|
||||
protected $params = array();
|
||||
|
||||
/** @var string|Node\Name|Node\NullableType|null */
|
||||
protected $returnType = null;
|
||||
|
||||
/**
|
||||
* Make the function return by reference.
|
||||
*
|
||||
@ -55,4 +57,19 @@ abstract class FunctionLike extends Declaration
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setReturnType($type)
|
||||
{
|
||||
$this->returnType = $this->normalizeType($type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +40,10 @@ class Function_ extends FunctionLike
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\Function_($this->name, array(
|
||||
'byRef' => $this->returnByRef,
|
||||
'params' => $this->params,
|
||||
'stmts' => $this->stmts,
|
||||
'byRef' => $this->returnByRef,
|
||||
'params' => $this->params,
|
||||
'returnType' => $this->returnType,
|
||||
'stmts' => $this->stmts,
|
||||
), $this->attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,7 @@ class Interface_ extends Declaration
|
||||
/**
|
||||
* Extends one or more interfaces.
|
||||
*
|
||||
* @param Name|string $interface Name of interface to extend
|
||||
* @param Name|string $... More interfaces to extend
|
||||
* @param Name|string ...$interfaces Names of interfaces to extend
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
|
@ -9,7 +9,9 @@ use PhpParser\Node\Stmt;
|
||||
class Method extends FunctionLike
|
||||
{
|
||||
protected $name;
|
||||
protected $type = 0;
|
||||
protected $flags = 0;
|
||||
|
||||
/** @var array|null */
|
||||
protected $stmts = array();
|
||||
|
||||
/**
|
||||
@ -116,10 +118,11 @@ class Method extends FunctionLike
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\ClassMethod($this->name, array(
|
||||
'type' => $this->type,
|
||||
'byRef' => $this->returnByRef,
|
||||
'params' => $this->params,
|
||||
'stmts' => $this->stmts,
|
||||
'flags' => $this->flags,
|
||||
'byRef' => $this->returnByRef,
|
||||
'params' => $this->params,
|
||||
'returnType' => $this->returnType,
|
||||
'stmts' => $this->stmts,
|
||||
), $this->attributes);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,10 @@ class Param extends PhpParser\BuilderAbstract
|
||||
protected $name;
|
||||
|
||||
protected $default = null;
|
||||
|
||||
/** @var string|Node\Name|Node\NullableType|null */
|
||||
protected $type = null;
|
||||
|
||||
protected $byRef = false;
|
||||
|
||||
/**
|
||||
@ -38,15 +41,14 @@ class Param extends PhpParser\BuilderAbstract
|
||||
/**
|
||||
* Sets type hint for the parameter.
|
||||
*
|
||||
* @param string|Node\Name $type Type hint to use
|
||||
* @param string|Node\Name|Node\NullableType $type Type hint to use
|
||||
*
|
||||
* @return $this The builder instance (for fluid interface)
|
||||
*/
|
||||
public function setTypeHint($type) {
|
||||
if ($type === 'array' || $type === 'callable') {
|
||||
$this->type = $type;
|
||||
} else {
|
||||
$this->type = $this->normalizeName($type);
|
||||
$this->type = $this->normalizeType($type);
|
||||
if ($this->type === 'void') {
|
||||
throw new \LogicException('Parameter type cannot be void');
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -73,4 +75,4 @@ class Param extends PhpParser\BuilderAbstract
|
||||
$this->name, $this->default, $this->type, $this->byRef
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class Property extends PhpParser\BuilderAbstract
|
||||
{
|
||||
protected $name;
|
||||
|
||||
protected $type = 0;
|
||||
protected $flags = 0;
|
||||
protected $default = null;
|
||||
protected $attributes = array();
|
||||
|
||||
@ -101,7 +101,7 @@ class Property extends PhpParser\BuilderAbstract
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\Property(
|
||||
$this->type !== 0 ? $this->type : Stmt\Class_::MODIFIER_PUBLIC,
|
||||
$this->flags !== 0 ? $this->flags : Stmt\Class_::MODIFIER_PUBLIC,
|
||||
array(
|
||||
new Stmt\PropertyProperty($this->name, $this->default)
|
||||
),
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class Trait_ extends Declaration
|
||||
@ -49,7 +48,9 @@ class Trait_ extends Declaration
|
||||
*/
|
||||
public function getNode() {
|
||||
return new Stmt\Trait_(
|
||||
$this->name, array_merge($this->properties, $this->methods), $this->attributes
|
||||
$this->name, array(
|
||||
'stmts' => array_merge($this->properties, $this->methods)
|
||||
), $this->attributes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
abstract class BuilderAbstract implements Builder {
|
||||
/**
|
||||
@ -53,6 +54,49 @@ abstract class BuilderAbstract implements Builder {
|
||||
throw new \LogicException('Name must be a string or an instance of PhpParser\Node\Name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a type: Converts plain-text type names into proper AST representation.
|
||||
*
|
||||
* In particular, builtin types are left as strings, custom types become Names and nullables
|
||||
* are wrapped in NullableType nodes.
|
||||
*
|
||||
* @param Name|string|NullableType $type The type to normalize
|
||||
*
|
||||
* @return Name|string|NullableType The normalized type
|
||||
*/
|
||||
protected function normalizeType($type) {
|
||||
if (!is_string($type)) {
|
||||
if (!$type instanceof Name && !$type instanceof NullableType) {
|
||||
throw new \LogicException(
|
||||
'Type must be a string, or an instance of Name or NullableType');
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
$nullable = false;
|
||||
if (strlen($type) > 0 && $type[0] === '?') {
|
||||
$nullable = true;
|
||||
$type = substr($type, 1);
|
||||
}
|
||||
|
||||
$builtinTypes = array(
|
||||
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void'
|
||||
);
|
||||
|
||||
$lowerType = strtolower($type);
|
||||
if (in_array($lowerType, $builtinTypes)) {
|
||||
$type = $lowerType;
|
||||
} else {
|
||||
$type = $this->normalizeName($type);
|
||||
}
|
||||
|
||||
if ($nullable && $type === 'void') {
|
||||
throw new \LogicException('void type cannot be nullable');
|
||||
}
|
||||
|
||||
return $nullable ? new Node\NullableType($type) : $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a value: Converts nulls, booleans, integers,
|
||||
* floats, strings and arrays into their respective nodes
|
||||
@ -125,7 +169,7 @@ abstract class BuilderAbstract implements Builder {
|
||||
* @param int $modifier Modifier to set
|
||||
*/
|
||||
protected function setModifier($modifier) {
|
||||
Stmt\Class_::verifyModifier($this->type, $modifier);
|
||||
$this->type |= $modifier;
|
||||
Stmt\Class_::verifyModifier($this->flags, $modifier);
|
||||
$this->flags |= $modifier;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class BuilderFactory
|
||||
{
|
||||
/**
|
||||
* Creates a namespace builder.
|
||||
*
|
||||
*
|
||||
* @param null|string|Node\Name $name Name of the namespace
|
||||
*
|
||||
* @return Builder\Namespace_ The created namespace builder
|
||||
|
@ -2,20 +2,23 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
class Comment
|
||||
class Comment implements \JsonSerializable
|
||||
{
|
||||
protected $text;
|
||||
protected $line;
|
||||
protected $filePos;
|
||||
|
||||
/**
|
||||
* Constructs a comment node.
|
||||
*
|
||||
* @param string $text Comment text (including comment delimiters like /*)
|
||||
* @param int $line Line number the comment started on
|
||||
* @param string $text Comment text (including comment delimiters like /*)
|
||||
* @param int $startLine Line number the comment started on
|
||||
* @param int $startFilePos File offset the comment started on
|
||||
*/
|
||||
public function __construct($text, $line = -1) {
|
||||
public function __construct($text, $startLine = -1, $startFilePos = -1) {
|
||||
$this->text = $text;
|
||||
$this->line = $line;
|
||||
$this->line = $startLine;
|
||||
$this->filePos = $startFilePos;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,15 +30,6 @@ class Comment
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the comment text.
|
||||
*
|
||||
* @param string $text The comment text (including comment delimiters like /*)
|
||||
*/
|
||||
public function setText($text) {
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number the comment started on.
|
||||
*
|
||||
@ -46,12 +40,12 @@ class Comment
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the line number the comment started on.
|
||||
* Gets the file offset the comment started on.
|
||||
*
|
||||
* @param int $line Line number
|
||||
* @return int File offset
|
||||
*/
|
||||
public function setLine($line) {
|
||||
$this->line = $line;
|
||||
public function getFilePos() {
|
||||
return $this->filePos;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,7 +69,8 @@ class Comment
|
||||
*/
|
||||
public function getReformattedText() {
|
||||
$text = trim($this->text);
|
||||
if (false === strpos($text, "\n")) {
|
||||
$newlinePos = strpos($text, "\n");
|
||||
if (false === $newlinePos) {
|
||||
// Single line comments don't need further processing
|
||||
return $text;
|
||||
} elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
|
||||
@ -105,15 +100,41 @@ class Comment
|
||||
//
|
||||
// /* Some text.
|
||||
// Some more text.
|
||||
// Indented text.
|
||||
// Even more text. */
|
||||
//
|
||||
// is handled by taking the length of the "/* " segment and leaving only that
|
||||
// many space characters before the lines. Thus in the above example only three
|
||||
// space characters are left at the start of every line.
|
||||
return preg_replace('(^\s*(?= {' . strlen($matches[0]) . '}(?!\s)))m', '', $text);
|
||||
// is handled by removing the difference between the shortest whitespace prefix on all
|
||||
// lines and the length of the "/* " opening sequence.
|
||||
$prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1));
|
||||
$removeLen = $prefixLen - strlen($matches[0]);
|
||||
return preg_replace('(^\s{' . $removeLen . '})m', '', $text);
|
||||
}
|
||||
|
||||
// No idea how to format this comment, so simply return as is
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function getShortestWhitespacePrefixLen($str) {
|
||||
$lines = explode("\n", $str);
|
||||
$shortestPrefixLen = INF;
|
||||
foreach ($lines as $line) {
|
||||
preg_match('(^\s*)', $line, $matches);
|
||||
$prefixLen = strlen($matches[0]);
|
||||
if ($prefixLen < $shortestPrefixLen) {
|
||||
$shortestPrefixLen = $prefixLen;
|
||||
}
|
||||
}
|
||||
return $shortestPrefixLen;
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
// Technically not a node, but we make it look like one anyway
|
||||
$type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment';
|
||||
return [
|
||||
'nodeType' => $type,
|
||||
'text' => $this->text,
|
||||
'line' => $this->line,
|
||||
'filePos' => $this->filePos,
|
||||
];
|
||||
}
|
||||
}
|
@ -61,6 +61,16 @@ class Error extends \RuntimeException
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attributes of the node/token the error occured at.
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function setAttributes(array $attributes) {
|
||||
$this->attributes = $attributes;
|
||||
$this->updateMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the line of the PHP file the error occurred in.
|
||||
*
|
||||
@ -120,6 +130,14 @@ class Error extends \RuntimeException
|
||||
return $this->toColumn($code, $this->attributes['endFilePos']);
|
||||
}
|
||||
|
||||
public function getMessageWithColumnInfo($code) {
|
||||
return sprintf(
|
||||
'%s from %d:%d to %d:%d', $this->getRawMessage(),
|
||||
$this->getStartLine(), $this->getStartColumn($code),
|
||||
$this->getEndLine(), $this->getEndColumn($code)
|
||||
);
|
||||
}
|
||||
|
||||
private function toColumn($code, $pos) {
|
||||
if ($pos > strlen($code)) {
|
||||
throw new \RuntimeException('Invalid position information');
|
||||
@ -145,14 +163,4 @@ class Error extends \RuntimeException
|
||||
$this->message .= ' on line ' . $this->getStartLine();
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use getStartLine() instead */
|
||||
public function getRawLine() {
|
||||
return $this->getStartLine();
|
||||
}
|
||||
|
||||
/** @deprecated Use setStartLine() instead */
|
||||
public function setRawLine($line) {
|
||||
$this->setStartLine($line);
|
||||
}
|
||||
}
|
||||
|
13
lib/PhpParser/ErrorHandler.php
Normal file
13
lib/PhpParser/ErrorHandler.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
interface ErrorHandler
|
||||
{
|
||||
/**
|
||||
* Handle an error generated during lexing, parsing or some other operation.
|
||||
*
|
||||
* @param Error $error The error that needs to be handled
|
||||
*/
|
||||
public function handleError(Error $error);
|
||||
}
|
46
lib/PhpParser/ErrorHandler/Collecting.php
Normal file
46
lib/PhpParser/ErrorHandler/Collecting.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser\ErrorHandler;
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
|
||||
/**
|
||||
* Error handler that collects all errors into an array.
|
||||
*
|
||||
* This allows graceful handling of errors.
|
||||
*/
|
||||
class Collecting implements ErrorHandler
|
||||
{
|
||||
/** @var Error[] Collected errors */
|
||||
private $errors = [];
|
||||
|
||||
public function handleError(Error $error) {
|
||||
$this->errors[] = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get collected errors.
|
||||
*
|
||||
* @return Error[]
|
||||
*/
|
||||
public function getErrors() {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there are any errors.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasErrors() {
|
||||
return !empty($this->errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset/clear collected errors.
|
||||
*/
|
||||
public function clearErrors() {
|
||||
$this->errors = [];
|
||||
}
|
||||
}
|
18
lib/PhpParser/ErrorHandler/Throwing.php
Normal file
18
lib/PhpParser/ErrorHandler/Throwing.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser\ErrorHandler;
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
|
||||
/**
|
||||
* Error handler that handles all errors by throwing them.
|
||||
*
|
||||
* This is the default strategy used by all components.
|
||||
*/
|
||||
class Throwing implements ErrorHandler
|
||||
{
|
||||
public function handleError(Error $error) {
|
||||
throw $error;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ class Lexer
|
||||
protected $pos;
|
||||
protected $line;
|
||||
protected $filePos;
|
||||
protected $prevCloseTagHasNewline;
|
||||
|
||||
protected $tokenMap;
|
||||
protected $dropTokens;
|
||||
@ -21,10 +22,10 @@ class Lexer
|
||||
* Creates a Lexer.
|
||||
*
|
||||
* @param array $options Options array. Currently only the 'usedAttributes' option is supported,
|
||||
* which is an array of attributes to add to the AST nodes. Possible attributes
|
||||
* are: 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos',
|
||||
* 'startFilePos', 'endFilePos'. The option defaults to the first three.
|
||||
* For more info see getNextToken() docs.
|
||||
* which is an array of attributes to add to the AST nodes. Possible
|
||||
* attributes are: 'comments', 'startLine', 'endLine', 'startTokenPos',
|
||||
* 'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the
|
||||
* first three. For more info see getNextToken() docs.
|
||||
*/
|
||||
public function __construct(array $options = array()) {
|
||||
// map from internal tokens to PhpParser tokens
|
||||
@ -32,7 +33,9 @@ class Lexer
|
||||
|
||||
// map of tokens to drop while lexing (the map is only used for isset lookup,
|
||||
// that's why the value is simply set to 1; the value is never actually used.)
|
||||
$this->dropTokens = array_fill_keys(array(T_WHITESPACE, T_OPEN_TAG), 1);
|
||||
$this->dropTokens = array_fill_keys(
|
||||
array(T_WHITESPACE, T_OPEN_TAG, T_COMMENT, T_DOC_COMMENT), 1
|
||||
);
|
||||
|
||||
// the usedAttributes member is a map of the used attribute names to a dummy
|
||||
// value (here "true")
|
||||
@ -45,57 +48,152 @@ class Lexer
|
||||
/**
|
||||
* Initializes the lexer for lexing the provided source code.
|
||||
*
|
||||
* @param string $code The source code to lex
|
||||
* This function does not throw if lexing errors occur. Instead, errors may be retrieved using
|
||||
* the getErrors() method.
|
||||
*
|
||||
* @throws Error on lexing errors (unterminated comment or unexpected character)
|
||||
* @param string $code The source code to lex
|
||||
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
|
||||
* ErrorHandler\Throwing
|
||||
*/
|
||||
public function startLexing($code) {
|
||||
$scream = ini_set('xdebug.scream', '0');
|
||||
|
||||
$this->resetErrors();
|
||||
$this->tokens = @token_get_all($code);
|
||||
$this->handleErrors();
|
||||
|
||||
if (false !== $scream) {
|
||||
ini_set('xdebug.scream', $scream);
|
||||
public function startLexing($code, ErrorHandler $errorHandler = null) {
|
||||
if (null === $errorHandler) {
|
||||
$errorHandler = new ErrorHandler\Throwing();
|
||||
}
|
||||
|
||||
$this->code = $code; // keep the code around for __halt_compiler() handling
|
||||
$this->pos = -1;
|
||||
$this->line = 1;
|
||||
$this->filePos = 0;
|
||||
|
||||
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
|
||||
// This ensures proper composability, because having a newline is the "safe" assumption.
|
||||
$this->prevCloseTagHasNewline = true;
|
||||
|
||||
$scream = ini_set('xdebug.scream', '0');
|
||||
|
||||
$this->resetErrors();
|
||||
$this->tokens = @token_get_all($code);
|
||||
$this->handleErrors($errorHandler);
|
||||
|
||||
if (false !== $scream) {
|
||||
ini_set('xdebug.scream', $scream);
|
||||
}
|
||||
}
|
||||
|
||||
protected function resetErrors() {
|
||||
// set error_get_last() to defined state by forcing an undefined variable error
|
||||
set_error_handler(function() { return false; }, 0);
|
||||
@$undefinedVariable;
|
||||
restore_error_handler();
|
||||
if (function_exists('error_clear_last')) {
|
||||
error_clear_last();
|
||||
} else {
|
||||
// set error_get_last() to defined state by forcing an undefined variable error
|
||||
set_error_handler(function() { return false; }, 0);
|
||||
@$undefinedVariable;
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleErrors() {
|
||||
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
|
||||
for ($i = $start; $i < $end; $i++) {
|
||||
$chr = $this->code[$i];
|
||||
if ($chr === 'b' || $chr === 'B') {
|
||||
// HHVM does not treat b" tokens correctly, so ignore these
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($chr === "\0") {
|
||||
// PHP cuts error message after null byte, so need special case
|
||||
$errorMsg = 'Unexpected null byte';
|
||||
} else {
|
||||
$errorMsg = sprintf(
|
||||
'Unexpected character "%s" (ASCII %d)', $chr, ord($chr)
|
||||
);
|
||||
}
|
||||
|
||||
$errorHandler->handleError(new Error($errorMsg, [
|
||||
'startLine' => $line,
|
||||
'endLine' => $line,
|
||||
'startFilePos' => $i,
|
||||
'endFilePos' => $i,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
private function isUnterminatedComment($token) {
|
||||
return ($token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT)
|
||||
&& substr($token[1], 0, 2) === '/*'
|
||||
&& substr($token[1], -2) !== '*/';
|
||||
}
|
||||
|
||||
private function errorMayHaveOccurred() {
|
||||
if (defined('HHVM_VERSION')) {
|
||||
// In HHVM token_get_all() does not throw warnings, so we need to conservatively
|
||||
// assume that an error occurred
|
||||
return true;
|
||||
}
|
||||
|
||||
$error = error_get_last();
|
||||
return null !== $error
|
||||
&& false === strpos($error['message'], 'Undefined variable');
|
||||
}
|
||||
|
||||
if (preg_match(
|
||||
'~^Unterminated comment starting line ([0-9]+)$~',
|
||||
$error['message'], $matches
|
||||
)) {
|
||||
throw new Error('Unterminated comment', (int) $matches[1]);
|
||||
protected function handleErrors(ErrorHandler $errorHandler) {
|
||||
if (!$this->errorMayHaveOccurred()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preg_match(
|
||||
'~^Unexpected character in input: \'(.)\' \(ASCII=([0-9]+)\)~s',
|
||||
$error['message'], $matches
|
||||
)) {
|
||||
throw new Error(sprintf(
|
||||
'Unexpected character "%s" (ASCII %d)',
|
||||
$matches[1], $matches[2]
|
||||
));
|
||||
// PHP's error handling for token_get_all() is rather bad, so if we want detailed
|
||||
// error information we need to compute it ourselves. Invalid character errors are
|
||||
// detected by finding "gaps" in the token array. Unterminated comments are detected
|
||||
// by checking if a trailing comment has a "*/" at the end.
|
||||
|
||||
$filePos = 0;
|
||||
$line = 1;
|
||||
foreach ($this->tokens as $i => $token) {
|
||||
$tokenValue = \is_string($token) ? $token : $token[1];
|
||||
$tokenLen = \strlen($tokenValue);
|
||||
|
||||
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
|
||||
// Something is missing, must be an invalid character
|
||||
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
|
||||
$this->handleInvalidCharacterRange(
|
||||
$filePos, $nextFilePos, $line, $errorHandler);
|
||||
$filePos = $nextFilePos;
|
||||
}
|
||||
|
||||
$filePos += $tokenLen;
|
||||
$line += substr_count($tokenValue, "\n");
|
||||
}
|
||||
|
||||
// PHP cuts error message after null byte, so need special case
|
||||
if (preg_match('~^Unexpected character in input: \'$~', $error['message'])) {
|
||||
throw new Error('Unexpected null byte');
|
||||
if ($filePos !== \strlen($this->code)) {
|
||||
if (substr($this->code, $filePos, 2) === '/*') {
|
||||
// Unlike PHP, HHVM will drop unterminated comments entirely
|
||||
$comment = substr($this->code, $filePos);
|
||||
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||
'startLine' => $line,
|
||||
'endLine' => $line + substr_count($comment, "\n"),
|
||||
'startFilePos' => $filePos,
|
||||
'endFilePos' => $filePos + \strlen($comment),
|
||||
]));
|
||||
|
||||
// Emulate the PHP behavior
|
||||
$isDocComment = isset($comment[3]) && $comment[3] === '*';
|
||||
$this->tokens[] = [$isDocComment ? T_DOC_COMMENT : T_COMMENT, $comment, $line];
|
||||
} else {
|
||||
// Invalid characters at the end of the input
|
||||
$this->handleInvalidCharacterRange(
|
||||
$filePos, \strlen($this->code), $line, $errorHandler);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for unterminated comment
|
||||
$lastToken = $this->tokens[count($this->tokens) - 1];
|
||||
if ($this->isUnterminatedComment($lastToken)) {
|
||||
$errorHandler->handleError(new Error('Unterminated comment', [
|
||||
'startLine' => $line - substr_count($lastToken[1], "\n"),
|
||||
'endLine' => $line,
|
||||
'startFilePos' => $filePos - \strlen($lastToken[1]),
|
||||
'endFilePos' => $filePos,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +211,7 @@ class Lexer
|
||||
* * 'startTokenPos' => Offset into the token array of the first token in the node.
|
||||
* * 'endTokenPos' => Offset into the token array of the last token in the node.
|
||||
* * 'startFilePos' => Offset into the code string of the first character that is part of the node.
|
||||
* * 'endFilePos' => Offset into the code string of the last character that is part of the node
|
||||
* * 'endFilePos' => Offset into the code string of the last character that is part of the node.
|
||||
*
|
||||
* @param mixed $value Variable to store token content in
|
||||
* @param mixed $startAttributes Variable to store start attributes in
|
||||
@ -133,6 +231,9 @@ class Lexer
|
||||
$token = "\0";
|
||||
}
|
||||
|
||||
if (isset($this->usedAttributes['startLine'])) {
|
||||
$startAttributes['startLine'] = $this->line;
|
||||
}
|
||||
if (isset($this->usedAttributes['startTokenPos'])) {
|
||||
$startAttributes['startTokenPos'] = $this->pos;
|
||||
}
|
||||
@ -140,63 +241,53 @@ class Lexer
|
||||
$startAttributes['startFilePos'] = $this->filePos;
|
||||
}
|
||||
|
||||
if (is_string($token)) {
|
||||
// bug in token_get_all
|
||||
if ('b"' === $token) {
|
||||
$value = 'b"';
|
||||
if (\is_string($token)) {
|
||||
$value = $token;
|
||||
if (isset($token[1])) {
|
||||
// bug in token_get_all
|
||||
$this->filePos += 2;
|
||||
$id = ord('"');
|
||||
} else {
|
||||
$value = $token;
|
||||
$this->filePos += 1;
|
||||
$id = ord($token);
|
||||
}
|
||||
|
||||
if (isset($this->usedAttributes['startLine'])) {
|
||||
$startAttributes['startLine'] = $this->line;
|
||||
}
|
||||
if (isset($this->usedAttributes['endLine'])) {
|
||||
$endAttributes['endLine'] = $this->line;
|
||||
}
|
||||
if (isset($this->usedAttributes['endTokenPos'])) {
|
||||
$endAttributes['endTokenPos'] = $this->pos;
|
||||
}
|
||||
if (isset($this->usedAttributes['endFilePos'])) {
|
||||
$endAttributes['endFilePos'] = $this->filePos - 1;
|
||||
} elseif (!isset($this->dropTokens[$token[0]])) {
|
||||
$value = $token[1];
|
||||
$id = $this->tokenMap[$token[0]];
|
||||
if (T_CLOSE_TAG === $token[0]) {
|
||||
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n");
|
||||
} else if (T_INLINE_HTML === $token[0]) {
|
||||
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
|
||||
}
|
||||
|
||||
return $id;
|
||||
$this->line += substr_count($value, "\n");
|
||||
$this->filePos += \strlen($value);
|
||||
} else {
|
||||
$this->line += substr_count($token[1], "\n");
|
||||
$this->filePos += strlen($token[1]);
|
||||
|
||||
if (T_COMMENT === $token[0]) {
|
||||
if (T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0]) {
|
||||
if (isset($this->usedAttributes['comments'])) {
|
||||
$startAttributes['comments'][] = new Comment($token[1], $token[2]);
|
||||
$comment = T_DOC_COMMENT === $token[0]
|
||||
? new Comment\Doc($token[1], $this->line, $this->filePos)
|
||||
: new Comment($token[1], $this->line, $this->filePos);
|
||||
$startAttributes['comments'][] = $comment;
|
||||
}
|
||||
} elseif (T_DOC_COMMENT === $token[0]) {
|
||||
if (isset($this->usedAttributes['comments'])) {
|
||||
$startAttributes['comments'][] = new Comment\Doc($token[1], $token[2]);
|
||||
}
|
||||
} elseif (!isset($this->dropTokens[$token[0]])) {
|
||||
$value = $token[1];
|
||||
|
||||
if (isset($this->usedAttributes['startLine'])) {
|
||||
$startAttributes['startLine'] = $token[2];
|
||||
}
|
||||
if (isset($this->usedAttributes['endLine'])) {
|
||||
$endAttributes['endLine'] = $this->line;
|
||||
}
|
||||
if (isset($this->usedAttributes['endTokenPos'])) {
|
||||
$endAttributes['endTokenPos'] = $this->pos;
|
||||
}
|
||||
if (isset($this->usedAttributes['endFilePos'])) {
|
||||
$endAttributes['endFilePos'] = $this->filePos - 1;
|
||||
}
|
||||
|
||||
return $this->tokenMap[$token[0]];
|
||||
}
|
||||
|
||||
$this->line += substr_count($token[1], "\n");
|
||||
$this->filePos += \strlen($token[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($this->usedAttributes['endLine'])) {
|
||||
$endAttributes['endLine'] = $this->line;
|
||||
}
|
||||
if (isset($this->usedAttributes['endTokenPos'])) {
|
||||
$endAttributes['endTokenPos'] = $this->pos;
|
||||
}
|
||||
if (isset($this->usedAttributes['endFilePos'])) {
|
||||
$endAttributes['endFilePos'] = $this->filePos - 1;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Reached end of lexer loop');
|
||||
|
@ -2,11 +2,9 @@
|
||||
|
||||
namespace PhpParser\Lexer;
|
||||
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Parser\Tokens;
|
||||
|
||||
/**
|
||||
* ATTENTION: This code is WRITE-ONLY. Do not try to read it.
|
||||
*/
|
||||
class Emulative extends \PhpParser\Lexer
|
||||
{
|
||||
protected $newKeywords;
|
||||
@ -21,16 +19,12 @@ class Emulative extends \PhpParser\Lexer
|
||||
|
||||
const PHP_7_0 = '7.0.0dev';
|
||||
const PHP_5_6 = '5.6.0rc1';
|
||||
const PHP_5_5 = '5.5.0beta1';
|
||||
|
||||
public function __construct(array $options = array()) {
|
||||
parent::__construct($options);
|
||||
|
||||
$newKeywordsPerVersion = array(
|
||||
self::PHP_5_5 => array(
|
||||
'finally' => Tokens::T_FINALLY,
|
||||
'yield' => Tokens::T_YIELD,
|
||||
),
|
||||
// No new keywords since PHP 5.5
|
||||
);
|
||||
|
||||
$this->newKeywords = array();
|
||||
@ -57,131 +51,106 @@ class Emulative extends \PhpParser\Lexer
|
||||
$this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
|
||||
}
|
||||
|
||||
public function startLexing($code) {
|
||||
public function startLexing($code, ErrorHandler $errorHandler = null) {
|
||||
$this->inObjectAccess = false;
|
||||
|
||||
$preprocessedCode = $this->preprocessCode($code);
|
||||
parent::startLexing($preprocessedCode);
|
||||
if ($preprocessedCode !== $code) {
|
||||
$this->postprocessTokens();
|
||||
parent::startLexing($code, $errorHandler);
|
||||
if ($this->requiresEmulation($code)) {
|
||||
$this->emulateTokens();
|
||||
}
|
||||
|
||||
// Set code property back to the original code, so __halt_compiler()
|
||||
// handling and (start|end)FilePos attributes use the correct offsets
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
/*
|
||||
* Replaces new features in the code by ~__EMU__{NAME}__{DATA}__~ sequences.
|
||||
* ~LABEL~ is never valid PHP code, that's why we can (to some degree) safely
|
||||
* use it here.
|
||||
* Later when preprocessing the tokens these sequences will either be replaced
|
||||
* by real tokens or replaced with their original content (e.g. if they occurred
|
||||
* inside a string, i.e. a place where they don't have a special meaning).
|
||||
* Checks if the code is potentially using features that require emulation.
|
||||
*/
|
||||
protected function preprocessCode($code) {
|
||||
protected function requiresEmulation($code) {
|
||||
if (version_compare(PHP_VERSION, self::PHP_7_0, '>=')) {
|
||||
return $code;
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = str_replace('??', '~__EMU__COALESCE__~', $code);
|
||||
$code = str_replace('<=>', '~__EMU__SPACESHIP__~', $code);
|
||||
$code = preg_replace_callback('(yield[ \n\r\t]+from)', function($matches) {
|
||||
// Encoding $0 in order to preserve exact whitespace
|
||||
return '~__EMU__YIELDFROM__' . bin2hex($matches[0]) . '__~';
|
||||
}, $code);
|
||||
if (preg_match('(\?\?|<=>|yield[ \n\r\t]+from)', $code)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
|
||||
return $code;
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = str_replace('...', '~__EMU__ELLIPSIS__~', $code);
|
||||
$code = preg_replace('((?<!/)\*\*=)', '~__EMU__POWEQUAL__~', $code);
|
||||
$code = preg_replace('((?<!/)\*\*(?!/))', '~__EMU__POW__~', $code);
|
||||
|
||||
return $code;
|
||||
return preg_match('(\.\.\.|(?<!/)\*\*(?!/))', $code);
|
||||
}
|
||||
|
||||
/*
|
||||
* Replaces the ~__EMU__...~ sequences with real tokens or their original
|
||||
* value.
|
||||
* Emulates tokens for newer PHP versions.
|
||||
*/
|
||||
protected function postprocessTokens() {
|
||||
// we need to manually iterate and manage a count because we'll change
|
||||
protected function emulateTokens() {
|
||||
// We need to manually iterate and manage a count because we'll change
|
||||
// the tokens array on the way
|
||||
$line = 1;
|
||||
for ($i = 0, $c = count($this->tokens); $i < $c; ++$i) {
|
||||
// first check that the following tokens are of form ~LABEL~,
|
||||
// then match the __EMU__... sequence.
|
||||
if ('~' === $this->tokens[$i]
|
||||
&& isset($this->tokens[$i + 2])
|
||||
&& '~' === $this->tokens[$i + 2]
|
||||
&& T_STRING === $this->tokens[$i + 1][0]
|
||||
&& preg_match('(^__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?$)', $this->tokens[$i + 1][1], $matches)
|
||||
) {
|
||||
if ('ELLIPSIS' === $matches[1]) {
|
||||
$replace = array(
|
||||
array(self::T_ELLIPSIS, '...', $this->tokens[$i + 1][2])
|
||||
);
|
||||
} else if ('POW' === $matches[1]) {
|
||||
$replace = array(
|
||||
array(self::T_POW, '**', $this->tokens[$i + 1][2])
|
||||
);
|
||||
} else if ('POWEQUAL' === $matches[1]) {
|
||||
$replace = array(
|
||||
array(self::T_POW_EQUAL, '**=', $this->tokens[$i + 1][2])
|
||||
);
|
||||
} else if ('COALESCE' === $matches[1]) {
|
||||
$replace = array(
|
||||
array(self::T_COALESCE, '??', $this->tokens[$i + 1][2])
|
||||
);
|
||||
} else if ('SPACESHIP' === $matches[1]) {
|
||||
$replace = array(
|
||||
array(self::T_SPACESHIP, '<=>', $this->tokens[$i + 1][2]),
|
||||
);
|
||||
} else if ('YIELDFROM' === $matches[1]) {
|
||||
$content = hex2bin($matches[2]);
|
||||
$replace = array(
|
||||
array(self::T_YIELD_FROM, $content, $this->tokens[$i + 1][2] - substr_count($content, "\n"))
|
||||
);
|
||||
} else {
|
||||
throw new \RuntimeException('Invalid __EMU__ sequence');
|
||||
$replace = null;
|
||||
if (isset($this->tokens[$i + 1])) {
|
||||
if ($this->tokens[$i] === '?' && $this->tokens[$i + 1] === '?') {
|
||||
array_splice($this->tokens, $i, 2, array(
|
||||
array(self::T_COALESCE, '??', $line)
|
||||
));
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
if ($this->tokens[$i][0] === T_IS_SMALLER_OR_EQUAL
|
||||
&& $this->tokens[$i + 1] === '>'
|
||||
) {
|
||||
array_splice($this->tokens, $i, 2, array(
|
||||
array(self::T_SPACESHIP, '<=>', $line)
|
||||
));
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
if ($this->tokens[$i] === '*' && $this->tokens[$i + 1] === '*') {
|
||||
array_splice($this->tokens, $i, 2, array(
|
||||
array(self::T_POW, '**', $line)
|
||||
));
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
if ($this->tokens[$i] === '*' && $this->tokens[$i + 1][0] === T_MUL_EQUAL) {
|
||||
array_splice($this->tokens, $i, 2, array(
|
||||
array(self::T_POW_EQUAL, '**=', $line)
|
||||
));
|
||||
$c--;
|
||||
continue;
|
||||
}
|
||||
|
||||
array_splice($this->tokens, $i, 3, $replace);
|
||||
$c -= 3 - count($replace);
|
||||
// for multichar tokens (e.g. strings) replace any ~__EMU__...~ sequences
|
||||
// in their content with the original character sequence
|
||||
} elseif (is_array($this->tokens[$i])
|
||||
&& 0 !== strpos($this->tokens[$i][1], '__EMU__')
|
||||
) {
|
||||
$this->tokens[$i][1] = preg_replace_callback(
|
||||
'(~__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?~)',
|
||||
array($this, 'restoreContentCallback'),
|
||||
$this->tokens[$i][1]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is a callback for restoring EMU sequences in
|
||||
* multichar tokens (like strings) to their original value.
|
||||
*/
|
||||
public function restoreContentCallback(array $matches) {
|
||||
if ('ELLIPSIS' === $matches[1]) {
|
||||
return '...';
|
||||
} else if ('POW' === $matches[1]) {
|
||||
return '**';
|
||||
} else if ('POWEQUAL' === $matches[1]) {
|
||||
return '**=';
|
||||
} else if ('COALESCE' === $matches[1]) {
|
||||
return '??';
|
||||
} else if ('SPACESHIP' === $matches[1]) {
|
||||
return '<=>';
|
||||
} else if ('YIELDFROM' === $matches[1]) {
|
||||
return hex2bin($matches[2]);
|
||||
} else {
|
||||
return $matches[0];
|
||||
if (isset($this->tokens[$i + 2])) {
|
||||
if ($this->tokens[$i][0] === T_YIELD && $this->tokens[$i + 1][0] === T_WHITESPACE
|
||||
&& $this->tokens[$i + 2][0] === T_STRING
|
||||
&& !strcasecmp($this->tokens[$i + 2][1], 'from')
|
||||
) {
|
||||
array_splice($this->tokens, $i, 3, array(
|
||||
array(
|
||||
self::T_YIELD_FROM,
|
||||
$this->tokens[$i][1] . $this->tokens[$i + 1][1] . $this->tokens[$i + 2][1],
|
||||
$line
|
||||
)
|
||||
));
|
||||
$c -= 2;
|
||||
$line += substr_count($this->tokens[$i][1], "\n");
|
||||
continue;
|
||||
}
|
||||
if ($this->tokens[$i] === '.' && $this->tokens[$i + 1] === '.'
|
||||
&& $this->tokens[$i + 2] === '.'
|
||||
) {
|
||||
array_splice($this->tokens, $i, 3, array(
|
||||
array(self::T_ELLIPSIS, '...', $line)
|
||||
));
|
||||
$c -= 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_array($this->tokens[$i])) {
|
||||
$line += substr_count($this->tokens[$i][1], "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,15 @@ interface Node
|
||||
*/
|
||||
public function getDocComment();
|
||||
|
||||
/**
|
||||
* Sets the doc comment of the node.
|
||||
*
|
||||
* This will either replace an existing doc comment or add it to the comments array.
|
||||
*
|
||||
* @param Comment\Doc $docComment Doc comment to set
|
||||
*/
|
||||
public function setDocComment(Comment\Doc $docComment);
|
||||
|
||||
/**
|
||||
* Sets an attribute on a node.
|
||||
*
|
||||
|
@ -6,6 +6,10 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class Array_ extends Expr
|
||||
{
|
||||
// For use in "kind" attribute
|
||||
const KIND_LONG = 1; // array() syntax
|
||||
const KIND_SHORT = 2; // [] syntax
|
||||
|
||||
/** @var ArrayItem[] Items */
|
||||
public $items;
|
||||
|
||||
|
@ -4,10 +4,6 @@ namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
/**
|
||||
* @property Expr $var Variable
|
||||
* @property Expr $expr Expression
|
||||
*/
|
||||
abstract class AssignOp extends Expr
|
||||
{
|
||||
/** @var Expr Variable */
|
||||
|
@ -4,10 +4,6 @@ namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
/**
|
||||
* @property Expr $var Variable reference is assigned to
|
||||
* @property Expr $expr Variable which is referenced
|
||||
*/
|
||||
class AssignRef extends Expr
|
||||
{
|
||||
/** @var Expr Variable reference is assigned to */
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
|
||||
class ClassConstFetch extends Expr
|
||||
{
|
||||
|
@ -16,7 +16,7 @@ class Closure extends Expr implements FunctionLike
|
||||
public $params;
|
||||
/** @var ClosureUse[] use()s */
|
||||
public $uses;
|
||||
/** @var null|string|Node\Name Return type */
|
||||
/** @var null|string|Node\Name|Node\NullableType Return type */
|
||||
public $returnType;
|
||||
/** @var Node[] Statements */
|
||||
public $stmts;
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
|
||||
class ConstFetch extends Expr
|
||||
{
|
||||
|
27
lib/PhpParser/Node/Expr/Error.php
Normal file
27
lib/PhpParser/Node/Expr/Error.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
/**
|
||||
* Error node used during parsing with error recovery.
|
||||
*
|
||||
* An error node may be placed at a position where an expression is required, but an error occurred.
|
||||
* Error nodes will not be present if the parser is run in throwOnError mode (the default).
|
||||
*/
|
||||
class Error extends Expr
|
||||
{
|
||||
/**
|
||||
* Constructs an error node.
|
||||
*
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array();
|
||||
}
|
||||
}
|
@ -6,6 +6,10 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class Exit_ extends Expr
|
||||
{
|
||||
/* For use in "kind" attribute */
|
||||
const KIND_EXIT = 1;
|
||||
const KIND_DIE = 2;
|
||||
|
||||
/** @var null|Expr Expression */
|
||||
public $expr;
|
||||
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
|
||||
class Instanceof_ extends Expr
|
||||
{
|
||||
|
@ -6,21 +6,21 @@ use PhpParser\Node\Expr;
|
||||
|
||||
class List_ extends Expr
|
||||
{
|
||||
/** @var Expr[] List of variables to assign to */
|
||||
public $vars;
|
||||
/** @var ArrayItem[] List of items to assign to */
|
||||
public $items;
|
||||
|
||||
/**
|
||||
* Constructs a list() destructuring node.
|
||||
*
|
||||
* @param Expr[] $vars List of variables to assign to
|
||||
* @param array $attributes Additional attributes
|
||||
* @param ArrayItem[] $items List of items to assign to
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $vars, array $attributes = array()) {
|
||||
public function __construct(array $items, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->vars = $vars;
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('vars');
|
||||
return array('items');
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace PhpParser\Node\Expr;
|
||||
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
|
||||
class StaticPropertyFetch extends Expr
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ interface FunctionLike extends Node
|
||||
/**
|
||||
* Get the declared return type or null
|
||||
*
|
||||
* @return null|string|Node\Name
|
||||
* @return null|string|Node\Name|Node\NullableType
|
||||
*/
|
||||
public function getReturnType();
|
||||
|
||||
|
@ -6,22 +6,21 @@ use PhpParser\NodeAbstract;
|
||||
|
||||
class Name extends NodeAbstract
|
||||
{
|
||||
/** @var string[] Parts of the name */
|
||||
/**
|
||||
* @var string[] Parts of the name
|
||||
* @deprecated Avoid directly accessing $parts, use methods instead.
|
||||
*/
|
||||
public $parts;
|
||||
|
||||
/**
|
||||
* Constructs a name node.
|
||||
*
|
||||
* @param string|array $parts Parts of the name (or name as string)
|
||||
* @param array $attributes Additional attributes
|
||||
* @param string|array|self $name Name as string, part array or Name instance (copy ctor)
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($parts, array $attributes = array()) {
|
||||
if (!is_array($parts)) {
|
||||
$parts = explode('\\', $parts);
|
||||
}
|
||||
|
||||
public function __construct($name, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->parts = $parts;
|
||||
$this->parts = self::prepareName($name);
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
@ -83,14 +82,13 @@ class Name extends NodeAbstract
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the name by imploding the namespace parts with a separator.
|
||||
*
|
||||
* @param string $separator The separator to use (defaults to the namespace separator \)
|
||||
* Returns a string representation of the name by imploding the namespace parts with the
|
||||
* namespace separator.
|
||||
*
|
||||
* @return string String representation
|
||||
*/
|
||||
public function toString($separator = '\\') {
|
||||
return implode($separator, $this->parts);
|
||||
public function toString() {
|
||||
return implode('\\', $this->parts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,79 +101,45 @@ class Name extends NodeAbstract
|
||||
return implode('\\', $this->parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the whole name.
|
||||
*
|
||||
* @deprecated Create a new Name instead, or manually modify the $parts property
|
||||
*
|
||||
* @param string|array|self $name The name to set the whole name to
|
||||
*/
|
||||
public function set($name) {
|
||||
$this->parts = self::prepareName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends a name to this name.
|
||||
*
|
||||
* @deprecated Use Name::concat($name1, $name2) instead
|
||||
*
|
||||
* @param string|array|self $name Name to prepend
|
||||
*/
|
||||
public function prepend($name) {
|
||||
$this->parts = array_merge(self::prepareName($name), $this->parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a name to this name.
|
||||
*
|
||||
* @deprecated Use Name::concat($name1, $name2) instead
|
||||
*
|
||||
* @param string|array|self $name Name to append
|
||||
*/
|
||||
public function append($name) {
|
||||
$this->parts = array_merge($this->parts, self::prepareName($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the first part of the name.
|
||||
*
|
||||
* @deprecated Use concat($first, $name->slice(1)) instead
|
||||
*
|
||||
* @param string|array|self $name The name to set the first part to
|
||||
*/
|
||||
public function setFirst($name) {
|
||||
array_splice($this->parts, 0, 1, self::prepareName($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last part of the name.
|
||||
*
|
||||
* @param string|array|self $name The name to set the last part to
|
||||
*/
|
||||
public function setLast($name) {
|
||||
array_splice($this->parts, -1, 1, self::prepareName($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a slice of a name (similar to array_slice).
|
||||
*
|
||||
* This method returns a new instance of the same type as the original and with the same
|
||||
* attributes.
|
||||
*
|
||||
* If the slice is empty, a Name with an empty parts array is returned. While this is
|
||||
* meaningless in itself, it works correctly in conjunction with concat().
|
||||
* If the slice is empty, null is returned. The null value will be correctly handled in
|
||||
* concatenations using concat().
|
||||
*
|
||||
* @param int $offset Offset to start the slice at
|
||||
* Offset and length have the same meaning as in array_slice().
|
||||
*
|
||||
* @return static Sliced name
|
||||
* @param int $offset Offset to start the slice at (may be negative)
|
||||
* @param int|null $length Length of the slice (may be negative)
|
||||
*
|
||||
* @return static|null Sliced name
|
||||
*/
|
||||
public function slice($offset) {
|
||||
// TODO negative offset and length
|
||||
if ($offset < 0 || $offset > count($this->parts)) {
|
||||
public function slice($offset, $length = null) {
|
||||
$numParts = count($this->parts);
|
||||
|
||||
$realOffset = $offset < 0 ? $offset + $numParts : $offset;
|
||||
if ($realOffset < 0 || $realOffset > $numParts) {
|
||||
throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset));
|
||||
}
|
||||
|
||||
return new static(array_slice($this->parts, $offset), $this->attributes);
|
||||
if (null === $length) {
|
||||
$realLength = $numParts - $realOffset;
|
||||
} else {
|
||||
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
|
||||
if ($realLength < 0 || $realLength > $numParts) {
|
||||
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
|
||||
}
|
||||
}
|
||||
|
||||
if ($realLength === 0) {
|
||||
// Empty slice is represented as null
|
||||
return null;
|
||||
}
|
||||
|
||||
return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,16 +148,29 @@ class Name extends NodeAbstract
|
||||
* The type of the generated instance depends on which class this method is called on, for
|
||||
* example Name\FullyQualified::concat() will yield a Name\FullyQualified instance.
|
||||
*
|
||||
* @param string|array|self $name1 The first name
|
||||
* @param string|array|self $name2 The second name
|
||||
* @param array $attributes Attributes to assign to concatenated name
|
||||
* If one of the arguments is null, a new instance of the other name will be returned. If both
|
||||
* arguments are null, null will be returned. As such, writing
|
||||
* Name::concat($namespace, $shortName)
|
||||
* where $namespace is a Name node or null will work as expected.
|
||||
*
|
||||
* @return static Concatenated name
|
||||
* @param string|array|self|null $name1 The first name
|
||||
* @param string|array|self|null $name2 The second name
|
||||
* @param array $attributes Attributes to assign to concatenated name
|
||||
*
|
||||
* @return static|null Concatenated name
|
||||
*/
|
||||
public static function concat($name1, $name2, array $attributes = []) {
|
||||
return new static(
|
||||
array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
|
||||
);
|
||||
if (null === $name1 && null === $name2) {
|
||||
return null;
|
||||
} elseif (null === $name1) {
|
||||
return new static(self::prepareName($name2), $attributes);
|
||||
} else if (null === $name2) {
|
||||
return new static(self::prepareName($name1), $attributes);
|
||||
} else {
|
||||
return new static(
|
||||
array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,16 +182,16 @@ class Name extends NodeAbstract
|
||||
* @return array Prepared name
|
||||
*/
|
||||
private static function prepareName($name) {
|
||||
if (is_string($name)) {
|
||||
if (\is_string($name)) {
|
||||
return explode('\\', $name);
|
||||
} elseif (is_array($name)) {
|
||||
} elseif (\is_array($name)) {
|
||||
return $name;
|
||||
} elseif ($name instanceof self) {
|
||||
return $name->parts;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(
|
||||
'When changing a name you need to pass either a string, an array or a Name node'
|
||||
'Expected string, array of parts or Name instance'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
26
lib/PhpParser/Node/NullableType.php
Normal file
26
lib/PhpParser/Node/NullableType.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class NullableType extends NodeAbstract
|
||||
{
|
||||
/** @var string|Name Type */
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Constructs a nullable type (wrapping another type).
|
||||
*
|
||||
* @param string|Name $type Type
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($type, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('type');
|
||||
}
|
||||
}
|
@ -2,12 +2,11 @@
|
||||
|
||||
namespace PhpParser\Node;
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\NodeAbstract;
|
||||
|
||||
class Param extends NodeAbstract
|
||||
{
|
||||
/** @var null|string|Name Typehint */
|
||||
/** @var null|string|Name|NullableType Typehint */
|
||||
public $type;
|
||||
/** @var bool Whether parameter is passed by reference */
|
||||
public $byRef;
|
||||
@ -21,12 +20,12 @@ class Param extends NodeAbstract
|
||||
/**
|
||||
* Constructs a parameter node.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @param null|Expr $default Default value
|
||||
* @param null|string|Name $type Typehint
|
||||
* @param bool $byRef Whether is passed by reference
|
||||
* @param bool $variadic Whether this is a variadic argument
|
||||
* @param array $attributes Additional attributes
|
||||
* @param string $name Name
|
||||
* @param null|Expr $default Default value
|
||||
* @param null|string|Name|NullableType $type Typehint
|
||||
* @param bool $byRef Whether is passed by reference
|
||||
* @param bool $variadic Whether this is a variadic argument
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, Expr $default = null, $type = null, $byRef = false, $variadic = false, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
@ -35,10 +34,6 @@ class Param extends NodeAbstract
|
||||
$this->variadic = $variadic;
|
||||
$this->name = $name;
|
||||
$this->default = $default;
|
||||
|
||||
if ($variadic && null !== $default) {
|
||||
throw new Error('Variadic parameter cannot have a default value', $default->getAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Scalar;
|
||||
|
||||
class Encapsed extends Scalar
|
||||
{
|
||||
/** @var array Encaps list */
|
||||
/** @var Expr[] list of string parts */
|
||||
public $parts;
|
||||
|
||||
/**
|
||||
|
26
lib/PhpParser/Node/Scalar/EncapsedStringPart.php
Normal file
26
lib/PhpParser/Node/Scalar/EncapsedStringPart.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
use PhpParser\Node\Scalar;
|
||||
|
||||
class EncapsedStringPart extends Scalar
|
||||
{
|
||||
/** @var string String value */
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* Constructs a node representing a string part of an encapsed string.
|
||||
*
|
||||
* @param string $value String value
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($value, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('value');
|
||||
}
|
||||
}
|
@ -2,10 +2,17 @@
|
||||
|
||||
namespace PhpParser\Node\Scalar;
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Node\Scalar;
|
||||
|
||||
class LNumber extends Scalar
|
||||
{
|
||||
/* For use in "kind" attribute */
|
||||
const KIND_BIN = 2;
|
||||
const KIND_OCT = 8;
|
||||
const KIND_DEC = 10;
|
||||
const KIND_HEX = 16;
|
||||
|
||||
/** @var int Number value */
|
||||
public $value;
|
||||
|
||||
@ -25,37 +32,36 @@ class LNumber extends Scalar
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Constructs an LNumber node from a string number literal.
|
||||
*
|
||||
* Parses an LNUMBER token (dec, hex, oct and bin notations) like PHP would.
|
||||
* @param string $str String number literal (decimal, octal, hex or binary)
|
||||
* @param array $attributes Additional attributes
|
||||
* @param bool $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5)
|
||||
*
|
||||
* @param string $str A string number
|
||||
*
|
||||
* @return int The parsed number
|
||||
* @return LNumber The constructed LNumber, including kind attribute
|
||||
*/
|
||||
public static function parse($str) {
|
||||
// handle plain 0 specially
|
||||
if ('0' === $str) {
|
||||
return 0;
|
||||
public static function fromString($str, array $attributes = array(), $allowInvalidOctal = false) {
|
||||
if ('0' !== $str[0] || '0' === $str) {
|
||||
$attributes['kind'] = LNumber::KIND_DEC;
|
||||
return new LNumber((int) $str, $attributes);
|
||||
}
|
||||
|
||||
// if first char is 0 (and number isn't 0) it's a special syntax
|
||||
if ('0' === $str[0]) {
|
||||
// hex
|
||||
if ('x' === $str[1] || 'X' === $str[1]) {
|
||||
return hexdec($str);
|
||||
}
|
||||
|
||||
// bin
|
||||
if ('b' === $str[1] || 'B' === $str[1]) {
|
||||
return bindec($str);
|
||||
}
|
||||
|
||||
// oct (intval instead of octdec to get proper cutting behavior with malformed numbers)
|
||||
return intval($str, 8);
|
||||
if ('x' === $str[1] || 'X' === $str[1]) {
|
||||
$attributes['kind'] = LNumber::KIND_HEX;
|
||||
return new LNumber(hexdec($str), $attributes);
|
||||
}
|
||||
|
||||
// dec
|
||||
return (int) $str;
|
||||
if ('b' === $str[1] || 'B' === $str[1]) {
|
||||
$attributes['kind'] = LNumber::KIND_BIN;
|
||||
return new LNumber(bindec($str), $attributes);
|
||||
}
|
||||
|
||||
if (!$allowInvalidOctal && strpbrk($str, '89')) {
|
||||
throw new Error('Invalid numeric literal', $attributes);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,12 @@ use PhpParser\Node\Scalar;
|
||||
|
||||
class String_ extends Scalar
|
||||
{
|
||||
/* For use in "kind" attribute */
|
||||
const KIND_SINGLE_QUOTED = 1;
|
||||
const KIND_DOUBLE_QUOTED = 2;
|
||||
const KIND_HEREDOC = 3;
|
||||
const KIND_NOWDOC = 4;
|
||||
|
||||
/** @var string String value */
|
||||
public $value;
|
||||
|
||||
@ -48,7 +54,7 @@ class String_ extends Scalar
|
||||
*/
|
||||
public static function parse($str, $parseUnicodeEscape = true) {
|
||||
$bLength = 0;
|
||||
if ('b' === $str[0]) {
|
||||
if ('b' === $str[0] || 'B' === $str[0]) {
|
||||
$bLength = 1;
|
||||
}
|
||||
|
||||
@ -135,7 +141,7 @@ class String_ extends Scalar
|
||||
*/
|
||||
public static function parseDocString($startToken, $str, $parseUnicodeEscape = true) {
|
||||
// strip last newline (thanks tokenizer for sticking it into the string!)
|
||||
$str = preg_replace('~(\r\n|\n|\r)$~', '', $str);
|
||||
$str = preg_replace('~(\r\n|\n|\r)\z~', '', $str);
|
||||
|
||||
// nowdoc string
|
||||
if (false !== strpos($startToken, '\'')) {
|
||||
|
@ -6,8 +6,8 @@ use PhpParser\Node;
|
||||
|
||||
class Catch_ extends Node\Stmt
|
||||
{
|
||||
/** @var Node\Name Class of exception */
|
||||
public $type;
|
||||
/** @var Node\Name[] Types of exceptions to catch */
|
||||
public $types;
|
||||
/** @var string Variable for exception */
|
||||
public $var;
|
||||
/** @var Node[] Statements */
|
||||
@ -16,19 +16,19 @@ class Catch_ extends Node\Stmt
|
||||
/**
|
||||
* Constructs a catch node.
|
||||
*
|
||||
* @param Node\Name $type Class of exception
|
||||
* @param string $var Variable for exception
|
||||
* @param Node[] $stmts Statements
|
||||
* @param array $attributes Additional attributes
|
||||
* @param Node\Name[] $types Types of exceptions to catch
|
||||
* @param string $var Variable for exception
|
||||
* @param Node[] $stmts Statements
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(Node\Name $type, $var, array $stmts = array(), array $attributes = array()) {
|
||||
public function __construct(array $types, $var, array $stmts = array(), array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->type = $type;
|
||||
$this->types = $types;
|
||||
$this->var = $var;
|
||||
$this->stmts = $stmts;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('type', 'var', 'stmts');
|
||||
return array('types', 'var', 'stmts');
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ use PhpParser\Node;
|
||||
|
||||
class ClassConst extends Node\Stmt
|
||||
{
|
||||
/** @var int Modifiers */
|
||||
public $flags;
|
||||
/** @var Node\Const_[] Constant declarations */
|
||||
public $consts;
|
||||
|
||||
@ -13,14 +15,33 @@ class ClassConst extends Node\Stmt
|
||||
* Constructs a class const list node.
|
||||
*
|
||||
* @param Node\Const_[] $consts Constant declarations
|
||||
* @param int $flags Modifiers
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $consts, array $attributes = array()) {
|
||||
public function __construct(array $consts, $flags = 0, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->flags = $flags;
|
||||
$this->consts = $consts;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('consts');
|
||||
return array('flags', 'consts');
|
||||
}
|
||||
|
||||
public function isPublic() {
|
||||
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|
||||
|| ($this->flags & Class_::VISIBILITY_MODIFER_MASK) === 0;
|
||||
}
|
||||
|
||||
public function isProtected() {
|
||||
return (bool) ($this->flags & Class_::MODIFIER_PROTECTED);
|
||||
}
|
||||
|
||||
public function isPrivate() {
|
||||
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
|
||||
}
|
||||
|
||||
public function isStatic() {
|
||||
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ namespace PhpParser\Node\Stmt;
|
||||
use PhpParser\Node;
|
||||
|
||||
abstract class ClassLike extends Node\Stmt {
|
||||
/** @var string Name */
|
||||
/** @var string|null Name */
|
||||
public $name;
|
||||
/** @var Node[] Statements */
|
||||
public $stmts;
|
||||
|
@ -4,29 +4,31 @@ namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Error;
|
||||
|
||||
class ClassMethod extends Node\Stmt implements FunctionLike
|
||||
{
|
||||
/** @var int Type */
|
||||
public $type;
|
||||
/** @var int Flags */
|
||||
public $flags;
|
||||
/** @var bool Whether to return by reference */
|
||||
public $byRef;
|
||||
/** @var string Name */
|
||||
public $name;
|
||||
/** @var Node\Param[] Parameters */
|
||||
public $params;
|
||||
/** @var null|string|Node\Name Return type */
|
||||
/** @var null|string|Node\Name|Node\NullableType Return type */
|
||||
public $returnType;
|
||||
/** @var Node[] Statements */
|
||||
public $stmts;
|
||||
|
||||
/** @deprecated Use $flags instead */
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Constructs a class method node.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @param array $subNodes Array of the following optional subnodes:
|
||||
* 'type' => MODIFIER_PUBLIC: Type
|
||||
* 'flags => MODIFIER_PUBLIC: Flags
|
||||
* 'byRef' => false : Whether to return by reference
|
||||
* 'params' => array() : Parameters
|
||||
* 'returnType' => null : Return type
|
||||
@ -35,27 +37,18 @@ class ClassMethod extends Node\Stmt implements FunctionLike
|
||||
*/
|
||||
public function __construct($name, array $subNodes = array(), array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->type = isset($subNodes['type']) ? $subNodes['type'] : 0;
|
||||
$this->flags = isset($subNodes['flags']) ? $subNodes['flags']
|
||||
: (isset($subNodes['type']) ? $subNodes['type'] : 0);
|
||||
$this->type = $this->flags;
|
||||
$this->byRef = isset($subNodes['byRef']) ? $subNodes['byRef'] : false;
|
||||
$this->name = $name;
|
||||
$this->params = isset($subNodes['params']) ? $subNodes['params'] : array();
|
||||
$this->returnType = isset($subNodes['returnType']) ? $subNodes['returnType'] : null;
|
||||
$this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : array();
|
||||
|
||||
if ($this->type & Class_::MODIFIER_STATIC) {
|
||||
switch (strtolower($this->name)) {
|
||||
case '__construct':
|
||||
throw new Error(sprintf('Constructor %s() cannot be static', $this->name));
|
||||
case '__destruct':
|
||||
throw new Error(sprintf('Destructor %s() cannot be static', $this->name));
|
||||
case '__clone':
|
||||
throw new Error(sprintf('Clone method %s() cannot be static', $this->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('type', 'byRef', 'name', 'params', 'returnType', 'stmts');
|
||||
return array('flags', 'byRef', 'name', 'params', 'returnType', 'stmts');
|
||||
}
|
||||
|
||||
public function returnsByRef() {
|
||||
@ -75,27 +68,27 @@ class ClassMethod extends Node\Stmt implements FunctionLike
|
||||
}
|
||||
|
||||
public function isPublic() {
|
||||
return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
|
||||
|| ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
|
||||
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|
||||
|| ($this->flags & Class_::VISIBILITY_MODIFER_MASK) === 0;
|
||||
}
|
||||
|
||||
public function isProtected() {
|
||||
return (bool) ($this->type & Class_::MODIFIER_PROTECTED);
|
||||
return (bool) ($this->flags & Class_::MODIFIER_PROTECTED);
|
||||
}
|
||||
|
||||
public function isPrivate() {
|
||||
return (bool) ($this->type & Class_::MODIFIER_PRIVATE);
|
||||
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
|
||||
}
|
||||
|
||||
public function isAbstract() {
|
||||
return (bool) ($this->type & Class_::MODIFIER_ABSTRACT);
|
||||
return (bool) ($this->flags & Class_::MODIFIER_ABSTRACT);
|
||||
}
|
||||
|
||||
public function isFinal() {
|
||||
return (bool) ($this->type & Class_::MODIFIER_FINAL);
|
||||
return (bool) ($this->flags & Class_::MODIFIER_FINAL);
|
||||
}
|
||||
|
||||
public function isStatic() {
|
||||
return (bool) ($this->type & Class_::MODIFIER_STATIC);
|
||||
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Node;
|
||||
|
||||
class Class_ extends ClassLike
|
||||
{
|
||||
@ -17,12 +17,15 @@ class Class_ extends ClassLike
|
||||
const VISIBILITY_MODIFER_MASK = 7; // 1 | 2 | 4
|
||||
|
||||
/** @var int Type */
|
||||
public $type;
|
||||
public $flags;
|
||||
/** @var null|Node\Name Name of extended class */
|
||||
public $extends;
|
||||
/** @var Node\Name[] Names of implemented interfaces */
|
||||
public $implements;
|
||||
|
||||
/** @deprecated Use $flags instead */
|
||||
public $type;
|
||||
|
||||
protected static $specialNames = array(
|
||||
'self' => true,
|
||||
'parent' => true,
|
||||
@ -34,7 +37,7 @@ class Class_ extends ClassLike
|
||||
*
|
||||
* @param string|null $name Name
|
||||
* @param array $subNodes Array of the following optional subnodes:
|
||||
* 'type' => 0 : Type
|
||||
* 'flags' => 0 : Flags
|
||||
* 'extends' => null : Name of extended class
|
||||
* 'implements' => array(): Names of implemented interfaces
|
||||
* 'stmts' => array(): Statements
|
||||
@ -42,43 +45,25 @@ class Class_ extends ClassLike
|
||||
*/
|
||||
public function __construct($name, array $subNodes = array(), array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->type = isset($subNodes['type']) ? $subNodes['type'] : 0;
|
||||
$this->flags = isset($subNodes['flags']) ? $subNodes['flags']
|
||||
: (isset($subNodes['type']) ? $subNodes['type'] : 0);
|
||||
$this->type = $this->flags;
|
||||
$this->name = $name;
|
||||
$this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : null;
|
||||
$this->implements = isset($subNodes['implements']) ? $subNodes['implements'] : array();
|
||||
$this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
|
||||
|
||||
if (null !== $this->name && isset(self::$specialNames[strtolower($this->name)])) {
|
||||
throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
|
||||
}
|
||||
|
||||
if (isset(self::$specialNames[strtolower($this->extends)])) {
|
||||
throw new Error(
|
||||
sprintf('Cannot use \'%s\' as class name as it is reserved', $this->extends),
|
||||
$this->extends->getAttributes()
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->implements as $interface) {
|
||||
if (isset(self::$specialNames[strtolower($interface)])) {
|
||||
throw new Error(
|
||||
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
|
||||
$interface->getAttributes()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('type', 'name', 'extends', 'implements', 'stmts');
|
||||
return array('flags', 'name', 'extends', 'implements', 'stmts');
|
||||
}
|
||||
|
||||
public function isAbstract() {
|
||||
return (bool) ($this->type & self::MODIFIER_ABSTRACT);
|
||||
return (bool) ($this->flags & self::MODIFIER_ABSTRACT);
|
||||
}
|
||||
|
||||
public function isFinal() {
|
||||
return (bool) ($this->type & self::MODIFIER_FINAL);
|
||||
return (bool) ($this->flags & self::MODIFIER_FINAL);
|
||||
}
|
||||
|
||||
public function isAnonymous() {
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
class Declare_ extends Node\Stmt
|
||||
{
|
||||
/** @var DeclareDeclare[] List of declares */
|
||||
@ -14,10 +15,10 @@ class Declare_ extends Node\Stmt
|
||||
* Constructs a declare node.
|
||||
*
|
||||
* @param DeclareDeclare[] $declares List of declares
|
||||
* @param Node[] $stmts Statements
|
||||
* @param Node[]|null $stmts Statements
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $declares, array $stmts, array $attributes = array()) {
|
||||
public function __construct(array $declares, array $stmts = null, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->declares = $declares;
|
||||
$this->stmts = $stmts;
|
||||
|
26
lib/PhpParser/Node/Stmt/Finally_.php
Normal file
26
lib/PhpParser/Node/Stmt/Finally_.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
class Finally_ extends Node\Stmt
|
||||
{
|
||||
/** @var Node[] Statements */
|
||||
public $stmts;
|
||||
|
||||
/**
|
||||
* Constructs a finally node.
|
||||
*
|
||||
* @param Node[] $stmts Statements
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $stmts = array(), array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->stmts = $stmts;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('stmts');
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ class Function_ extends Node\Stmt implements FunctionLike
|
||||
public $name;
|
||||
/** @var Node\Param[] Parameters */
|
||||
public $params;
|
||||
/** @var null|string|Node\Name Return type */
|
||||
/** @var null|string|Node\Name|Node\NullableType Return type */
|
||||
public $returnType;
|
||||
/** @var Node[] Statements */
|
||||
public $stmts;
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class GroupUse extends Stmt
|
||||
{
|
||||
|
@ -3,19 +3,12 @@
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Error;
|
||||
|
||||
class Interface_ extends ClassLike
|
||||
{
|
||||
/** @var Node\Name[] Extended interfaces */
|
||||
public $extends;
|
||||
|
||||
protected static $specialNames = array(
|
||||
'self' => true,
|
||||
'parent' => true,
|
||||
'static' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs a class node.
|
||||
*
|
||||
@ -30,19 +23,6 @@ class Interface_ extends ClassLike
|
||||
$this->name = $name;
|
||||
$this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : array();
|
||||
$this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
|
||||
|
||||
if (isset(self::$specialNames[strtolower($this->name)])) {
|
||||
throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
|
||||
}
|
||||
|
||||
foreach ($this->extends as $interface) {
|
||||
if (isset(self::$specialNames[strtolower($interface)])) {
|
||||
throw new Error(
|
||||
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
|
||||
$interface->getAttributes()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Error;
|
||||
|
||||
class Namespace_ extends Node\Stmt
|
||||
{
|
||||
@ -12,12 +11,6 @@ class Namespace_ extends Node\Stmt
|
||||
/** @var Node[] Statements */
|
||||
public $stmts;
|
||||
|
||||
protected static $specialNames = array(
|
||||
'self' => true,
|
||||
'parent' => true,
|
||||
'static' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs a namespace node.
|
||||
*
|
||||
@ -29,21 +22,6 @@ class Namespace_ extends Node\Stmt
|
||||
parent::__construct($attributes);
|
||||
$this->name = $name;
|
||||
$this->stmts = $stmts;
|
||||
|
||||
if (isset(self::$specialNames[strtolower($this->name)])) {
|
||||
throw new Error(
|
||||
sprintf('Cannot use \'%s\' as namespace name', $this->name),
|
||||
$this->name->getAttributes()
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $this->stmts) {
|
||||
foreach ($this->stmts as $stmt) {
|
||||
if ($stmt instanceof self) {
|
||||
throw new Error('Namespace declarations cannot be nested', $stmt->getAttributes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
|
13
lib/PhpParser/Node/Stmt/Nop.php
Normal file
13
lib/PhpParser/Node/Stmt/Nop.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
|
||||
/** Nop/empty statement (;). */
|
||||
class Nop extends Node\Stmt
|
||||
{
|
||||
public function getSubNodeNames() {
|
||||
return array();
|
||||
}
|
||||
}
|
@ -3,54 +3,49 @@
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Error;
|
||||
|
||||
class Property extends Node\Stmt
|
||||
{
|
||||
/** @var int Modifiers */
|
||||
public $type;
|
||||
public $flags;
|
||||
/** @var PropertyProperty[] Properties */
|
||||
public $props;
|
||||
|
||||
/** @deprecated Use $flags instead */
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Constructs a class property list node.
|
||||
*
|
||||
* @param int $type Modifiers
|
||||
* @param int $flags Modifiers
|
||||
* @param PropertyProperty[] $props Properties
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($type, array $props, array $attributes = array()) {
|
||||
if ($type & Class_::MODIFIER_ABSTRACT) {
|
||||
throw new Error('Properties cannot be declared abstract');
|
||||
}
|
||||
|
||||
if ($type & Class_::MODIFIER_FINAL) {
|
||||
throw new Error('Properties cannot be declared final');
|
||||
}
|
||||
|
||||
public function __construct($flags, array $props, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->type = $type;
|
||||
$this->flags = $flags;
|
||||
$this->type = $flags;
|
||||
$this->props = $props;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('type', 'props');
|
||||
return array('flags', 'props');
|
||||
}
|
||||
|
||||
public function isPublic() {
|
||||
return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
|
||||
|| ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
|
||||
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|
||||
|| ($this->flags & Class_::VISIBILITY_MODIFER_MASK) === 0;
|
||||
}
|
||||
|
||||
public function isProtected() {
|
||||
return (bool) ($this->type & Class_::MODIFIER_PROTECTED);
|
||||
return (bool) ($this->flags & Class_::MODIFIER_PROTECTED);
|
||||
}
|
||||
|
||||
public function isPrivate() {
|
||||
return (bool) ($this->type & Class_::MODIFIER_PRIVATE);
|
||||
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
|
||||
}
|
||||
|
||||
public function isStatic() {
|
||||
return (bool) ($this->type & Class_::MODIFIER_STATIC);
|
||||
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,14 @@ class Trait_ extends ClassLike
|
||||
* Constructs a trait node.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @param Node[] $stmts Statements
|
||||
* @param array $subNodes Array of the following optional subnodes:
|
||||
* 'stmts' => array(): Statements
|
||||
* @param array $attributes Additional attributes
|
||||
*/
|
||||
public function __construct($name, array $stmts = array(), array $attributes = array()) {
|
||||
public function __construct($name, array $subNodes = array(), array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->name = $name;
|
||||
$this->stmts = $stmts;
|
||||
$this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Error;
|
||||
|
||||
class TryCatch extends Node\Stmt
|
||||
{
|
||||
@ -11,29 +10,25 @@ class TryCatch extends Node\Stmt
|
||||
public $stmts;
|
||||
/** @var Catch_[] Catches */
|
||||
public $catches;
|
||||
/** @var null|Node[] Finally statements */
|
||||
public $finallyStmts;
|
||||
/** @var null|Finally_ Optional finally node */
|
||||
public $finally;
|
||||
|
||||
/**
|
||||
* Constructs a try catch node.
|
||||
*
|
||||
* @param Node[] $stmts Statements
|
||||
* @param Catch_[] $catches Catches
|
||||
* @param null|Node[] $finallyStmts Finally statements (null means no finally clause)
|
||||
* @param array|null $attributes Additional attributes
|
||||
* @param Node[] $stmts Statements
|
||||
* @param Catch_[] $catches Catches
|
||||
* @param null|Finally_ $finally Optionaly finally node
|
||||
* @param array|null $attributes Additional attributes
|
||||
*/
|
||||
public function __construct(array $stmts, array $catches, array $finallyStmts = null, array $attributes = array()) {
|
||||
if (empty($catches) && null === $finallyStmts) {
|
||||
throw new Error('Cannot use try without catch or finally');
|
||||
}
|
||||
|
||||
public function __construct(array $stmts, array $catches, Finally_ $finally = null, array $attributes = array()) {
|
||||
parent::__construct($attributes);
|
||||
$this->stmts = $stmts;
|
||||
$this->catches = $catches;
|
||||
$this->finallyStmts = $finallyStmts;
|
||||
$this->finally = $finally;
|
||||
}
|
||||
|
||||
public function getSubNodeNames() {
|
||||
return array('stmts', 'catches', 'finallyStmts');
|
||||
return array('stmts', 'catches', 'finally');
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace PhpParser\Node\Stmt;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Error;
|
||||
|
||||
class UseUse extends Node\Stmt
|
||||
{
|
||||
@ -27,13 +26,6 @@ class UseUse extends Node\Stmt
|
||||
$alias = $name->getLast();
|
||||
}
|
||||
|
||||
if ('self' == strtolower($alias) || 'parent' == strtolower($alias)) {
|
||||
throw new Error(sprintf(
|
||||
'Cannot use %s as %s because \'%2$s\' is a special class name',
|
||||
$name, $alias
|
||||
));
|
||||
}
|
||||
|
||||
parent::__construct($attributes);
|
||||
$this->type = $type;
|
||||
$this->name = $name;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
abstract class NodeAbstract implements Node
|
||||
abstract class NodeAbstract implements Node, \JsonSerializable
|
||||
{
|
||||
protected $attributes;
|
||||
|
||||
@ -63,6 +63,28 @@ abstract class NodeAbstract implements Node
|
||||
return $lastComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the doc comment of the node.
|
||||
*
|
||||
* This will either replace an existing doc comment or add it to the comments array.
|
||||
*
|
||||
* @param Comment\Doc $docComment Doc comment to set
|
||||
*/
|
||||
public function setDocComment(Comment\Doc $docComment) {
|
||||
$comments = $this->getAttribute('comments', []);
|
||||
|
||||
$numComments = count($comments);
|
||||
if ($numComments > 0 && $comments[$numComments - 1] instanceof Comment\Doc) {
|
||||
// Replace existing doc comment
|
||||
$comments[$numComments - 1] = $docComment;
|
||||
} else {
|
||||
// Append new comment
|
||||
$comments[] = $docComment;
|
||||
}
|
||||
|
||||
$this->setAttribute('comments', $comments);
|
||||
}
|
||||
|
||||
public function setAttribute($key, $value) {
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
@ -82,4 +104,8 @@ abstract class NodeAbstract implements Node
|
||||
public function getAttributes() {
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
return ['nodeType' => $this->getType()] + get_object_vars($this);
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,55 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Expr\Include_;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\GroupUse;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PhpParser\Node\Stmt\UseUse;
|
||||
|
||||
class NodeDumper
|
||||
{
|
||||
private $dumpComments;
|
||||
private $dumpPositions;
|
||||
private $code;
|
||||
|
||||
/**
|
||||
* Constructs a NodeDumper.
|
||||
*
|
||||
* Supported options:
|
||||
* * bool dumpComments: Whether comments should be dumped.
|
||||
* * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
|
||||
* information, the code needs to be passed to dump().
|
||||
*
|
||||
* @param array $options Options (see description)
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
$this->dumpComments = !empty($options['dumpComments']);
|
||||
$this->dumpPositions = !empty($options['dumpPositions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps a node or array.
|
||||
*
|
||||
* @param array|Node $node Node or array to dump
|
||||
* @param array|Node $node Node or array to dump
|
||||
* @param string|null $code Code corresponding to dumped AST. This only needs to be passed if
|
||||
* the dumpPositions option is enabled and the dumping of node offsets
|
||||
* is desired.
|
||||
*
|
||||
* @return string Dumped value
|
||||
*/
|
||||
public function dump($node) {
|
||||
public function dump($node, $code = null) {
|
||||
$this->code = $code;
|
||||
return $this->dumpRecursive($node);
|
||||
}
|
||||
|
||||
protected function dumpRecursive($node) {
|
||||
if ($node instanceof Node) {
|
||||
$r = $node->getType() . '(';
|
||||
$r = $node->getType();
|
||||
if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
|
||||
$r .= $p;
|
||||
}
|
||||
$r .= '(';
|
||||
|
||||
foreach ($node->getSubNodeNames() as $key) {
|
||||
$r .= "\n " . $key . ': ';
|
||||
@ -26,11 +63,24 @@ class NodeDumper
|
||||
} elseif (true === $value) {
|
||||
$r .= 'true';
|
||||
} elseif (is_scalar($value)) {
|
||||
$r .= $value;
|
||||
if ('flags' === $key || 'newModifier' === $key) {
|
||||
$r .= $this->dumpFlags($value);
|
||||
} else if ('type' === $key && $node instanceof Include_) {
|
||||
$r .= $this->dumpIncludeType($value);
|
||||
} else if ('type' === $key
|
||||
&& ($node instanceof Use_ || $node instanceof UseUse || $node instanceof GroupUse)) {
|
||||
$r .= $this->dumpUseType($value);
|
||||
} else {
|
||||
$r .= $value;
|
||||
}
|
||||
} else {
|
||||
$r .= str_replace("\n", "\n ", $this->dump($value));
|
||||
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->dumpComments && $comments = $node->getAttribute('comments')) {
|
||||
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments));
|
||||
}
|
||||
} elseif (is_array($node)) {
|
||||
$r = 'array(';
|
||||
|
||||
@ -46,13 +96,101 @@ class NodeDumper
|
||||
} elseif (is_scalar($value)) {
|
||||
$r .= $value;
|
||||
} else {
|
||||
$r .= str_replace("\n", "\n ", $this->dump($value));
|
||||
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
|
||||
}
|
||||
}
|
||||
} elseif ($node instanceof Comment) {
|
||||
return $node->getReformattedText();
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Can only dump nodes and arrays.');
|
||||
}
|
||||
|
||||
return $r . "\n)";
|
||||
}
|
||||
|
||||
protected function dumpFlags($flags) {
|
||||
$strs = [];
|
||||
if ($flags & Class_::MODIFIER_PUBLIC) {
|
||||
$strs[] = 'MODIFIER_PUBLIC';
|
||||
}
|
||||
if ($flags & Class_::MODIFIER_PROTECTED) {
|
||||
$strs[] = 'MODIFIER_PROTECTED';
|
||||
}
|
||||
if ($flags & Class_::MODIFIER_PRIVATE) {
|
||||
$strs[] = 'MODIFIER_PRIVATE';
|
||||
}
|
||||
if ($flags & Class_::MODIFIER_ABSTRACT) {
|
||||
$strs[] = 'MODIFIER_ABSTRACT';
|
||||
}
|
||||
if ($flags & Class_::MODIFIER_STATIC) {
|
||||
$strs[] = 'MODIFIER_STATIC';
|
||||
}
|
||||
if ($flags & Class_::MODIFIER_FINAL) {
|
||||
$strs[] = 'MODIFIER_FINAL';
|
||||
}
|
||||
|
||||
if ($strs) {
|
||||
return implode(' | ', $strs) . ' (' . $flags . ')';
|
||||
} else {
|
||||
return $flags;
|
||||
}
|
||||
}
|
||||
|
||||
protected function dumpIncludeType($type) {
|
||||
$map = [
|
||||
Include_::TYPE_INCLUDE => 'TYPE_INCLUDE',
|
||||
Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE',
|
||||
Include_::TYPE_REQUIRE => 'TYPE_REQUIRE',
|
||||
Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQURE_ONCE',
|
||||
];
|
||||
|
||||
if (!isset($map[$type])) {
|
||||
return $type;
|
||||
}
|
||||
return $map[$type] . ' (' . $type . ')';
|
||||
}
|
||||
|
||||
protected function dumpUseType($type) {
|
||||
$map = [
|
||||
Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN',
|
||||
Use_::TYPE_NORMAL => 'TYPE_NORMAL',
|
||||
Use_::TYPE_FUNCTION => 'TYPE_FUNCTION',
|
||||
Use_::TYPE_CONSTANT => 'TYPE_CONSTANT',
|
||||
];
|
||||
|
||||
if (!isset($map[$type])) {
|
||||
return $type;
|
||||
}
|
||||
return $map[$type] . ' (' . $type . ')';
|
||||
}
|
||||
|
||||
protected function dumpPosition(Node $node) {
|
||||
if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$start = $node->getAttribute('startLine');
|
||||
$end = $node->getAttribute('endLine');
|
||||
if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos')
|
||||
&& null !== $this->code
|
||||
) {
|
||||
$start .= ':' . $this->toColumn($this->code, $node->getAttribute('startFilePos'));
|
||||
$end .= ':' . $this->toColumn($this->code, $node->getAttribute('endFilePos'));
|
||||
}
|
||||
return "[$start - $end]";
|
||||
}
|
||||
|
||||
// Copied from Error class
|
||||
private function toColumn($code, $pos) {
|
||||
if ($pos > strlen($code)) {
|
||||
throw new \RuntimeException('Invalid position information');
|
||||
}
|
||||
|
||||
$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
|
||||
if (false === $lineStartPos) {
|
||||
$lineStartPos = -1;
|
||||
}
|
||||
|
||||
return $pos - $lineStartPos;
|
||||
}
|
||||
}
|
||||
|
@ -5,23 +5,31 @@ namespace PhpParser;
|
||||
class NodeTraverser implements NodeTraverserInterface
|
||||
{
|
||||
/**
|
||||
* @var NodeVisitor[] Visitors
|
||||
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
|
||||
* of the current node will not be traversed for any visitors.
|
||||
*
|
||||
* For subsequent visitors enterNode() will still be called on the current
|
||||
* node and leaveNode() will also be invoked for the current node.
|
||||
*/
|
||||
const DONT_TRAVERSE_CHILDREN = 1;
|
||||
|
||||
/**
|
||||
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
|
||||
* in an array, it will be removed from the array.
|
||||
*
|
||||
* For subsequent visitors leaveNode() will still be invoked for the
|
||||
* removed node.
|
||||
*/
|
||||
const REMOVE_NODE = false;
|
||||
|
||||
/** @var NodeVisitor[] Visitors */
|
||||
protected $visitors;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $cloneNodes;
|
||||
|
||||
/**
|
||||
* Constructs a node traverser.
|
||||
*
|
||||
* @param bool $cloneNodes Should the traverser clone the nodes when traversing the AST
|
||||
*/
|
||||
public function __construct($cloneNodes = false) {
|
||||
public function __construct() {
|
||||
$this->visitors = array();
|
||||
$this->cloneNodes = $cloneNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,10 +81,6 @@ class NodeTraverser implements NodeTraverserInterface
|
||||
}
|
||||
|
||||
protected function traverseNode(Node $node) {
|
||||
if ($this->cloneNodes) {
|
||||
$node = clone $node;
|
||||
}
|
||||
|
||||
foreach ($node->getSubNodeNames() as $name) {
|
||||
$subNode =& $node->$name;
|
||||
|
||||
@ -99,6 +103,12 @@ class NodeTraverser implements NodeTraverserInterface
|
||||
|
||||
foreach ($this->visitors as $visitor) {
|
||||
if (null !== $return = $visitor->leaveNode($subNode)) {
|
||||
if (is_array($return)) {
|
||||
throw new \LogicException(
|
||||
'leaveNode() may only return an array ' .
|
||||
'if the parent structure is an array'
|
||||
);
|
||||
}
|
||||
$subNode = $return;
|
||||
}
|
||||
}
|
||||
|
@ -4,24 +4,6 @@ namespace PhpParser;
|
||||
|
||||
interface NodeTraverserInterface
|
||||
{
|
||||
/**
|
||||
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
|
||||
* of the current node will not be traversed for any visitors.
|
||||
*
|
||||
* For subsequent visitors enterNode() will still be called on the current
|
||||
* node and leaveNode() will also be invoked for the current node.
|
||||
*/
|
||||
const DONT_TRAVERSE_CHILDREN = 1;
|
||||
|
||||
/**
|
||||
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
|
||||
* in an array, it will be removed from the array.
|
||||
*
|
||||
* For subsequent visitors leaveNode() will still be invoked for the
|
||||
* removed node.
|
||||
*/
|
||||
const REMOVE_NODE = false;
|
||||
|
||||
/**
|
||||
* Adds a visitor.
|
||||
*
|
||||
|
@ -2,13 +2,14 @@
|
||||
|
||||
namespace PhpParser\NodeVisitor;
|
||||
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
|
||||
class NameResolver extends NodeVisitorAbstract
|
||||
{
|
||||
@ -18,6 +19,18 @@ class NameResolver extends NodeVisitorAbstract
|
||||
/** @var array Map of format [aliasType => [aliasName => originalName]] */
|
||||
protected $aliases;
|
||||
|
||||
/** @var ErrorHandler Error handler */
|
||||
protected $errorHandler;
|
||||
|
||||
/**
|
||||
* Constructs a name resolution visitor.
|
||||
*
|
||||
* @param ErrorHandler|null $errorHandler Error handler
|
||||
*/
|
||||
public function __construct(ErrorHandler $errorHandler = null) {
|
||||
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
|
||||
}
|
||||
|
||||
public function beforeTraverse(array $nodes) {
|
||||
$this->resetState();
|
||||
}
|
||||
@ -74,7 +87,9 @@ class NameResolver extends NodeVisitorAbstract
|
||||
$node->class = $this->resolveClassName($node->class);
|
||||
}
|
||||
} elseif ($node instanceof Stmt\Catch_) {
|
||||
$node->type = $this->resolveClassName($node->type);
|
||||
foreach ($node->types as &$type) {
|
||||
$type = $this->resolveClassName($type);
|
||||
}
|
||||
} elseif ($node instanceof Expr\FuncCall) {
|
||||
if ($node->name instanceof Name) {
|
||||
$node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION);
|
||||
@ -97,7 +112,10 @@ class NameResolver extends NodeVisitorAbstract
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($node instanceof Node\NullableType) {
|
||||
if ($node->type instanceof Name) {
|
||||
$node->type = $this->resolveClassName($node->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,13 +148,14 @@ class NameResolver extends NodeVisitorAbstract
|
||||
Stmt\Use_::TYPE_CONSTANT => 'const ',
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
$this->errorHandler->handleError(new Error(
|
||||
sprintf(
|
||||
'Cannot use %s%s as %s because the name is already in use',
|
||||
$typeStringMap[$type], $name, $use->alias
|
||||
),
|
||||
$use->getLine()
|
||||
);
|
||||
$use->getAttributes()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->aliases[$type][$aliasName] = $name;
|
||||
@ -158,10 +177,10 @@ class NameResolver extends NodeVisitorAbstract
|
||||
// don't resolve special class names
|
||||
if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
|
||||
if (!$name->isUnqualified()) {
|
||||
throw new Error(
|
||||
$this->errorHandler->handleError(new Error(
|
||||
sprintf("'\\%s' is an invalid class name", $name->toString()),
|
||||
$name->getLine()
|
||||
);
|
||||
$name->getAttributes()
|
||||
));
|
||||
}
|
||||
|
||||
return $name;
|
||||
@ -179,12 +198,8 @@ class NameResolver extends NodeVisitorAbstract
|
||||
return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
|
||||
}
|
||||
|
||||
if (null !== $this->namespace) {
|
||||
// if no alias exists prepend current namespace
|
||||
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
|
||||
}
|
||||
|
||||
return new FullyQualified($name->parts, $name->getAttributes());
|
||||
// if no alias exists prepend current namespace
|
||||
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
|
||||
}
|
||||
|
||||
protected function resolveOtherName(Name $name, $type) {
|
||||
@ -206,28 +221,28 @@ class NameResolver extends NodeVisitorAbstract
|
||||
$aliasName = $name->getFirst();
|
||||
}
|
||||
|
||||
if (!isset($this->aliases[$type][$aliasName])) {
|
||||
// unqualified, unaliased names cannot be resolved at compile-time
|
||||
return $name;
|
||||
if (isset($this->aliases[$type][$aliasName])) {
|
||||
// resolve unqualified aliases
|
||||
return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
|
||||
}
|
||||
|
||||
// resolve unqualified aliases
|
||||
return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
|
||||
if (null === $this->namespace) {
|
||||
// outside of a namespace unaliased unqualified is same as fully qualified
|
||||
return new FullyQualified($name, $name->getAttributes());
|
||||
}
|
||||
|
||||
// unqualified names inside a namespace cannot be resolved at compile-time
|
||||
// add the namespaced version of the name as an attribute
|
||||
$name->setAttribute('namespacedName',
|
||||
FullyQualified::concat($this->namespace, $name, $name->getAttributes()));
|
||||
return $name;
|
||||
}
|
||||
|
||||
if (null !== $this->namespace) {
|
||||
// if no alias exists prepend current namespace
|
||||
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
|
||||
}
|
||||
|
||||
return new FullyQualified($name->parts, $name->getAttributes());
|
||||
// if no alias exists prepend current namespace
|
||||
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
|
||||
}
|
||||
|
||||
protected function addNamespacedName(Node $node) {
|
||||
if (null !== $this->namespace) {
|
||||
$node->namespacedName = Name::concat($this->namespace, $node->name);
|
||||
} else {
|
||||
$node->namespacedName = new Name($node->name);
|
||||
}
|
||||
$node->namespacedName = Name::concat($this->namespace, $node->name);
|
||||
}
|
||||
}
|
||||
|
@ -7,18 +7,11 @@ interface Parser {
|
||||
* Parses PHP code into a node tree.
|
||||
*
|
||||
* @param string $code The source code to parse
|
||||
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
|
||||
* to ErrorHandler\Throwing.
|
||||
*
|
||||
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
|
||||
* unable to recover from an error).
|
||||
*/
|
||||
public function parse($code);
|
||||
|
||||
/**
|
||||
* Get array of errors that occurred during the last parse.
|
||||
*
|
||||
* This method may only return multiple errors if the 'throwOnError' option is disabled.
|
||||
*
|
||||
* @return Error[]
|
||||
*/
|
||||
public function getErrors();
|
||||
public function parse($code, ErrorHandler $errorHandler = null);
|
||||
}
|
||||
|
@ -3,61 +3,52 @@
|
||||
namespace PhpParser\Parser;
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\ErrorHandler;
|
||||
use PhpParser\Parser;
|
||||
|
||||
class Multiple implements Parser {
|
||||
/** @var Parser[] List of parsers to try, in order of preference */
|
||||
private $parsers;
|
||||
/** @var Error[] Errors collected during last parse */
|
||||
private $errors;
|
||||
|
||||
/**
|
||||
* Create a parser which will try multiple parsers in an order of preference.
|
||||
*
|
||||
* Parsers will be invoked in the order they're provided to the constructor. If one of the
|
||||
* parsers runs without errors, it's output is returned. Otherwise the errors (and
|
||||
* PhpParser\Error exception) of the first parser are used.
|
||||
* parsers runs without throwing, it's output is returned. Otherwise the exception that the
|
||||
* first parser generated is thrown.
|
||||
*
|
||||
* @param Parser[] $parsers
|
||||
*/
|
||||
public function __construct(array $parsers) {
|
||||
$this->parsers = $parsers;
|
||||
$this->errors = [];
|
||||
}
|
||||
|
||||
public function parse($code) {
|
||||
list($firstStmts, $firstErrors, $firstError) = $this->tryParse($this->parsers[0], $code);
|
||||
if ($firstErrors === []) {
|
||||
$this->errors = [];
|
||||
public function parse($code, ErrorHandler $errorHandler = null) {
|
||||
if (null === $errorHandler) {
|
||||
$errorHandler = new ErrorHandler\Throwing;
|
||||
}
|
||||
|
||||
list($firstStmts, $firstError) = $this->tryParse($this->parsers[0], $errorHandler, $code);
|
||||
if ($firstError === null) {
|
||||
return $firstStmts;
|
||||
}
|
||||
|
||||
for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) {
|
||||
list($stmts, $errors) = $this->tryParse($this->parsers[$i], $code);
|
||||
if ($errors === []) {
|
||||
$this->errors = [];
|
||||
list($stmts, $error) = $this->tryParse($this->parsers[$i], $errorHandler, $code);
|
||||
if ($error === null) {
|
||||
return $stmts;
|
||||
}
|
||||
}
|
||||
|
||||
$this->errors = $firstErrors;
|
||||
if ($firstError) {
|
||||
throw $firstError;
|
||||
}
|
||||
return $firstStmts;
|
||||
throw $firstError;
|
||||
}
|
||||
|
||||
public function getErrors() {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
private function tryParse(Parser $parser, $code) {
|
||||
private function tryParse(Parser $parser, ErrorHandler $errorHandler, $code) {
|
||||
$stmts = null;
|
||||
$error = null;
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
$stmts = $parser->parse($code, $errorHandler);
|
||||
} catch (Error $error) {}
|
||||
$errors = $parser->getErrors();
|
||||
return [$stmts, $errors, $error];
|
||||
return [$stmts, $error];
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,16 @@ namespace PhpParser;
|
||||
* turn is based on work by Masato Bito.
|
||||
*/
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassConst;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PhpParser\Node\Stmt\Interface_;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PhpParser\Node\Stmt\TryCatch;
|
||||
use PhpParser\Node\Stmt\UseUse;
|
||||
|
||||
abstract class ParserAbstract implements Parser
|
||||
{
|
||||
@ -86,50 +96,54 @@ abstract class ParserAbstract implements Parser
|
||||
protected $semStack;
|
||||
/** @var array[] Start attribute stack */
|
||||
protected $startAttributeStack;
|
||||
/** @var array[] End attribute stack */
|
||||
protected $endAttributeStack;
|
||||
/** @var array End attributes of last *shifted* token */
|
||||
protected $endAttributes;
|
||||
/** @var array Start attributes of last *read* token */
|
||||
protected $lookaheadStartAttributes;
|
||||
|
||||
/** @var bool Whether to throw on first error */
|
||||
protected $throwOnError;
|
||||
/** @var ErrorHandler Error handler */
|
||||
protected $errorHandler;
|
||||
/** @var Error[] Errors collected during last parse */
|
||||
protected $errors;
|
||||
/** @var int Error state, used to avoid error floods */
|
||||
protected $errorState;
|
||||
|
||||
/**
|
||||
* Creates a parser instance.
|
||||
*
|
||||
* @param Lexer $lexer A lexer
|
||||
* @param array $options Options array. The boolean 'throwOnError' option determines whether an exception should be
|
||||
* thrown on first error, or if the parser should try to continue parsing the remaining code
|
||||
* and build a partial AST.
|
||||
* @param array $options Options array. Currently no options are supported.
|
||||
*/
|
||||
public function __construct(Lexer $lexer, array $options = array()) {
|
||||
$this->lexer = $lexer;
|
||||
$this->errors = array();
|
||||
$this->throwOnError = isset($options['throwOnError']) ? $options['throwOnError'] : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of errors that occurred during the last parse.
|
||||
*
|
||||
* This method may only return multiple errors if the 'throwOnError' option is disabled.
|
||||
*
|
||||
* @return Error[]
|
||||
*/
|
||||
public function getErrors() {
|
||||
return $this->errors;
|
||||
if (isset($options['throwOnError'])) {
|
||||
throw new \LogicException(
|
||||
'"throwOnError" is no longer supported, use "errorHandler" instead');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses PHP code into a node tree.
|
||||
*
|
||||
* If a non-throwing error handler is used, the parser will continue parsing after an error
|
||||
* occurred and attempt to build a partial AST.
|
||||
*
|
||||
* @param string $code The source code to parse
|
||||
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
|
||||
* to ErrorHandler\Throwing.
|
||||
*
|
||||
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
|
||||
* unable to recover from an error).
|
||||
*/
|
||||
public function parse($code) {
|
||||
$this->lexer->startLexing($code);
|
||||
$this->errors = array();
|
||||
public function parse($code, ErrorHandler $errorHandler = null) {
|
||||
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
|
||||
|
||||
// Initialize the lexer
|
||||
$this->lexer->startLexing($code, $this->errorHandler);
|
||||
|
||||
// We start off with no lookahead-token
|
||||
$symbol = self::SYMBOL_NONE;
|
||||
@ -141,9 +155,9 @@ abstract class ParserAbstract implements Parser
|
||||
$endAttributes = '*POISON';
|
||||
$this->endAttributes = $endAttributes;
|
||||
|
||||
// In order to figure out the attributes for the starting token, we have to keep
|
||||
// them in a stack
|
||||
// Keep stack of start and end attributes
|
||||
$this->startAttributeStack = array();
|
||||
$this->endAttributeStack = array($endAttributes);
|
||||
|
||||
// Start off in the initial state and keep a stack of previous states
|
||||
$state = 0;
|
||||
@ -155,7 +169,7 @@ abstract class ParserAbstract implements Parser
|
||||
// Current position in the stack(s)
|
||||
$this->stackPos = 0;
|
||||
|
||||
$errorState = 0;
|
||||
$this->errorState = 0;
|
||||
|
||||
for (;;) {
|
||||
//$this->traceNewState($state, $symbol);
|
||||
@ -185,6 +199,8 @@ 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[$this->stackPos+1] = $startAttributes;
|
||||
$this->endAttributeStack[$this->stackPos+1] = $endAttributes;
|
||||
$this->lookaheadStartAttributes = $startAttributes;
|
||||
|
||||
//$this->traceRead($symbol);
|
||||
}
|
||||
@ -210,11 +226,12 @@ abstract class ParserAbstract implements Parser
|
||||
$stateStack[$this->stackPos] = $state = $action;
|
||||
$this->semStack[$this->stackPos] = $tokenValue;
|
||||
$this->startAttributeStack[$this->stackPos] = $startAttributes;
|
||||
$this->endAttributeStack[$this->stackPos] = $endAttributes;
|
||||
$this->endAttributes = $endAttributes;
|
||||
$symbol = self::SYMBOL_NONE;
|
||||
|
||||
if ($errorState) {
|
||||
--$errorState;
|
||||
if ($this->errorState) {
|
||||
--$this->errorState;
|
||||
}
|
||||
|
||||
if ($action < $this->YYNLSTATES) {
|
||||
@ -247,16 +264,13 @@ abstract class ParserAbstract implements Parser
|
||||
$e->setStartLine($startAttributes['startLine']);
|
||||
}
|
||||
|
||||
$this->errors[] = $e;
|
||||
if ($this->throwOnError) {
|
||||
throw $e;
|
||||
} else {
|
||||
// Currently can't recover from "special" errors
|
||||
return null;
|
||||
}
|
||||
$this->emitError($e);
|
||||
// Can't recover from this type of error
|
||||
return null;
|
||||
}
|
||||
|
||||
/* Goto - shift nonterminal */
|
||||
$lastEndAttributes = $this->endAttributeStack[$this->stackPos];
|
||||
$this->stackPos -= $this->ruleToLength[$rule];
|
||||
$nonTerminal = $this->ruleToNonTerminal[$rule];
|
||||
$idx = $this->gotoBase[$nonTerminal] + $stateStack[$this->stackPos];
|
||||
@ -269,20 +283,17 @@ abstract class ParserAbstract implements Parser
|
||||
++$this->stackPos;
|
||||
$stateStack[$this->stackPos] = $state;
|
||||
$this->semStack[$this->stackPos] = $this->semValue;
|
||||
$this->endAttributeStack[$this->stackPos] = $lastEndAttributes;
|
||||
} else {
|
||||
/* error */
|
||||
switch ($errorState) {
|
||||
switch ($this->errorState) {
|
||||
case 0:
|
||||
$msg = $this->getErrorMessage($symbol, $state);
|
||||
$error = new Error($msg, $startAttributes + $endAttributes);
|
||||
$this->errors[] = $error;
|
||||
if ($this->throwOnError) {
|
||||
throw $error;
|
||||
}
|
||||
$this->emitError(new Error($msg, $startAttributes + $endAttributes));
|
||||
// Break missing intentionally
|
||||
case 1:
|
||||
case 2:
|
||||
$errorState = 3;
|
||||
$this->errorState = 3;
|
||||
|
||||
// Pop until error-expecting state uncovered
|
||||
while (!(
|
||||
@ -301,7 +312,9 @@ abstract class ParserAbstract implements Parser
|
||||
}
|
||||
|
||||
//$this->traceShift($this->errorSymbol);
|
||||
$stateStack[++$this->stackPos] = $state = $action;
|
||||
++$this->stackPos;
|
||||
$stateStack[$this->stackPos] = $state = $action;
|
||||
$this->endAttributes = $this->endAttributeStack[$this->stackPos];
|
||||
break;
|
||||
|
||||
case 3:
|
||||
@ -328,6 +341,10 @@ abstract class ParserAbstract implements Parser
|
||||
throw new \RuntimeException('Reached end of parser loop');
|
||||
}
|
||||
|
||||
protected function emitError(Error $error) {
|
||||
$this->errorHandler->handleError($error);
|
||||
}
|
||||
|
||||
protected function getErrorMessage($symbol, $state) {
|
||||
$expectedString = '';
|
||||
if ($expected = $this->getExpectedTokens($state)) {
|
||||
@ -348,7 +365,10 @@ abstract class ParserAbstract implements Parser
|
||||
&& ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
|
||||
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
|
||||
) {
|
||||
if ($this->action[$idx] != $this->unexpectedTokenRule) {
|
||||
if ($this->action[$idx] != $this->unexpectedTokenRule
|
||||
&& $this->action[$idx] != $this->defaultAction
|
||||
&& $symbol != $this->errorSymbol
|
||||
) {
|
||||
if (count($expected) == 4) {
|
||||
/* Too many expected tokens */
|
||||
return array();
|
||||
@ -408,6 +428,7 @@ abstract class ParserAbstract implements Parser
|
||||
* @return Node[]
|
||||
*/
|
||||
protected function handleNamespaces(array $stmts) {
|
||||
$hasErrored = false;
|
||||
$style = $this->getNamespacingStyle($stmts);
|
||||
if (null === $style) {
|
||||
// not namespaced, nothing to do
|
||||
@ -418,8 +439,11 @@ abstract class ParserAbstract implements Parser
|
||||
foreach ($stmts as $stmt) {
|
||||
if ($stmt instanceof Node\Stmt\Namespace_) {
|
||||
$afterFirstNamespace = true;
|
||||
} elseif (!$stmt instanceof Node\Stmt\HaltCompiler && $afterFirstNamespace) {
|
||||
throw new Error('No code may exist outside of namespace {}', $stmt->getLine());
|
||||
} elseif (!$stmt instanceof Node\Stmt\HaltCompiler
|
||||
&& $afterFirstNamespace && !$hasErrored) {
|
||||
$this->emitError(new Error(
|
||||
'No code may exist outside of namespace {}', $stmt->getAttributes()));
|
||||
$hasErrored = true; // Avoid one error for every statement
|
||||
}
|
||||
}
|
||||
return $stmts;
|
||||
@ -429,9 +453,15 @@ abstract class ParserAbstract implements Parser
|
||||
$targetStmts =& $resultStmts;
|
||||
foreach ($stmts as $stmt) {
|
||||
if ($stmt instanceof Node\Stmt\Namespace_) {
|
||||
$stmt->stmts = array();
|
||||
$targetStmts =& $stmt->stmts;
|
||||
$resultStmts[] = $stmt;
|
||||
if ($stmt->stmts === null) {
|
||||
$stmt->stmts = array();
|
||||
$targetStmts =& $stmt->stmts;
|
||||
$resultStmts[] = $stmt;
|
||||
} else {
|
||||
// This handles the invalid case of mixed style namespaces
|
||||
$resultStmts[] = $stmt;
|
||||
$targetStmts =& $resultStmts;
|
||||
}
|
||||
} elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
|
||||
// __halt_compiler() is not moved into the namespace
|
||||
$resultStmts[] = $stmt;
|
||||
@ -446,30 +476,54 @@ abstract class ParserAbstract implements Parser
|
||||
private function getNamespacingStyle(array $stmts) {
|
||||
$style = null;
|
||||
$hasNotAllowedStmts = false;
|
||||
foreach ($stmts as $stmt) {
|
||||
foreach ($stmts as $i => $stmt) {
|
||||
if ($stmt instanceof Node\Stmt\Namespace_) {
|
||||
$currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace';
|
||||
if (null === $style) {
|
||||
$style = $currentStyle;
|
||||
if ($hasNotAllowedStmts) {
|
||||
throw new Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine());
|
||||
$this->emitError(new Error(
|
||||
'Namespace declaration statement has to be the very first statement in the script',
|
||||
$stmt->getLine() // Avoid marking the entire namespace as an error
|
||||
));
|
||||
}
|
||||
} elseif ($style !== $currentStyle) {
|
||||
throw new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine());
|
||||
$this->emitError(new Error(
|
||||
'Cannot mix bracketed namespace declarations with unbracketed namespace declarations',
|
||||
$stmt->getLine() // Avoid marking the entire namespace as an error
|
||||
));
|
||||
// Treat like semicolon style for namespace normalization
|
||||
return 'semicolon';
|
||||
}
|
||||
} elseif (!$stmt instanceof Node\Stmt\Declare_ && !$stmt instanceof Node\Stmt\HaltCompiler) {
|
||||
$hasNotAllowedStmts = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* declare(), __halt_compiler() and nops can be used before a namespace declaration */
|
||||
if ($stmt instanceof Node\Stmt\Declare_
|
||||
|| $stmt instanceof Node\Stmt\HaltCompiler
|
||||
|| $stmt instanceof Node\Stmt\Nop) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* There may be a hashbang line at the very start of the file */
|
||||
if ($i == 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Everything else if forbidden before namespace declarations */
|
||||
$hasNotAllowedStmts = true;
|
||||
}
|
||||
return $style;
|
||||
}
|
||||
|
||||
protected function handleScalarTypes(Name $name) {
|
||||
protected function handleBuiltinTypes(Name $name) {
|
||||
$scalarTypes = [
|
||||
'bool' => true,
|
||||
'int' => true,
|
||||
'float' => true,
|
||||
'string' => true,
|
||||
'bool' => true,
|
||||
'int' => true,
|
||||
'float' => true,
|
||||
'string' => true,
|
||||
'iterable' => true,
|
||||
'void' => true,
|
||||
];
|
||||
|
||||
if (!$name->isUnqualified()) {
|
||||
@ -479,4 +533,177 @@ abstract class ParserAbstract implements Parser
|
||||
$lowerName = strtolower($name->toString());
|
||||
return isset($scalarTypes[$lowerName]) ? $lowerName : $name;
|
||||
}
|
||||
|
||||
protected static $specialNames = array(
|
||||
'self' => true,
|
||||
'parent' => true,
|
||||
'static' => true,
|
||||
);
|
||||
|
||||
protected function getAttributesAt($pos) {
|
||||
return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos];
|
||||
}
|
||||
|
||||
protected function parseLNumber($str, $attributes, $allowInvalidOctal = false) {
|
||||
try {
|
||||
return LNumber::fromString($str, $attributes, $allowInvalidOctal);
|
||||
} catch (Error $error) {
|
||||
$this->emitError($error);
|
||||
// Use dummy value
|
||||
return new LNumber(0, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkModifier($a, $b, $modifierPos) {
|
||||
// Jumping through some hoops here because verifyModifier() is also used elsewhere
|
||||
try {
|
||||
Class_::verifyModifier($a, $b);
|
||||
} catch (Error $error) {
|
||||
$error->setAttributes($this->getAttributesAt($modifierPos));
|
||||
$this->emitError($error);
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkParam(Param $node) {
|
||||
if ($node->variadic && null !== $node->default) {
|
||||
$this->emitError(new Error(
|
||||
'Variadic parameter cannot have a default value',
|
||||
$node->default->getAttributes()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkTryCatch(TryCatch $node) {
|
||||
if (empty($node->catches) && null === $node->finally) {
|
||||
$this->emitError(new Error(
|
||||
'Cannot use try without catch or finally', $node->getAttributes()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkNamespace(Namespace_ $node) {
|
||||
if (isset(self::$specialNames[strtolower($node->name)])) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as namespace name', $node->name),
|
||||
$node->name->getAttributes()
|
||||
));
|
||||
}
|
||||
|
||||
if (null !== $node->stmts) {
|
||||
foreach ($node->stmts as $stmt) {
|
||||
if ($stmt instanceof Namespace_) {
|
||||
$this->emitError(new Error(
|
||||
'Namespace declarations cannot be nested', $stmt->getAttributes()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkClass(Class_ $node, $namePos) {
|
||||
if (null !== $node->name && isset(self::$specialNames[strtolower($node->name)])) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
|
||||
$this->getAttributesAt($namePos)
|
||||
));
|
||||
}
|
||||
|
||||
if (isset(self::$specialNames[strtolower($node->extends)])) {
|
||||
$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) {
|
||||
if (isset(self::$specialNames[strtolower($interface)])) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
|
||||
$interface->getAttributes()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkInterface(Interface_ $node, $namePos) {
|
||||
if (null !== $node->name && isset(self::$specialNames[strtolower($node->name)])) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
|
||||
$this->getAttributesAt($namePos)
|
||||
));
|
||||
}
|
||||
|
||||
foreach ($node->extends as $interface) {
|
||||
if (isset(self::$specialNames[strtolower($interface)])) {
|
||||
$this->emitError(new Error(
|
||||
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
|
||||
$interface->getAttributes()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkClassMethod(ClassMethod $node, $modifierPos) {
|
||||
if ($node->flags & Class_::MODIFIER_STATIC) {
|
||||
switch (strtolower($node->name)) {
|
||||
case '__construct':
|
||||
$this->emitError(new Error(
|
||||
sprintf('Constructor %s() cannot be static', $node->name),
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
break;
|
||||
case '__destruct':
|
||||
$this->emitError(new Error(
|
||||
sprintf('Destructor %s() cannot be static', $node->name),
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
break;
|
||||
case '__clone':
|
||||
$this->emitError(new Error(
|
||||
sprintf('Clone method %s() cannot be static', $node->name),
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkClassConst(ClassConst $node, $modifierPos) {
|
||||
if ($node->flags & Class_::MODIFIER_STATIC) {
|
||||
$this->emitError(new Error(
|
||||
"Cannot use 'static' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
if ($node->flags & Class_::MODIFIER_ABSTRACT) {
|
||||
$this->emitError(new Error(
|
||||
"Cannot use 'abstract' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
if ($node->flags & Class_::MODIFIER_FINAL) {
|
||||
$this->emitError(new Error(
|
||||
"Cannot use 'final' as constant modifier",
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkProperty(Property $node, $modifierPos) {
|
||||
if ($node->flags & Class_::MODIFIER_ABSTRACT) {
|
||||
$this->emitError(new Error('Properties cannot be declared abstract',
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
|
||||
if ($node->flags & Class_::MODIFIER_FINAL) {
|
||||
$this->emitError(new Error('Properties cannot be declared final',
|
||||
$this->getAttributesAt($modifierPos)));
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkUseUse(UseUse $node, $namePos) {
|
||||
if ('self' == strtolower($node->alias) || 'parent' == strtolower($node->alias)) {
|
||||
$this->emitError(new Error(
|
||||
sprintf(
|
||||
'Cannot use %s as %s because \'%2$s\' is a special class name',
|
||||
$node->name, $node->alias
|
||||
),
|
||||
$this->getAttributesAt($namePos)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -75,10 +75,25 @@ abstract class PrettyPrinterAbstract
|
||||
);
|
||||
|
||||
protected $noIndentToken;
|
||||
protected $docStringEndToken;
|
||||
protected $canUseSemicolonNamespaces;
|
||||
protected $options;
|
||||
|
||||
public function __construct() {
|
||||
/**
|
||||
* Creates a pretty printer instance using the given options.
|
||||
*
|
||||
* Supported options:
|
||||
* * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array
|
||||
* syntax, if the node does not specify a format.
|
||||
*
|
||||
* @param array $options Dictionary of formatting options
|
||||
*/
|
||||
public function __construct(array $options = []) {
|
||||
$this->noIndentToken = '_NO_INDENT_' . mt_rand();
|
||||
$this->docStringEndToken = '_DOC_STRING_END_' . mt_rand();
|
||||
|
||||
$defaultOptions = ['shortArraySyntax' => false];
|
||||
$this->options = $options + $defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,7 +106,7 @@ abstract class PrettyPrinterAbstract
|
||||
public function prettyPrint(array $stmts) {
|
||||
$this->preprocessNodes($stmts);
|
||||
|
||||
return ltrim(str_replace("\n" . $this->noIndentToken, "\n", $this->pStmts($stmts, false)));
|
||||
return ltrim($this->handleMagicTokens($this->pStmts($stmts, false)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +117,7 @@ abstract class PrettyPrinterAbstract
|
||||
* @return string Pretty printed node
|
||||
*/
|
||||
public function prettyPrintExpr(Expr $node) {
|
||||
return str_replace("\n" . $this->noIndentToken, "\n", $this->p($node));
|
||||
return $this->handleMagicTokens($this->p($node));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,13 +128,17 @@ abstract class PrettyPrinterAbstract
|
||||
* @return string Pretty printed statements
|
||||
*/
|
||||
public function prettyPrintFile(array $stmts) {
|
||||
$p = rtrim($this->prettyPrint($stmts));
|
||||
if (!$stmts) {
|
||||
return "<?php\n\n";
|
||||
}
|
||||
|
||||
$p = preg_replace('/^\?>\n?/', '', $p, -1, $count);
|
||||
$p = preg_replace('/<\?php$/', '', $p);
|
||||
$p = "<?php\n\n" . $this->prettyPrint($stmts);
|
||||
|
||||
if (!$count) {
|
||||
$p = "<?php\n\n" . $p;
|
||||
if ($stmts[0] instanceof Stmt\InlineHTML) {
|
||||
$p = preg_replace('/^<\?php\s+\?>\n?/', '', $p);
|
||||
}
|
||||
if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) {
|
||||
$p = preg_replace('/<\?php$/', '', rtrim($p));
|
||||
}
|
||||
|
||||
return $p;
|
||||
@ -140,6 +159,17 @@ abstract class PrettyPrinterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleMagicTokens($str) {
|
||||
// Drop no-indent tokens
|
||||
$str = str_replace($this->noIndentToken, '', $str);
|
||||
|
||||
// Replace doc-string-end tokens with nothing or a newline
|
||||
$str = str_replace($this->docStringEndToken . ";\n", ";\n", $str);
|
||||
$str = str_replace($this->docStringEndToken, "\n", $str);
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty prints an array of nodes (statements) and indents them optionally.
|
||||
*
|
||||
@ -151,10 +181,15 @@ abstract class PrettyPrinterAbstract
|
||||
protected function pStmts(array $nodes, $indent = true) {
|
||||
$result = '';
|
||||
foreach ($nodes as $node) {
|
||||
$result .= "\n"
|
||||
. $this->pComments($node->getAttribute('comments', array()))
|
||||
. $this->p($node)
|
||||
. ($node instanceof Expr ? ';' : '');
|
||||
$comments = $node->getAttribute('comments', array());
|
||||
if ($comments) {
|
||||
$result .= "\n" . $this->pComments($comments);
|
||||
if ($node instanceof Stmt\Nop) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$result .= "\n" . $this->p($node) . ($node instanceof Expr ? ';' : '');
|
||||
}
|
||||
|
||||
if ($indent) {
|
||||
@ -230,7 +265,11 @@ abstract class PrettyPrinterAbstract
|
||||
protected function pImplode(array $nodes, $glue = '') {
|
||||
$pNodes = array();
|
||||
foreach ($nodes as $node) {
|
||||
$pNodes[] = $this->p($node);
|
||||
if (null === $node) {
|
||||
$pNodes[] = '';
|
||||
} else {
|
||||
$pNodes[] = $this->p($node);
|
||||
}
|
||||
}
|
||||
|
||||
return implode($glue, $pNodes);
|
||||
@ -252,19 +291,26 @@ abstract class PrettyPrinterAbstract
|
||||
*
|
||||
* @param string $string Not to be indented string
|
||||
*
|
||||
* @return mixed String marked with $this->noIndentToken's.
|
||||
* @return string String marked with $this->noIndentToken's.
|
||||
*/
|
||||
protected function pNoIndent($string) {
|
||||
return str_replace("\n", "\n" . $this->noIndentToken, $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints reformatted text of the passed comments.
|
||||
*
|
||||
* @param Comment[] $comments List of comments
|
||||
*
|
||||
* @return string Reformatted text of comments
|
||||
*/
|
||||
protected function pComments(array $comments) {
|
||||
$result = '';
|
||||
$formattedComments = [];
|
||||
|
||||
foreach ($comments as $comment) {
|
||||
$result .= $comment->getReformattedText() . "\n";
|
||||
$formattedComments[] = $comment->getReformattedText();
|
||||
}
|
||||
|
||||
return $result;
|
||||
return implode("\n", $formattedComments);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
namespace PhpParser\Serializer;
|
||||
|
||||
use XMLWriter;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Serializer;
|
||||
use XMLWriter;
|
||||
|
||||
class XML implements Serializer
|
||||
{
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
namespace PhpParser\Unserializer;
|
||||
|
||||
use XMLReader;
|
||||
use DomainException;
|
||||
use PhpParser\Unserializer;
|
||||
use XMLReader;
|
||||
|
||||
class XML implements Unserializer
|
||||
{
|
||||
|
@ -2,9 +2,6 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Scalar;
|
||||
|
||||
/* The autoloader is already active at this point, so we only check effects here. */
|
||||
|
||||
class AutoloaderTest extends \PHPUnit_Framework_TestCase {
|
||||
|
@ -43,7 +43,7 @@ class ClassTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Class_('Test', array(
|
||||
'type' => Stmt\Class_::MODIFIER_ABSTRACT
|
||||
'flags' => Stmt\Class_::MODIFIER_ABSTRACT
|
||||
)),
|
||||
$node
|
||||
);
|
||||
@ -57,7 +57,7 @@ class ClassTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\Class_('Test', array(
|
||||
'type' => Stmt\Class_::MODIFIER_FINAL
|
||||
'flags' => Stmt\Class_::MODIFIER_FINAL
|
||||
)),
|
||||
$node
|
||||
);
|
||||
|
@ -4,9 +4,9 @@ namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Expr\Print_;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class FunctionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -76,6 +76,24 @@ class FunctionTest extends \PHPUnit_Framework_TestCase
|
||||
)), $node);
|
||||
}
|
||||
|
||||
public function testReturnType() {
|
||||
$node = $this->createFunctionBuilder('test')
|
||||
->setReturnType('void')
|
||||
->getNode();
|
||||
|
||||
$this->assertEquals(new Stmt\Function_('test', array(
|
||||
'returnType' => 'void'
|
||||
), array()), $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
* @expectedExceptionMessage void type cannot be nullable
|
||||
*/
|
||||
public function testInvalidNullableVoidType() {
|
||||
$this->createFunctionBuilder('test')->setReturnType('?void');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
* @expectedExceptionMessage Expected parameter node, got "Name"
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Scalar\DNumber;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Scalar\DNumber;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class InterfaceTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Print_;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Comment;
|
||||
|
||||
class MethodTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
@ -24,9 +24,9 @@ class MethodTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassMethod('test', array(
|
||||
'type' => Stmt\Class_::MODIFIER_PUBLIC
|
||||
| Stmt\Class_::MODIFIER_ABSTRACT
|
||||
| Stmt\Class_::MODIFIER_STATIC,
|
||||
'flags' => Stmt\Class_::MODIFIER_PUBLIC
|
||||
| Stmt\Class_::MODIFIER_ABSTRACT
|
||||
| Stmt\Class_::MODIFIER_STATIC,
|
||||
'stmts' => null,
|
||||
)),
|
||||
$node
|
||||
@ -40,8 +40,8 @@ class MethodTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->assertEquals(
|
||||
new Stmt\ClassMethod('test', array(
|
||||
'type' => Stmt\Class_::MODIFIER_PROTECTED
|
||||
| Stmt\Class_::MODIFIER_FINAL
|
||||
'flags' => Stmt\Class_::MODIFIER_PROTECTED
|
||||
| Stmt\Class_::MODIFIER_FINAL
|
||||
)),
|
||||
$node
|
||||
);
|
||||
@ -120,6 +120,15 @@ class MethodTest extends \PHPUnit_Framework_TestCase
|
||||
)), $node);
|
||||
}
|
||||
|
||||
public function testReturnType() {
|
||||
$node = $this->createMethodBuilder('test')
|
||||
->setReturnType('bool')
|
||||
->getNode();
|
||||
$this->assertEquals(new Stmt\ClassMethod('test', array(
|
||||
'returnType' => 'bool'
|
||||
), array()), $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
* @expectedExceptionMessage Cannot add statements to an abstract method
|
||||
|
@ -78,36 +78,70 @@ class ParamTest extends \PHPUnit_Framework_TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testTypeHints() {
|
||||
/**
|
||||
* @dataProvider provideTestTypeHints
|
||||
*/
|
||||
public function testTypeHints($typeHint, $expectedType) {
|
||||
$node = $this->createParamBuilder('test')
|
||||
->setTypeHint('array')
|
||||
->setTypeHint($typeHint)
|
||||
->getNode()
|
||||
;
|
||||
$type = $node->type;
|
||||
|
||||
$this->assertEquals(
|
||||
new Node\Param('test', null, 'array'),
|
||||
$node
|
||||
/* Manually implement comparison to avoid __toString stupidity */
|
||||
if ($expectedType instanceof Node\NullableType) {
|
||||
$this->assertInstanceOf(get_class($expectedType), $type);
|
||||
$expectedType = $expectedType->type;
|
||||
$type = $type->type;
|
||||
}
|
||||
|
||||
if ($expectedType instanceof Node\Name) {
|
||||
$this->assertInstanceOf(get_class($expectedType), $type);
|
||||
$this->assertEquals($expectedType, $type);
|
||||
} else {
|
||||
$this->assertSame($expectedType, $type);
|
||||
}
|
||||
}
|
||||
|
||||
public function provideTestTypeHints() {
|
||||
return array(
|
||||
array('array', 'array'),
|
||||
array('callable', 'callable'),
|
||||
array('bool', 'bool'),
|
||||
array('int', 'int'),
|
||||
array('float', 'float'),
|
||||
array('string', 'string'),
|
||||
array('iterable', 'iterable'),
|
||||
array('Array', 'array'),
|
||||
array('CALLABLE', 'callable'),
|
||||
array('Some\Class', new Node\Name('Some\Class')),
|
||||
array('\Foo', new Node\Name\FullyQualified('Foo')),
|
||||
array('self', new Node\Name('self')),
|
||||
array('?array', new Node\NullableType('array')),
|
||||
array('?Some\Class', new Node\NullableType(new Node\Name('Some\Class'))),
|
||||
array(new Node\Name('Some\Class'), new Node\Name('Some\Class')),
|
||||
array(new Node\NullableType('int'), new Node\NullableType('int')),
|
||||
array(
|
||||
new Node\NullableType(new Node\Name('Some\Class')),
|
||||
new Node\NullableType(new Node\Name('Some\Class'))
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$node = $this->createParamBuilder('test')
|
||||
->setTypeHint('callable')
|
||||
->getNode()
|
||||
;
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
* @expectedExceptionMessage Parameter type cannot be void
|
||||
*/
|
||||
public function testVoidTypeError() {
|
||||
$this->createParamBuilder('test')->setTypeHint('void');
|
||||
}
|
||||
|
||||
$this->assertEquals(
|
||||
new Node\Param('test', null, 'callable'),
|
||||
$node
|
||||
);
|
||||
|
||||
$node = $this->createParamBuilder('test')
|
||||
->setTypeHint('Some\Class')
|
||||
->getNode()
|
||||
;
|
||||
|
||||
$this->assertEquals(
|
||||
new Node\Param('test', null, new Node\Name('Some\Class')),
|
||||
$node
|
||||
);
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
* @expectedExceptionMessage Type must be a string, or an instance of Name or NullableType
|
||||
*/
|
||||
public function testInvalidTypeError() {
|
||||
$this->createParamBuilder('test')->setTypeHint(new \stdClass);
|
||||
}
|
||||
|
||||
public function testByRef() {
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class PropertyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
|
@ -3,8 +3,6 @@
|
||||
namespace PhpParser\Builder;
|
||||
|
||||
use PhpParser\Comment;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
|
||||
class TraitTest extends \PHPUnit_Framework_TestCase
|
||||
@ -27,7 +25,7 @@ class TraitTest extends \PHPUnit_Framework_TestCase
|
||||
->addStmt($prop)
|
||||
->getNode();
|
||||
$this->assertEquals(new Stmt\Trait_('TestTrait', array(
|
||||
$prop, $method1, $method2, $method3
|
||||
'stmts' => array($prop, $method1, $method2, $method3)
|
||||
), array(
|
||||
'comments' => array(
|
||||
new Comment\Doc('/** Nice trait */')
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
namespace PhpParser;
|
||||
|
||||
use PhpParser\Comment;
|
||||
|
||||
require_once __DIR__ . '/CodeTestAbstract.php';
|
||||
|
||||
class CodeParsingTest extends CodeTestAbstract
|
||||
@ -11,24 +9,28 @@ class CodeParsingTest extends CodeTestAbstract
|
||||
/**
|
||||
* @dataProvider provideTestParse
|
||||
*/
|
||||
public function testParse($name, $code, $expected, $mode) {
|
||||
public function testParse($name, $code, $expected, $modeLine) {
|
||||
if (null !== $modeLine) {
|
||||
$modes = array_fill_keys(explode(',', $modeLine), true);
|
||||
} else {
|
||||
$modes = [];
|
||||
}
|
||||
|
||||
|
||||
$lexer = new Lexer\Emulative(array('usedAttributes' => array(
|
||||
'startLine', 'endLine', 'startFilePos', 'endFilePos'
|
||||
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
|
||||
)));
|
||||
$parser5 = new Parser\Php5($lexer, array(
|
||||
'throwOnError' => false,
|
||||
));
|
||||
$parser7 = new Parser\Php7($lexer, array(
|
||||
'throwOnError' => false,
|
||||
));
|
||||
$parser5 = new Parser\Php5($lexer);
|
||||
$parser7 = new Parser\Php7($lexer);
|
||||
|
||||
$output5 = $this->getParseOutput($parser5, $code);
|
||||
$output7 = $this->getParseOutput($parser7, $code);
|
||||
$dumpPositions = isset($modes['positions']);
|
||||
$output5 = $this->getParseOutput($parser5, $code, $dumpPositions);
|
||||
$output7 = $this->getParseOutput($parser7, $code, $dumpPositions);
|
||||
|
||||
if ($mode === 'php5') {
|
||||
if (isset($modes['php5'])) {
|
||||
$this->assertSame($expected, $output5, $name);
|
||||
$this->assertNotSame($expected, $output7, $name);
|
||||
} else if ($mode === 'php7') {
|
||||
} else if (isset($modes['php7'])) {
|
||||
$this->assertNotSame($expected, $output5, $name);
|
||||
$this->assertSame($expected, $output7, $name);
|
||||
} else {
|
||||
@ -37,18 +39,18 @@ class CodeParsingTest extends CodeTestAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function getParseOutput(Parser $parser, $code) {
|
||||
$stmts = $parser->parse($code);
|
||||
$errors = $parser->getErrors();
|
||||
private function getParseOutput(Parser $parser, $code, $dumpPositions) {
|
||||
$errors = new ErrorHandler\Collecting;
|
||||
$stmts = $parser->parse($code, $errors);
|
||||
|
||||
$output = '';
|
||||
foreach ($errors as $error) {
|
||||
foreach ($errors->getErrors() as $error) {
|
||||
$output .= $this->formatErrorMessage($error, $code) . "\n";
|
||||
}
|
||||
|
||||
if (null !== $stmts) {
|
||||
$dumper = new NodeDumper;
|
||||
$output .= $dumper->dump($stmts);
|
||||
$dumper = new NodeDumper(['dumpComments' => true, 'dumpPositions' => $dumpPositions]);
|
||||
$output .= $dumper->dump($stmts, $code);
|
||||
}
|
||||
|
||||
return canonicalize($output);
|
||||
@ -60,8 +62,7 @@ class CodeParsingTest extends CodeTestAbstract
|
||||
|
||||
private function formatErrorMessage(Error $e, $code) {
|
||||
if ($e->hasColumnInfo()) {
|
||||
return $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
|
||||
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
|
||||
return $e->getMessageWithColumnInfo($code);
|
||||
} else {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
@ -5,14 +5,16 @@ namespace PhpParser;
|
||||
abstract class CodeTestAbstract extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function getTests($directory, $fileExtension) {
|
||||
$directory = realpath($directory);
|
||||
$it = new \RecursiveDirectoryIterator($directory);
|
||||
$it = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::LEAVES_ONLY);
|
||||
$it = new \RegexIterator($it, '(\.' . preg_quote($fileExtension) . '$)');
|
||||
|
||||
$tests = array();
|
||||
foreach ($it as $file) {
|
||||
$fileName = realpath($file->getPathname());
|
||||
$fileName = $file->getPathname();
|
||||
$fileContents = file_get_contents($fileName);
|
||||
$fileContents = canonicalize($fileContents);
|
||||
|
||||
// evaluate @@{expr}@@ expressions
|
||||
$fileContents = preg_replace_callback(
|
||||
@ -24,15 +26,18 @@ abstract class CodeTestAbstract extends \PHPUnit_Framework_TestCase
|
||||
);
|
||||
|
||||
// parse sections
|
||||
$parts = array_map('trim', explode('-----', $fileContents));
|
||||
$parts = preg_split("/\n-----(?:\n|$)/", $fileContents);
|
||||
|
||||
// first part is the name
|
||||
$name = array_shift($parts) . ' (' . $fileName . ')';
|
||||
$shortName = ltrim(str_replace($directory, '', $fileName), '/\\');
|
||||
|
||||
// multiple sections possible with always two forming a pair
|
||||
foreach (array_chunk($parts, 2) as $chunk) {
|
||||
list($expected, $mode) = $this->extractMode(canonicalize($chunk[1]));
|
||||
$tests[] = array($name, $chunk[0], $expected, $mode);
|
||||
$chunks = array_chunk($parts, 2);
|
||||
foreach ($chunks as $i => $chunk) {
|
||||
$dataSetName = $shortName . (count($chunks) > 1 ? '#' . $i : '');
|
||||
list($expected, $mode) = $this->extractMode($chunk[1]);
|
||||
$tests[$dataSetName] = array($name, $chunk[0], $expected, $mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,18 +5,12 @@ namespace PhpParser;
|
||||
class CommentTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGetSet() {
|
||||
$comment = new Comment('/* Some comment */', 1);
|
||||
$comment = new Comment('/* Some comment */', 1, 10);
|
||||
|
||||
$this->assertSame('/* Some comment */', $comment->getText());
|
||||
$this->assertSame('/* Some comment */', (string) $comment);
|
||||
$this->assertSame(1, $comment->getLine());
|
||||
|
||||
$comment->setText('/* Some other comment */');
|
||||
$comment->setLine(10);
|
||||
|
||||
$this->assertSame('/* Some other comment */', $comment->getText());
|
||||
$this->assertSame('/* Some other comment */', (string) $comment);
|
||||
$this->assertSame(10, $comment->getLine());
|
||||
$this->assertSame(10, $comment->getFilePos());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,6 +52,14 @@ class CommentTest extends \PHPUnit_Framework_TestCase
|
||||
'/* Some text.
|
||||
More text.
|
||||
Even more text. */'
|
||||
),
|
||||
array(
|
||||
'/* Some text.
|
||||
More text.
|
||||
Indented text. */',
|
||||
'/* Some text.
|
||||
More text.
|
||||
Indented text. */',
|
||||
),
|
||||
// invalid comment -> no reformatting
|
||||
array(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user