mirror of
https://github.com/nikic/PHP-Parser.git
synced 2025-07-09 16:36:31 +02:00
Compare commits
38 Commits
v1.0.0beta
...
v1.0.0
Author | SHA1 | Date | |
---|---|---|---|
2b96ab8edc | |||
7503356e03 | |||
22ef0de7ef | |||
5d7fec2027 | |||
6423864160 | |||
fd064dac6c | |||
d9bd550414 | |||
94eca2ce44 | |||
e65fd664d1 | |||
7a3789f1a9 | |||
b31f36bf89 | |||
3d583ab19c | |||
616be1d0fc | |||
7c81229261 | |||
452e1c0180 | |||
31bc022d0d | |||
ce1078bc00 | |||
6d0589d14f | |||
ef121e690c | |||
c0340053d1 | |||
39f323b5ad | |||
22c76a3da4 | |||
a332352dbc | |||
ef70767475 | |||
1cecf9efc5 | |||
1f143393e5 | |||
6d1f77132c | |||
2e195d7cb2 | |||
947a897238 | |||
1edbc89749 | |||
0faa844a75 | |||
3db3ad7d1e | |||
4743e9b0b8 | |||
8499696021 | |||
e4e56511b9 | |||
5960ecfc10 | |||
c341ab2ecf | |||
c62ffedfca |
@ -4,6 +4,7 @@ php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- hhvm
|
||||
|
||||
matrix:
|
||||
|
36
CHANGELOG.md
36
CHANGELOG.md
@ -1,8 +1,40 @@
|
||||
Version 1.0.0-dev
|
||||
Version 1.0.1-dev
|
||||
-----------------
|
||||
|
||||
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)
|
||||
--------------------------------
|
||||
|
||||
@ -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__`).
|
||||
|
||||
**The following changes are also included in 0.9.5-dev**:
|
||||
**The following changes are also included in 0.9.5**:
|
||||
|
||||
* [BC] Deprecated `PHPParser_Template` and `PHPParser_TemplateLoader`. This functionality does not belong in the main project
|
||||
and - as far as I know - nobody is using it.
|
||||
|
15
README.md
15
README.md
@ -1,23 +1,18 @@
|
||||
PHP Parser
|
||||
==========
|
||||
|
||||
This is a PHP 5.6 (and older) parser written in PHP. It's purpose is to simplify static code analysis and
|
||||
This is a PHP 5.2 to PHP 5.6 parser written in PHP. It's purpose is to simplify static code analysis and
|
||||
manipulation.
|
||||
|
||||
[**Documentation for version 0.9.x**][doc_0_9] (stable; for running on PHP 5.2).
|
||||
[**Documentation for version 1.0.x**][doc_master] (stable; for running on PHP >= 5.3).
|
||||
|
||||
[**Documentation for version 1.0.x**][doc_master] (beta; for running on PHP >= 5.3).
|
||||
|
||||
***Note: This project is experimental, so the API is subject to change.***
|
||||
[Documentation for version 0.9.x][doc_0_9] (unsupported; for running on PHP 5.2).
|
||||
|
||||
In a Nutshell
|
||||
-------------
|
||||
|
||||
Basically, the parser does nothing more than turn some PHP code into an abstract syntax tree. ("nothing
|
||||
more" is kind of sarcastic here as PHP has a ... uhm, let's just say "not nice" ... grammar, which makes
|
||||
parsing PHP very hard.)
|
||||
|
||||
For example, if you stick this code in the parser:
|
||||
The parser turns PHP source code into an abstract syntax tree. For example, if you pass the following code into the
|
||||
parser:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
121
UPGRADE-1.0.md
Normal file
121
UPGRADE-1.0.md
Normal 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
132
bin/php-parse.php
Normal 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);
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
This project is a PHP 5.5 (and older) parser **written in PHP itself**.
|
||||
This project is a PHP 5.2 to PHP 5.6 parser **written in PHP itself**.
|
||||
|
||||
What is this for?
|
||||
-----------------
|
||||
|
||||
A parser is useful for [static analysis][0] and manipulation of code and basically any other
|
||||
A parser is useful for [static analysis][0], manipulation of code and basically any other
|
||||
application dealing with code programmatically. A parser constructs an [Abstract Syntax Tree][1]
|
||||
(AST) of the code and thus allows dealing with it in an abstract and robust way.
|
||||
|
||||
There are other ways of dealing with source code. One that PHP supports natively is using the
|
||||
There are other ways of processing source code. One that PHP supports natively is using the
|
||||
token stream generated by [`token_get_all`][2]. The token stream is much more low level than
|
||||
the AST and thus has different applications: It allows to also analyze the exact formatting of
|
||||
a file. On the other hand the token stream is much harder to deal with for more complex analysis.
|
||||
@ -26,13 +26,13 @@ programmatic PHP code analysis are incidentally PHP developers, not C developers
|
||||
What can it parse?
|
||||
------------------
|
||||
|
||||
The parser uses a PHP 5.5 compliant grammar, which is backwards compatible with at least PHP 5.4, PHP 5.3
|
||||
and PHP 5.2 (and maybe older).
|
||||
The parser uses a PHP 5.6 compliant grammar, which is backwards compatible with all PHP version from PHP 5.2
|
||||
upwards (and maybe older).
|
||||
|
||||
As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP
|
||||
version it runs on), additionally a wrapper for emulating new tokens from 5.3, 5.4 and 5.5 is provided. This
|
||||
allows to parse PHP 5.5 source code running on PHP 5.2, for example. This emulation is very hacky and not
|
||||
yet perfect, but it should work well on any sane code.
|
||||
version it runs on), additionally a wrapper for emulating new tokens from 5.3, 5.4, 5.5 and 5.6 is provided.
|
||||
his allows to parse PHP 5.6 source code running on PHP 5.3, for example. This emulation is very hacky and not
|
||||
perfect, but it should work well on any sane code.
|
||||
|
||||
What output does it produce?
|
||||
----------------------------
|
||||
@ -56,7 +56,7 @@ array(
|
||||
)
|
||||
```
|
||||
|
||||
This matches the semantics the program had: An echo statement, which takes two strings as expressions,
|
||||
This matches the structure of the code: An echo statement, which takes two strings as expressions,
|
||||
with the values `Hi` and `World!`.
|
||||
|
||||
You can also see that the AST does not contain any whitespace information (but most comments are saved).
|
||||
|
@ -3,11 +3,6 @@ Installation
|
||||
|
||||
There are multiple ways to include the PHP parser into your project:
|
||||
|
||||
Installing from the Zip- or Tarball
|
||||
-----------------------------------
|
||||
|
||||
Download the latest version from [the download page][2], unpack it and move the files somewhere into your project.
|
||||
|
||||
Installing via Composer
|
||||
-----------------------
|
||||
|
||||
@ -15,7 +10,7 @@ Create a `composer.json` file in your project root and use it to define your dep
|
||||
|
||||
{
|
||||
"require": {
|
||||
"nikic/php-parser": "1.0.0-beta1"
|
||||
"nikic/php-parser": "~1.0.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
|
||||
|
||||
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
|
||||
|
@ -6,12 +6,14 @@ This document explains how to use the parser, the pretty printer and the node tr
|
||||
Bootstrapping
|
||||
-------------
|
||||
|
||||
The library needs to register a class autoloader; this is done by including the
|
||||
`bootstrap.php` file:
|
||||
The library needs to register a class autoloader. You can either use the ''vendor/autoload.php'' file generated by
|
||||
Composer or by including the bundled `lib/bootstrap.php` file:
|
||||
|
||||
```php
|
||||
<?php
|
||||
require 'path/to/PHP-Parser/lib/bootstrap.php';
|
||||
// Or, if you're using Composer:
|
||||
require 'path/to/vendor/autoload.php';
|
||||
```
|
||||
|
||||
Additionally you may want to set the `xdebug.max_nesting_level` ini option to a higher value:
|
||||
@ -26,31 +28,38 @@ This ensures that there will be no errors when traversing highly nested node tre
|
||||
Parsing
|
||||
-------
|
||||
|
||||
In order to parse some source code you first have to create a `PhpParser\Parser` object (which
|
||||
needs to be passed a `PhpParser\Lexer` instance) and then pass the code (including `<?php` opening
|
||||
tags) to the `parse` method. If a syntax error is encountered `PhpParser\Error` is thrown, so this
|
||||
exception should be `catch`ed.
|
||||
In order to parse some source code you first have to create a `PhpParser\Parser` object, which
|
||||
needs to be passed a `PhpParser\Lexer` instance:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
// or
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
|
||||
```
|
||||
|
||||
Use of the emulative lexer is required if you want to parse PHP code from newer versions than the one
|
||||
you're running on. For example it will allow you to parse PHP 5.6 code while running on PHP 5.3.
|
||||
|
||||
Subsequently you can pass PHP code (including the opening `<?php` tag) to the `parse` method in order to
|
||||
create a syntax tree. If a syntax error is encountered, an `PhpParser\Error` exception will be thrown:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$code = '<?php // some code';
|
||||
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
// $stmts is an array of statement nodes
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
The `parse` method will return an array of statement nodes (`$stmts`).
|
||||
|
||||
### Emulative lexer
|
||||
|
||||
Instead of `PhpParser\Lexer` one can also use `PhpParser\Lexer\Emulative`. This class will emulate tokens
|
||||
of newer PHP versions and as such allow parsing PHP 5.5 on PHP 5.2, for example. So if you want to parse
|
||||
PHP code of newer versions than the one you are running, you should use the emulative lexer.
|
||||
A parser instance can be reused to parse multiple files.
|
||||
|
||||
Node tree
|
||||
---------
|
||||
@ -104,7 +113,7 @@ with a PHP keyword.
|
||||
|
||||
Every node has a (possibly zero) number of subnodes. You can access subnodes by writing
|
||||
`$node->subNodeName`. The `Stmt\Echo_` node has only one subnode `exprs`. So in order to access it
|
||||
in the above example you would write `$stmts[0]->exprs`. If you wanted to access name of the function
|
||||
in the above example you would write `$stmts[0]->exprs`. If you wanted to access the name of the function
|
||||
call, you would write `$stmts[0]->exprs[1]->name`.
|
||||
|
||||
All nodes also define a `getType()` method that returns the node type. The type is the class name
|
||||
@ -131,7 +140,7 @@ namely `PhpParser\PrettyPrinter\Standard`.
|
||||
<?php
|
||||
$code = "<?php echo 'Hi ', hi\\getTarget();";
|
||||
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
|
||||
|
||||
try {
|
||||
@ -143,10 +152,10 @@ try {
|
||||
->exprs // sub expressions
|
||||
[0] // the first of them (the string node)
|
||||
->value // it's value, i.e. 'Hi '
|
||||
= 'Hallo '; // change to 'Hallo '
|
||||
= 'Hello '; // change to 'Hello '
|
||||
|
||||
// pretty print
|
||||
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts);
|
||||
$code = $prettyPrinter->prettyPrint($stmts);
|
||||
|
||||
echo $code;
|
||||
} catch (PhpParser\Error $e) {
|
||||
@ -156,7 +165,7 @@ try {
|
||||
|
||||
The above code will output:
|
||||
|
||||
<?php echo 'Hallo ', hi\getTarget();
|
||||
<?php echo 'Hello ', hi\getTarget();
|
||||
|
||||
As you can see the source code was first parsed using `PhpParser\Parser->parse()`, then changed and then
|
||||
again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
|
||||
@ -164,6 +173,9 @@ again converted to code using `PhpParser\PrettyPrinter\Standard->prettyPrint()`.
|
||||
The `prettyPrint()` method pretty prints a statements array. It is also possible to pretty print only a
|
||||
single expression using `prettyPrintExpr()`.
|
||||
|
||||
The `prettyPrintFile()` method can be used to print an entire file. This will include the opening `<?php` tag
|
||||
and handle inline HTML as the first/last statement more gracefully.
|
||||
|
||||
Node traversation
|
||||
-----------------
|
||||
|
||||
@ -177,9 +189,8 @@ structure of a program using this `PhpParser\NodeTraverser` looks like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$code = "<?php // some code";
|
||||
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
|
||||
$traverser = new PhpParser\NodeTraverser;
|
||||
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
|
||||
|
||||
@ -187,6 +198,8 @@ $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
|
||||
$traverser->addVisitor(new MyNodeVisitor);
|
||||
|
||||
try {
|
||||
$code = file_get_contents($fileName);
|
||||
|
||||
// parse
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
@ -194,7 +207,7 @@ try {
|
||||
$stmts = $traverser->traverse($stmts);
|
||||
|
||||
// pretty print
|
||||
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts);
|
||||
$code = $prettyPrinter->prettyPrintFile($stmts);
|
||||
|
||||
echo $code;
|
||||
} catch (PhpParser\Error $e) {
|
||||
@ -202,14 +215,16 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
A same node visitor for this code might look like this:
|
||||
The corresponding node visitor might look like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
use PhpParser\Node;
|
||||
|
||||
class MyNodeVisitor extends PhpParser\NodeVisitorAbstract
|
||||
{
|
||||
public function leaveNode(PhpParser\Node $node) {
|
||||
if ($node instanceof PhpParser\Node\Scalar\String) {
|
||||
public function leaveNode(Node $node) {
|
||||
if ($node instanceof Node\Scalar\String) {
|
||||
$node->value = 'foo';
|
||||
}
|
||||
}
|
||||
@ -218,7 +233,7 @@ class MyNodeVisitor extends PhpParser\NodeVisitorAbstract
|
||||
|
||||
The above node visitor would change all string literals in the program to `'foo'`.
|
||||
|
||||
All visitors must implement the `PhpParser\NodeVisitor` interface, which defined the following four
|
||||
All visitors must implement the `PhpParser\NodeVisitor` interface, which defines the following four
|
||||
methods:
|
||||
|
||||
public function beforeTraverse(array $nodes);
|
||||
@ -237,11 +252,12 @@ The `enterNode` and `leaveNode` methods are called on every node, the former whe
|
||||
i.e. before its subnodes are traversed, the latter when it is left.
|
||||
|
||||
All four methods can either return the changed node or not return at all (i.e. `null`) in which
|
||||
case the current node is not changed. The `leaveNode` method can furthermore return two special
|
||||
values: If `false` is returned the current node will be removed from the parent array. If an `array`
|
||||
is returned the current node will be merged into the parent array at the offset of the current node.
|
||||
I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will be
|
||||
`array(A, X, Y, Z, C)`.
|
||||
case the current node is not changed. The `leaveNode` method can additionally return two special
|
||||
values:
|
||||
|
||||
If `false` is returned the current node will be removed from the parent array. If an array is returned
|
||||
it will be merged into the parent array at the offset of the current node. I.e. if in `array(A, B, C)`
|
||||
the node `B` should be replaced with `array(X, Y, Z)` the result will be `array(A, X, Y, Z, C)`.
|
||||
|
||||
Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract`
|
||||
class, which will define empty default implementations for all the above methods.
|
||||
@ -280,10 +296,9 @@ We start off with the following base code:
|
||||
|
||||
```php
|
||||
<?php
|
||||
const IN_DIR = '/some/path';
|
||||
const OUT_DIR = '/some/other/path';
|
||||
$inDir = '/some/path';
|
||||
$outDir = '/some/other/path';
|
||||
|
||||
// use the emulative lexer here, as we are running PHP 5.2 but want to parse PHP 5.3
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
|
||||
$traverser = new PhpParser\NodeTraverser;
|
||||
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
|
||||
@ -292,7 +307,7 @@ $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); // we will need
|
||||
$traverser->addVisitor(new NodeVisitor\NamespaceConverter); // our own node visitor
|
||||
|
||||
// iterate over all .php files in the directory
|
||||
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(IN_DIR));
|
||||
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($inDir));
|
||||
$files = new RegexIterator($files, '/\.php$/');
|
||||
|
||||
foreach ($files as $file) {
|
||||
@ -307,11 +322,11 @@ foreach ($files as $file) {
|
||||
$stmts = $traverser->traverse($stmts);
|
||||
|
||||
// pretty print
|
||||
$code = '<?php ' . $prettyPrinter->prettyPrint($stmts);
|
||||
$code = $prettyPrinter->prettyPrintFile($stmts);
|
||||
|
||||
// write the converted file to the target directory
|
||||
file_put_contents(
|
||||
substr_replace($file->getPathname(), OUT_DIR, 0, strlen(IN_DIR)),
|
||||
substr_replace($file->getPathname(), $outDir, 0, strlen($inDir)),
|
||||
$code
|
||||
);
|
||||
} catch (PhpParser\Error $e) {
|
||||
@ -320,7 +335,7 @@ foreach ($files as $file) {
|
||||
}
|
||||
```
|
||||
|
||||
Now lets start with the main code, the `NodeVisitor_NamespaceConverter`. One thing it needs to do
|
||||
Now lets start with the main code, the `NodeVisitor\NamespaceConverter`. One thing it needs to do
|
||||
is convert `A\\B` style names to `A_B` style ones.
|
||||
|
||||
```php
|
||||
@ -337,14 +352,14 @@ class NodeVisitor_NamespaceConverter extends PhpParser\NodeVisitorAbstract
|
||||
```
|
||||
|
||||
The above code profits from the fact that the `NameResolver` already resolved all names as far as
|
||||
possible, so we don't need to do that. All the need to create a string with the name parts separated
|
||||
possible, so we don't need to do that. We only need to create a string with the name parts separated
|
||||
by underscores instead of backslashes. This is what `$node->toString('_')` does. (If you want to
|
||||
create a name with backslashes either write `$node->toString()` or `(string) $node`.) Then we create
|
||||
a new name from the string and return it. Returning a new node replaces the old node.
|
||||
|
||||
Another thing we need to do is change the class/function/const declarations. Currently they contain
|
||||
only the shortname (i.e. the last part of the name), but they need to contain the complete class
|
||||
name:
|
||||
only the shortname (i.e. the last part of the name), but they need to contain the complete name inclduing
|
||||
the namespace prefix:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
@ -1,7 +1,7 @@
|
||||
Other node tree representations
|
||||
===============================
|
||||
|
||||
It is possible to convert the AST in several textual representations, which serve different uses.
|
||||
It is possible to convert the AST into several textual representations, which serve different uses.
|
||||
|
||||
Simple serialization
|
||||
--------------------
|
||||
@ -13,33 +13,34 @@ but PHP, but it is compact and generates fast. The main application thus is in c
|
||||
Human readable dumping
|
||||
----------------------
|
||||
|
||||
Furthermore it is possible to dump nodes into a human readable form using the `dump` method of
|
||||
Furthermore it is possible to dump nodes into a human readable format using the `dump` method of
|
||||
`PhpParser\NodeDumper`. This can be used for debugging.
|
||||
|
||||
```php
|
||||
<?php
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
printLine('Hallo World!!!');
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
printLine('Hello World!!!');
|
||||
CODE;
|
||||
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
$nodeDumper = new PhpParser\NodeDumper;
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
echo '<pre>' . htmlspecialchars($nodeDumper->dump($stmts)) . '</pre>';
|
||||
echo $nodeDumper->dump($stmts), "\n";
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
The above output will have an output looking roughly like this:
|
||||
The above script will have an output looking roughly like this:
|
||||
|
||||
```
|
||||
array(
|
||||
@ -77,7 +78,7 @@ array(
|
||||
args: array(
|
||||
0: Arg(
|
||||
value: Scalar_String(
|
||||
value: Hallo World!!!
|
||||
value: Hello World!!!
|
||||
)
|
||||
byRef: false
|
||||
)
|
||||
@ -97,20 +98,21 @@ interfacing with other languages and applications or for doing transformation us
|
||||
<?php
|
||||
$code = <<<'CODE'
|
||||
<?php
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
printLine('Hallo World!!!');
|
||||
function printLine($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
printLine('Hello World!!!');
|
||||
CODE;
|
||||
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
$parser = new PhpParser\Parser(new PhpParser\Lexer);
|
||||
$serializer = new PhpParser\Serializer\XML;
|
||||
|
||||
try {
|
||||
$stmts = $parser->parse($code);
|
||||
|
||||
echo '<pre>' . htmlspecialchars($serializer->serialize($stmts)) . '</pre>';
|
||||
echo $serializer->serialize($stmts);
|
||||
} catch (PhpParser\Error $e) {
|
||||
echo 'Parse Error: ', $e->getMessage();
|
||||
}
|
||||
@ -185,7 +187,7 @@ Produces:
|
||||
<subNode:value>
|
||||
<node:Scalar_String line="6">
|
||||
<subNode:value>
|
||||
<scalar:string>Hallo World!!!</scalar:string>
|
||||
<scalar:string>Hello World!!!</scalar:string>
|
||||
</subNode:value>
|
||||
</node:Scalar_String>
|
||||
</subNode:value>
|
||||
|
@ -3,13 +3,8 @@ Code generation
|
||||
|
||||
It is also possible to generate code using the parser, by first creating an Abstract Syntax Tree and then using the
|
||||
pretty printer to convert it to PHP code. To simplify code generation, the project comes with a set of builders for
|
||||
common structures as well as simple templating support. Both features are described in the following:
|
||||
|
||||
Builders
|
||||
--------
|
||||
|
||||
The project provides builders for classes, interfaces, methods, functions, parameters and properties, which
|
||||
allow creating node trees with a fluid interface, instead of instantiating all nodes manually.
|
||||
classes, interfaces, methods, functions, parameters and properties. The builders allow creating node trees using a
|
||||
fluid interface, instead of instantiating all nodes manually.
|
||||
|
||||
Here is an example:
|
||||
|
||||
@ -59,209 +54,3 @@ abstract class SomeClass extends SomeOtherClass implements A\Few, Interfaces
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Templates
|
||||
---------
|
||||
|
||||
> **DEPRECATED**: This feature is deprecated and will be removed in PHP-Parser 1.0.
|
||||
|
||||
Additionally it is possible to generate code from reusable templates.
|
||||
|
||||
As an example consider the following template, which defines a general getter/setter skeleton in terms of a property
|
||||
`__name__` and its `__type__`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class GetterSetterTemplate
|
||||
{
|
||||
/**
|
||||
* @var __type__ The __name__
|
||||
*/
|
||||
protected $__name__;
|
||||
|
||||
/**
|
||||
* Gets the __name__.
|
||||
*
|
||||
* @return __type__ The __name__
|
||||
*/
|
||||
public function get__Name__() {
|
||||
return $this->__name__;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the __name__.
|
||||
*
|
||||
* @param __type__ $__name__ The new __name__
|
||||
*/
|
||||
public function set__Name__($__name__) {
|
||||
$this->__name__ = $__name__;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Using this template we can easily create a class with multiple properties and their respective getters and setters:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// $templateString contains the above template
|
||||
$template = new PhpParser\Template($parser, $templateString);
|
||||
|
||||
// We only have to specify the __name__ placeholder, as the
|
||||
// capitalized __Name__ placeholder is automatically created
|
||||
$properties = [
|
||||
['name' => 'title', 'type' => 'string'],
|
||||
['name' => 'body', 'type' => 'string'],
|
||||
['name' => 'author', 'type' => 'User'],
|
||||
['name' => 'timestamp', 'type' => 'DateTime'],
|
||||
];
|
||||
|
||||
$class = $factory->class('BlogPost')->implement('Post');
|
||||
|
||||
foreach ($properties as $propertyPlaceholders) {
|
||||
$stmts = $template->getStmts($propertyPlaceholders);
|
||||
|
||||
$class->addStmts(
|
||||
// $stmts contains all statements from the template. So [0] fetches the class statement
|
||||
// and ->stmts retrieves the methods.
|
||||
$stmts[0]->stmts
|
||||
);
|
||||
}
|
||||
|
||||
echo $prettyPrinter->prettyPrint(array($class->getNode()));
|
||||
```
|
||||
|
||||
The result would look roughly like this:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class BlogPost implements Post
|
||||
{
|
||||
/**
|
||||
* @var string The title
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* @var string The body
|
||||
*/
|
||||
protected $body;
|
||||
|
||||
/**
|
||||
* @var User The author
|
||||
*/
|
||||
protected $author;
|
||||
|
||||
/**
|
||||
* @var DateTime The timestamp
|
||||
*/
|
||||
protected $timestamp;
|
||||
|
||||
/**
|
||||
* Gets the title.
|
||||
*
|
||||
* @return string The title
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title.
|
||||
*
|
||||
* @param string $title The new title
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the body.
|
||||
*
|
||||
* @return string The body
|
||||
*/
|
||||
public function getBody()
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the body.
|
||||
*
|
||||
* @param string $body The new body
|
||||
*/
|
||||
public function setBody($body)
|
||||
{
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the author.
|
||||
*
|
||||
* @return User The author
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the author.
|
||||
*
|
||||
* @param User $author The new author
|
||||
*/
|
||||
public function setAuthor($author)
|
||||
{
|
||||
$this->author = $author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the timestamp.
|
||||
*
|
||||
* @return DateTime The timestamp
|
||||
*/
|
||||
public function getTimestamp()
|
||||
{
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timestamp.
|
||||
*
|
||||
* @param DateTime $timestamp The new timestamp
|
||||
*/
|
||||
public function setTimestamp($timestamp)
|
||||
{
|
||||
$this->timestamp = $timestamp;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When using multiple templates it is easier to manage them on the filesystem. They can be loaded using the
|
||||
`TemplateLoader`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// We'll store our templates in ./templates and give them a .php suffix
|
||||
$loader = new PhpParser\TemplateLoader($parser, './templates', '.php');
|
||||
|
||||
// loads ./templates/GetterSetter.php
|
||||
$getterSetterTemplate = $loader->load('GetterSetter');
|
||||
|
||||
// loads ./templates/Collection.php
|
||||
$collectionTemplate = $loader->load('Collection');
|
||||
|
||||
// The use of a suffix is optional. The following code for example is equivalent:
|
||||
$loader = new PhpParser\TemplateLoader($parser, './templates');
|
||||
|
||||
// loads ./templates/GetterSetter.php
|
||||
$getterSetterTemplate = $loader->load('GetterSetter.php');
|
||||
|
||||
// loads ./templates/Collection.php
|
||||
$collectionTemplate = $loader->load('Collection.php');
|
||||
```
|
@ -42,7 +42,7 @@ getNextToken
|
||||
------------
|
||||
|
||||
`getNextToken` returns the ID of the next token and sets some additional information in the three variables which it
|
||||
accepts by-ref. If no more tokens are available it has to return `0`, which is the ID of the `EOF` token.
|
||||
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`
|
||||
etc in the parser.
|
||||
|
@ -1,9 +1,9 @@
|
||||
<?php
|
||||
$meta #
|
||||
#semval($) $this->yyval
|
||||
#semval($,%t) $this->yyval
|
||||
#semval(%n) $this->yyastk[$this->stackPos-(%l-%n)]
|
||||
#semval(%n,%t) $this->yyastk[$this->stackPos-(%l-%n)]
|
||||
#semval($) $this->semValue
|
||||
#semval($,%t) $this->semValue
|
||||
#semval(%n) $this->semStack[$this->stackPos-(%l-%n)]
|
||||
#semval(%n,%t) $this->semStack[$this->stackPos-(%l-%n)]
|
||||
#include;
|
||||
|
||||
namespace PhpParser;
|
||||
@ -11,353 +11,89 @@ namespace PhpParser;
|
||||
/* This is an automatically GENERATED file, which should not be manually edited.
|
||||
* Instead edit one of the following:
|
||||
* * the grammar file grammar/zend_language_parser.phpy
|
||||
* * the parser skeleton grammar/kymacc.php.parser
|
||||
* * the skeleton file grammar/kymacc.php.parser
|
||||
* * the preprocessing script grammar/rebuildParser.php
|
||||
*
|
||||
* The skeleton for this parser was written by Moriyoshi Koizumi and is based on
|
||||
* the work by Masato Bito and is in the PUBLIC DOMAIN.
|
||||
*/
|
||||
#if -t
|
||||
class #(-p)_Debug extends #(-p)
|
||||
#endif
|
||||
#ifnot -t
|
||||
class #(-p)
|
||||
#endif
|
||||
class Parser extends ParserAbstract
|
||||
{
|
||||
#ifnot -t
|
||||
const TOKEN_NONE = -1;
|
||||
const TOKEN_INVALID = #(YYBADCH);
|
||||
protected $tokenToSymbolMapSize = #(YYMAXLEX);
|
||||
protected $actionTableSize = #(YYLAST);
|
||||
protected $gotoTableSize = #(YYGLAST);
|
||||
|
||||
const TOKEN_MAP_SIZE = #(YYMAXLEX);
|
||||
protected $invalidToken = #(YYBADCH);
|
||||
protected $defaultAction = #(YYDEFAULT);
|
||||
protected $unexpectedTokenRule = #(YYUNEXPECTED);
|
||||
|
||||
const YYLAST = #(YYLAST);
|
||||
const YY2TBLSTATE = #(YY2TBLSTATE);
|
||||
const YYGLAST = #(YYGLAST);
|
||||
const YYNLSTATES = #(YYNLSTATES);
|
||||
const YYUNEXPECTED = #(YYUNEXPECTED);
|
||||
const YYDEFAULT = #(YYDEFAULT);
|
||||
protected $YY2TBLSTATE = #(YY2TBLSTATE);
|
||||
protected $YYNLSTATES = #(YYNLSTATES);
|
||||
|
||||
// {{{ Tokens
|
||||
#tokenval
|
||||
const %s = %n;
|
||||
#endtokenval
|
||||
// }}}
|
||||
|
||||
/* @var array Map of token ids to their respective names */
|
||||
protected static $terminals = array(
|
||||
protected $symbolToName = array(
|
||||
#listvar terminals
|
||||
, "???"
|
||||
);
|
||||
|
||||
/* @var array Map which translates lexer tokens to internal tokens */
|
||||
protected static $translate = array(
|
||||
protected $tokenToSymbol = array(
|
||||
#listvar yytranslate
|
||||
);
|
||||
|
||||
protected static $yyaction = array(
|
||||
protected $action = array(
|
||||
#listvar yyaction
|
||||
);
|
||||
|
||||
protected static $yycheck = array(
|
||||
protected $actionCheck = array(
|
||||
#listvar yycheck
|
||||
);
|
||||
|
||||
protected static $yybase = array(
|
||||
protected $actionBase = array(
|
||||
#listvar yybase
|
||||
);
|
||||
|
||||
protected static $yydefault = array(
|
||||
protected $actionDefault = array(
|
||||
#listvar yydefault
|
||||
);
|
||||
|
||||
protected static $yygoto = array(
|
||||
protected $goto = array(
|
||||
#listvar yygoto
|
||||
);
|
||||
|
||||
protected static $yygcheck = array(
|
||||
protected $gotoCheck = array(
|
||||
#listvar yygcheck
|
||||
);
|
||||
|
||||
protected static $yygbase = array(
|
||||
protected $gotoBase = array(
|
||||
#listvar yygbase
|
||||
);
|
||||
|
||||
protected static $yygdefault = array(
|
||||
protected $gotoDefault = array(
|
||||
#listvar yygdefault
|
||||
);
|
||||
|
||||
protected static $yylhs = array(
|
||||
protected $ruleToNonTerminal = array(
|
||||
#listvar yylhs
|
||||
);
|
||||
|
||||
protected static $yylen = array(
|
||||
protected $ruleToLength = array(
|
||||
#listvar yylen
|
||||
);
|
||||
|
||||
protected $yyval;
|
||||
protected $yyastk;
|
||||
protected $stackPos;
|
||||
protected $lexer;
|
||||
|
||||
/**
|
||||
* Creates a parser instance.
|
||||
*
|
||||
* @param Lexer $lexer A lexer
|
||||
*/
|
||||
public function __construct(Lexer $lexer) {
|
||||
$this->lexer = $lexer;
|
||||
}
|
||||
#endif
|
||||
#if -t
|
||||
protected static $yyproduction = array(
|
||||
|
||||
protected $productions = array(
|
||||
#production-strings;
|
||||
);
|
||||
|
||||
protected function yyprintln($msg) {
|
||||
echo $msg, "\n";
|
||||
}
|
||||
|
||||
protected function YYTRACE_NEWSTATE($state, $tokenId) {
|
||||
$this->yyprintln(
|
||||
'% State ' . $state
|
||||
. ', Lookahead ' . ($tokenId == self::TOKEN_NONE ? '--none--' : self::$terminals[$tokenId])
|
||||
);
|
||||
}
|
||||
|
||||
protected function YYTRACE_READ($tokenId) {
|
||||
$this->yyprintln('% Reading ' . self::$terminals[$tokenId]);
|
||||
}
|
||||
|
||||
protected function YYTRACE_SHIFT($tokenId) {
|
||||
$this->yyprintln('% Shift ' . self::$terminals[$tokenId]);
|
||||
}
|
||||
|
||||
protected function YYTRACE_ACCEPT() {
|
||||
$this->yyprintln('% Accepted.');
|
||||
}
|
||||
|
||||
protected function YYTRACE_REDUCE($n) {
|
||||
$this->yyprintln('% Reduce by (' . $n . ') ' . self::$yyproduction[$n]);
|
||||
}
|
||||
|
||||
protected function YYTRACE_POP($state) {
|
||||
$this->yyprintln('% Recovering, uncovers state ' . $state);
|
||||
}
|
||||
|
||||
protected function YYTRACE_DISCARD($tokenId) {
|
||||
$this->yyprintln('% Discard ' . self::$terminals[$tokenId]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
#ifnot -t
|
||||
* Parses PHP code into a node tree.
|
||||
#endif
|
||||
#if -t
|
||||
* Parses PHP code into a node tree and prints out debugging information.
|
||||
#endif
|
||||
*
|
||||
* @param string $code The source code to parse
|
||||
*
|
||||
* @return Node[] Array of statements
|
||||
*/
|
||||
public function parse($code) {
|
||||
$this->lexer->startLexing($code);
|
||||
|
||||
// We start off with no lookahead-token
|
||||
$tokenId = self::TOKEN_NONE;
|
||||
|
||||
// The attributes for a node are taken from the first and last token of the node.
|
||||
// From the first token only the startAttributes are taken and from the last only
|
||||
// the endAttributes. Both are merged using the array union operator (+).
|
||||
$startAttributes = array('startLine' => 1);
|
||||
$endAttributes = array();
|
||||
|
||||
// In order to figure out the attributes for the starting token, we have to keep
|
||||
// them in a stack
|
||||
$attributeStack = array($startAttributes);
|
||||
|
||||
// Start off in the initial state and keep a stack of previous states
|
||||
$state = 0;
|
||||
$stateStack = array($state);
|
||||
|
||||
// AST stack (?)
|
||||
$this->yyastk = array();
|
||||
|
||||
// Current position in the stack(s)
|
||||
$this->stackPos = 0;
|
||||
|
||||
for (;;) {
|
||||
#if -t
|
||||
$this->YYTRACE_NEWSTATE($state, $tokenId);
|
||||
|
||||
#endif
|
||||
if (self::$yybase[$state] == 0) {
|
||||
$yyn = self::$yydefault[$state];
|
||||
} else {
|
||||
if ($tokenId === self::TOKEN_NONE) {
|
||||
// Fetch the next token id from the lexer and fetch additional info by-ref.
|
||||
// The end attributes are fetched into a temporary variable and only set once the token is really
|
||||
// shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
|
||||
// reduced after a token was read but not yet shifted.
|
||||
$origTokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $nextEndAttributes);
|
||||
|
||||
// map the lexer token id to the internally used token id's
|
||||
$tokenId = $origTokenId >= 0 && $origTokenId < self::TOKEN_MAP_SIZE
|
||||
? self::$translate[$origTokenId]
|
||||
: self::TOKEN_INVALID;
|
||||
|
||||
if ($tokenId === self::TOKEN_INVALID) {
|
||||
throw new \RangeException(sprintf(
|
||||
'The lexer returned an invalid token (id=%d, value=%s)',
|
||||
$origTokenId, $tokenValue
|
||||
));
|
||||
}
|
||||
|
||||
$attributeStack[$this->stackPos] = $startAttributes;
|
||||
#if -t
|
||||
|
||||
$this->YYTRACE_READ($tokenId);
|
||||
#endif
|
||||
}
|
||||
|
||||
if ((($yyn = self::$yybase[$state] + $tokenId) >= 0
|
||||
&& $yyn < self::YYLAST && self::$yycheck[$yyn] == $tokenId
|
||||
|| ($state < self::YY2TBLSTATE
|
||||
&& ($yyn = self::$yybase[$state + self::YYNLSTATES] + $tokenId) >= 0
|
||||
&& $yyn < self::YYLAST
|
||||
&& self::$yycheck[$yyn] == $tokenId))
|
||||
&& ($yyn = self::$yyaction[$yyn]) != self::YYDEFAULT) {
|
||||
/*
|
||||
* >= YYNLSTATE: shift and reduce
|
||||
* > 0: shift
|
||||
* = 0: accept
|
||||
* < 0: reduce
|
||||
* = -YYUNEXPECTED: error
|
||||
*/
|
||||
if ($yyn > 0) {
|
||||
/* shift */
|
||||
#if -t
|
||||
$this->YYTRACE_SHIFT($tokenId);
|
||||
|
||||
#endif
|
||||
++$this->stackPos;
|
||||
|
||||
$stateStack[$this->stackPos] = $state = $yyn;
|
||||
$this->yyastk[$this->stackPos] = $tokenValue;
|
||||
$attributeStack[$this->stackPos] = $startAttributes;
|
||||
$endAttributes = $nextEndAttributes;
|
||||
$tokenId = self::TOKEN_NONE;
|
||||
|
||||
if ($yyn < self::YYNLSTATES)
|
||||
continue;
|
||||
|
||||
/* $yyn >= YYNLSTATES means shift-and-reduce */
|
||||
$yyn -= self::YYNLSTATES;
|
||||
} else {
|
||||
$yyn = -$yyn;
|
||||
}
|
||||
} else {
|
||||
$yyn = self::$yydefault[$state];
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
/* reduce/error */
|
||||
if ($yyn == 0) {
|
||||
/* accept */
|
||||
#if -t
|
||||
$this->YYTRACE_ACCEPT();
|
||||
#endif
|
||||
return $this->yyval;
|
||||
} elseif ($yyn != self::YYUNEXPECTED) {
|
||||
/* reduce */
|
||||
#if -t
|
||||
$this->YYTRACE_REDUCE($yyn);
|
||||
#endif
|
||||
try {
|
||||
$this->{'yyn' . $yyn}(
|
||||
$attributeStack[$this->stackPos - self::$yylen[$yyn]]
|
||||
+ $endAttributes
|
||||
);
|
||||
} catch (Error $e) {
|
||||
if (-1 === $e->getRawLine()) {
|
||||
$e->setRawLine($startAttributes['startLine']);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/* Goto - shift nonterminal */
|
||||
$this->stackPos -= self::$yylen[$yyn];
|
||||
$yyn = self::$yylhs[$yyn];
|
||||
if (($yyp = self::$yygbase[$yyn] + $stateStack[$this->stackPos]) >= 0
|
||||
&& $yyp < self::YYGLAST
|
||||
&& self::$yygcheck[$yyp] == $yyn) {
|
||||
$state = self::$yygoto[$yyp];
|
||||
} else {
|
||||
$state = self::$yygdefault[$yyn];
|
||||
}
|
||||
|
||||
++$this->stackPos;
|
||||
|
||||
$stateStack[$this->stackPos] = $state;
|
||||
$this->yyastk[$this->stackPos] = $this->yyval;
|
||||
$attributeStack[$this->stackPos] = $startAttributes;
|
||||
} else {
|
||||
/* error */
|
||||
$expected = array();
|
||||
|
||||
$base = self::$yybase[$state];
|
||||
for ($i = 0; $i < self::TOKEN_MAP_SIZE; ++$i) {
|
||||
$n = $base + $i;
|
||||
if ($n >= 0 && $n < self::YYLAST && self::$yycheck[$n] == $i
|
||||
|| $state < self::YY2TBLSTATE
|
||||
&& ($n = self::$yybase[$state + self::YYNLSTATES] + $i) >= 0
|
||||
&& $n < self::YYLAST && self::$yycheck[$n] == $i
|
||||
) {
|
||||
if (self::$yyaction[$n] != self::YYUNEXPECTED) {
|
||||
if (count($expected) == 4) {
|
||||
/* Too many expected tokens */
|
||||
$expected = array();
|
||||
break;
|
||||
}
|
||||
|
||||
$expected[] = self::$terminals[$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$expectedString = '';
|
||||
if ($expected) {
|
||||
$expectedString = ', expecting ' . implode(' or ', $expected);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Syntax error, unexpected ' . self::$terminals[$tokenId] . $expectedString,
|
||||
$startAttributes['startLine']
|
||||
);
|
||||
}
|
||||
|
||||
if ($state < self::YYNLSTATES)
|
||||
break;
|
||||
/* >= YYNLSTATES means shift-and-reduce */
|
||||
$yyn = $state - self::YYNLSTATES;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifnot -t
|
||||
#reduce
|
||||
|
||||
protected function yyn%n($attributes) {
|
||||
protected function reduceRule%n($attributes) {
|
||||
%b
|
||||
}
|
||||
#noact
|
||||
|
||||
protected function yyn%n() {
|
||||
$this->yyval = $this->yyastk[$this->stackPos];
|
||||
protected function reduceRule%n() {
|
||||
$this->semValue = $this->semStack[$this->stackPos];
|
||||
}
|
||||
#endreduce
|
||||
#endif
|
||||
}
|
||||
#tailcode;
|
||||
|
@ -5,7 +5,6 @@ $skeletonFile = __DIR__ . '/kmyacc.php.parser';
|
||||
$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy';
|
||||
$tmpResultFile = __DIR__ . '/tmp_parser.php';
|
||||
$parserResultFile = __DIR__ . '/../lib/PhpParser/Parser.php';
|
||||
$debugParserResultFile = __DIR__ . '/../lib/PhpParser/Parser/Debug.php';
|
||||
|
||||
// check for kmyacc.exe binary in this directory, otherwise fall back to global name
|
||||
$kmyacc = __DIR__ . '/kmyacc.exe';
|
||||
@ -47,20 +46,14 @@ $grammarCode = resolveArrays($grammarCode);
|
||||
|
||||
file_put_contents($tmpGrammarFile, $grammarCode);
|
||||
|
||||
$additionalArgs = $optionDebug ? '-t -v' : '';
|
||||
|
||||
echo "Building parser.\n";
|
||||
$output = trim(shell_exec("$kmyacc -l -m $skeletonFile -p Parser $tmpGrammarFile 2>&1"));
|
||||
$output = trim(shell_exec("$kmyacc $additionalArgs -l -m $skeletonFile $tmpGrammarFile 2>&1"));
|
||||
echo "Output: \"$output\"\n";
|
||||
|
||||
moveFileWithDirCheck($tmpResultFile, $parserResultFile);
|
||||
|
||||
if ($optionDebug) {
|
||||
echo "Building debug parser.\n";
|
||||
$output = trim(shell_exec("$kmyacc -t -v -l -m $skeletonFile -p Parser $tmpGrammarFile 2>&1"));
|
||||
echo "Output: \"$output\"\n";
|
||||
|
||||
moveFileWithDirCheck($tmpResultFile, $debugParserResultFile);
|
||||
}
|
||||
|
||||
if (!$optionKeepTmpGrammar) {
|
||||
unlink($tmpGrammarFile);
|
||||
}
|
||||
|
@ -112,7 +112,7 @@
|
||||
%%
|
||||
|
||||
start:
|
||||
top_statement_list { $$ = Stmt\Namespace_::postprocess($1); }
|
||||
top_statement_list { $$ = $this->handleNamespaces($1); }
|
||||
;
|
||||
|
||||
top_statement_list:
|
||||
@ -628,6 +628,7 @@ scalar_dereference:
|
||||
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
|
||||
{ $$ = Expr\ArrayDimFetch[Scalar\String[Scalar\String::parse($1)], $3]; }
|
||||
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
/* alternative array syntax missing intentionally */
|
||||
;
|
||||
@ -678,7 +679,7 @@ function_call:
|
||||
;
|
||||
|
||||
class_name:
|
||||
T_STATIC { $$ = Name['static']; }
|
||||
T_STATIC { $$ = Name[$1]; }
|
||||
| name { $$ = $1; }
|
||||
;
|
||||
|
||||
@ -745,61 +746,62 @@ common_scalar:
|
||||
{ $$ = Scalar\String[Scalar\String::parseDocString($1, $2)]; }
|
||||
| T_START_HEREDOC T_END_HEREDOC
|
||||
{ $$ = Scalar\String['']; }
|
||||
| name { $$ = Expr\ConstFetch[$1]; }
|
||||
;
|
||||
|
||||
/* Arrays are currently not allowed in static scalar operations */
|
||||
static_scalar:
|
||||
static_scalar_value { $$ = $1; }
|
||||
| T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; }
|
||||
| '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; }
|
||||
;
|
||||
|
||||
static_scalar_value:
|
||||
common_scalar { $$ = $1; }
|
||||
| class_name T_PAAMAYIM_NEKUDOTAYIM class_const_name { $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
| name { $$ = Expr\ConstFetch[$1]; }
|
||||
| T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; }
|
||||
| '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; }
|
||||
| static_operation { $$ = $1; }
|
||||
;
|
||||
|
||||
static_operation:
|
||||
static_scalar_value T_BOOLEAN_OR static_scalar_value { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
|
||||
| static_scalar_value T_BOOLEAN_AND static_scalar_value { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
|
||||
| static_scalar_value T_LOGICAL_OR static_scalar_value { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
|
||||
| static_scalar_value T_LOGICAL_AND static_scalar_value { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
||||
| static_scalar_value T_LOGICAL_XOR static_scalar_value { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
||||
| static_scalar_value '|' static_scalar_value { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
||||
| static_scalar_value '&' static_scalar_value { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| static_scalar_value '^' static_scalar_value { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
||||
| static_scalar_value '.' static_scalar_value { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
||||
| static_scalar_value '+' static_scalar_value { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
||||
| static_scalar_value '-' static_scalar_value { $$ = Expr\BinaryOp\Minus [$1, $3]; }
|
||||
| static_scalar_value '*' static_scalar_value { $$ = Expr\BinaryOp\Mul [$1, $3]; }
|
||||
| static_scalar_value '/' static_scalar_value { $$ = Expr\BinaryOp\Div [$1, $3]; }
|
||||
| static_scalar_value '%' static_scalar_value { $$ = Expr\BinaryOp\Mod [$1, $3]; }
|
||||
| static_scalar_value T_SL static_scalar_value { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
|
||||
| static_scalar_value T_SR static_scalar_value { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
|
||||
| static_scalar_value T_POW static_scalar_value { $$ = Expr\BinaryOp\Pow [$1, $3]; }
|
||||
| '+' static_scalar_value %prec T_INC { $$ = Expr\UnaryPlus [$2]; }
|
||||
| '-' static_scalar_value %prec T_INC { $$ = Expr\UnaryMinus[$2]; }
|
||||
| '!' static_scalar_value { $$ = Expr\BooleanNot[$2]; }
|
||||
| '~' static_scalar_value { $$ = Expr\BitwiseNot[$2]; }
|
||||
| static_scalar_value T_IS_IDENTICAL static_scalar_value { $$ = Expr\BinaryOp\Identical [$1, $3]; }
|
||||
| static_scalar_value T_IS_NOT_IDENTICAL static_scalar_value { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; }
|
||||
| static_scalar_value T_IS_EQUAL static_scalar_value { $$ = Expr\BinaryOp\Equal [$1, $3]; }
|
||||
| static_scalar_value T_IS_NOT_EQUAL static_scalar_value { $$ = Expr\BinaryOp\NotEqual [$1, $3]; }
|
||||
| static_scalar_value '<' static_scalar_value { $$ = Expr\BinaryOp\Smaller [$1, $3]; }
|
||||
| static_scalar_value T_IS_SMALLER_OR_EQUAL static_scalar_value { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
|
||||
| static_scalar_value '>' static_scalar_value { $$ = Expr\BinaryOp\Greater [$1, $3]; }
|
||||
| static_scalar_value T_IS_GREATER_OR_EQUAL static_scalar_value { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
|
||||
| static_scalar_value '?' static_scalar_value ':' static_scalar_value { $$ = Expr\Ternary[$1, $3, $5]; }
|
||||
| static_scalar_value '?' ':' static_scalar_value { $$ = Expr\Ternary[$1, null, $4]; }
|
||||
| '(' static_scalar_value ')' { $$ = $2; }
|
||||
static_scalar T_BOOLEAN_OR static_scalar { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
|
||||
| static_scalar T_BOOLEAN_AND static_scalar { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
|
||||
| static_scalar T_LOGICAL_OR static_scalar { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
|
||||
| static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
|
||||
| static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
|
||||
| static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
|
||||
| static_scalar '&' static_scalar { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
|
||||
| static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
|
||||
| static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; }
|
||||
| static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; }
|
||||
| static_scalar '-' static_scalar { $$ = Expr\BinaryOp\Minus [$1, $3]; }
|
||||
| static_scalar '*' static_scalar { $$ = Expr\BinaryOp\Mul [$1, $3]; }
|
||||
| static_scalar '/' static_scalar { $$ = Expr\BinaryOp\Div [$1, $3]; }
|
||||
| static_scalar '%' static_scalar { $$ = Expr\BinaryOp\Mod [$1, $3]; }
|
||||
| static_scalar T_SL static_scalar { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
|
||||
| static_scalar T_SR static_scalar { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
|
||||
| static_scalar T_POW static_scalar { $$ = Expr\BinaryOp\Pow [$1, $3]; }
|
||||
| '+' static_scalar %prec T_INC { $$ = Expr\UnaryPlus [$2]; }
|
||||
| '-' static_scalar %prec T_INC { $$ = Expr\UnaryMinus[$2]; }
|
||||
| '!' static_scalar { $$ = Expr\BooleanNot[$2]; }
|
||||
| '~' static_scalar { $$ = Expr\BitwiseNot[$2]; }
|
||||
| static_scalar T_IS_IDENTICAL static_scalar { $$ = Expr\BinaryOp\Identical [$1, $3]; }
|
||||
| static_scalar T_IS_NOT_IDENTICAL static_scalar { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; }
|
||||
| static_scalar T_IS_EQUAL static_scalar { $$ = Expr\BinaryOp\Equal [$1, $3]; }
|
||||
| static_scalar T_IS_NOT_EQUAL static_scalar { $$ = Expr\BinaryOp\NotEqual [$1, $3]; }
|
||||
| static_scalar '<' static_scalar { $$ = Expr\BinaryOp\Smaller [$1, $3]; }
|
||||
| static_scalar T_IS_SMALLER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
|
||||
| static_scalar '>' static_scalar { $$ = Expr\BinaryOp\Greater [$1, $3]; }
|
||||
| static_scalar T_IS_GREATER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
|
||||
| static_scalar '?' static_scalar ':' static_scalar { $$ = Expr\Ternary[$1, $3, $5]; }
|
||||
| static_scalar '?' ':' static_scalar { $$ = Expr\Ternary[$1, null, $4]; }
|
||||
| static_scalar '[' static_scalar ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
|
||||
| '(' static_scalar ')' { $$ = $2; }
|
||||
;
|
||||
|
||||
constant:
|
||||
name { $$ = Expr\ConstFetch[$1]; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM class_const_name
|
||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
;
|
||||
|
||||
scalar:
|
||||
common_scalar { $$ = $1; }
|
||||
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM class_const_name
|
||||
{ $$ = Expr\ClassConstFetch[$1, $3]; }
|
||||
| constant { $$ = $1; }
|
||||
| '"' encaps_list '"'
|
||||
{ parseEncapsed($2, '"'); $$ = Scalar\Encapsed[$2]; }
|
||||
| T_START_HEREDOC encaps_list T_END_HEREDOC
|
||||
|
@ -24,8 +24,10 @@ class Autoloader
|
||||
*/
|
||||
static public function autoload($class) {
|
||||
if (0 === strpos($class, 'PhpParser\\')) {
|
||||
$fileName = dirname(__DIR__) . '/' . rtrim(strtr($class, '\\', '/'), '_') . '.php';
|
||||
require $fileName;
|
||||
$fileName = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
|
||||
if (file_exists($fileName)) {
|
||||
require $fileName;
|
||||
}
|
||||
} else if (0 === strpos($class, 'PHPParser_')) {
|
||||
if (isset(self::$oldToNewMap[$class])) {
|
||||
self::registerLegacyAliases();
|
||||
@ -62,8 +64,6 @@ class Autoloader
|
||||
'PHPParser_PrettyPrinter_Zend' => 'PhpParser\PrettyPrinter\Standard',
|
||||
'PHPParser_Serializer' => 'PhpParser\Serializer',
|
||||
'PHPParser_Serializer_XML' => 'PhpParser\Serializer\XML',
|
||||
'PHPParser_Template' => 'PhpParser\Template',
|
||||
'PHPParser_TemplateLoader' => 'PhpParser\TemplateLoader',
|
||||
'PHPParser_Unserializer' => 'PhpParser\Unserializer',
|
||||
'PHPParser_Unserializer_XML' => 'PhpParser\Unserializer\XML',
|
||||
|
||||
|
@ -9,7 +9,7 @@ use PhpParser\Builder;
|
||||
* _interface() and _function() in the class and are made available as class(), interface() and function()
|
||||
* through __call() magic.
|
||||
*
|
||||
* @method Builder\Class class(string $name) Creates a class builder.
|
||||
* @method Builder\Class_ class(string $name) Creates a class builder.
|
||||
* @method Builder\Function_ function(string $name) Creates a function builder
|
||||
* @method Builder\Interface_ interface(string $name) Creates an interface builder.
|
||||
*/
|
||||
|
@ -32,10 +32,14 @@ class Lexer
|
||||
* @throws Error on lexing errors (unterminated comment or unexpected character)
|
||||
*/
|
||||
public function startLexing($code) {
|
||||
$scream = ini_set('xdebug.scream', 0);
|
||||
|
||||
$this->resetErrors();
|
||||
$this->tokens = @token_get_all($code);
|
||||
$this->handleErrors();
|
||||
|
||||
ini_set('xdebug.scream', $scream);
|
||||
|
||||
$this->code = $code; // keep the code around for __halt_compiler() handling
|
||||
$this->pos = -1;
|
||||
$this->line = 1;
|
||||
|
@ -16,7 +16,7 @@ class Emulative extends \PhpParser\Lexer
|
||||
const T_POW = 1002;
|
||||
const T_POW_EQUAL = 1003;
|
||||
|
||||
const PHP_5_6 = '5.6.0beta1';
|
||||
const PHP_5_6 = '5.6.0rc1';
|
||||
const PHP_5_5 = '5.5.0beta1';
|
||||
const PHP_5_4 = '5.4.0beta1';
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
48
lib/PhpParser/Node/Stmt/Namespace_.php
Normal file
48
lib/PhpParser/Node/Stmt/Namespace_.php
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -122,7 +122,14 @@ class NameResolver extends NodeVisitorAbstract
|
||||
|
||||
protected function resolveClassName(Name $name) {
|
||||
// don't resolve special class names
|
||||
if (in_array((string) $name, array('self', 'parent', 'static'))) {
|
||||
if (in_array(strtolower($name), array('self', 'parent', 'static'))) {
|
||||
if (!$name->isUnqualified()) {
|
||||
throw new Error(
|
||||
sprintf("'\\%s' is an invalid class name", $name->toString()),
|
||||
$name->getLine()
|
||||
);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
367
lib/PhpParser/ParserAbstract.php
Normal file
367
lib/PhpParser/ParserAbstract.php
Normal 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;
|
||||
}
|
||||
}
|
@ -87,7 +87,7 @@ abstract class PrettyPrinterAbstract
|
||||
public function prettyPrint(array $stmts) {
|
||||
$this->preprocessNodes($stmts);
|
||||
|
||||
return trim(str_replace("\n" . $this->noIndentToken, "\n", $this->pStmts($stmts, false)));
|
||||
return ltrim(str_replace("\n" . $this->noIndentToken, "\n", $this->pStmts($stmts, false)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,7 +109,7 @@ abstract class PrettyPrinterAbstract
|
||||
* @return string Pretty printed statements
|
||||
*/
|
||||
public function prettyPrintFile(array $stmts) {
|
||||
$p = $this->prettyPrint($stmts);
|
||||
$p = rtrim($this->prettyPrint($stmts));
|
||||
|
||||
$p = preg_replace('/^\?>\n?/', '', $p, -1, $count);
|
||||
$p = preg_replace('/<\?php$/', '', $p);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -49,9 +49,8 @@ class XML implements Unserializer
|
||||
}
|
||||
}
|
||||
|
||||
protected function readNode()
|
||||
{
|
||||
$className = 'PHPParser_Node_' . $this->reader->localName;
|
||||
protected function readNode() {
|
||||
$className = $this->getClassNameFromType($this->reader->localName);
|
||||
|
||||
// create the node without calling it's constructor
|
||||
$node = unserialize(
|
||||
@ -136,4 +135,15 @@ class XML implements Unserializer
|
||||
$this->reader->getAttribute('line')
|
||||
);
|
||||
}
|
||||
|
||||
protected function getClassNameFromType($type) {
|
||||
$className = 'PhpParser\\Node\\' . strtr($type, '_', '\\');
|
||||
if (!class_exists($className)) {
|
||||
$className .= '_';
|
||||
}
|
||||
if (!class_exists($className)) {
|
||||
throw new DomainException(sprintf('Unknown node type "%s"', $type));
|
||||
}
|
||||
return $className;
|
||||
}
|
||||
}
|
||||
|
25
test/PhpParser/AutoloaderTest.php
Normal file
25
test/PhpParser/AutoloaderTest.php
Normal 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'));
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ use PhpParser;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Expr;
|
||||
|
||||
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);
|
||||
|
||||
$traverser = new PhpParser\NodeTraverser;
|
||||
$traverser->addVisitor(new NameResolver);
|
||||
$traverser->traverse(array($use));
|
||||
$traverser->traverse(array($stmt));
|
||||
}
|
||||
|
||||
public function provideTestAlreadyInUseError() {
|
||||
public function provideTestError() {
|
||||
return array(
|
||||
array(
|
||||
new Stmt\Use_(array(
|
||||
@ -291,6 +292,22 @@ EOC;
|
||||
), Stmt\Use_::TYPE_CONSTANT),
|
||||
'Cannot use const C\D as B because the name is already in use on line 2'
|
||||
),
|
||||
array(
|
||||
new Expr\New_(new Name\FullyQualified('self', array('startLine' => 3))),
|
||||
"'\\self' is an invalid class name on line 3"
|
||||
),
|
||||
array(
|
||||
new Expr\New_(new Name\Relative('self', array('startLine' => 3))),
|
||||
"'\\self' is an invalid class name on line 3"
|
||||
),
|
||||
array(
|
||||
new Expr\New_(new Name\FullyQualified('PARENT', array('startLine' => 3))),
|
||||
"'\\PARENT' is an invalid class name on line 3"
|
||||
),
|
||||
array(
|
||||
new Expr\New_(new Name\Relative('STATIC', array('startLine' => 3))),
|
||||
"'\\STATIC' is an invalid class name on line 3"
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -314,4 +331,35 @@ EOC;
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
@ -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;'
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ XML;
|
||||
$xml = <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<AST xmlns:node="http://nikic.github.com/PHPParser/XML/node">
|
||||
<node:Scalar_ClassConst />
|
||||
<node:Scalar_MagicConst_Class />
|
||||
</AST>
|
||||
XML;
|
||||
|
||||
@ -141,6 +141,10 @@ XML;
|
||||
'<node:Scalar_String><subNode:value/></node:Scalar_String>',
|
||||
'Expected node or scalar'
|
||||
),
|
||||
array(
|
||||
'<node:Foo><subNode:value/></node:Foo>',
|
||||
'Unknown node type "Foo"'
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ const T_24 = 1 == "1";
|
||||
const T_25 = 1 + 2 * 3;
|
||||
const T_26 = "1" + 2 + "3";
|
||||
const T_27 = 2 ** 3;
|
||||
const T_28 = [1, 2, 3][1];
|
||||
-----
|
||||
array(
|
||||
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
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
@ -10,6 +10,10 @@ Array/string dereferencing
|
||||
|
||||
array(1, 2, 3)[2];
|
||||
array(1, 2, 3)[2][0][0];
|
||||
|
||||
FOO[0];
|
||||
Foo::BAR[1];
|
||||
$foo::BAR[2][1][0];
|
||||
-----
|
||||
array(
|
||||
0: Expr_ArrayDimFetch(
|
||||
@ -178,4 +182,50 @@ array(
|
||||
value: 0
|
||||
)
|
||||
)
|
||||
6: Expr_ArrayDimFetch(
|
||||
var: Expr_ConstFetch(
|
||||
name: Name(
|
||||
parts: array(
|
||||
0: FOO
|
||||
)
|
||||
)
|
||||
)
|
||||
dim: Scalar_LNumber(
|
||||
value: 0
|
||||
)
|
||||
)
|
||||
7: Expr_ArrayDimFetch(
|
||||
var: Expr_ClassConstFetch(
|
||||
class: Name(
|
||||
parts: array(
|
||||
0: Foo
|
||||
)
|
||||
)
|
||||
name: BAR
|
||||
)
|
||||
dim: Scalar_LNumber(
|
||||
value: 1
|
||||
)
|
||||
)
|
||||
8: Expr_ArrayDimFetch(
|
||||
var: Expr_ArrayDimFetch(
|
||||
var: Expr_ArrayDimFetch(
|
||||
var: Expr_ClassConstFetch(
|
||||
class: Expr_Variable(
|
||||
name: foo
|
||||
)
|
||||
name: BAR
|
||||
)
|
||||
dim: Scalar_LNumber(
|
||||
value: 2
|
||||
)
|
||||
)
|
||||
dim: Scalar_LNumber(
|
||||
value: 1
|
||||
)
|
||||
)
|
||||
dim: Scalar_LNumber(
|
||||
value: 0
|
||||
)
|
||||
)
|
||||
)
|
@ -10,4 +10,11 @@ Namespace declaration statement has to be the very first statement in the script
|
||||
namespace A {}
|
||||
echo 1;
|
||||
-----
|
||||
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
|
Reference in New Issue
Block a user