Compare commits

..

55 Commits

Author SHA1 Message Date
a8ffc6fcfc Release PHP-Parser 1.0.2 2014-11-04 23:12:46 +01:00
7fbdb79a08 Fix whitespace when printing trait alias modifiers 2014-11-03 16:16:15 +01:00
6fad8ff32a Make NameResolver resolve trait alias and precedence names 2014-11-03 16:06:43 +01:00
b9a60372f2 Release version 1.0.1 2014-10-14 21:40:07 +02:00
eb20e32914 Update changelog 2014-10-14 21:37:04 +02:00
f41a4c9acb Fix wrong function name in docs 2014-10-14 21:31:29 +02:00
767f23c3a9 Update lexer docs
Remove some very questionable examples for changing startLexing()
to accept a file name.

Add token offset lexer implementation and usage example.
2014-10-11 21:47:11 +02:00
f8678ad7e9 Merge pull request #138 from jbrooksuk/patch-1
Fixed a spelling mistake
2014-10-01 12:18:18 +02:00
63c18b29e4 Fixed a spelling mistake 2014-10-01 09:18:01 +01:00
99df8b86ae Support HHVM T_ONUMBER token 2014-09-30 20:55:58 +02:00
66fd29cb58 Use stricter assertions where possible 2014-09-30 20:38:09 +02:00
3d40e2217d Annotate some APIs as @internal 2014-09-30 20:26:06 +02:00
16dff7c2e6 Add ability to pass code directly to php-parse.php 2014-09-28 13:14:37 +02:00
88e2d42ba4 Fix var_dump truncation with xdebug in php-parse.php 2014-09-28 13:08:59 +02:00
69701430c1 Cover remaining constant scalar expressions 2014-09-28 13:05:23 +02:00
6dc24fa9f5 Fix coverage annotations 2014-09-28 12:49:12 +02:00
3e1665bbbd Disallow new without a class name
Fixes #137.
2014-09-28 12:41:35 +02:00
2b96ab8edc Release PHP-Parser 1.0.0 2014-09-12 14:48:23 +02:00
7503356e03 Fix typos 2014-09-12 14:44:32 +02:00
22ef0de7ef Add migration guide for 0.9 -> 1.0 2014-09-12 14:40:17 +02:00
5d7fec2027 Mention that composer autoload.php will work as well 2014-09-12 13:51:17 +02:00
6423864160 Add Autoloader tests 2014-09-12 13:45:34 +02:00
fd064dac6c Very that node type is valid in XML unserializer 2014-09-12 00:39:59 +02:00
d9bd550414 Fix XML unserializer 2014-09-12 00:37:21 +02:00
94eca2ce44 Remove deprecated Template and TemplateLoader 2014-09-12 00:25:30 +02:00
e65fd664d1 Small docs touchups and typo fixes 2014-09-12 00:21:27 +02:00
7a3789f1a9 Remove "experimental" message 2014-08-31 17:05:11 +02:00
b31f36bf89 Release PHP-Parser 1.0.0 Beta 2 2014-08-31 16:54:25 +02:00
3d583ab19c Update changelog 2014-08-31 16:39:53 +02:00
616be1d0fc Use emulative lexer for < 5.6RC1
Earlier releases come with incorrect tokenizer data for T_POW and
T_POW_EQUAL.
2014-08-31 16:33:41 +02:00
7c81229261 Disable xdebug var_dump in php-parse script 2014-08-31 16:21:21 +02:00
452e1c0180 Add constant dereferencing, a list-minute 5.6 change 2014-08-31 16:14:36 +02:00
31bc022d0d Merge pull request #124 from sasezaki/patch-1
Add PHP 5.6 to .travis.yml
2014-08-30 12:12:32 +02:00
ce1078bc00 add PHP 5.6 to .travis.yml 2014-08-29 00:44:47 +09:00
6d0589d14f Ensure that special class names are unqualified
Replicates the PHP error message
2014-08-11 22:04:52 +02:00
ef121e690c Preserve case of "static" class name 2014-08-11 21:44:50 +02:00
c0340053d1 Fix case sensitivity for special class names 2014-08-11 21:41:54 +02:00
39f323b5ad Fix classname of Class_ in docblock of BuilderFactory 2014-08-11 20:53:18 +02:00
22c76a3da4 Update changelog 2014-07-23 21:35:24 +02:00
a332352dbc Merge branch '0.9' 2014-07-23 21:24:21 +02:00
ef70767475 Release version 0.9.5 2014-07-23 20:24:17 +02:00
1cecf9efc5 Revert change to NodeTraverserInterface
Only add this method in 1.0, to avoid any BC breaks.
2014-07-23 20:21:42 +02:00
1f143393e5 Rewrite namespace handling code
Add a check for disallowed statements between braced namespaces
while at it.
2014-04-21 15:16:00 +02:00
6d1f77132c Move Stmt\Namespace_::postprocess() to parser 2014-04-21 12:30:55 +02:00
2e195d7cb2 Make sure that pretty printer preserves whitespace after <?php 2014-04-21 11:15:33 +02:00
947a897238 Make names in the parser more descriptive
And improve the code a tad bit in general.

I left YY2TBLSTATES and YYNLSTATES around, because I don't fully
understand their role in the action double indexing.
2014-04-20 23:05:51 +02:00
1edbc89749 Use normal properties instead of static ones 2014-04-20 00:34:31 +02:00
0faa844a75 Separate parser code from generated data 2014-04-20 00:19:35 +02:00
3db3ad7d1e Add experimental php-parse script
Script supporting dumping, pretty printing, serializing and resolving
names. Intended to help exploring and debugging the node tree.
2014-04-19 23:14:28 +02:00
4743e9b0b8 Update constant scalar expression support 2014-04-19 22:53:13 +02:00
8499696021 Add note about prettyPrintFile() to docs 2014-04-19 22:30:20 +02:00
e4e56511b9 Merge branch '0.9' 2014-04-19 22:26:35 +02:00
5960ecfc10 Disable xdebug.scream while lexing 2014-04-19 22:26:05 +02:00
c341ab2ecf Make autoloader for new names PSR-0 compliant 2014-04-02 09:44:45 +02:00
c62ffedfca Require file only if file exists
Allows usage of class_exists() on undefined classes.
2014-03-27 15:40:08 +01:00
103 changed files with 2896 additions and 2792 deletions

View File

@ -4,6 +4,7 @@ php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
matrix:

View File

@ -1,8 +1,58 @@
Version 1.0.0-dev
Version 1.0.3-dev
-----------------
Nothing yet.
Version 1.0.2 (04.11.2014)
--------------------------
* 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 (14.10.2014)
--------------------------
* 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 (12.09.2014)
--------------------------
* [BC] Removed deprecated `Template` and `TemplateLoader` classes.
* Fixed XML unserializer to properly work with new namespaced node names.
Version 1.0.0-beta2 (31.08.2014)
--------------------------------
* [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 (27.03.2014)
--------------------------------
@ -45,7 +95,7 @@ Version 1.0.0-beta1 (27.03.2014)
* 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-dev**:
**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.
@ -62,4 +112,4 @@ Version 1.0.0-beta1 (27.03.2014)
**This changelog only includes changes from the 1.0 series. For older changes see the [0.9 series changelog][1].**
[1]: https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md
[1]: https://github.com/nikic/PHP-Parser/blob/0.9/CHANGELOG.md

View File

@ -1,23 +1,18 @@
PHP Parser
==========
This is a PHP 5.6 (and older) parser written in PHP. It's purpose is to simplify static code analysis and
This is a PHP 5.2 to PHP 5.6 parser written in PHP. It's purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 0.9.x**][doc_0_9] (stable; for running on PHP 5.2).
[**Documentation for version 1.0.x**][doc_master] (stable; for running on PHP >= 5.3).
[**Documentation for version 1.0.x**][doc_master] (beta; for running on PHP >= 5.3).
***Note: This project is experimental, so the API is subject to change.***
[Documentation for version 0.9.x][doc_0_9] (unsupported; for running on PHP 5.2).
In a Nutshell
-------------
Basically, the parser does nothing more than turn some PHP code into an abstract syntax tree. ("nothing
more" is kind of sarcastic here as PHP has a ... uhm, let's just say "not nice" ... grammar, which makes
parsing PHP very hard.)
For example, if you stick this code in the parser:
The parser turns PHP source code into an abstract syntax tree. For example, if you pass the following code into the
parser:
```php
<?php

121
UPGRADE-1.0.md Normal file
View File

@ -0,0 +1,121 @@
Upgrading from PHP-Parser 0.9 to 1.0
====================================
### PHP version requirements
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.
### Move to namespaced names
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.
Old (still works, but discouraged):
```php
$parser = new \PHPParser_Parser(new \PHPParser_Lexer_Emulative);
$prettyPrinter = new \PHPParser_PrettyPrinter_Default;
```
New:
```php
$parser = new \PhpParser\Parser(new PhpParser\Lexer\Emulative);
$prettyPrinter = new \PhpParser\PrettyPrinter\Standard;
```
Note that the `PHPParser` prefix was changed to `PhpParser`. While PHP class names are technically case-insensitive,
the autoloader will not be able to load `PHPParser\Parser` or other case variants.
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.)
### Changes to `Node::getType()`
The `Node::getType()` method continues to return names using underscores instead of namespace separators and also does
not contain the trailing underscore that may be present in the class name. As such its output will not change in many
cases.
However, some node classes have been moved to a different namespace or renamed, which will result in a different
`Node::getType()` output:
```
Expr_AssignBitwiseAnd => Expr_AssignOp_BitwiseAnd
Expr_AssignBitwiseOr => Expr_AssignOp_BitwiseOr
Expr_AssignBitwiseXor => Expr_AssignOp_BitwiseXor
Expr_AssignConcat => Expr_AssignOp_Concat
Expr_AssignDiv => Expr_AssignOp_Div
Expr_AssignMinus => Expr_AssignOp_Minus
Expr_AssignMod => Expr_AssignOp_Mod
Expr_AssignMul => Expr_AssignOp_Mul
Expr_AssignPlus => Expr_AssignOp_Plus
Expr_AssignShiftLeft => Expr_AssignOp_ShiftLeft
Expr_AssignShiftRight => Expr_AssignOp_ShiftRight
Expr_BitwiseAnd => Expr_BinaryOp_BitwiseAnd
Expr_BitwiseOr => Expr_BinaryOp_BitwiseOr
Expr_BitwiseXor => Expr_BinaryOp_BitwiseXor
Expr_BooleanAnd => Expr_BinaryOp_BooleanAnd
Expr_BooleanOr => Expr_BinaryOp_BooleanOr
Expr_Concat => Expr_BinaryOp_Concat
Expr_Div => Expr_BinaryOp_Div
Expr_Equal => Expr_BinaryOp_Equal
Expr_Greater => Expr_BinaryOp_Greater
Expr_GreaterOrEqual => Expr_BinaryOp_GreaterOrEqual
Expr_Identical => Expr_BinaryOp_Identical
Expr_LogicalAnd => Expr_BinaryOp_LogicalAnd
Expr_LogicalOr => Expr_BinaryOp_LogicalOr
Expr_LogicalXor => Expr_BinaryOp_LogicalXor
Expr_Minus => Expr_BinaryOp_Minus
Expr_Mod => Expr_BinaryOp_Mod
Expr_Mul => Expr_BinaryOp_Mul
Expr_NotEqual => Expr_BinaryOp_NotEqual
Expr_NotIdentical => Expr_BinaryOp_NotIdentical
Expr_Plus => Expr_BinaryOp_Plus
Expr_ShiftLeft => Expr_BinaryOp_ShiftLeft
Expr_ShiftRight => Expr_BinaryOp_ShiftRight
Expr_Smaller => Expr_BinaryOp_Smaller
Expr_SmallerOrEqual => Expr_BinaryOp_SmallerOrEqual
Scalar_ClassConst => Scalar_MagicConst_Class
Scalar_DirConst => Scalar_MagicConst_Dir
Scalar_FileConst => Scalar_MagicConst_File
Scalar_FuncConst => Scalar_MagicConst_Function
Scalar_LineConst => Scalar_MagicConst_Line
Scalar_MethodConst => Scalar_MagicConst_Method
Scalar_NSConst => Scalar_MagicConst_Namespace
Scalar_TraitConst => Scalar_MagicConst_Trait
```
These changes may affect custom pretty printers and code comparing the return value of `Node::getType()` to specific
strings.
### Miscellaneous
* The classes `Template` and `TemplateLoader` have been removed. You should use some other [code generation][code_gen]
project built on top of PHP-Parser instead.
* The `PrettyPrinterAbstract::pStmts()` method now emits a leading newline if the statement list is not empty.
Custom pretty printers should remove the explicit newline before `pStmts()` calls.
Old:
```php
public function pStmt_Trait(PHPParser_Node_Stmt_Trait $node) {
return 'trait ' . $node->name
. "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
}
```
New:
```php
public function pStmt_Trait(Stmt\Trait_ $node) {
return 'trait ' . $node->name
. "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
}
```
[code_gen]: https://github.com/nikic/PHP-Parser/wiki/Projects-using-the-PHP-Parser#code-generation

142
bin/php-parse.php Normal file
View File

@ -0,0 +1,142 @@
<?php
require __DIR__ . '/../lib/bootstrap.php';
ini_set('xdebug.max_nesting_level', 2000);
// Disable XDebug var_dump() output truncation
ini_set('xdebug.var_display_max_children', -1);
ini_set('xdebug.var_display_max_data', -1);
ini_set('xdebug.var_display_max_depth', -1);
list($operations, $files) = parseArgs($argv);
/* Dump nodes by default */
if (empty($operations)) {
$operations[] = 'dump';
}
if (empty($files)) {
showHelp("Must specify at least one file.");
}
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$dumper = new PhpParser\NodeDumper;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$serializer = new PhpParser\Serializer\XML;
$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
foreach ($files as $file) {
if (strpos($file, '<?php') === 0) {
$code = $file;
echo "====> Code $code\n";
} else {
if (!file_exists($file)) {
die("File $file does not exist.\n");
}
$code = file_get_contents($file);
echo "====> File $file:\n";
}
try {
$stmts = $parser->parse($code);
} catch (PhpParser\Error $e) {
die("==> Parse Error: {$e->getMessage()}\n");
}
foreach ($operations as $operation) {
if ('dump' === $operation) {
echo "==> Node dump:\n";
echo $dumper->dump($stmts), "\n";
} elseif ('pretty-print' === $operation) {
echo "==> Pretty print:\n";
echo $prettyPrinter->prettyPrintFile($stmts), "\n";
} elseif ('serialize-xml' === $operation) {
echo "==> Serialized XML:\n";
echo $serializer->serialize($stmts), "\n";
} elseif ('var-dump' === $operation) {
echo "==> var_dump():\n";
var_dump($stmts);
} elseif ('resolve-names' === $operation) {
echo "==> Resolved names.\n";
$stmts = $traverser->traverse($stmts);
}
}
}
function showHelp($error) {
die($error . "\n\n" .
<<<OUTPUT
Usage:
php php-parse.php [operations] file1.php [file2.php ...]
The file arguments can also be replaced with a code string:
php php-parse.php [operations] "<?php code"
Operations is a list of the following options (--dump by default):
--dump -d Dump nodes using NodeDumper
--pretty-print -p Pretty print file using PrettyPrinter\Standard
--serialize-xml Serialize nodes using Serializer\XML
--var-dump var_dump() nodes (for exact structure)
--resolve-names -N Resolve names using NodeVisitor\NameResolver
Example:
php php-parse.php -d -p -N -d file.php
Dumps nodes, pretty prints them, then resolves names and dumps them again.
OUTPUT
);
}
function parseArgs($args) {
$operations = array();
$files = array();
array_shift($args);
$parseOptions = true;
foreach ($args as $arg) {
if (!$parseOptions) {
$files[] = $arg;
continue;
}
switch ($arg) {
case '--dump':
case '-d':
$operations[] = 'dump';
break;
case '--pretty-print':
case '-p':
$operations[] = 'pretty-print';
break;
case '--serialize-xml':
$operations[] = 'serialize-xml';
break;
case '--var-dump':
$operations[] = 'var-dump';
break;
case '--resolve-names':
case '-N';
$operations[] = 'resolve-names';
break;
case '--':
$parseOptions = false;
break;
default:
if ($arg[0] === '-') {
showHelp("Invalid operation $arg.");
} else {
$files[] = $arg;
}
}
}
return array($operations, $files);
}

View File

@ -1,16 +1,16 @@
Introduction
============
This project is a PHP 5.5 (and older) parser **written in PHP itself**.
This project is a PHP 5.2 to PHP 5.6 parser **written in PHP itself**.
What is this for?
-----------------
A parser is useful for [static analysis][0] and manipulation of code and basically any other
A parser is useful for [static analysis][0], manipulation of code and basically any other
application dealing with code programmatically. A parser constructs an [Abstract Syntax Tree][1]
(AST) of the code and thus allows dealing with it in an abstract and robust way.
There are other ways of dealing with source code. One that PHP supports natively is using the
There are other ways of processing source code. One that PHP supports natively is using the
token stream generated by [`token_get_all`][2]. The token stream is much more low level than
the AST and thus has different applications: It allows to also analyze the exact formatting of
a file. On the other hand the token stream is much harder to deal with for more complex analysis.
@ -26,13 +26,13 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
What can it parse?
------------------
The parser uses a PHP 5.5 compliant grammar, which is backwards compatible with at least PHP 5.4, PHP 5.3
and PHP 5.2 (and maybe older).
The parser uses a PHP 5.6 compliant grammar, which is backwards compatible with all PHP version from PHP 5.2
upwards (and maybe older).
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.3, 5.4 and 5.5 is provided. This
allows to parse PHP 5.5 source code running on PHP 5.2, for example. This emulation is very hacky and not
yet perfect, but it should work well on any sane code.
version it runs on), additionally a wrapper for emulating new tokens from 5.3, 5.4, 5.5 and 5.6 is provided.
This allows to parse PHP 5.6 source code running on PHP 5.3, for example. This emulation is very hacky and not
perfect, but it should work well on any sane code.
What output does it produce?
----------------------------
@ -56,7 +56,7 @@ array(
)
```
This matches the semantics the program had: An echo statement, which takes two strings as expressions,
This matches the structure of the code: An echo statement, which takes two strings as expressions,
with the values `Hi` and `World!`.
You can also see that the AST does not contain any whitespace information (but most comments are saved).
@ -78,4 +78,4 @@ Apart from the parser itself this package also bundles support for some other, r
[0]: http://en.wikipedia.org/wiki/Static_program_analysis
[1]: http://en.wikipedia.org/wiki/Abstract_syntax_tree
[2]: http://php.net/token_get_all
[2]: http://php.net/token_get_all

View File

@ -3,11 +3,6 @@ Installation
There are multiple ways to include the PHP parser into your project:
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.
Installing via Composer
-----------------------
@ -15,7 +10,7 @@ Create a `composer.json` file in your project root and use it to define your dep
{
"require": {
"nikic/php-parser": "1.0.0-beta1"
"nikic/php-parser": "~1.0.2"
}
}
@ -34,6 +29,10 @@ Run the following command to install the parser into the `vendor/PHP-Parser` fol
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]: http://getcomposer.org/composer.phar

View File

@ -6,12 +6,14 @@ This document explains how to use the parser, the pretty printer and the node tr
Bootstrapping
-------------
The library needs to register a class autoloader; this is done by including the
`bootstrap.php` file:
The library needs to register a class autoloader. You can either use the ''vendor/autoload.php'' file generated by
Composer or by including the bundled `lib/bootstrap.php` file:
```php
<?php
require 'path/to/PHP-Parser/lib/bootstrap.php';
// Or, if you're using Composer:
require 'path/to/vendor/autoload.php';
```
Additionally you may want to set the `xdebug.max_nesting_level` ini option to a higher value:
@ -26,31 +28,38 @@ This ensures that there will be no errors when traversing highly nested node tre
Parsing
-------
In order to parse some source code you first have to create a `PhpParser\Parser` object (which
needs to be passed a `PhpParser\Lexer` instance) and then pass the code (including `<?php` opening
tags) to the `parse` method. If a syntax error is encountered `PhpParser\Error` is thrown, so this
exception should be `catch`ed.
In order to parse some source code you first have to create a `PhpParser\Parser` object, which
needs to be passed a `PhpParser\Lexer` instance:
```php
<?php
$parser = new PhpParser\Parser(new PhpParser\Lexer);
// or
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
```
Use of the emulative lexer is required if you want to parse PHP code from newer versions than the one
you're running on. For example it will allow you to parse PHP 5.6 code while running on PHP 5.3.
Subsequently you can pass PHP code (including the opening `<?php` tag) to the `parse` method in order to
create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will be thrown:
```php
<?php
$code = '<?php // some code';
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
try {
$stmts = $parser->parse($code);
// $stmts is an array of statement nodes
} catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage();
}
```
The `parse` method will return an array of statement nodes (`$stmts`).
### Emulative lexer
Instead of `PhpParser\Lexer` one can also use `PhpParser\Lexer\Emulative`. This class will emulate tokens
of newer PHP versions and as such allow parsing PHP 5.5 on PHP 5.2, for example. So if you want to parse
PHP code of newer versions than the one you are running, you should use the emulative lexer.
A parser instance can be reused to parse multiple files.
Node tree
---------
@ -104,7 +113,7 @@ with a PHP keyword.
Every node has a (possibly zero) number of subnodes. You can access subnodes by writing
`$node->subNodeName`. The `Stmt\Echo_` node has only one subnode `exprs`. So in order to access it
in the above example you would write `$stmts[0]->exprs`. If you wanted to access name of the function
in the above example you would write `$stmts[0]->exprs`. If you wanted to access the name of the function
call, you would write `$stmts[0]->exprs[1]->name`.
All nodes also define a `getType()` method that returns the node type. The type is the class name
@ -131,7 +140,7 @@ namely `PhpParser\PrettyPrinter\Standard`.
<?php
$code = "<?php echo 'Hi ', hi\\getTarget();";
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
try {
@ -143,10 +152,10 @@ try {
->exprs // sub expressions
[0] // the first of them (the string node)
->value // it's value, i.e. 'Hi '
= 'Hallo '; // change to 'Hallo '
= 'Hello '; // change to 'Hello '
// pretty print
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts);
$code = $prettyPrinter->prettyPrint($stmts);
echo $code;
} catch (PhpParser\Error $e) {
@ -156,7 +165,7 @@ try {
The above code will output:
<?php echo 'Hallo ', hi\getTarget();
<?php echo 'Hello ', hi\getTarget();
As you can see the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
@ -164,6 +173,9 @@ again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
The `prettyPrint()` method pretty prints a statements array. It is also possible to pretty print only a
single expression using `prettyPrintExpr()`.
The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `<?php` tag
and handle inline HTML as the first/last statement more gracefully.
Node traversation
-----------------
@ -177,9 +189,8 @@ structure of a program using this `PhpParser\NodeTraverser` looks like this:
```php
<?php
$code = "<?php // some code";
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$traverser = new PhpParser\NodeTraverser;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
@ -187,6 +198,8 @@ $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser->addVisitor(new MyNodeVisitor);
try {
$code = file_get_contents($fileName);
// parse
$stmts = $parser->parse($code);
@ -194,7 +207,7 @@ try {
$stmts = $traverser->traverse($stmts);
// pretty print
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts);
$code = $prettyPrinter->prettyPrintFile($stmts);
echo $code;
} catch (PhpParser\Error $e) {
@ -202,14 +215,16 @@ try {
}
```
A same node visitor for this code might look like this:
The corresponding node visitor might look like this:
```php
<?php
use PhpParser\Node;
class MyNodeVisitor extends PhpParser\NodeVisitorAbstract
{
public function leaveNode(PhpParser\Node $node) {
if ($node instanceof PhpParser\Node\Scalar\String) {
public function leaveNode(Node $node) {
if ($node instanceof Node\Scalar\String) {
$node->value = 'foo';
}
}
@ -218,7 +233,7 @@ class MyNodeVisitor extends PhpParser\NodeVisitorAbstract
The above node visitor would change all string literals in the program to `'foo'`.
All visitors must implement the `PhpParser\NodeVisitor` interface, which defined the following four
All visitors must implement the `PhpParser\NodeVisitor` interface, which defines the following four
methods:
public function beforeTraverse(array $nodes);
@ -237,11 +252,12 @@ The `enterNode` and `leaveNode` methods are called on every node, the former whe
i.e. before its subnodes are traversed, the latter when it is left.
All four methods can either return the changed node or not return at all (i.e. `null`) in which
case the current node is not changed. The `leaveNode` method can furthermore return two special
values: If `false` is returned the current node will be removed from the parent array. If an `array`
is returned the current node 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)`.
case the current node is not changed. The `leaveNode` method can additionally return two special
values:
If `false` is returned the current node will be removed from the parent array. If an array is returned
it 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)`.
Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
class, which will define empty default implementations for all the above methods.
@ -280,10 +296,9 @@ We start off with the following base code:
```php
<?php
const IN_DIR = '/some/path';
const OUT_DIR = '/some/other/path';
$inDir = '/some/path';
$outDir = '/some/other/path';
// use the emulative lexer here, as we are running PHP 5.2 but want to parse PHP 5.3
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$traverser = new PhpParser\NodeTraverser;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
@ -292,7 +307,7 @@ $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); // we will need
$traverser->addVisitor(new NodeVisitor\NamespaceConverter); // our own node visitor
// iterate over all .php files in the directory
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(IN_DIR));
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($inDir));
$files = new RegexIterator($files, '/\.php$/');
foreach ($files as $file) {
@ -307,11 +322,11 @@ foreach ($files as $file) {
$stmts = $traverser->traverse($stmts);
// pretty print
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts);
$code = $prettyPrinter->prettyPrintFile($stmts);
// write the converted file to the target directory
file_put_contents(
substr_replace($file->getPathname(), OUT_DIR, 0, strlen(IN_DIR)),
substr_replace($file->getPathname(), $outDir, 0, strlen($inDir)),
$code
);
} catch (PhpParser\Error $e) {
@ -320,7 +335,7 @@ foreach ($files as $file) {
}
```
Now lets start with the main code, the `NodeVisitor_NamespaceConverter`. One thing it needs to do
Now lets start with the main code, the `NodeVisitor\NamespaceConverter`. One thing it needs to do
is convert `A\\B` style names to `A_B` style ones.
```php
@ -337,14 +352,14 @@ class NodeVisitor_NamespaceConverter extends PhpParser\NodeVisitorAbstract
```
The above code profits from the fact that the `NameResolver` already resolved all names as far as
possible, so we don't need to do that. All the need to create a string with the name parts separated
possible, so we don't need to do that. We only need to create a string with the name parts separated
by underscores instead of backslashes. This is what `$node->toString('_')` does. (If you want to
create a name with backslashes either write `$node->toString()` or `(string) $node`.) Then we create
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 class
name:
only the shortname (i.e. the last part of the name), but they need to contain the complete name inclduing
the namespace prefix:
```php
<?php

View File

@ -1,7 +1,7 @@
Other node tree representations
===============================
It is possible to convert the AST in several textual representations, which serve different uses.
It is possible to convert the AST into several textual representations, which serve different uses.
Simple serialization
--------------------
@ -13,33 +13,34 @@ but PHP, but it is compact and generates fast. The main application thus is in c
Human readable dumping
----------------------
Furthermore it is possible to dump nodes into a human readable form using the `dump` method of
Furthermore it is possible to dump nodes into a human readable format using the `dump` method of
`PhpParser\NodeDumper`. This can be used for debugging.
```php
<?php
$code = <<<'CODE'
<?php
function printLine($msg) {
echo $msg, "\n";
}
printLine('Hallo World!!!');
function printLine($msg) {
echo $msg, "\n";
}
printLine('Hello World!!!');
CODE;
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$nodeDumper = new PhpParser\NodeDumper;
try {
$stmts = $parser->parse($code);
echo '<pre>' . htmlspecialchars($nodeDumper->dump($stmts)) . '</pre>';
echo $nodeDumper->dump($stmts), "\n";
} catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage();
}
```
The above output will have an output looking roughly like this:
The above script will have an output looking roughly like this:
```
array(
@ -77,7 +78,7 @@ array(
args: array(
0: Arg(
value: Scalar_String(
value: Hallo World!!!
value: Hello World!!!
)
byRef: false
)
@ -97,20 +98,21 @@ interfacing with other languages and applications or for doing transformation us
<?php
$code = <<<'CODE'
<?php
function printLine($msg) {
echo $msg, "\n";
}
printLine('Hallo World!!!');
function printLine($msg) {
echo $msg, "\n";
}
printLine('Hello World!!!');
CODE;
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$serializer = new PhpParser\Serializer\XML;
try {
$stmts = $parser->parse($code);
echo '<pre>' . htmlspecialchars($serializer->serialize($stmts)) . '</pre>';
echo $serializer->serialize($stmts);
} catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage();
}
@ -185,7 +187,7 @@ Produces:
<subNode:value>
<node:Scalar_String line="6">
<subNode:value>
<scalar:string>Hallo World!!!</scalar:string>
<scalar:string>Hello World!!!</scalar:string>
</subNode:value>
</node:Scalar_String>
</subNode:value>

View File

@ -3,13 +3,8 @@ Code generation
It is also possible to generate code using the parser, by first creating an Abstract Syntax Tree and then using the
pretty printer to convert it to PHP code. To simplify code generation, the project comes with a set of builders for
common structures as well as simple templating support. Both features are described in the following:
Builders
--------
The project provides builders for classes, interfaces, methods, functions, parameters and properties, which
allow creating node trees with a fluid interface, instead of instantiating all nodes manually.
classes, interfaces, methods, functions, parameters and properties. The builders allow creating node trees using a
fluid interface, instead of instantiating all nodes manually.
Here is an example:
@ -59,209 +54,3 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, Interfaces
}
}
```
Templates
---------
> **DEPRECATED**: This feature is deprecated and will be removed in PHP-Parser 1.0.
Additionally it is possible to generate code from reusable templates.
As an example consider the following template, which defines a general getter/setter skeleton in terms of a property
`__name__` and its `__type__`:
```php
<?php
class GetterSetterTemplate
{
/**
* @var __type__ The __name__
*/
protected $__name__;
/**
* Gets the __name__.
*
* @return __type__ The __name__
*/
public function get__Name__() {
return $this->__name__;
}
/**
* Sets the __name__.
*
* @param __type__ $__name__ The new __name__
*/
public function set__Name__($__name__) {
$this->__name__ = $__name__;
}
}
```
Using this template we can easily create a class with multiple properties and their respective getters and setters:
```php
<?php
// $templateString contains the above template
$template = new PhpParser\Template($parser, $templateString);
// We only have to specify the __name__ placeholder, as the
// capitalized __Name__ placeholder is automatically created
$properties = [
['name' => 'title', 'type' => 'string'],
['name' => 'body', 'type' => 'string'],
['name' => 'author', 'type' => 'User'],
['name' => 'timestamp', 'type' => 'DateTime'],
];
$class = $factory->class('BlogPost')->implement('Post');
foreach ($properties as $propertyPlaceholders) {
$stmts = $template->getStmts($propertyPlaceholders);
$class->addStmts(
// $stmts contains all statements from the template. So [0] fetches the class statement
// and ->stmts retrieves the methods.
$stmts[0]->stmts
);
}
echo $prettyPrinter->prettyPrint(array($class->getNode()));
```
The result would look roughly like this:
```php
<?php
class BlogPost implements Post
{
/**
* @var string The title
*/
protected $title;
/**
* @var string The body
*/
protected $body;
/**
* @var User The author
*/
protected $author;
/**
* @var DateTime The timestamp
*/
protected $timestamp;
/**
* Gets the title.
*
* @return string The title
*/
public function getTitle()
{
return $this->title;
}
/**
* Sets the title.
*
* @param string $title The new title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* Gets the body.
*
* @return string The body
*/
public function getBody()
{
return $this->body;
}
/**
* Sets the body.
*
* @param string $body The new body
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Gets the author.
*
* @return User The author
*/
public function getAuthor()
{
return $this->author;
}
/**
* Sets the author.
*
* @param User $author The new author
*/
public function setAuthor($author)
{
$this->author = $author;
}
/**
* Gets the timestamp.
*
* @return DateTime The timestamp
*/
public function getTimestamp()
{
return $this->timestamp;
}
/**
* Sets the timestamp.
*
* @param DateTime $timestamp The new timestamp
*/
public function setTimestamp($timestamp)
{
$this->timestamp = $timestamp;
}
}
```
When using multiple templates it is easier to manage them on the filesystem. They can be loaded using the
`TemplateLoader`:
```php
<?php
// We'll store our templates in ./templates and give them a .php suffix
$loader = new PhpParser\TemplateLoader($parser, './templates', '.php');
// loads ./templates/GetterSetter.php
$getterSetterTemplate = $loader->load('GetterSetter');
// loads ./templates/Collection.php
$collectionTemplate = $loader->load('Collection');
// The use of a suffix is optional. The following code for example is equivalent:
$loader = new PhpParser\TemplateLoader($parser, './templates');
// loads ./templates/GetterSetter.php
$getterSetterTemplate = $loader->load('GetterSetter.php');
// loads ./templates/Collection.php
$collectionTemplate = $loader->load('Collection.php');
```

View File

@ -7,45 +7,22 @@ newer PHP versions and thus allows parsing of new code on older versions.
A lexer has to define the following public interface:
startLexing($code);
getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null);
handleHaltCompiler();
void startLexing(string $code);
string handleHaltCompiler();
int getNextToken(string &$value = null, array &$startAttributes = null, array &$endAttributes = null);
startLexing
-----------
The `startLexing()` method is invoked with the source code that is to be lexed (including the opening tag) whenever the
`parse()` method of the parser is called. It can be used to reset state or preprocess the source code or tokens.
The `startLexing` method is invoked when the `parse()` method of the parser is called. It's argument will be whatever
was passed to the `parse()` method.
The `handleHaltCompiler()` method is called whenever a `T_HALT_COMPILER` token is encountered. It has to return the
remaining string after the construct (not including `();`).
Even though `startLexing` is meant to accept a source code string, you could for example overwrite it to accept a file:
The `getNextToken()` method returns the ID of the next token (as defined by the `Parser::T_*` constants). If no more
tokens are available it must return `0`, which is the ID of the `EOF` token. Furthermore the string content of the
token should be written into the by-reference `$value` parameter (which will then be available as `$n` in the parser).
```php
<?php
class FileLexer extends PhpParser\Lexer {
public function startLexing($fileName) {
if (!file_exists($fileName)) {
throw new InvalidArgumentException(sprintf('File "%s" does not exist', $fileName));
}
parent::startLexing(file_get_contents($fileName));
}
}
$parser = new PhpParser\Parser(new FileLexer);
var_dump($parser->parse('someFile.php'));
var_dump($parser->parse('someOtherFile.php'));
```
getNextToken
------------
`getNextToken` returns the ID of the next token and sets some additional information in the three variables which it
accepts by-ref. If no more tokens are available it has to return `0`, which is the ID of the `EOF` token.
The first by-ref variable `$value` should contain the textual content of the token. It is what will be available as `$1`
etc in the parser.
Attribute handling
------------------
The other two by-ref variables `$startAttributes` and `$endAttributes` define which attributes will eventually be
assigned to the generated nodes: The parser will take the `$startAttributes` from the first token which is part of the
@ -76,39 +53,71 @@ class LessAttributesLexer extends PhpParser\Lexer {
}
```
You can obviously also add additional attributes. E.g. in conjunction with the above `FileLexer` you might want to add
a `fileName` attribute to all nodes:
Token offset lexer
------------------
A useful application for custom attributes is the token offset lexer, which provides the start and end token for a node
as attributes:
```php
<?php
class FileLexer extends PhpParser\Lexer {
protected $fileName;
public function startLexing($fileName) {
if (!file_exists($fileName)) {
throw new InvalidArgumentException(sprintf('File "%s" does not exist', $fileName));
}
$this->fileName = $fileName;
parent::startLexing(file_get_contents($fileName));
}
class TokenOffsetLexer extends PhpParser\Lexer {
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
$tokenId = parent::getNextToken($value, $startAttributes, $endAttributes);
// we could use either $startAttributes or $endAttributes here, because the fileName is always the same
// (regardless of whether it is the start or end token). We choose $endAttributes, because it is slightly
// more efficient (as the parser has to keep a stack for the $startAttributes).
$endAttributes['fileName'] = $this->fileName;
$startAttributes['startOffset'] = $endAttributes['endOffset'] = $this->pos;
return $tokenId;
}
public function getTokens() {
return $this->tokens;
}
}
```
handleHaltCompiler
------------------
This information can now be used to examine the exact formatting used for a node. For example the AST does not
distinguish whether a property was declared using `public` or using `var`, but you can retrieve this information based
on the token offset:
The method is invoked whenever a `T_HALT_COMPILER` token is encountered. It has to return the remaining string after the
construct (not including `();`).
```php
function isDeclaredUsingVar(array $tokens, PhpParser\Node\Stmt\Property $prop) {
$i = $prop->getAttribute('startOffset');
return $tokens[$i][0] === T_VAR;
}
```
In order to make use of this function, you will have to provide the tokens from the lexer to your node visitor using
code similar to the following:
```php
class MyNodeVisitor extends PhpParser\NodeVisitorAbstract {
private $tokens;
public function setTokens(array $tokens) {
$this->tokens = $tokens;
}
public function leaveNode(PhpParser\Node $node) {
if ($node instanceof PhpParser\Node\Stmt\Property) {
var_dump(isDeclaredUsingVar($this->tokens, $node));
}
}
}
$lexer = new TokenOffsetLexer();
$parser = new PhpParser\Parser($lexer);
$visitor = new MyNodeVisitor();
$traverser = new PhpParser\NodeTraverser();
$traverser->addVisitor($visitor);
try {
$stmts = $parser->parse($code);
$visitor->setTokens($lexer->getTokens());
$stmts = $traverser->traverse($stmts);
} catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage();
}
```
The same approach can also be used to perform specific modifications in the code, without changing the formatting in
other places (which is the case when using the pretty printer).

View File

@ -1,9 +1,9 @@
<?php
$meta #
#semval($) $this->yyval
#semval($,%t) $this->yyval
#semval(%n) $this->yyastk[$this->stackPos-(%l-%n)]
#semval(%n,%t) $this->yyastk[$this->stackPos-(%l-%n)]
#semval($) $this->semValue
#semval($,%t) $this->semValue
#semval(%n) $this->semStack[$this->stackPos-(%l-%n)]
#semval(%n,%t) $this->semStack[$this->stackPos-(%l-%n)]
#include;
namespace PhpParser;
@ -11,353 +11,89 @@ namespace PhpParser;
/* This is an automatically GENERATED file, which should not be manually edited.
* Instead edit one of the following:
* * the grammar file grammar/zend_language_parser.phpy
* * the parser skeleton grammar/kymacc.php.parser
* * the skeleton file grammar/kymacc.php.parser
* * the preprocessing script grammar/rebuildParser.php
*
* The skeleton for this parser was written by Moriyoshi Koizumi and is based on
* the work by Masato Bito and is in the PUBLIC DOMAIN.
*/
#if -t
class #(-p)_Debug extends #(-p)
#endif
#ifnot -t
class #(-p)
#endif
class Parser extends ParserAbstract
{
#ifnot -t
const TOKEN_NONE = -1;
const TOKEN_INVALID = #(YYBADCH);
protected $tokenToSymbolMapSize = #(YYMAXLEX);
protected $actionTableSize = #(YYLAST);
protected $gotoTableSize = #(YYGLAST);
const TOKEN_MAP_SIZE = #(YYMAXLEX);
protected $invalidToken = #(YYBADCH);
protected $defaultAction = #(YYDEFAULT);
protected $unexpectedTokenRule = #(YYUNEXPECTED);
const YYLAST = #(YYLAST);
const YY2TBLSTATE = #(YY2TBLSTATE);
const YYGLAST = #(YYGLAST);
const YYNLSTATES = #(YYNLSTATES);
const YYUNEXPECTED = #(YYUNEXPECTED);
const YYDEFAULT = #(YYDEFAULT);
protected $YY2TBLSTATE = #(YY2TBLSTATE);
protected $YYNLSTATES = #(YYNLSTATES);
// {{{ Tokens
#tokenval
const %s = %n;
#endtokenval
// }}}
/* @var array Map of token ids to their respective names */
protected static $terminals = array(
protected $symbolToName = array(
#listvar terminals
, "???"
);
/* @var array Map which translates lexer tokens to internal tokens */
protected static $translate = array(
protected $tokenToSymbol = array(
#listvar yytranslate
);
protected static $yyaction = array(
protected $action = array(
#listvar yyaction
);
protected static $yycheck = array(
protected $actionCheck = array(
#listvar yycheck
);
protected static $yybase = array(
protected $actionBase = array(
#listvar yybase
);
protected static $yydefault = array(
protected $actionDefault = array(
#listvar yydefault
);
protected static $yygoto = array(
protected $goto = array(
#listvar yygoto
);
protected static $yygcheck = array(
protected $gotoCheck = array(
#listvar yygcheck
);
protected static $yygbase = array(
protected $gotoBase = array(
#listvar yygbase
);
protected static $yygdefault = array(
protected $gotoDefault = array(
#listvar yygdefault
);
protected static $yylhs = array(
protected $ruleToNonTerminal = array(
#listvar yylhs
);
protected static $yylen = array(
protected $ruleToLength = array(
#listvar yylen
);
protected $yyval;
protected $yyastk;
protected $stackPos;
protected $lexer;
/**
* Creates a parser instance.
*
* @param Lexer $lexer A lexer
*/
public function __construct(Lexer $lexer) {
$this->lexer = $lexer;
}
#endif
#if -t
protected static $yyproduction = array(
protected $productions = array(
#production-strings;
);
protected function yyprintln($msg) {
echo $msg, "\n";
}
protected function YYTRACE_NEWSTATE($state, $tokenId) {
$this->yyprintln(
'% State ' . $state
. ', Lookahead ' . ($tokenId == self::TOKEN_NONE ? '--none--' : self::$terminals[$tokenId])
);
}
protected function YYTRACE_READ($tokenId) {
$this->yyprintln('% Reading ' . self::$terminals[$tokenId]);
}
protected function YYTRACE_SHIFT($tokenId) {
$this->yyprintln('% Shift ' . self::$terminals[$tokenId]);
}
protected function YYTRACE_ACCEPT() {
$this->yyprintln('% Accepted.');
}
protected function YYTRACE_REDUCE($n) {
$this->yyprintln('% Reduce by (' . $n . ') ' . self::$yyproduction[$n]);
}
protected function YYTRACE_POP($state) {
$this->yyprintln('% Recovering, uncovers state ' . $state);
}
protected function YYTRACE_DISCARD($tokenId) {
$this->yyprintln('% Discard ' . self::$terminals[$tokenId]);
}
#endif
/**
#ifnot -t
* Parses PHP code into a node tree.
#endif
#if -t
* Parses PHP code into a node tree and prints out debugging information.
#endif
*
* @param string $code The source code to parse
*
* @return Node[] Array of statements
*/
public function parse($code) {
$this->lexer->startLexing($code);
// We start off with no lookahead-token
$tokenId = self::TOKEN_NONE;
// The attributes for a node are taken from the first and last token of the node.
// From the first token only the startAttributes are taken and from the last only
// the endAttributes. Both are merged using the array union operator (+).
$startAttributes = array('startLine' => 1);
$endAttributes = array();
// In order to figure out the attributes for the starting token, we have to keep
// them in a stack
$attributeStack = array($startAttributes);
// Start off in the initial state and keep a stack of previous states
$state = 0;
$stateStack = array($state);
// AST stack (?)
$this->yyastk = array();
// Current position in the stack(s)
$this->stackPos = 0;
for (;;) {
#if -t
$this->YYTRACE_NEWSTATE($state, $tokenId);
#endif
if (self::$yybase[$state] == 0) {
$yyn = self::$yydefault[$state];
} else {
if ($tokenId === self::TOKEN_NONE) {
// Fetch the next token id from the lexer and fetch additional info by-ref.
// The end attributes are fetched into a temporary variable and only set once the token is really
// shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
// reduced after a token was read but not yet shifted.
$origTokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $nextEndAttributes);
// map the lexer token id to the internally used token id's
$tokenId = $origTokenId >= 0 && $origTokenId < self::TOKEN_MAP_SIZE
? self::$translate[$origTokenId]
: self::TOKEN_INVALID;
if ($tokenId === self::TOKEN_INVALID) {
throw new \RangeException(sprintf(
'The lexer returned an invalid token (id=%d, value=%s)',
$origTokenId, $tokenValue
));
}
$attributeStack[$this->stackPos] = $startAttributes;
#if -t
$this->YYTRACE_READ($tokenId);
#endif
}
if ((($yyn = self::$yybase[$state] + $tokenId) >= 0
&& $yyn < self::YYLAST && self::$yycheck[$yyn] == $tokenId
|| ($state < self::YY2TBLSTATE
&& ($yyn = self::$yybase[$state + self::YYNLSTATES] + $tokenId) >= 0
&& $yyn < self::YYLAST
&& self::$yycheck[$yyn] == $tokenId))
&& ($yyn = self::$yyaction[$yyn]) != self::YYDEFAULT) {
/*
* >= YYNLSTATE: shift and reduce
* > 0: shift
* = 0: accept
* < 0: reduce
* = -YYUNEXPECTED: error
*/
if ($yyn > 0) {
/* shift */
#if -t
$this->YYTRACE_SHIFT($tokenId);
#endif
++$this->stackPos;
$stateStack[$this->stackPos] = $state = $yyn;
$this->yyastk[$this->stackPos] = $tokenValue;
$attributeStack[$this->stackPos] = $startAttributes;
$endAttributes = $nextEndAttributes;
$tokenId = self::TOKEN_NONE;
if ($yyn < self::YYNLSTATES)
continue;
/* $yyn >= YYNLSTATES means shift-and-reduce */
$yyn -= self::YYNLSTATES;
} else {
$yyn = -$yyn;
}
} else {
$yyn = self::$yydefault[$state];
}
}
for (;;) {
/* reduce/error */
if ($yyn == 0) {
/* accept */
#if -t
$this->YYTRACE_ACCEPT();
#endif
return $this->yyval;
} elseif ($yyn != self::YYUNEXPECTED) {
/* reduce */
#if -t
$this->YYTRACE_REDUCE($yyn);
#endif
try {
$this->{'yyn' . $yyn}(
$attributeStack[$this->stackPos - self::$yylen[$yyn]]
+ $endAttributes
);
} catch (Error $e) {
if (-1 === $e->getRawLine()) {
$e->setRawLine($startAttributes['startLine']);
}
throw $e;
}
/* Goto - shift nonterminal */
$this->stackPos -= self::$yylen[$yyn];
$yyn = self::$yylhs[$yyn];
if (($yyp = self::$yygbase[$yyn] + $stateStack[$this->stackPos]) >= 0
&& $yyp < self::YYGLAST
&& self::$yygcheck[$yyp] == $yyn) {
$state = self::$yygoto[$yyp];
} else {
$state = self::$yygdefault[$yyn];
}
++$this->stackPos;
$stateStack[$this->stackPos] = $state;
$this->yyastk[$this->stackPos] = $this->yyval;
$attributeStack[$this->stackPos] = $startAttributes;
} else {
/* error */
$expected = array();
$base = self::$yybase[$state];
for ($i = 0; $i < self::TOKEN_MAP_SIZE; ++$i) {
$n = $base + $i;
if ($n >= 0 && $n < self::YYLAST && self::$yycheck[$n] == $i
|| $state < self::YY2TBLSTATE
&& ($n = self::$yybase[$state + self::YYNLSTATES] + $i) >= 0
&& $n < self::YYLAST && self::$yycheck[$n] == $i
) {
if (self::$yyaction[$n] != self::YYUNEXPECTED) {
if (count($expected) == 4) {
/* Too many expected tokens */
$expected = array();
break;
}
$expected[] = self::$terminals[$i];
}
}
}
$expectedString = '';
if ($expected) {
$expectedString = ', expecting ' . implode(' or ', $expected);
}
throw new Error(
'Syntax error, unexpected ' . self::$terminals[$tokenId] . $expectedString,
$startAttributes['startLine']
);
}
if ($state < self::YYNLSTATES)
break;
/* >= YYNLSTATES means shift-and-reduce */
$yyn = $state - self::YYNLSTATES;
}
}
}
#ifnot -t
#reduce
protected function yyn%n($attributes) {
protected function reduceRule%n($attributes) {
%b
}
#noact
protected function yyn%n() {
$this->yyval = $this->yyastk[$this->stackPos];
protected function reduceRule%n() {
$this->semValue = $this->semStack[$this->stackPos];
}
#endreduce
#endif
}
#tailcode;

View File

@ -5,7 +5,6 @@ $skeletonFile = __DIR__ . '/kmyacc.php.parser';
$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy';
$tmpResultFile = __DIR__ . '/tmp_parser.php';
$parserResultFile = __DIR__ . '/../lib/PhpParser/Parser.php';
$debugParserResultFile = __DIR__ . '/../lib/PhpParser/Parser/Debug.php';
// check for kmyacc.exe binary in this directory, otherwise fall back to global name
$kmyacc = __DIR__ . '/kmyacc.exe';
@ -47,20 +46,14 @@ $grammarCode = resolveArrays($grammarCode);
file_put_contents($tmpGrammarFile, $grammarCode);
$additionalArgs = $optionDebug ? '-t -v' : '';
echo "Building parser.\n";
$output = trim(shell_exec("$kmyacc -l -m $skeletonFile -p Parser $tmpGrammarFile 2>&1"));
$output = trim(shell_exec("$kmyacc $additionalArgs -l -m $skeletonFile $tmpGrammarFile 2>&1"));
echo "Output: \"$output\"\n";
moveFileWithDirCheck($tmpResultFile, $parserResultFile);
if ($optionDebug) {
echo "Building debug parser.\n";
$output = trim(shell_exec("$kmyacc -t -v -l -m $skeletonFile -p Parser $tmpGrammarFile 2>&1"));
echo "Output: \"$output\"\n";
moveFileWithDirCheck($tmpResultFile, $debugParserResultFile);
}
if (!$optionKeepTmpGrammar) {
unlink($tmpGrammarFile);
}

View File

@ -112,7 +112,7 @@
%%
start:
top_statement_list { $$ = Stmt\Namespace_::postprocess($1); }
top_statement_list { $$ = $this->handleNamespaces($1); }
;
top_statement_list:
@ -628,6 +628,7 @@ scalar_dereference:
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
{ $$ = Expr\ArrayDimFetch[Scalar\String[Scalar\String::parse($1)], $3]; }
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */
;
@ -678,7 +679,7 @@ function_call:
;
class_name:
T_STATIC { $$ = Name['static']; }
T_STATIC { $$ = Name[$1]; }
| name { $$ = $1; }
;
@ -704,7 +705,7 @@ class_name_or_var:
;
object_access_for_dcnr:
| base_variable T_OBJECT_OPERATOR object_property
base_variable T_OBJECT_OPERATOR object_property
{ $$ = Expr\PropertyFetch[$1, $3]; }
| object_access_for_dcnr T_OBJECT_OPERATOR object_property
{ $$ = Expr\PropertyFetch[$1, $3]; }
@ -745,61 +746,62 @@ common_scalar:
{ $$ = Scalar\String[Scalar\String::parseDocString($1, $2)]; }
| T_START_HEREDOC T_END_HEREDOC
{ $$ = Scalar\String['']; }
| name { $$ = Expr\ConstFetch[$1]; }
;
/* Arrays are currently not allowed in static scalar operations */
static_scalar:
static_scalar_value { $$ = $1; }
| T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; }
| '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; }
;
static_scalar_value:
common_scalar { $$ = $1; }
| class_name T_PAAMAYIM_NEKUDOTAYIM class_const_name { $$ = Expr\ClassConstFetch[$1, $3]; }
| name { $$ = Expr\ConstFetch[$1]; }
| T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; }
| '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; }
| static_operation { $$ = $1; }
;
static_operation:
static_scalar_value T_BOOLEAN_OR static_scalar_value { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
| static_scalar_value T_BOOLEAN_AND static_scalar_value { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
| static_scalar_value T_LOGICAL_OR static_scalar_value { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
| static_scalar_value T_LOGICAL_AND static_scalar_value { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| static_scalar_value T_LOGICAL_XOR static_scalar_value { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| static_scalar_value '|' static_scalar_value { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| static_scalar_value '&' static_scalar_value { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar_value '^' static_scalar_value { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| static_scalar_value '.' static_scalar_value { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| static_scalar_value '+' static_scalar_value { $$ = Expr\BinaryOp\Plus [$1, $3]; }
| static_scalar_value '-' static_scalar_value { $$ = Expr\BinaryOp\Minus [$1, $3]; }
| static_scalar_value '*' static_scalar_value { $$ = Expr\BinaryOp\Mul [$1, $3]; }
| static_scalar_value '/' static_scalar_value { $$ = Expr\BinaryOp\Div [$1, $3]; }
| static_scalar_value '%' static_scalar_value { $$ = Expr\BinaryOp\Mod [$1, $3]; }
| static_scalar_value T_SL static_scalar_value { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
| static_scalar_value T_SR static_scalar_value { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
| static_scalar_value T_POW static_scalar_value { $$ = Expr\BinaryOp\Pow [$1, $3]; }
| '+' static_scalar_value %prec T_INC { $$ = Expr\UnaryPlus [$2]; }
| '-' static_scalar_value %prec T_INC { $$ = Expr\UnaryMinus[$2]; }
| '!' static_scalar_value { $$ = Expr\BooleanNot[$2]; }
| '~' static_scalar_value { $$ = Expr\BitwiseNot[$2]; }
| static_scalar_value T_IS_IDENTICAL static_scalar_value { $$ = Expr\BinaryOp\Identical [$1, $3]; }
| static_scalar_value T_IS_NOT_IDENTICAL static_scalar_value { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; }
| static_scalar_value T_IS_EQUAL static_scalar_value { $$ = Expr\BinaryOp\Equal [$1, $3]; }
| static_scalar_value T_IS_NOT_EQUAL static_scalar_value { $$ = Expr\BinaryOp\NotEqual [$1, $3]; }
| static_scalar_value '<' static_scalar_value { $$ = Expr\BinaryOp\Smaller [$1, $3]; }
| static_scalar_value T_IS_SMALLER_OR_EQUAL static_scalar_value { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
| static_scalar_value '>' static_scalar_value { $$ = Expr\BinaryOp\Greater [$1, $3]; }
| static_scalar_value T_IS_GREATER_OR_EQUAL static_scalar_value { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
| static_scalar_value '?' static_scalar_value ':' static_scalar_value { $$ = Expr\Ternary[$1, $3, $5]; }
| static_scalar_value '?' ':' static_scalar_value { $$ = Expr\Ternary[$1, null, $4]; }
| '(' static_scalar_value ')' { $$ = $2; }
static_scalar T_BOOLEAN_OR static_scalar { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
| static_scalar T_BOOLEAN_AND static_scalar { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
| static_scalar T_LOGICAL_OR static_scalar { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
| static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| static_scalar '&' static_scalar { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; }
| static_scalar '-' static_scalar { $$ = Expr\BinaryOp\Minus [$1, $3]; }
| static_scalar '*' static_scalar { $$ = Expr\BinaryOp\Mul [$1, $3]; }
| static_scalar '/' static_scalar { $$ = Expr\BinaryOp\Div [$1, $3]; }
| static_scalar '%' static_scalar { $$ = Expr\BinaryOp\Mod [$1, $3]; }
| static_scalar T_SL static_scalar { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
| static_scalar T_SR static_scalar { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
| static_scalar T_POW static_scalar { $$ = Expr\BinaryOp\Pow [$1, $3]; }
| '+' static_scalar %prec T_INC { $$ = Expr\UnaryPlus [$2]; }
| '-' static_scalar %prec T_INC { $$ = Expr\UnaryMinus[$2]; }
| '!' static_scalar { $$ = Expr\BooleanNot[$2]; }
| '~' static_scalar { $$ = Expr\BitwiseNot[$2]; }
| static_scalar T_IS_IDENTICAL static_scalar { $$ = Expr\BinaryOp\Identical [$1, $3]; }
| static_scalar T_IS_NOT_IDENTICAL static_scalar { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; }
| static_scalar T_IS_EQUAL static_scalar { $$ = Expr\BinaryOp\Equal [$1, $3]; }
| static_scalar T_IS_NOT_EQUAL static_scalar { $$ = Expr\BinaryOp\NotEqual [$1, $3]; }
| static_scalar '<' static_scalar { $$ = Expr\BinaryOp\Smaller [$1, $3]; }
| static_scalar T_IS_SMALLER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
| static_scalar '>' static_scalar { $$ = Expr\BinaryOp\Greater [$1, $3]; }
| static_scalar T_IS_GREATER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
| static_scalar '?' static_scalar ':' static_scalar { $$ = Expr\Ternary[$1, $3, $5]; }
| static_scalar '?' ':' static_scalar { $$ = Expr\Ternary[$1, null, $4]; }
| static_scalar '[' static_scalar ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| '(' static_scalar ')' { $$ = $2; }
;
constant:
name { $$ = Expr\ConstFetch[$1]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM class_const_name
{ $$ = Expr\ClassConstFetch[$1, $3]; }
;
scalar:
common_scalar { $$ = $1; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM class_const_name
{ $$ = Expr\ClassConstFetch[$1, $3]; }
| constant { $$ = $1; }
| '"' encaps_list '"'
{ parseEncapsed($2, '"'); $$ = Scalar\Encapsed[$2]; }
| T_START_HEREDOC encaps_list T_END_HEREDOC

View File

@ -24,8 +24,10 @@ class Autoloader
*/
static public function autoload($class) {
if (0 === strpos($class, 'PhpParser\\')) {
$fileName = dirname(__DIR__) . '/' . rtrim(strtr($class, '\\', '/'), '_') . '.php';
require $fileName;
$fileName = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
if (file_exists($fileName)) {
require $fileName;
}
} else if (0 === strpos($class, 'PHPParser_')) {
if (isset(self::$oldToNewMap[$class])) {
self::registerLegacyAliases();
@ -62,8 +64,6 @@ class Autoloader
'PHPParser_PrettyPrinter_Zend' => 'PhpParser\PrettyPrinter\Standard',
'PHPParser_Serializer' => 'PhpParser\Serializer',
'PHPParser_Serializer_XML' => 'PhpParser\Serializer\XML',
'PHPParser_Template' => 'PhpParser\Template',
'PHPParser_TemplateLoader' => 'PhpParser\TemplateLoader',
'PHPParser_Unserializer' => 'PhpParser\Unserializer',
'PHPParser_Unserializer_XML' => 'PhpParser\Unserializer\XML',

View File

@ -9,7 +9,7 @@ use PhpParser\Builder;
* _interface() and _function() in the class and are made available as class(), interface() and function()
* through __call() magic.
*
* @method Builder\Class class(string $name) Creates a class builder.
* @method Builder\Class_ class(string $name) Creates a class builder.
* @method Builder\Function_ function(string $name) Creates a function builder
* @method Builder\Interface_ interface(string $name) Creates an interface builder.
*/

View File

@ -32,10 +32,14 @@ class Lexer
* @throws Error on lexing errors (unterminated comment or unexpected character)
*/
public function startLexing($code) {
$scream = ini_set('xdebug.scream', 0);
$this->resetErrors();
$this->tokens = @token_get_all($code);
$this->handleErrors();
ini_set('xdebug.scream', $scream);
$this->code = $code; // keep the code around for __halt_compiler() handling
$this->pos = -1;
$this->line = 1;
@ -190,6 +194,11 @@ class Lexer
}
}
// HHVM uses a special token for numbers that overflow to double
if (defined('T_ONUMBER')) {
$tokenMap[T_ONUMBER] = Parser::T_DNUMBER;
}
return $tokenMap;
}
}

View File

@ -16,7 +16,7 @@ class Emulative extends \PhpParser\Lexer
const T_POW = 1002;
const T_POW_EQUAL = 1003;
const PHP_5_6 = '5.6.0beta1';
const PHP_5_6 = '5.6.0rc1';
const PHP_5_5 = '5.5.0beta1';
const PHP_5_4 = '5.4.0beta1';

View File

@ -25,6 +25,8 @@ class DNumber extends Scalar
}
/**
* @internal
*
* Parses a DNUMBER token like PHP would.
*
* @param string $str A string number

View File

@ -25,6 +25,8 @@ class LNumber extends Scalar
}
/**
* @internal
*
* Parses an LNUMBER token (dec, hex, oct and bin notations) like PHP would.
*
* @param string $str A string number

View File

@ -36,6 +36,8 @@ class String extends Scalar
}
/**
* @internal
*
* Parses a string token.
*
* @param string $str String token content
@ -60,6 +62,8 @@ class String extends Scalar
}
/**
* @internal
*
* Parses escape sequences in strings (all string types apart from single quoted).
*
* @param string $str String without quotes
@ -79,7 +83,7 @@ class String extends Scalar
);
}
public static function parseCallback($matches) {
private static function parseCallback($matches) {
$str = $matches[1];
if (isset(self::$replacements[$str])) {
@ -92,6 +96,8 @@ class String extends Scalar
}
/**
* @internal
*
* Parses a constant doc string.
*
* @param string $startToken Doc string start token content (<<<SMTHG)

View File

@ -83,6 +83,9 @@ class Class_ extends Node\Stmt
return $methods;
}
/**
* @internal
*/
public static function verifyModifier($a, $b) {
if ($a & 7 && $b & 7) {
throw new Error('Multiple access type modifiers are not allowed');

View File

@ -1,127 +0,0 @@
<?php
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
/**
* @property null|Node\Name $name Name
* @property Node[] $stmts Statements
*/
class Namespace_ extends Node\Stmt
{
protected static $specialNames = array(
'self' => true,
'parent' => true,
'static' => true,
);
/**
* Constructs a namespace node.
*
* @param null|Node\Name $name Name
* @param Node[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Name $name = null, $stmts = array(), array $attributes = array()) {
parent::__construct(
array(
'name' => $name,
'stmts' => $stmts,
),
$attributes
);
if (isset(self::$specialNames[(string) $this->name])) {
throw new Error(sprintf('Cannot use \'%s\' as namespace name', $this->name));
}
if (null !== $this->stmts) {
foreach ($this->stmts as $stmt) {
if ($stmt instanceof self) {
throw new Error('Namespace declarations cannot be nested', $stmt->getLine());
}
}
}
}
public static function postprocess(array $stmts) {
// null = not in namespace, false = semicolon style, true = bracket style
$bracketed = null;
// whether any statements that aren't allowed before a namespace declaration are encountered
// (the only valid statement currently is a declare)
$hasNotAllowedStmts = false;
// offsets for semicolon style namespaces
// (required for transplanting the following statements into their ->stmts property)
$nsOffsets = array();
foreach ($stmts as $i => $stmt) {
if ($stmt instanceof self) {
// ->stmts is null if semicolon style is used
$currentBracketed = null !== $stmt->stmts;
// if no namespace statement has been encountered yet
if (!isset($bracketed)) {
// set the namespacing style
$bracketed = $currentBracketed;
// and ensure that it isn't preceded by a not allowed statement
if ($hasNotAllowedStmts) {
throw new Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine());
}
// otherwise ensure that the style of the current namespace matches the style of
// namespaceing used before in this document
} elseif ($bracketed !== $currentBracketed) {
throw new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine());
}
// for semicolon style namespaces remember the offset
if (!$bracketed) {
$nsOffsets[] = $i;
}
// declare() and __halt_compiler() are the only valid statements outside of namespace declarations
} elseif (!$stmt instanceof Declare_
&& !$stmt instanceof HaltCompiler
) {
if (true === $bracketed) {
throw new Error('No code may exist outside of namespace {}', $stmt->getLine());
}
$hasNotAllowedStmts = true;
}
}
// if bracketed namespaces were used or no namespaces were used at all just return the
// original statements
if (!isset($bracketed) || true === $bracketed) {
return $stmts;
// for semicolon style transplant statements
} else {
// take all statements preceding the first namespace
$newStmts = array_slice($stmts, 0, $nsOffsets[0]);
// iterate over all following namespaces
for ($i = 0, $c = count($nsOffsets); $i < $c; ++$i) {
$newStmts[] = $nsStmt = $stmts[$nsOffsets[$i]];
// the last namespace takes all statements after it
if ($c === $i + 1) {
$nsStmt->stmts = array_slice($stmts, $nsOffsets[$i] + 1);
// if the last statement is __halt_compiler() put it outside the namespace
if (end($nsStmt->stmts) instanceof HaltCompiler) {
$newStmts[] = array_pop($nsStmt->stmts);
}
// and all the others take all statements between the current and the following one
} else {
$nsStmt->stmts = array_slice($stmts, $nsOffsets[$i] + 1, $nsOffsets[$i + 1] - $nsOffsets[$i] - 1);
}
}
return $newStmts;
}
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Error;
/**
* @property null|Node\Name $name Name
* @property Node[] $stmts Statements
*/
class Namespace_ extends Node\Stmt
{
protected static $specialNames = array(
'self' => true,
'parent' => true,
'static' => true,
);
/**
* Constructs a namespace node.
*
* @param null|Node\Name $name Name
* @param Node[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Name $name = null, $stmts = array(), array $attributes = array()) {
parent::__construct(
array(
'name' => $name,
'stmts' => $stmts,
),
$attributes
);
if (isset(self::$specialNames[(string) $this->name])) {
throw new Error(sprintf('Cannot use \'%s\' as namespace name', $this->name));
}
if (null !== $this->stmts) {
foreach ($this->stmts as $stmt) {
if ($stmt instanceof self) {
throw new Error('Namespace declarations cannot be nested', $stmt->getLine());
}
}
}
}
}

View File

@ -2,8 +2,12 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
use PhpParser\Node;
abstract class TraitUseAdaptation extends Stmt
/**
* @property Node\Name $trait Trait name
* @property string $method Method name
*/
abstract class TraitUseAdaptation extends Node\Stmt
{
}

View File

@ -77,6 +77,19 @@ class NameResolver extends NodeVisitorAbstract
foreach ($node->traits as &$trait) {
$trait = $this->resolveClassName($trait);
}
foreach($node->adaptations as $adaptation) {
if (null !== $adaptation->trait) {
$adaptation->trait = $this->resolveClassName($adaptation->trait);
}
if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
foreach ($adaptation->insteadof as &$insteadof) {
$insteadof = $this->resolveClassName($insteadof);
}
}
}
} elseif ($node instanceof Node\Param
&& $node->type instanceof Name
) {
@ -122,7 +135,14 @@ class NameResolver extends NodeVisitorAbstract
protected function resolveClassName(Name $name) {
// don't resolve special class names
if (in_array((string) $name, array('self', 'parent', 'static'))) {
if (in_array(strtolower($name), array('self', 'parent', 'static'))) {
if (!$name->isUnqualified()) {
throw new Error(
sprintf("'\\%s' is an invalid class name", $name->toString()),
$name->getLine()
);
}
return $name;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,367 @@
<?php
namespace PhpParser;
/*
* This parser is based on a skeleton written by Moriyoshi Koizumi, which in
* turn is based on work by Masato Bito.
*/
abstract class ParserAbstract
{
const SYMBOL_NONE = -1;
/*
* The following members will be filled with generated parsing data:
*/
/** @var int Size of $tokenToSymbol map */
protected $tokenToSymbolMapSize;
/** @var int Size of $action table */
protected $actionTableSize;
/** @var int Size of $goto table */
protected $gotoTableSize;
/** @var int Symbol number signifying an invalid token */
protected $invalidSymbol;
/** @var int Action number signifying default action */
protected $defaultAction;
/** @var int Rule number signifying that an unexpected token was encountered */
protected $unexpectedTokenRule;
protected $YY2TBLSTATE;
protected $YYNLSTATES;
/** @var array Map of lexer tokens to internal symbols */
protected $tokenToSymbol;
/** @var array Map of symbols to their names */
protected $symbolToName;
/** @var array Names of the production rules (only necessary for debugging) */
protected $productions;
/** @var array Map of states to a displacement into the $action table. The corresponding action for this
* state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
action is defaulted, i.e. $actionDefault[$state] should be used instead. */
protected $actionBase;
/** @var array Table of actions. Indexed according to $actionBase comment. */
protected $action;
/** @var array Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol
* then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */
protected $actionCheck;
/** @var array Map of states to their default action */
protected $actionDefault;
/** @var array Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this
* non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */
protected $gotoBase;
/** @var array Table of states to goto after reduction. Indexed according to $gotoBase comment. */
protected $goto;
/** @var array Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal
* then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */
protected $gotoCheck;
/** @var array Map of non-terminals to the default state to goto after their reduction */
protected $gotoDefault;
/** @var array Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for
* determining the state to goto after reduction. */
protected $ruleToNonTerminal;
/** @var array Map of rules to the length of their right-hand side, which is the number of elements that have to
* be popped from the stack(s) on reduction. */
protected $ruleToLength;
/*
* The following members are part of the parser state:
*/
/** @var Lexer Lexer that is used when parsing */
protected $lexer;
/** @var mixed Temporary value containing the result of last semantic action (reduction) */
protected $semValue;
/** @var array Semantic value stack (contains values of tokens and semantic action results) */
protected $semStack;
/** @var int Position in stacks (state stack, semantic value stack, attribute stack) */
protected $stackPos;
/**
* Creates a parser instance.
*
* @param Lexer $lexer A lexer
*/
public function __construct(Lexer $lexer) {
$this->lexer = $lexer;
}
/**
* Parses PHP code into a node tree.
*
* @param string $code The source code to parse
*
* @return Node[] Array of statements
*/
public function parse($code) {
$this->lexer->startLexing($code);
// We start off with no lookahead-token
$symbol = self::SYMBOL_NONE;
// The attributes for a node are taken from the first and last token of the node.
// From the first token only the startAttributes are taken and from the last only
// the endAttributes. Both are merged using the array union operator (+).
$startAttributes = array('startLine' => 1);
$endAttributes = array();
// In order to figure out the attributes for the starting token, we have to keep
// them in a stack
$attributeStack = array($startAttributes);
// Start off in the initial state and keep a stack of previous states
$state = 0;
$stateStack = array($state);
// Semantic value stack (contains values of tokens and semantic action results)
$this->semStack = array();
// Current position in the stack(s)
$this->stackPos = 0;
for (;;) {
//$this->traceNewState($state, $symbol);
if ($this->actionBase[$state] == 0) {
$rule = $this->actionDefault[$state];
} else {
if ($symbol === self::SYMBOL_NONE) {
// Fetch the next token id from the lexer and fetch additional info by-ref.
// The end attributes are fetched into a temporary variable and only set once the token is really
// shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
// reduced after a token was read but not yet shifted.
$tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $nextEndAttributes);
// map the lexer token id to the internally used symbols
$symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
? $this->tokenToSymbol[$tokenId]
: $this->invalidSymbol;
if ($symbol === $this->invalidSymbol) {
throw new \RangeException(sprintf(
'The lexer returned an invalid token (id=%d, value=%s)',
$tokenId, $tokenValue
));
}
$attributeStack[$this->stackPos] = $startAttributes;
//$this->traceRead($symbol);
}
$idx = $this->actionBase[$state] + $symbol;
if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol)
|| ($state < $this->YY2TBLSTATE
&& ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] == $symbol))
&& ($action = $this->action[$idx]) != $this->defaultAction) {
/*
* >= YYNLSTATES: shift and reduce
* > 0: shift
* = 0: accept
* < 0: reduce
* = -YYUNEXPECTED: error
*/
if ($action > 0) {
/* shift */
//$this->traceShift($symbol);
++$this->stackPos;
$stateStack[$this->stackPos] = $state = $action;
$this->semStack[$this->stackPos] = $tokenValue;
$attributeStack[$this->stackPos] = $startAttributes;
$endAttributes = $nextEndAttributes;
$symbol = self::SYMBOL_NONE;
if ($action < $this->YYNLSTATES)
continue;
/* $yyn >= YYNLSTATES means shift-and-reduce */
$rule = $action - $this->YYNLSTATES;
} else {
$rule = -$action;
}
} else {
$rule = $this->actionDefault[$state];
}
}
for (;;) {
if ($rule === 0) {
/* accept */
//$this->traceAccept();
return $this->semValue;
} elseif ($rule !== $this->unexpectedTokenRule) {
/* reduce */
//$this->traceReduce($rule);
try {
$this->{'reduceRule' . $rule}(
$attributeStack[$this->stackPos - $this->ruleToLength[$rule]]
+ $endAttributes
);
} catch (Error $e) {
if (-1 === $e->getRawLine()) {
$e->setRawLine($startAttributes['startLine']);
}
throw $e;
}
/* Goto - shift nonterminal */
$this->stackPos -= $this->ruleToLength[$rule];
$nonTerminal = $this->ruleToNonTerminal[$rule];
$idx = $this->gotoBase[$nonTerminal] + $stateStack[$this->stackPos];
if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] == $nonTerminal) {
$state = $this->goto[$idx];
} else {
$state = $this->gotoDefault[$nonTerminal];
}
++$this->stackPos;
$stateStack[$this->stackPos] = $state;
$this->semStack[$this->stackPos] = $this->semValue;
$attributeStack[$this->stackPos] = $startAttributes;
if ($state < $this->YYNLSTATES)
break;
/* >= YYNLSTATES means shift-and-reduce */
$rule = $state - $this->YYNLSTATES;
} else {
/* error */
if ($expected = $this->getExpectedTokens($state)) {
$expectedString = ', expecting ' . implode(' or ', $expected);
} else {
$expectedString = '';
}
throw new Error(
'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString,
$startAttributes['startLine']
);
}
}
}
}
protected function getExpectedTokens($state) {
$expected = array();
$base = $this->actionBase[$state];
foreach ($this->symbolToName as $symbol => $name) {
$idx = $base + $symbol;
if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
|| $state < $this->YY2TBLSTATE
&& ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $symbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
) {
if ($this->action[$idx] != $this->unexpectedTokenRule) {
if (count($expected) == 4) {
/* Too many expected tokens */
return array();
}
$expected[] = $name;
}
}
}
return $expected;
}
/*
* Tracing functions used for debugging the parser.
*/
protected function traceNewState($state, $symbol) {
echo '% State ' . $state
. ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
}
protected function traceRead($symbol) {
echo '% Reading ' . $this->symbolToName[$symbol] . "\n";
}
protected function traceShift($symbol) {
echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
}
protected function traceAccept() {
echo "% Accepted.\n";
}
protected function traceReduce($n) {
echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
}
/*
* Helper functions invoked by semantic actions
*/
/**
* Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions.
*
* @param Node[] $stmts
* @return Node[]
*/
protected function handleNamespaces(array $stmts) {
$style = $this->getNamespacingStyle($stmts);
if (null === $style) {
// not namespaced, nothing to do
return $stmts;
} elseif ('brace' === $style) {
// For braced namespaces we only have to check that there are no invalid statements between the namespaces
$afterFirstNamespace = false;
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());
}
}
return $stmts;
} else {
// For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
$resultStmts = array();
$targetStmts =& $resultStmts;
foreach ($stmts as $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
$stmt->stmts = array();
$targetStmts =& $stmt->stmts;
$resultStmts[] = $stmt;
} elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
// __halt_compiler() is not moved into the namespace
$resultStmts[] = $stmt;
} else {
$targetStmts[] = $stmt;
}
}
return $resultStmts;
}
}
private function getNamespacingStyle(array $stmts) {
$style = null;
$hasNotAllowedStmts = false;
foreach ($stmts as $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());
}
} elseif ($style !== $currentStyle) {
throw new Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine());
}
} elseif (!$stmt instanceof Node\Stmt\Declare_ && !$stmt instanceof Node\Stmt\HaltCompiler) {
$hasNotAllowedStmts = true;
}
}
return $style;
}
}

View File

@ -541,7 +541,7 @@ class Standard extends PrettyPrinterAbstract
public function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) {
return (null !== $node->trait ? $this->p($node->trait) . '::' : '')
. $node->method . ' as'
. (null !== $node->newModifier ? ' ' . $this->pModifiers($node->newModifier) : '')
. (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '')
. (null !== $node->newName ? ' ' . $node->newName : '')
. ';';
}

View File

@ -87,7 +87,7 @@ abstract class PrettyPrinterAbstract
public function prettyPrint(array $stmts) {
$this->preprocessNodes($stmts);
return trim(str_replace("\n" . $this->noIndentToken, "\n", $this->pStmts($stmts, false)));
return ltrim(str_replace("\n" . $this->noIndentToken, "\n", $this->pStmts($stmts, false)));
}
/**
@ -109,7 +109,7 @@ abstract class PrettyPrinterAbstract
* @return string Pretty printed statements
*/
public function prettyPrintFile(array $stmts) {
$p = $this->prettyPrint($stmts);
$p = rtrim($this->prettyPrint($stmts));
$p = preg_replace('/^\?>\n?/', '', $p, -1, $count);
$p = preg_replace('/<\?php$/', '', $p);

View File

@ -1,77 +0,0 @@
<?php
namespace PhpParser;
/**
* @deprecated
*/
class Template
{
protected $parser;
protected $template;
/**
* Creates a new code template from a template string.
*
* @param Parser $parser A parser instance
* @param string $template The template string
*/
public function __construct(Parser $parser, $template) {
$this->parser = $parser;
$this->template = $template;
}
/**
* Get the statements of the template with the passed in placeholders
* replaced.
*
* @param array $placeholders Placeholders
*
* @return Node[] Statements
*/
public function getStmts(array $placeholders) {
return $this->parser->parse(
$this->getTemplateWithPlaceholdersReplaced($placeholders)
);
}
protected function getTemplateWithPlaceholdersReplaced(array $placeholders) {
if (empty($placeholders)) {
return $this->template;
}
return strtr($this->template, $this->preparePlaceholders($placeholders));
}
/*
* Prepare the placeholders for replacement. This means that
* a) all placeholders will be surrounded with __.
* b) ucfirst/lcfirst variations of the placeholders are generated.
*
* E.g. for an input array of ['foo' => 'bar'] the result will be
* ['__foo__' => 'bar', '__Foo__' => 'Bar'].
*/
protected function preparePlaceholders(array $placeholders) {
$preparedPlaceholders = array();
foreach ($placeholders as $name => $value) {
$preparedPlaceholders['__' . $name . '__'] = $value;
if (ctype_lower($name[0])) {
$ucfirstName = ucfirst($name);
if (!isset($placeholders[$ucfirstName])) {
$preparedPlaceholders['__' . $ucfirstName . '__'] = ucfirst($value);
}
}
if (ctype_upper($name[0])) {
$lcfirstName = lcfirst($name);
if (!isset($placeholders[$lcfirstName])) {
$preparedPlaceholders['__' . $lcfirstName . '__'] = lcfirst($value);
}
}
}
return $preparedPlaceholders;
}
}

View File

@ -1,53 +0,0 @@
<?php
namespace PhpParser;
/**
* @deprecated
*/
class TemplateLoader
{
protected $parser;
protected $baseDir;
protected $suffix;
/**
* Constructs a filesystem template loader.
*
* The templates are loaded from {baseDir}/{name}{suffix}.
*
* @param Parser $parser A PHP parser instance
* @param string $baseDir The base directory to load templates from
* @param string $suffix An optional suffix to append after the template name
*/
public function __construct(Parser $parser, $baseDir, $suffix = '') {
if (!is_dir($baseDir)) {
throw new \InvalidArgumentException(
sprintf('The specified base directory "%s" does not exist', $baseDir)
);
}
$this->parser = $parser;
$this->baseDir = $baseDir;
$this->suffix = $suffix;
}
/**
* Loads the template with the specified name.
*
* @param string $name The name of template
*
* @return Template The loaded template
*/
public function load($name) {
$file = $this->baseDir . '/' . $name . $this->suffix;
if (!is_file($file)) {
throw new \InvalidArgumentException(
sprintf('The file "%s" does not exist', $file)
);
}
return new Template($this->parser, file_get_contents($file));
}
}

View File

@ -49,9 +49,8 @@ class XML implements Unserializer
}
}
protected function readNode()
{
$className = 'PHPParser_Node_' . $this->reader->localName;
protected function readNode() {
$className = $this->getClassNameFromType($this->reader->localName);
// create the node without calling it's constructor
$node = unserialize(
@ -136,4 +135,15 @@ class XML implements Unserializer
$this->reader->getAttribute('line')
);
}
protected function getClassNameFromType($type) {
$className = 'PhpParser\\Node\\' . strtr($type, '_', '\\');
if (!class_exists($className)) {
$className .= '_';
}
if (!class_exists($className)) {
throw new DomainException(sprintf('Unknown node type "%s"', $type));
}
return $className;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace PhpParser;
/* The autoloader is already active at this point, so we only check effects here. */
class AutoloaderTest extends \PHPUnit_Framework_TestCase {
public function testLegacyNames() {
$lexer = new \PHPParser_Lexer;
$parser = new \PHPParser_Parser($lexer);
$prettyPrinter = new \PHPParser_PrettyPrinter_Default;
$this->assertInstanceof('PhpParser\Lexer', $lexer);
$this->assertInstanceof('PhpParser\Parser', $parser);
$this->assertInstanceof('PhpParser\PrettyPrinter\Standard', $prettyPrinter);
}
public function testClassExists() {
$this->assertTrue(class_exists('PhpParser\NodeVisitorAbstract'));
$this->assertTrue(class_exists('PHPParser_NodeVisitor_NameResolver'));
$this->assertFalse(class_exists('PhpParser\FooBar'));
$this->assertFalse(class_exists('PHPParser_FooBar'));
}
}

View File

@ -23,7 +23,7 @@ class InterfaceTest extends \PHPUnit_Framework_TestCase
public function testEmpty() {
$contract = $this->builder->getNode();
$this->assertInstanceOf('PhpParser\Node\Stmt\Interface_', $contract);
$this->assertEquals('Contract', $contract->name);
$this->assertSame('Contract', $contract->name);
}
public function testExtending() {
@ -41,7 +41,7 @@ class InterfaceTest extends \PHPUnit_Framework_TestCase
public function testAddMethod() {
$method = new Stmt\ClassMethod('doSomething');
$contract = $this->builder->addStmt($method)->getNode();
$this->assertEquals(array($method), $contract->stmts);
$this->assertSame(array($method), $contract->stmts);
}
public function testAddConst() {
@ -49,7 +49,7 @@ class InterfaceTest extends \PHPUnit_Framework_TestCase
new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458))
));
$contract = $this->builder->addStmt($const)->getNode();
$this->assertEquals(299792458, $contract->stmts[0]->consts[0]->value->value);
$this->assertSame(299792458, $contract->stmts[0]->consts[0]->value->value);
}
public function testOrder() {

View File

@ -7,16 +7,16 @@ class CommentTest extends \PHPUnit_Framework_TestCase
public function testGetSet() {
$comment = new Comment('/* Some comment */', 1);
$this->assertEquals('/* Some comment */', $comment->getText());
$this->assertEquals('/* Some comment */', (string) $comment);
$this->assertEquals(1, $comment->getLine());
$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->assertEquals('/* Some other comment */', $comment->getText());
$this->assertEquals('/* Some other comment */', (string) $comment);
$this->assertEquals(10, $comment->getLine());
$this->assertSame('/* Some other comment */', $comment->getText());
$this->assertSame('/* Some other comment */', (string) $comment);
$this->assertSame(10, $comment->getLine());
}
/**
@ -24,7 +24,7 @@ class CommentTest extends \PHPUnit_Framework_TestCase
*/
public function testReformatting($commentText, $reformattedText) {
$comment = new Comment($commentText);
$this->assertEquals($reformattedText, $comment->getReformattedText());
$this->assertSame($reformattedText, $comment->getReformattedText());
}
public function provideTestReformatting() {

View File

@ -7,9 +7,9 @@ class ErrorTest extends \PHPUnit_Framework_TestCase
public function testConstruct() {
$error = new Error('Some error', 10);
$this->assertEquals('Some error', $error->getRawMessage());
$this->assertEquals(10, $error->getRawLine());
$this->assertEquals('Some error on line 10', $error->getMessage());
$this->assertSame('Some error', $error->getRawMessage());
$this->assertSame(10, $error->getRawLine());
$this->assertSame('Some error on line 10', $error->getMessage());
return $error;
}
@ -21,15 +21,15 @@ class ErrorTest extends \PHPUnit_Framework_TestCase
$error->setRawMessage('Some other error');
$error->setRawLine(15);
$this->assertEquals('Some other error', $error->getRawMessage());
$this->assertEquals(15, $error->getRawLine());
$this->assertEquals('Some other error on line 15', $error->getMessage());
$this->assertSame('Some other error', $error->getRawMessage());
$this->assertSame(15, $error->getRawLine());
$this->assertSame('Some other error on line 15', $error->getMessage());
}
public function testUnknownLine() {
$error = new Error('Some error');
$this->assertEquals(-1, $error->getRawLine());
$this->assertEquals('Some error on unknown line', $error->getMessage());
$this->assertSame(-1, $error->getRawLine());
$this->assertSame('Some error on unknown line', $error->getMessage());
}
}

View File

@ -19,8 +19,8 @@ class EmulativeTest extends \PHPUnit_Framework_TestCase
public function testReplaceKeywords($keyword, $expectedToken) {
$this->lexer->startLexing('<?php ' . $keyword);
$this->assertEquals($expectedToken, $this->lexer->getNextToken());
$this->assertEquals(0, $this->lexer->getNextToken());
$this->assertSame($expectedToken, $this->lexer->getNextToken());
$this->assertSame(0, $this->lexer->getNextToken());
}
/**
@ -29,9 +29,9 @@ class EmulativeTest extends \PHPUnit_Framework_TestCase
public function testNoReplaceKeywordsAfterObjectOperator($keyword) {
$this->lexer->startLexing('<?php ->' . $keyword);
$this->assertEquals(Parser::T_OBJECT_OPERATOR, $this->lexer->getNextToken());
$this->assertEquals(Parser::T_STRING, $this->lexer->getNextToken());
$this->assertEquals(0, $this->lexer->getNextToken());
$this->assertSame(Parser::T_OBJECT_OPERATOR, $this->lexer->getNextToken());
$this->assertSame(Parser::T_STRING, $this->lexer->getNextToken());
$this->assertSame(0, $this->lexer->getNextToken());
}
public function provideTestReplaceKeywords() {
@ -62,10 +62,10 @@ class EmulativeTest extends \PHPUnit_Framework_TestCase
foreach ($expectedTokens as $expectedToken) {
list($expectedTokenType, $expectedTokenText) = $expectedToken;
$this->assertEquals($expectedTokenType, $this->lexer->getNextToken($text));
$this->assertEquals($expectedTokenText, $text);
$this->assertSame($expectedTokenType, $this->lexer->getNextToken($text));
$this->assertSame($expectedTokenText, $text);
}
$this->assertEquals(0, $this->lexer->getNextToken());
$this->assertSame(0, $this->lexer->getNextToken());
}
/**
@ -75,9 +75,9 @@ class EmulativeTest extends \PHPUnit_Framework_TestCase
$stringifiedToken = '"' . addcslashes($code, '"\\') . '"';
$this->lexer->startLexing('<?php ' . $stringifiedToken);
$this->assertEquals(Parser::T_CONSTANT_ENCAPSED_STRING, $this->lexer->getNextToken($text));
$this->assertEquals($stringifiedToken, $text);
$this->assertEquals(0, $this->lexer->getNextToken());
$this->assertSame(Parser::T_CONSTANT_ENCAPSED_STRING, $this->lexer->getNextToken($text));
$this->assertSame($stringifiedToken, $text);
$this->assertSame(0, $this->lexer->getNextToken());
}
public function provideTestLexNewFeatures() {

View File

@ -18,7 +18,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
try {
$this->lexer->startLexing($code);
} catch (Error $e) {
$this->assertEquals($message, $e->getMessage());
$this->assertSame($message, $e->getMessage());
return;
}
@ -42,8 +42,8 @@ class LexerTest extends \PHPUnit_Framework_TestCase
while ($id = $this->lexer->getNextToken($value, $startAttributes, $endAttributes)) {
$token = array_shift($tokens);
$this->assertEquals($token[0], $id);
$this->assertEquals($token[1], $value);
$this->assertSame($token[0], $id);
$this->assertSame($token[1], $value);
$this->assertEquals($token[2], $startAttributes);
$this->assertEquals($token[3], $endAttributes);
}
@ -131,8 +131,8 @@ class LexerTest extends \PHPUnit_Framework_TestCase
while (Parser::T_HALT_COMPILER !== $this->lexer->getNextToken());
$this->assertEquals($this->lexer->handleHaltCompiler(), $remaining);
$this->assertEquals(0, $this->lexer->getNextToken());
$this->assertSame($this->lexer->handleHaltCompiler(), $remaining);
$this->assertSame(0, $this->lexer->getNextToken());
}
public function provideTestHaltCompiler() {

View File

@ -6,93 +6,93 @@ class NameTest extends \PHPUnit_Framework_TestCase
{
public function testConstruct() {
$name = new Name(array('foo', 'bar'));
$this->assertEquals(array('foo', 'bar'), $name->parts);
$this->assertSame(array('foo', 'bar'), $name->parts);
$name = new Name('foo\bar');
$this->assertEquals(array('foo', 'bar'), $name->parts);
$this->assertSame(array('foo', 'bar'), $name->parts);
}
public function testGet() {
$name = new Name('foo');
$this->assertEquals('foo', $name->getFirst());
$this->assertEquals('foo', $name->getLast());
$this->assertSame('foo', $name->getFirst());
$this->assertSame('foo', $name->getLast());
$name = new Name('foo\bar');
$this->assertEquals('foo', $name->getFirst());
$this->assertEquals('bar', $name->getLast());
$this->assertSame('foo', $name->getFirst());
$this->assertSame('bar', $name->getLast());
}
public function testToString() {
$name = new Name('foo\bar');
$this->assertEquals('foo\bar', (string) $name);
$this->assertEquals('foo\bar', $name->toString());
$this->assertEquals('foo_bar', $name->toString('_'));
$this->assertSame('foo\bar', (string) $name);
$this->assertSame('foo\bar', $name->toString());
$this->assertSame('foo_bar', $name->toString('_'));
}
public function testSet() {
$name = new Name('foo');
$name->set('foo\bar');
$this->assertEquals('foo\bar', $name->toString());
$this->assertSame('foo\bar', $name->toString());
$name->set(array('foo', 'bar'));
$this->assertEquals('foo\bar', $name->toString());
$this->assertSame('foo\bar', $name->toString());
$name->set(new Name('foo\bar'));
$this->assertEquals('foo\bar', $name->toString());
$this->assertSame('foo\bar', $name->toString());
}
public function testSetFirst() {
$name = new Name('foo');
$name->setFirst('bar');
$this->assertEquals('bar', $name->toString());
$this->assertSame('bar', $name->toString());
$name->setFirst('A\B');
$this->assertEquals('A\B', $name->toString());
$this->assertSame('A\B', $name->toString());
$name->setFirst('C');
$this->assertEquals('C\B', $name->toString());
$this->assertSame('C\B', $name->toString());
$name->setFirst('D\E');
$this->assertEquals('D\E\B', $name->toString());
$this->assertSame('D\E\B', $name->toString());
}
public function testSetLast() {
$name = new Name('foo');
$name->setLast('bar');
$this->assertEquals('bar', $name->toString());
$this->assertSame('bar', $name->toString());
$name->setLast('A\B');
$this->assertEquals('A\B', $name->toString());
$this->assertSame('A\B', $name->toString());
$name->setLast('C');
$this->assertEquals('A\C', $name->toString());
$this->assertSame('A\C', $name->toString());
$name->setLast('D\E');
$this->assertEquals('A\D\E', $name->toString());
$this->assertSame('A\D\E', $name->toString());
}
public function testAppend() {
$name = new Name('foo');
$name->append('bar');
$this->assertEquals('foo\bar', $name->toString());
$this->assertSame('foo\bar', $name->toString());
$name->append('bar\foo');
$this->assertEquals('foo\bar\bar\foo', $name->toString());
$this->assertSame('foo\bar\bar\foo', $name->toString());
}
public function testPrepend() {
$name = new Name('foo');
$name->prepend('bar');
$this->assertEquals('bar\foo', $name->toString());
$this->assertSame('bar\foo', $name->toString());
$name->prepend('foo\bar');
$this->assertEquals('foo\bar\bar\foo', $name->toString());
$this->assertSame('foo\bar\bar\foo', $name->toString());
}
public function testIs() {

View File

@ -8,7 +8,7 @@ class StringTest extends \PHPUnit_Framework_TestCase
* @dataProvider provideTestParseEscapeSequences
*/
public function testParseEscapeSequences($expected, $string, $quote) {
$this->assertEquals(
$this->assertSame(
$expected,
String::parseEscapeSequences($string, $quote)
);
@ -18,7 +18,7 @@ class StringTest extends \PHPUnit_Framework_TestCase
* @dataProvider provideTestParse
*/
public function testCreate($expected, $string) {
$this->assertEquals(
$this->assertSame(
$expected,
String::parse($string)
);

View File

@ -37,6 +37,6 @@ class ClassTest extends \PHPUnit_Framework_TestCase
)
));
$this->assertEquals($methods, $class->getMethods());
$this->assertSame($methods, $class->getMethods());
}
}

View File

@ -25,13 +25,13 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
'PhpParser_Node_Dummy'
);
$this->assertEquals('Dummy', $node->getType());
$this->assertEquals(array('subNode'), $node->getSubNodeNames());
$this->assertEquals(10, $node->getLine());
$this->assertEquals('/** doc comment */', $node->getDocComment());
$this->assertEquals('value', $node->subNode);
$this->assertSame('Dummy', $node->getType());
$this->assertSame(array('subNode'), $node->getSubNodeNames());
$this->assertSame(10, $node->getLine());
$this->assertSame('/** doc comment */', $node->getDocComment()->getText());
$this->assertSame('value', $node->subNode);
$this->assertTrue(isset($node->subNode));
$this->assertEquals($attributes, $node->getAttributes());
$this->assertSame($attributes, $node->getAttributes());
return $node;
}
@ -40,7 +40,7 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
* @depends testConstruct
*/
public function testGetDocComment(Node $node) {
$this->assertEquals('/** doc comment */', $node->getDocComment());
$this->assertSame('/** doc comment */', $node->getDocComment()->getText());
array_pop($node->getAttribute('comments')); // remove doc comment
$this->assertNull($node->getDocComment());
array_pop($node->getAttribute('comments')); // remove comment
@ -53,16 +53,16 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
public function testChange(Node $node) {
// change of line
$node->setLine(15);
$this->assertEquals(15, $node->getLine());
$this->assertSame(15, $node->getLine());
// direct modification
$node->subNode = 'newValue';
$this->assertEquals('newValue', $node->subNode);
$this->assertSame('newValue', $node->subNode);
// indirect modification
$subNode =& $node->subNode;
$subNode = 'newNewValue';
$this->assertEquals('newNewValue', $node->subNode);
$this->assertSame('newNewValue', $node->subNode);
// removal
unset($node->subNode);
@ -77,18 +77,18 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
$node->setAttribute('key', 'value');
$this->assertTrue($node->hasAttribute('key'));
$this->assertEquals('value', $node->getAttribute('key'));
$this->assertSame('value', $node->getAttribute('key'));
$this->assertFalse($node->hasAttribute('doesNotExist'));
$this->assertNull($node->getAttribute('doesNotExist'));
$this->assertEquals('default', $node->getAttribute('doesNotExist', 'default'));
$this->assertSame('default', $node->getAttribute('doesNotExist', 'default'));
$node->setAttribute('null', null);
$this->assertTrue($node->hasAttribute('null'));
$this->assertNull($node->getAttribute('null'));
$this->assertNull($node->getAttribute('null', 'default'));
$this->assertEquals(
$this->assertSame(
array(
'key' => 'value',
'null' => null,

View File

@ -6,12 +6,12 @@ class NodeDumperTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideTestDump
* @covers NodeDumper::dump
* @covers PhpParser\NodeDumper::dump
*/
public function testDump($node, $dump) {
$dumper = new NodeDumper;
$this->assertEquals($dump, $dumper->dump($node));
$this->assertSame($dump, $dumper->dump($node));
}
public function provideTestDump() {

View File

@ -6,11 +6,12 @@ use PhpParser;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
class NameResolverTest extends \PHPUnit_Framework_TestCase
{
/**
* @covers NameResolver
* @covers PhpParser\NodeVisitor\NameResolver
*/
public function testResolveNames() {
$code = <<<'EOC'
@ -132,22 +133,26 @@ EOC;
$stmts = $parser->parse($code);
$stmts = $traverser->traverse($stmts);
$this->assertEquals($expectedCode, $prettyPrinter->prettyPrint($stmts));
$this->assertSame($expectedCode, $prettyPrinter->prettyPrint($stmts));
}
/**
* @covers NameResolver
* @covers PhpParser\NodeVisitor\NameResolver
*/
public function testResolveLocations() {
$code = <<<'EOC'
<?php
namespace NS;
class A extends B implements C {
use A;
class A extends B implements C, D {
use E, F, G {
f as private g;
E::h as i;
E::j insteadof F, G;
}
}
interface A extends C {
interface A extends C, D {
public function a(A $a);
}
@ -169,11 +174,15 @@ EOC;
$expectedCode = <<<'EOC'
namespace NS;
class A extends \NS\B implements \NS\C
class A extends \NS\B implements \NS\C, \NS\D
{
use \NS\A;
use \NS\E, \NS\F, \NS\G {
f as private g;
\NS\E::h as i;
\NS\E::j insteadof \NS\F, \NS\G;
}
}
interface A extends \NS\C
interface A extends \NS\C, \NS\D
{
public function a(\NS\A $a);
}
@ -199,7 +208,7 @@ EOC;
$stmts = $parser->parse($code);
$stmts = $traverser->traverse($stmts);
$this->assertEquals($expectedCode, $prettyPrinter->prettyPrint($stmts));
$this->assertSame($expectedCode, $prettyPrinter->prettyPrint($stmts));
}
public function testNoResolveSpecialName() {
@ -233,14 +242,14 @@ EOC;
$stmts = $traverser->traverse($stmts);
$this->assertEquals('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
$this->assertEquals('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
$this->assertEquals('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
$this->assertEquals('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
$this->assertEquals('A', (string) $stmts[1]->stmts[0]->namespacedName);
$this->assertEquals('B', (string) $stmts[1]->stmts[1]->namespacedName);
$this->assertEquals('C', (string) $stmts[1]->stmts[2]->namespacedName);
$this->assertEquals('D', (string) $stmts[1]->stmts[3]->consts[0]->namespacedName);
$this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
$this->assertSame('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
$this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
$this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
$this->assertSame('A', (string) $stmts[1]->stmts[0]->namespacedName);
$this->assertSame('B', (string) $stmts[1]->stmts[1]->namespacedName);
$this->assertSame('C', (string) $stmts[1]->stmts[2]->namespacedName);
$this->assertSame('D', (string) $stmts[1]->stmts[3]->consts[0]->namespacedName);
}
public function testAddTraitNamespacedName() {
@ -253,22 +262,22 @@ EOC;
$stmts = $traverser->traverse($stmts);
$this->assertEquals('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
$this->assertEquals('A', (string) $stmts[1]->stmts[0]->namespacedName);
$this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
$this->assertSame('A', (string) $stmts[1]->stmts[0]->namespacedName);
}
/**
* @dataProvider provideTestAlreadyInUseError
* @dataProvider provideTestError
*/
public function testAlreadyInUseError(Stmt\Use_ $use, $errorMsg) {
public function testError(Node $stmt, $errorMsg) {
$this->setExpectedException('PhpParser\Error', $errorMsg);
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$traverser->traverse(array($use));
$traverser->traverse(array($stmt));
}
public function provideTestAlreadyInUseError() {
public function provideTestError() {
return array(
array(
new Stmt\Use_(array(
@ -291,6 +300,22 @@ EOC;
), Stmt\Use_::TYPE_CONSTANT),
'Cannot use const C\D as B because the name is already in use on line 2'
),
array(
new Expr\New_(new Name\FullyQualified('self', array('startLine' => 3))),
"'\\self' is an invalid class name on line 3"
),
array(
new Expr\New_(new Name\Relative('self', array('startLine' => 3))),
"'\\self' is an invalid class name on line 3"
),
array(
new Expr\New_(new Name\FullyQualified('PARENT', array('startLine' => 3))),
"'\\PARENT' is an invalid class name on line 3"
),
array(
new Expr\New_(new Name\Relative('STATIC', array('startLine' => 3))),
"'\\STATIC' is an invalid class name on line 3"
),
);
}
@ -312,6 +337,37 @@ EOC;
$stmts = $traverser->traverse($stmts);
$stmt = $stmts[0];
$this->assertEquals(array('Bar', 'Baz'), $stmt->stmts[1]->expr->class->parts);
$this->assertSame(array('Bar', 'Baz'), $stmt->stmts[1]->expr->class->parts);
}
public function testSpecialClassNamesAreCaseInsensitive() {
$source = <<<'EOC'
<?php
namespace Foo;
class Bar
{
public static function method()
{
SELF::method();
PARENT::method();
STATIC::method();
}
}
EOC;
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$stmts = $parser->parse($source);
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$stmts = $traverser->traverse($stmts);
$classStmt = $stmts[0];
$methodStmt = $classStmt->stmts[0]->stmts[0];
$this->assertSame('SELF', (string)$methodStmt->stmts[0]->class);
$this->assertSame('PARENT', (string)$methodStmt->stmts[1]->class);
$this->assertSame('STATIC', (string)$methodStmt->stmts[2]->class);
}
}

View File

@ -14,7 +14,7 @@ class ParserTest extends CodeTestAbstract
$dumper = new NodeDumper;
$stmts = $parser->parse($code);
$this->assertEquals(
$this->assertSame(
$this->canonicalize($dump),
$this->canonicalize($dumper->dump($stmts)),
$name
@ -36,7 +36,7 @@ class ParserTest extends CodeTestAbstract
$this->fail(sprintf('"%s": Expected Error', $name));
} catch (Error $e) {
$this->assertEquals($msg, $e->getMessage(), $name);
$this->assertSame($msg, $e->getMessage(), $name);
}
}

View File

@ -11,7 +11,7 @@ class PrettyPrinterTest extends CodeTestAbstract
$prettyPrinter = new PrettyPrinter\Standard;
$stmts = $parser->parse($code);
$this->assertEquals(
$this->assertSame(
$this->canonicalize($dump),
$this->canonicalize($prettyPrinter->$method($stmts)),
$name
@ -20,7 +20,7 @@ class PrettyPrinterTest extends CodeTestAbstract
/**
* @dataProvider provideTestPrettyPrint
* @covers PrettyPrinter\Standard<extended>
* @covers PhpParser\PrettyPrinter\Standard<extended>
*/
public function testPrettyPrint($name, $code, $dump) {
$this->doTestPrettyPrintMethod('prettyPrint', $name, $code, $dump);
@ -28,7 +28,7 @@ class PrettyPrinterTest extends CodeTestAbstract
/**
* @dataProvider provideTestPrettyPrintFile
* @covers PrettyPrinter\Standard<extended>
* @covers PhpParser\PrettyPrinter\Standard<extended>
*/
public function testPrettyPrintFile($name, $code, $dump) {
$this->doTestPrettyPrintMethod('prettyPrintFile', $name, $code, $dump);

View File

@ -7,7 +7,7 @@ use PhpParser;
class XMLTest extends \PHPUnit_Framework_TestCase
{
/**
* @covers XML<extended>
* @covers PhpParser\Serializer\XML<extended>
*/
public function testSerialize() {
$code = <<<CODE

View File

@ -1,50 +0,0 @@
<?php
namespace PhpParser;
class TemplateLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoadWithoutSuffix() {
$templateLoader = new TemplateLoader(
new Parser(new Lexer),
__DIR__
);
// load this file as a template, as we don't really care about the contents
$template = $templateLoader->load('TemplateLoaderTest.php');
$this->assertInstanceOf('PhpParser\Template', $template);
}
public function testLoadWithSuffix() {
$templateLoader = new TemplateLoader(
new Parser(new Lexer),
__DIR__, '.php'
);
// load this file as a template, as we don't really care about the contents
$template = $templateLoader->load('TemplateLoaderTest');
$this->assertInstanceOf('PhpParser\Template', $template);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testNonexistentBaseDirectoryError() {
new TemplateLoader(
new Parser(new Lexer),
__DIR__ . '/someDirectoryThatDoesNotExist'
);
}
/**
* @expectedException \InvalidArgumentException
*/
public function testNonexistentFileError() {
$templateLoader = new TemplateLoader(
new Parser(new Lexer),
__DIR__
);
$templateLoader->load('SomeTemplateThatDoesNotExist');
}
}

View File

@ -1,61 +0,0 @@
<?php
namespace PhpParser;
class TemplateTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideTestPlaceholderReplacement
* @covers Template
*/
public function testPlaceholderReplacement($templateCode, $placeholders, $expectedPrettyPrint) {
$parser = new Parser(new Lexer);
$prettyPrinter = new PrettyPrinter\Standard;
$template = new Template($parser, $templateCode);
$this->assertEquals(
$expectedPrettyPrint,
$prettyPrinter->prettyPrint($template->getStmts($placeholders))
);
}
public function provideTestPlaceholderReplacement() {
return array(
array(
'<?php $__name__ + $__Name__;',
array('name' => 'foo'),
'$foo + $Foo;'
),
array(
'<?php $__name__ + $__Name__;',
array('Name' => 'Foo'),
'$foo + $Foo;'
),
array(
'<?php $__name__ + $__Name__;',
array('name' => 'foo', 'Name' => 'Bar'),
'$foo + $Bar;'
),
array(
'<?php $__name__ + $__Name__;',
array('Name' => 'Bar', 'name' => 'foo'),
'$foo + $Bar;'
),
array(
'<?php $prefix__Name__Suffix;',
array('name' => 'infix'),
'$prefixInfixSuffix;'
),
array(
'<?php $___name___;',
array('name' => 'foo'),
'$_foo_;'
),
array(
'<?php $foobar;',
array(),
'$foobar;'
),
);
}
}

View File

@ -46,7 +46,7 @@ XML;
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<AST xmlns:node="http://nikic.github.com/PHPParser/XML/node">
<node:Scalar_ClassConst />
<node:Scalar_MagicConst_Class />
</AST>
XML;
@ -141,6 +141,10 @@ XML;
'<node:Scalar_String><subNode:value/></node:Scalar_String>',
'Expected node or scalar'
),
array(
'<node:Foo><subNode:value/></node:Foo>',
'Unknown node type "Foo"'
),
);
}
}

View File

@ -31,6 +31,14 @@ const T_24 = 1 == "1";
const T_25 = 1 + 2 * 3;
const T_26 = "1" + 2 + "3";
const T_27 = 2 ** 3;
const T_28 = [1, 2, 3][1];
const T_29 = 12 - 13;
const T_30 = 12 ^ 13;
const T_31 = 12 & 13;
const T_32 = 12 | 13;
const T_33 = 12 % 3;
const T_34 = 100 >> 4;
const T_35 = !false;
-----
array(
0: Stmt_Const(
@ -467,4 +475,147 @@ array(
)
)
)
27: Stmt_Const(
consts: array(
0: Const(
name: T_28
value: Expr_ArrayDimFetch(
var: Expr_Array(
items: array(
0: Expr_ArrayItem(
key: null
value: Scalar_LNumber(
value: 1
)
byRef: false
)
1: Expr_ArrayItem(
key: null
value: Scalar_LNumber(
value: 2
)
byRef: false
)
2: Expr_ArrayItem(
key: null
value: Scalar_LNumber(
value: 3
)
byRef: false
)
)
)
dim: Scalar_LNumber(
value: 1
)
)
)
)
)
28: Stmt_Const(
consts: array(
0: Const(
name: T_29
value: Expr_BinaryOp_Minus(
left: Scalar_LNumber(
value: 12
)
right: Scalar_LNumber(
value: 13
)
)
)
)
)
29: Stmt_Const(
consts: array(
0: Const(
name: T_30
value: Expr_BinaryOp_BitwiseXor(
left: Scalar_LNumber(
value: 12
)
right: Scalar_LNumber(
value: 13
)
)
)
)
)
30: Stmt_Const(
consts: array(
0: Const(
name: T_31
value: Expr_BinaryOp_BitwiseAnd(
left: Scalar_LNumber(
value: 12
)
right: Scalar_LNumber(
value: 13
)
)
)
)
)
31: Stmt_Const(
consts: array(
0: Const(
name: T_32
value: Expr_BinaryOp_BitwiseOr(
left: Scalar_LNumber(
value: 12
)
right: Scalar_LNumber(
value: 13
)
)
)
)
)
32: Stmt_Const(
consts: array(
0: Const(
name: T_33
value: Expr_BinaryOp_Mod(
left: Scalar_LNumber(
value: 12
)
right: Scalar_LNumber(
value: 3
)
)
)
)
)
33: Stmt_Const(
consts: array(
0: Const(
name: T_34
value: Expr_BinaryOp_ShiftRight(
left: Scalar_LNumber(
value: 100
)
right: Scalar_LNumber(
value: 4
)
)
)
)
)
34: Stmt_Const(
consts: array(
0: Const(
name: T_35
value: Expr_BooleanNot(
expr: Expr_ConstFetch(
name: Name(
parts: array(
0: false
)
)
)
)
)
)
)
)

View File

@ -10,6 +10,10 @@ Array/string dereferencing
array(1, 2, 3)[2];
array(1, 2, 3)[2][0][0];
FOO[0];
Foo::BAR[1];
$foo::BAR[2][1][0];
-----
array(
0: Expr_ArrayDimFetch(
@ -178,4 +182,50 @@ array(
value: 0
)
)
6: Expr_ArrayDimFetch(
var: Expr_ConstFetch(
name: Name(
parts: array(
0: FOO
)
)
)
dim: Scalar_LNumber(
value: 0
)
)
7: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Name(
parts: array(
0: Foo
)
)
name: BAR
)
dim: Scalar_LNumber(
value: 1
)
)
8: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ArrayDimFetch(
var: Expr_ClassConstFetch(
class: Expr_Variable(
name: foo
)
name: BAR
)
dim: Scalar_LNumber(
value: 2
)
)
dim: Scalar_LNumber(
value: 1
)
)
dim: Scalar_LNumber(
value: 0
)
)
)

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