Compare commits

...

38 Commits

Author SHA1 Message Date
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
83 changed files with 2507 additions and 2623 deletions

View File

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

View File

@ -1,8 +1,40 @@
Version 1.0.0-dev Version 1.0.1-dev
----------------- -----------------
Nothing yet. Nothing yet.
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) Version 1.0.0-beta1 (27.03.2014)
-------------------------------- --------------------------------
@ -45,7 +77,7 @@ Version 1.0.0-beta1 (27.03.2014)
* Added `Scalar\MagicConstant->getName()`, which returns the name of the magic constant (e.g. `__CLASS__`). * 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 * [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. and - as far as I know - nobody is using it.

View File

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

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

@ -0,0 +1,132 @@
<?php
require __DIR__ . '/../lib/bootstrap.php';
ini_set('xdebug.max_nesting_level', 2000);
/* The fancy var_dump function provided by XDebug will cut off the output way too
* early to be of use. */
ini_set('xdebug.overload_var_dump', 0);
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 (!file_exists($file)) {
die("File $file does not exist.\n");
}
echo "====> File $file:\n";
$code = file_get_contents($file);
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 ...]
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 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? 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] 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. (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 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 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. 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? 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 The parser uses a PHP 5.6 compliant grammar, which is backwards compatible with all PHP version from PHP 5.2
and PHP 5.2 (and maybe older). 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 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 version it runs on), additionally a wrapper for emulating new tokens from 5.3, 5.4, 5.5 and 5.6 is provided.
allows to parse PHP 5.5 source code running on PHP 5.2, for example. This emulation is very hacky and not his allows to parse PHP 5.6 source code running on PHP 5.3, for example. This emulation is very hacky and not
yet perfect, but it should work well on any sane code. perfect, but it should work well on any sane code.
What output does it produce? 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!`. with the values `Hi` and `World!`.
You can also see that the AST does not contain any whitespace information (but most comments are saved). You can also see that the AST does not contain any whitespace information (but most comments are saved).

View File

@ -3,11 +3,6 @@ Installation
There are multiple ways to include the PHP parser into your project: 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 Installing via Composer
----------------------- -----------------------
@ -15,7 +10,7 @@ Create a `composer.json` file in your project root and use it to define your dep
{ {
"require": { "require": {
"nikic/php-parser": "1.0.0-beta1" "nikic/php-parser": "~1.0.0"
} }
} }
@ -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 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 [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 Bootstrapping
------------- -------------
The library needs to register a class autoloader; this is done by including the The library needs to register a class autoloader. You can either use the ''vendor/autoload.php'' file generated by
`bootstrap.php` file: Composer or by including the bundled `lib/bootstrap.php` file:
```php ```php
<?php <?php
require 'path/to/PHP-Parser/lib/bootstrap.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: 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 Parsing
------- -------
In order to parse some source code you first have to create a `PhpParser\Parser` object (which 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 needs to be passed a `PhpParser\Lexer` instance:
tags) to the `parse` method. If a syntax error is encountered `PhpParser\Error` is thrown, so this
exception should be `catch`ed. ```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
<?php <?php
$code = '<?php // some code'; $code = '<?php // some code';
$parser = new PhpParser\Parser(new PhpParser\Lexer); $parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
try { try {
$stmts = $parser->parse($code); $stmts = $parser->parse($code);
// $stmts is an array of statement nodes
} catch (PhpParser\Error $e) { } catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage(); echo 'Parse Error: ', $e->getMessage();
} }
``` ```
The `parse` method will return an array of statement nodes (`$stmts`). A parser instance can be reused to parse multiple files.
### 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.
Node tree 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 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 `$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`. 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 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 <?php
$code = "<?php echo 'Hi ', hi\\getTarget();"; $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; $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
try { try {
@ -143,10 +152,10 @@ try {
->exprs // sub expressions ->exprs // sub expressions
[0] // the first of them (the string node) [0] // the first of them (the string node)
->value // it's value, i.e. 'Hi ' ->value // it's value, i.e. 'Hi '
= 'Hallo '; // change to 'Hallo ' = 'Hello '; // change to 'Hello '
// pretty print // pretty print
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts); $code = $prettyPrinter->prettyPrint($stmts);
echo $code; echo $code;
} catch (PhpParser\Error $e) { } catch (PhpParser\Error $e) {
@ -156,7 +165,7 @@ try {
The above code will output: 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 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()`. 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 The `prettyPrint()` method pretty prints a statements array. It is also possible to pretty print only a
single expression using `prettyPrintExpr()`. 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 Node traversation
----------------- -----------------
@ -177,9 +189,8 @@ structure of a program using this `PhpParser\NodeTraverser` looks like this:
```php ```php
<?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; $traverser = new PhpParser\NodeTraverser;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard; $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
@ -187,6 +198,8 @@ $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser->addVisitor(new MyNodeVisitor); $traverser->addVisitor(new MyNodeVisitor);
try { try {
$code = file_get_contents($fileName);
// parse // parse
$stmts = $parser->parse($code); $stmts = $parser->parse($code);
@ -194,7 +207,7 @@ try {
$stmts = $traverser->traverse($stmts); $stmts = $traverser->traverse($stmts);
// pretty print // pretty print
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts); $code = $prettyPrinter->prettyPrintFile($stmts);
echo $code; echo $code;
} catch (PhpParser\Error $e) { } 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
<?php <?php
use PhpParser\Node;
class MyNodeVisitor extends PhpParser\NodeVisitorAbstract class MyNodeVisitor extends PhpParser\NodeVisitorAbstract
{ {
public function leaveNode(PhpParser\Node $node) { public function leaveNode(Node $node) {
if ($node instanceof PhpParser\Node\Scalar\String) { if ($node instanceof Node\Scalar\String) {
$node->value = 'foo'; $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'`. 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: methods:
public function beforeTraverse(array $nodes); 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. 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 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 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` values:
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 If `false` is returned the current node will be removed from the parent array. If an array is returned
`array(A, X, Y, Z, C)`. 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` 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. 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
<?php <?php
const IN_DIR = '/some/path'; $inDir = '/some/path';
const OUT_DIR = '/some/other/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); $parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$traverser = new PhpParser\NodeTraverser; $traverser = new PhpParser\NodeTraverser;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard; $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 $traverser->addVisitor(new NodeVisitor\NamespaceConverter); // our own node visitor
// iterate over all .php files in the directory // 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$/'); $files = new RegexIterator($files, '/\.php$/');
foreach ($files as $file) { foreach ($files as $file) {
@ -307,11 +322,11 @@ foreach ($files as $file) {
$stmts = $traverser->traverse($stmts); $stmts = $traverser->traverse($stmts);
// pretty print // pretty print
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts); $code = $prettyPrinter->prettyPrintFile($stmts);
// write the converted file to the target directory // write the converted file to the target directory
file_put_contents( file_put_contents(
substr_replace($file->getPathname(), OUT_DIR, 0, strlen(IN_DIR)), substr_replace($file->getPathname(), $outDir, 0, strlen($inDir)),
$code $code
); );
} catch (PhpParser\Error $e) { } 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. is convert `A\\B` style names to `A_B` style ones.
```php ```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 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 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 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. 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 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 only the shortname (i.e. the last part of the name), but they need to contain the complete name inclduing
name: the namespace prefix:
```php ```php
<?php <?php

View File

@ -1,7 +1,7 @@
Other node tree representations 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 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 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. `PhpParser\NodeDumper`. This can be used for debugging.
```php ```php
<?php <?php
$code = <<<'CODE' $code = <<<'CODE'
<?php <?php
function printLine($msg) {
echo $msg, "\n";
}
printLine('Hallo World!!!'); function printLine($msg) {
echo $msg, "\n";
}
printLine('Hello World!!!');
CODE; CODE;
$parser = new PhpParser\Parser(new PhpParser\Lexer); $parser = new PhpParser\Parser(new PhpParser\Lexer);
$nodeDumper = new PhpParser\NodeDumper; $nodeDumper = new PhpParser\NodeDumper;
try { try {
$stmts = $parser->parse($code); $stmts = $parser->parse($code);
echo '<pre>' . htmlspecialchars($nodeDumper->dump($stmts)) . '</pre>'; echo $nodeDumper->dump($stmts), "\n";
} catch (PhpParser\Error $e) { } catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage(); 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( array(
@ -77,7 +78,7 @@ array(
args: array( args: array(
0: Arg( 0: Arg(
value: Scalar_String( value: Scalar_String(
value: Hallo World!!! value: Hello World!!!
) )
byRef: false byRef: false
) )
@ -97,20 +98,21 @@ interfacing with other languages and applications or for doing transformation us
<?php <?php
$code = <<<'CODE' $code = <<<'CODE'
<?php <?php
function printLine($msg) {
echo $msg, "\n";
}
printLine('Hallo World!!!'); function printLine($msg) {
echo $msg, "\n";
}
printLine('Hello World!!!');
CODE; CODE;
$parser = new PhpParser\Parser(new PhpParser\Lexer); $parser = new PhpParser\Parser(new PhpParser\Lexer);
$serializer = new PhpParser\Serializer\XML; $serializer = new PhpParser\Serializer\XML;
try { try {
$stmts = $parser->parse($code); $stmts = $parser->parse($code);
echo '<pre>' . htmlspecialchars($serializer->serialize($stmts)) . '</pre>'; echo $serializer->serialize($stmts);
} catch (PhpParser\Error $e) { } catch (PhpParser\Error $e) {
echo 'Parse Error: ', $e->getMessage(); echo 'Parse Error: ', $e->getMessage();
} }
@ -185,7 +187,7 @@ Produces:
<subNode:value> <subNode:value>
<node:Scalar_String line="6"> <node:Scalar_String line="6">
<subNode:value> <subNode:value>
<scalar:string>Hallo World!!!</scalar:string> <scalar:string>Hello World!!!</scalar:string>
</subNode:value> </subNode:value>
</node:Scalar_String> </node:Scalar_String>
</subNode:value> </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 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 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: classes, interfaces, methods, functions, parameters and properties. The builders allow creating node trees using a
fluid interface, instead of instantiating all nodes manually.
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.
Here is an example: 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

@ -42,7 +42,7 @@ getNextToken
------------ ------------
`getNextToken` returns the ID of the next token and sets some additional information in the three variables which it `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. accepts by-ref. If no more tokens are available it must 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` 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. etc in the parser.

View File

@ -1,9 +1,9 @@
<?php <?php
$meta # $meta #
#semval($) $this->yyval #semval($) $this->semValue
#semval($,%t) $this->yyval #semval($,%t) $this->semValue
#semval(%n) $this->yyastk[$this->stackPos-(%l-%n)] #semval(%n) $this->semStack[$this->stackPos-(%l-%n)]
#semval(%n,%t) $this->yyastk[$this->stackPos-(%l-%n)] #semval(%n,%t) $this->semStack[$this->stackPos-(%l-%n)]
#include; #include;
namespace PhpParser; namespace PhpParser;
@ -11,353 +11,89 @@ namespace PhpParser;
/* This is an automatically GENERATED file, which should not be manually edited. /* This is an automatically GENERATED file, which should not be manually edited.
* Instead edit one of the following: * Instead edit one of the following:
* * the grammar file grammar/zend_language_parser.phpy * * 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 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 Parser extends ParserAbstract
class #(-p)_Debug extends #(-p)
#endif
#ifnot -t
class #(-p)
#endif
{ {
#ifnot -t protected $tokenToSymbolMapSize = #(YYMAXLEX);
const TOKEN_NONE = -1; protected $actionTableSize = #(YYLAST);
const TOKEN_INVALID = #(YYBADCH); protected $gotoTableSize = #(YYGLAST);
const TOKEN_MAP_SIZE = #(YYMAXLEX); protected $invalidToken = #(YYBADCH);
protected $defaultAction = #(YYDEFAULT);
protected $unexpectedTokenRule = #(YYUNEXPECTED);
const YYLAST = #(YYLAST); protected $YY2TBLSTATE = #(YY2TBLSTATE);
const YY2TBLSTATE = #(YY2TBLSTATE); protected $YYNLSTATES = #(YYNLSTATES);
const YYGLAST = #(YYGLAST);
const YYNLSTATES = #(YYNLSTATES);
const YYUNEXPECTED = #(YYUNEXPECTED);
const YYDEFAULT = #(YYDEFAULT);
// {{{ Tokens
#tokenval #tokenval
const %s = %n; const %s = %n;
#endtokenval #endtokenval
// }}}
/* @var array Map of token ids to their respective names */ protected $symbolToName = array(
protected static $terminals = array(
#listvar terminals #listvar terminals
, "???"
); );
/* @var array Map which translates lexer tokens to internal tokens */ protected $tokenToSymbol = array(
protected static $translate = array(
#listvar yytranslate #listvar yytranslate
); );
protected static $yyaction = array( protected $action = array(
#listvar yyaction #listvar yyaction
); );
protected static $yycheck = array( protected $actionCheck = array(
#listvar yycheck #listvar yycheck
); );
protected static $yybase = array( protected $actionBase = array(
#listvar yybase #listvar yybase
); );
protected static $yydefault = array( protected $actionDefault = array(
#listvar yydefault #listvar yydefault
); );
protected static $yygoto = array( protected $goto = array(
#listvar yygoto #listvar yygoto
); );
protected static $yygcheck = array( protected $gotoCheck = array(
#listvar yygcheck #listvar yygcheck
); );
protected static $yygbase = array( protected $gotoBase = array(
#listvar yygbase #listvar yygbase
); );
protected static $yygdefault = array( protected $gotoDefault = array(
#listvar yygdefault #listvar yygdefault
); );
protected static $yylhs = array( protected $ruleToNonTerminal = array(
#listvar yylhs #listvar yylhs
); );
protected static $yylen = array( protected $ruleToLength = array(
#listvar yylen #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 #if -t
protected static $yyproduction = array(
protected $productions = array(
#production-strings; #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 #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 #reduce
protected function yyn%n($attributes) { protected function reduceRule%n($attributes) {
%b %b
} }
#noact #noact
protected function yyn%n() { protected function reduceRule%n() {
$this->yyval = $this->yyastk[$this->stackPos]; $this->semValue = $this->semStack[$this->stackPos];
} }
#endreduce #endreduce
#endif
} }
#tailcode; #tailcode;

View File

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

View File

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

View File

@ -24,8 +24,10 @@ class Autoloader
*/ */
static public function autoload($class) { static public function autoload($class) {
if (0 === strpos($class, 'PhpParser\\')) { if (0 === strpos($class, 'PhpParser\\')) {
$fileName = dirname(__DIR__) . '/' . rtrim(strtr($class, '\\', '/'), '_') . '.php'; $fileName = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
require $fileName; if (file_exists($fileName)) {
require $fileName;
}
} else if (0 === strpos($class, 'PHPParser_')) { } else if (0 === strpos($class, 'PHPParser_')) {
if (isset(self::$oldToNewMap[$class])) { if (isset(self::$oldToNewMap[$class])) {
self::registerLegacyAliases(); self::registerLegacyAliases();
@ -62,8 +64,6 @@ class Autoloader
'PHPParser_PrettyPrinter_Zend' => 'PhpParser\PrettyPrinter\Standard', 'PHPParser_PrettyPrinter_Zend' => 'PhpParser\PrettyPrinter\Standard',
'PHPParser_Serializer' => 'PhpParser\Serializer', 'PHPParser_Serializer' => 'PhpParser\Serializer',
'PHPParser_Serializer_XML' => 'PhpParser\Serializer\XML', 'PHPParser_Serializer_XML' => 'PhpParser\Serializer\XML',
'PHPParser_Template' => 'PhpParser\Template',
'PHPParser_TemplateLoader' => 'PhpParser\TemplateLoader',
'PHPParser_Unserializer' => 'PhpParser\Unserializer', 'PHPParser_Unserializer' => 'PhpParser\Unserializer',
'PHPParser_Unserializer_XML' => 'PhpParser\Unserializer\XML', '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() * _interface() and _function() in the class and are made available as class(), interface() and function()
* through __call() magic. * 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\Function_ function(string $name) Creates a function builder
* @method Builder\Interface_ interface(string $name) Creates an interface 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) * @throws Error on lexing errors (unterminated comment or unexpected character)
*/ */
public function startLexing($code) { public function startLexing($code) {
$scream = ini_set('xdebug.scream', 0);
$this->resetErrors(); $this->resetErrors();
$this->tokens = @token_get_all($code); $this->tokens = @token_get_all($code);
$this->handleErrors(); $this->handleErrors();
ini_set('xdebug.scream', $scream);
$this->code = $code; // keep the code around for __halt_compiler() handling $this->code = $code; // keep the code around for __halt_compiler() handling
$this->pos = -1; $this->pos = -1;
$this->line = 1; $this->line = 1;

View File

@ -16,7 +16,7 @@ class Emulative extends \PhpParser\Lexer
const T_POW = 1002; const T_POW = 1002;
const T_POW_EQUAL = 1003; 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_5 = '5.5.0beta1';
const PHP_5_4 = '5.4.0beta1'; const PHP_5_4 = '5.4.0beta1';

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

@ -122,7 +122,14 @@ class NameResolver extends NodeVisitorAbstract
protected function resolveClassName(Name $name) { protected function resolveClassName(Name $name) {
// don't resolve special class names // 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; 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

@ -87,7 +87,7 @@ abstract class PrettyPrinterAbstract
public function prettyPrint(array $stmts) { public function prettyPrint(array $stmts) {
$this->preprocessNodes($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 * @return string Pretty printed statements
*/ */
public function prettyPrintFile(array $stmts) { public function prettyPrintFile(array $stmts) {
$p = $this->prettyPrint($stmts); $p = rtrim($this->prettyPrint($stmts));
$p = preg_replace('/^\?>\n?/', '', $p, -1, $count); $p = preg_replace('/^\?>\n?/', '', $p, -1, $count);
$p = preg_replace('/<\?php$/', '', $p); $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() protected function readNode() {
{ $className = $this->getClassNameFromType($this->reader->localName);
$className = 'PHPParser_Node_' . $this->reader->localName;
// create the node without calling it's constructor // create the node without calling it's constructor
$node = unserialize( $node = unserialize(
@ -136,4 +135,15 @@ class XML implements Unserializer
$this->reader->getAttribute('line') $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

@ -6,6 +6,7 @@ use PhpParser;
use PhpParser\Node; use PhpParser\Node;
use PhpParser\Node\Name; use PhpParser\Node\Name;
use PhpParser\Node\Stmt; use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
class NameResolverTest extends \PHPUnit_Framework_TestCase class NameResolverTest extends \PHPUnit_Framework_TestCase
{ {
@ -258,17 +259,17 @@ EOC;
} }
/** /**
* @dataProvider provideTestAlreadyInUseError * @dataProvider provideTestError
*/ */
public function testAlreadyInUseError(Stmt\Use_ $use, $errorMsg) { public function testError(Node $stmt, $errorMsg) {
$this->setExpectedException('PhpParser\Error', $errorMsg); $this->setExpectedException('PhpParser\Error', $errorMsg);
$traverser = new PhpParser\NodeTraverser; $traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver); $traverser->addVisitor(new NameResolver);
$traverser->traverse(array($use)); $traverser->traverse(array($stmt));
} }
public function provideTestAlreadyInUseError() { public function provideTestError() {
return array( return array(
array( array(
new Stmt\Use_(array( new Stmt\Use_(array(
@ -291,6 +292,22 @@ EOC;
), Stmt\Use_::TYPE_CONSTANT), ), Stmt\Use_::TYPE_CONSTANT),
'Cannot use const C\D as B because the name is already in use on line 2' '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"
),
); );
} }
@ -314,4 +331,35 @@ EOC;
$this->assertEquals(array('Bar', 'Baz'), $stmt->stmts[1]->expr->class->parts); $this->assertEquals(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->assertEquals('SELF', (string)$methodStmt->stmts[0]->class);
$this->assertEquals('PARENT', (string)$methodStmt->stmts[1]->class);
$this->assertEquals('STATIC', (string)$methodStmt->stmts[2]->class);
}
} }

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

View File

@ -31,6 +31,7 @@ const T_24 = 1 == "1";
const T_25 = 1 + 2 * 3; const T_25 = 1 + 2 * 3;
const T_26 = "1" + 2 + "3"; const T_26 = "1" + 2 + "3";
const T_27 = 2 ** 3; const T_27 = 2 ** 3;
const T_28 = [1, 2, 3][1];
----- -----
array( array(
0: Stmt_Const( 0: Stmt_Const(
@ -467,4 +468,41 @@ 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
)
)
)
)
)
) )

View File

@ -10,6 +10,10 @@ Array/string dereferencing
array(1, 2, 3)[2]; array(1, 2, 3)[2];
array(1, 2, 3)[2][0][0]; array(1, 2, 3)[2][0][0];
FOO[0];
Foo::BAR[1];
$foo::BAR[2][1][0];
----- -----
array( array(
0: Expr_ArrayDimFetch( 0: Expr_ArrayDimFetch(
@ -178,4 +182,50 @@ array(
value: 0 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
)
)
) )

View File

@ -11,3 +11,10 @@ namespace A {}
echo 1; echo 1;
----- -----
No code may exist outside of namespace {} on line 3 No code may exist outside of namespace {} on line 3
-----
<?php
namespace A {}
declare(ticks=1);
namespace B {}
-----
No code may exist outside of namespace {} on line 3