Compare commits

...

53 Commits

Author SHA1 Message Date
196f177cfe Prepare PHP-Parser 1.4.0 release 2015-07-14 19:31:05 +02:00
b9afcdfd92 Revert "Make tests compatible with PHP 7"
This reverts commit 91295a0790.

This should no longer be necessary.

Conflicts:
	test/PhpParser/ParserTest.php
2015-07-14 19:21:57 +02:00
d341d94976 Add ClassLike::getMethod($name)
To allow getting individual method with a given name.
2015-07-14 17:07:45 +02:00
5ede167835 Fix infinite recursion in Use builder __call 2015-07-12 22:11:43 +02:00
b862de1f5b Support properties in trait builder 2015-07-11 22:31:45 +02:00
1d62e9d8cc Fix implicit visibility for properties as well
Also switch to using PPP mask.
2015-07-05 20:17:52 +02:00
e3195c246f Fix public modifier check for ClassMethod node 2015-07-05 20:17:50 +02:00
2a3bc608dc Add broken test for implicit public nodes 2015-07-05 20:17:49 +02:00
d5668f536d Fix __HALT_COMPILER_OFFSET__ support on HHVM 2015-07-04 13:15:48 +02:00
7fbbf83011 Added syntax highlighting in README 2015-07-02 18:09:12 +02:00
42e368e964 Fix returnType typehints
Fixes issues #207
2015-07-02 18:06:41 +02:00
fd7ee2e083 Add missing regex anchor 2015-06-20 12:34:10 +02:00
179d32cfaf Simplify handleHaltCompiler() implementation
Nowadays we're already tracking the filePos, no need to
recompute it.
2015-06-20 12:33:33 +02:00
bb2c5303ae Add a FunctionLike interface to Methods, Functions and Closures.
The elements listed above share common elements like parameters, possible return types and bodies.
2015-06-05 18:56:25 +02:00
69c00ebbe4 Make help output help2man-friendly
These changes follow regular advices for CLI output and allow help2man
to produce a meaningful manpage out of it.
2015-05-03 19:11:04 +02:00
30abe062f2 Update documentation link 2015-05-02 22:03:33 +02:00
dff239267f Release PHP-Parser 1.3.0 2015-05-02 17:40:40 +02:00
8b64195cf2 Try .17G print if .16G is not enough
This should be enough for all cases, because: A double has 53 bits
of mantissa (including the implicit 1 bit), which is 53*ln(2)/ln(10)
= 15.95 decimal digits. However the leading decimal digit may encode
less than the usual 3.32 bits, which will push this over the edge to
requiring 17 decimal digits.
2015-05-02 11:48:55 +02:00
9caa51b3a5 Add some more tests 2015-05-01 20:18:04 +02:00
5513073a53 Drop Lexer::getPosition()
This was a leftover from the original column info implementation.
2015-05-01 20:18:01 +02:00
659d26c231 Update changelog, fix typos 2015-05-01 19:57:10 +02:00
648800f07f Increase float pretty printing precision
This removes the reliance on the "precision" ini setting.
Furthermore the default of precision=14 is not sufficient.
2015-05-01 19:15:33 +02:00
23bf4f0c13 Add some error documentation 2015-04-30 21:58:45 +02:00
57ac7e39bf Drop name magic from rebuildParser.php
Now using explicitly imported names, instead of magically
prepending Name\.

Also remove trailing whitespace from generated file.
2015-04-30 19:20:38 +02:00
272ab6c8d8 Add some recovery tests 2015-04-30 19:02:51 +02:00
0731b47655 Don't distinguish test-fail / test
With error recovery this is fluid. Using .test for everything.
2015-04-30 17:45:36 +02:00
3b7d8e8b5d Enable basic error recovery
Adding only a single recovery rule for now.

The API is now:
 * throwOnError parser option must be disabled.
 * List of Errors is available through $parser->getErrors(). This
   method is available either way.
 * If no recovery is possible $parser->parse() will return null.
   (Obviously only if throwOnError is disabled).
2015-04-30 17:41:57 +02:00
a35c2a2067 Add column info to EOF errors
EOF errors are now located one past the end of the file.
2015-04-27 15:37:41 +02:00
55b2ead967 Make special class name checks case insensitive 2015-04-26 23:13:27 +02:00
e1a0ec3724 Add support for anonymous classes
Has not landed upstream yet, but syntax is unlikely to change.
2015-04-26 23:04:31 +02:00
66896dbde6 Poison attribute initializations in parser
Those should error if they end up being used.
2015-04-26 22:32:18 +02:00
338bc1f8e7 Fix "print" pretty-printing
Precedence was previously ignored.
2015-04-26 11:56:04 +02:00
ab80054e97 Add support for "yield from" 2015-04-26 11:56:02 +02:00
a6d2cd69f8 Clarify attribute assignment code
* Don't assign to attribute stack on reduce - why was that there
in the first place?
* Assign attributes to the position in the stack where the first
token of the production is, instead of one position earlier.
* Add a comment to clarify why we also assign attributes on read,
instead of just on shift.
2015-04-26 11:56:01 +02:00
6996db1e3a Build node attributes inside semantic action methods
Minor performance improvement for parsing, also allows to access
attributes with higher granulity in the parser, though this is not
currently done.

* #n can now be used to access the stack position of a token. $n
  is the same as $this->semStack[#n]. (Post-translate $n will
  actually be the stack position.)
* $attributeStack is now $this->startAttributeStack and
  $endAttributes is now $this->endAttributes.
* Attributes for a node are now computed inside the individual
  reduction methods, instead of being passed as a parameter.
  Accessible through the attributes() macro.
2015-04-26 11:55:59 +02:00
e30c3ac01b Merge pull request #195 from GrahamCampbell/patch-1
Bump version to 1.3
2015-04-19 20:52:51 +02:00
a5477a4eaf Version bump 2015-04-19 17:57:38 +01:00
7a40498cb4 Regenerate parser
Also drop the error() parser macro.
2015-04-19 18:12:56 +02:00
65f1adbe65 Merge primary error recovery code
The grammar does not yet actually implement error recovery.
2015-04-19 18:11:05 +02:00
c8454271e1 Make column info 1-based 2015-04-18 21:20:45 +02:00
62f83a0dc2 Add column info for non-syntax errors where relatively precise
Should it also be added if only rough information is available? E.g.
spanning an entire class?
2015-04-18 13:27:58 +02:00
611fa5c7f1 Test parser position information (syntax errors) 2015-04-18 13:27:57 +02:00
4defbc2174 Make Error column info attribute based 2015-04-18 12:51:26 +02:00
33a39fae06 added column-numbers to syntax errors 2015-04-18 10:14:49 +02:00
e4eab9ec0c Simplify code
The new class couldn't already exist at that point - if it did, the
old name would have been aliased already and the autoloader wouldn't
trigger.
2015-04-03 22:30:30 +02:00
6aaa87f143 Fix PHP 7 alias registration
This fixes the case where the old name is used before the new one
is ever used, e.g. when manually constructing nodes, as opposed to
parsing them.

The previous approach would try to register the alias from OLD to
NEW. This would trigger autoloading on NEW and afterwards it would
register the alias from OLD to NEW. Afterwards the alias registration
which originally triggered the autoload would run, thus redeclaring
the class.

TL;DR aliases suck, closes #192.
2015-04-03 22:22:39 +02:00
08f97eb4ef Release PHP-Parser 1.2.2 2015-04-03 16:33:59 +02:00
8d18848fb0 Don't set unserialize_callback_func in Autoloader::register() as it has side effects even when the library is not used 2015-04-03 16:25:33 +02:00
805078e0a9 Add missing type hints 2015-03-28 18:14:24 +01:00
52dafafd10 Resolve param type hints earlier
For convenience of concurrent visitors that want to perform
enterNode actions based on Stmt\Function_ etc.

Fixes #188.
2015-03-25 20:57:39 +01:00
dba7524b37 Release PHP-Parser 1.2.1 2015-03-24 20:10:28 +01:00
617bf0aa41 Fix registering of PHP 7 aliases
The previous approach was causing issues, because "instanceof"
does not invoke the autoloader.
2015-03-24 20:04:50 +01:00
dce19b074b Strict type compliance
Were this library to be fully annotated with scalar types and
return types where possible and were strict types to be enabled
for all files, the test suite would now pass.
2015-03-24 11:19:17 +01:00
76 changed files with 3311 additions and 1986 deletions

View File

@ -1,8 +1,62 @@
Version 1.2.1-dev
Version 1.4.1-dev
-----------------
Nothing yet.
Version 1.4.0 (2015-07-14)
--------------------------
### Added
* Added interface `PhpParser\Node\FunctionLike`, which is implemented by `Stmt\ClassMethod`,
`Stmt\Function_` and `Expr\Closure` nodes. This interface provides getters for their common
subnodes.
* Added `Node\Stmt\ClassLike::getMethod()` to look up a specific method on a class/interface/trait.
### Fixed
* Fixed `isPublic()` return value for implicitly public properties and methods that define and
additional modifier like `static` or `abstract`.
* Properties are now accepted by the trait builder.
* Fixed `__HALT_COMPILER_OFFSET__` support on HHVM.
Version 1.3.0 (2015-05-02)
--------------------------
### Added
* Errors can now store the attributes of the node/token where the error occurred. Previously only the start line was
stored.
* If file positions are enabled in the lexer, errors can now provide column information if it is available. See
[documentation](https://github.com/nikic/PHP-Parser/blob/master/doc/component/Error.markdown#column-information).
* The parser now provides an experimental error recovery mode, which can be enabled by disabling the `throwOnError`
parser option. In this mode the parser will try to construct a partial AST even if the code is not valid PHP. See
[documentation](https://github.com/nikic/PHP-Parser/blob/master/doc/component/Error.markdown#error-recovery).
* Added support for PHP 7 `yield from` expression. It is represented by `Expr\YieldFrom`.
* Added support for PHP 7 anonymous classes. These are represented by ordinary `Stmt\Class_` nodes with the name set to
`null`. Furthermore this implies that `Expr\New_` can now contain a `Stmt\Class_` in its `class` subnode.
### Fixed
* Fixed registration of PHP 7 aliases, for the case where the old name was used before the new name.
* Fixed handling of precedence when pretty-printing `print` expressions.
* Floating point numbers are now pretty-printed with a higher precision.
* Checks for special class names like `self` are now case-insensitive.
Version 1.2.2 (2015-04-03)
--------------------------
* The `NameResolver` now resolves parameter type hints when entering the function/method/closure node. As such other
visitors running after it will be able to make use of the resolved names at that point already.
* The autoloader no longer sets the `unserialize_callback_func` ini option on registration - this is not necessary and
may cause issues when running PhpUnit tests with process isolation.
Version 1.2.1 (2015-03-24)
--------------------------
* Fixed registration of the aliases introduced in 1.2.0. Previously the old class names could not be used in
`instanceof` checks under some circumstances.
Version 1.2.0 (2015-03-22)
--------------------------

View File

@ -4,7 +4,7 @@ PHP Parser
This is a PHP 5.2 to PHP 5.6 parser written in PHP. Its purpose is to simplify static code analysis and
manipulation.
[**Documentation for version 1.x**][doc_master] (stable; for running on PHP >= 5.3).
[**Documentation for version 1.x**][doc_1_x] (stable; for running on PHP >= 5.3).
[Documentation for version 0.9.x][doc_0_9] (unsupported; for running on PHP 5.2).
@ -22,7 +22,7 @@ hello\world('foo', 'bar' . 'baz');
You'll get a syntax tree looking roughly like this:
```
```php
array(
0: Stmt_Echo(
exprs: array(
@ -84,4 +84,4 @@ Component documentation:
1. [Lexer](doc/component/Lexer.markdown)
[doc_0_9]: https://github.com/nikic/PHP-Parser/tree/0.9/doc
[doc_master]: https://github.com/nikic/PHP-Parser/tree/master/doc
[doc_1_x]: https://github.com/nikic/PHP-Parser/tree/1.x/doc

View File

@ -10,7 +10,7 @@ ini_set('xdebug.var_display_max_children', -1);
ini_set('xdebug.var_display_max_data', -1);
ini_set('xdebug.var_display_max_depth', -1);
list($operations, $files) = parseArgs($argv);
list($operations, $files, $attributes) = parseArgs($argv);
/* Dump nodes by default */
if (empty($operations)) {
@ -21,7 +21,10 @@ if (empty($files)) {
showHelp("Must specify at least one file.");
}
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$lexer = new PhpParser\Lexer\Emulative(array('usedAttributes' => array(
'startLine', 'endLine', 'startFilePos', 'endFilePos'
)));
$parser = new PhpParser\Parser($lexer);
$dumper = new PhpParser\NodeDumper;
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$serializer = new PhpParser\Serializer\XML;
@ -45,7 +48,17 @@ foreach ($files as $file) {
try {
$stmts = $parser->parse($code);
} catch (PhpParser\Error $e) {
die("==> Parse Error: {$e->getMessage()}\n");
if ($attributes['with-column-info'] && $e->hasColumnInfo()) {
$startLine = $e->getStartLine();
$endLine = $e->getEndLine();
$startColumn = $e->getStartColumn($code);
$endColumn = $e->getEndColumn($code);
$message .= $e->getRawMessage() . " from $startLine:$startColumn to $endLine:$endColumn";
} else {
$message = $e->getMessage();
}
die($message . "\n");
}
foreach ($operations as $operation) {
@ -71,24 +84,20 @@ foreach ($files as $file) {
function showHelp($error) {
die($error . "\n\n" .
<<<OUTPUT
Usage:
php php-parse.php [operations] file1.php [file2.php ...]
The file arguments can also be replaced with a code string:
php php-parse.php [operations] "<?php code"
Usage: php php-parse.php [operations] file1.php [file2.php ...]
or: php php-parse.php [operations] "<?php code"
Turn PHP source code into an abstract syntax tree.
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
-d, --dump Dump nodes using NodeDumper
-p, --pretty-print Pretty print file using PrettyPrinter\Standard
--serialize-xml Serialize nodes using Serializer\XML
--var-dump var_dump() nodes (for exact structure)
-N, --resolve-names Resolve names using NodeVisitor\NameResolver
-c, --with-column-info Show column-numbers for errors (if available)
Example:
php php-parse.php -d -p -N -d file.php
Dumps nodes, pretty prints them, then resolves names and dumps them again.
@ -101,6 +110,9 @@ OUTPUT
function parseArgs($args) {
$operations = array();
$files = array();
$attributes = array(
'with-column-info' => false,
);
array_shift($args);
$parseOptions = true;
@ -129,6 +141,10 @@ function parseArgs($args) {
case '-N';
$operations[] = 'resolve-names';
break;
case '--with-column-info':
case '-c';
$attributes['with-column-info'] = true;
break;
case '--':
$parseOptions = false;
break;
@ -141,5 +157,5 @@ function parseArgs($args) {
}
}
return array($operations, $files);
return array($operations, $files, $attributes);
}

View File

@ -18,7 +18,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
"dev-master": "1.4-dev"
}
}
}

View File

@ -0,0 +1,77 @@
Error handling
==============
Errors during parsing or analysis are represented using the `PhpParser\Error` exception class. In addition to an error
message, an error can also store additional information about the location the error occurred at.
How much location information is available depends on the origin of the error and how many lexer attributes have been
enabled. At a minimum the start line of the error is usually available.
Column information
------------------
In order to receive information about not only the line, but also the column span an error occurred at, the file
position attributes in the lexer need to be enabled:
```php
$lexer = new PhpParser\Lexer(array(
'usedAttributes' => array('comments', 'startLine', 'endLine', 'startFilePos', 'endFilePos'),
));
$parser = new PhpParser\Parser($lexer);
try {
$stmts = $parser->parse($code);
// ...
} catch (PhpParser\Error $e) {
// ...
}
```
Before using column information its availability needs to be checked with `$e->hasColumnInfo()`, as the precise
location of an error cannot always be determined. The methods for retrieving column information also have to be passed
the source code of the parsed file. An example for printing an error:
```php
if ($e->hasColumnInfo()) {
echo $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
} else {
echo $e->getMessage();
}
```
Both line numbers and column numbers are 1-based. EOF errors will be located at the position one past the end of the
file.
Error recovery
--------------
> **EXPERIMENTAL**
By default the parser will throw an exception upon encountering the first error during parsing. An alternative mode is
also supported, in which the parser will remember the error, but try to continue parsing the rest of the source code.
To enable this mode the `throwOnError` parser option needs to be disabled. Any errors that occurred during parsing can
then be retrieved using `$parser->getErrors()`. The `$parser->parse()` method will either return a partial syntax tree
or `null` if recovery fails.
A usage example:
```php
$parser = new PhpParser\Parser(new PhpParser\Lexer, array(
'throwOnError' => false,
));
$stmts = $parser->parse($code);
$errors = $parser->getErrors();
foreach ($errors as $error) {
// $error is an ordinary PhpParser\Error
}
if (null !== $stmts) {
// $stmts is a best-effort partial AST
}
```
The error recovery implementation is experimental -- it currently won't be able to recover from many types of errors.

View File

@ -13,8 +13,7 @@ The `.phpy` file is a normal grammer in `kmyacc` (`yacc`) style, with some trans
applied to it:
* Nodes are created using the syntax `Name[..., ...]`. This is transformed into
`new Node\Name(..., ..., $attributes)`
* `Name::abc` is transformed to `Node\Name::abc`
`new Name(..., ..., attributes())`
* Some function-like constructs are resolved (see `rebuildParser.php` for a list)
* Associative arrays are written as `[key: value, ...]`, which is transformed to
`array('key' => value, ...)`
@ -25,5 +24,5 @@ Building the parser
In order to rebuild the parser, you need [moriyoshi's fork of kmyacc](https://github.com/moriyoshi/kmyacc-forked).
After you compiled/installed it, run the `rebuildParser.php` script.
By default only the `Parser.php` is built. If you want to additionally build `Parser/Debug.php` and `y.output` run the
script with `--debug`. If you want to retain the preprocessed grammar pass `--keep-tmp-grammar`.
By default only the `Parser.php` is built. If you want to additionally emit debug symbols and create `y.output`, run the
script with `--debug`. If you want to retain the preprocessed grammar pass `--keep-tmp-grammar`.

View File

@ -2,11 +2,11 @@
$meta #
#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;
#semval(%n) $this->stackPos-(%l-%n)
#semval(%n,%t) $this->stackPos-(%l-%n)
namespace PhpParser;
#include;
/* This is an automatically GENERATED file, which should not be manually edited.
* Instead edit one of the following:
@ -20,7 +20,8 @@ class Parser extends ParserAbstract
protected $actionTableSize = #(YYLAST);
protected $gotoTableSize = #(YYGLAST);
protected $invalidToken = #(YYBADCH);
protected $invalidSymbol = #(YYBADCH);
protected $errorSymbol = #(YYINTERRTOK);
protected $defaultAction = #(YYDEFAULT);
protected $unexpectedTokenRule = #(YYUNEXPECTED);
@ -86,7 +87,7 @@ class Parser extends ParserAbstract
#endif
#reduce
protected function reduceRule%n($attributes) {
protected function reduceRule%n() {
%b
}
#noact

View File

@ -39,10 +39,10 @@ echo 'Building temporary preproprocessed grammar file.', "\n";
$grammarCode = file_get_contents($grammarFile);
$grammarCode = resolveConstants($grammarCode);
$grammarCode = resolveNodes($grammarCode);
$grammarCode = resolveMacros($grammarCode);
$grammarCode = resolveArrays($grammarCode);
$grammarCode = resolveStackAccess($grammarCode);
file_put_contents($tmpGrammarFile, $grammarCode);
@ -52,7 +52,12 @@ echo "Building parser.\n";
$output = trim(shell_exec("$kmyacc $additionalArgs -l -m $skeletonFile $tmpGrammarFile 2>&1"));
echo "Output: \"$output\"\n";
moveFileWithDirCheck($tmpResultFile, $parserResultFile);
$resultCode = file_get_contents($tmpResultFile);
$resultCode = removeTrailingWhitespace($resultCode);
ensureDirExists(dirname($parserResultFile));
file_put_contents($parserResultFile, $resultCode);
unlink($tmpResultFile);
if (!$optionKeepTmpGrammar) {
unlink($tmpGrammarFile);
@ -62,10 +67,6 @@ if (!$optionKeepTmpGrammar) {
/// Preprocessing functions ///
///////////////////////////////
function resolveConstants($code) {
return preg_replace('~[A-Z][a-zA-Z_\\\\]++::~', 'Node\\\\$0', $code);
}
function resolveNodes($code) {
return preg_replace_callback(
'~(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
@ -83,7 +84,7 @@ function resolveNodes($code) {
$paramCode .= $param . ', ';
}
return 'new Node\\' . $matches['name'] . '(' . $paramCode . '$attributes)';
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
},
$code
);
@ -102,10 +103,9 @@ function resolveMacros($code) {
$matches['args']
);
if ('error' == $name) {
assertArgs(1, $args, $name);
return 'throw new Error(' . $args[0] . ')';
if ('attributes' == $name) {
assertArgs(0, $args, $name);
return '$this->startAttributeStack[#1] + $this->endAttributes';
}
if ('init' == $name) {
@ -148,7 +148,7 @@ function resolveMacros($code) {
return 'foreach (' . $args[0] . ' as &$s) { if (is_string($s)) { $s = Node\Scalar\String_::parseEscapeSequences($s, null); } } $s = preg_replace(\'~(\r\n|\n|\r)$~\', \'\', $s); if (\'\' === $s) array_pop(' . $args[0] . ');';
}
throw new Exception(sprintf('Unknown macro "%s"', $name));
return $matches[0];
},
$code
);
@ -191,12 +191,22 @@ function resolveArrays($code) {
);
}
function moveFileWithDirCheck($fromPath, $toPath) {
$dir = dirname($toPath);
function resolveStackAccess($code) {
$code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
$code = preg_replace('/#(\d+)/', '$$1', $code);
return $code;
}
function removeTrailingWhitespace($code) {
$lines = explode("\n", $code);
$lines = array_map('rtrim', $lines);
return implode("\n", $lines);
}
function ensureDirExists($dir) {
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
rename($fromPath, $toPath);
}
//////////////////////////////

View File

@ -8,6 +8,7 @@
%left T_LOGICAL_AND
%right T_PRINT
%right T_YIELD
%right T_YIELD_FROM
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL
%left '?' ':'
%right T_COALESCE
@ -110,6 +111,13 @@
%token T_NS_SEPARATOR
%token T_ELLIPSIS
%{
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
%}
%%
start:
@ -163,7 +171,7 @@ constant_declaration_list:
;
constant_declaration:
T_STRING '=' static_scalar { $$ = Const_[$1, $3]; }
T_STRING '=' static_scalar { $$ = Node\Const_[$1, $3]; }
;
inner_statement_list:
@ -175,7 +183,8 @@ inner_statement:
statement { $$ = $1; }
| function_declaration_statement { $$ = $1; }
| class_declaration_statement { $$ = $1; }
| T_HALT_COMPILER { error('__HALT_COMPILER() can only be used from the outermost scope'); }
| T_HALT_COMPILER
{ throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
;
statement:
@ -213,6 +222,7 @@ statement:
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
| T_GOTO T_STRING ';' { $$ = Stmt\Goto_[$2]; }
| T_STRING ':' { $$ = Stmt\Label[$1]; }
| error { $$ = array(); /* means: no statement */ }
;
catches:
@ -382,9 +392,9 @@ non_empty_parameter_list:
parameter:
optional_param_type optional_ref optional_ellipsis T_VARIABLE
{ $$ = Param[parseVar($4), null, $1, $2, $3]; }
{ $$ = Node\Param[parseVar($4), null, $1, $2, $3]; }
| optional_param_type optional_ref optional_ellipsis T_VARIABLE '=' static_scalar
{ $$ = Param[parseVar($4), $6, $1, $2, $3]; }
{ $$ = Node\Param[parseVar($4), $6, $1, $2, $3]; }
;
type:
@ -406,7 +416,7 @@ optional_return_type:
argument_list:
'(' ')' { $$ = array(); }
| '(' non_empty_argument_list ')' { $$ = $2; }
| '(' yield_expr ')' { $$ = array(Arg[$2, false, false]); }
| '(' yield_expr ')' { $$ = array(Node\Arg[$2, false, false]); }
;
non_empty_argument_list:
@ -415,9 +425,9 @@ non_empty_argument_list:
;
argument:
expr { $$ = Arg[$1, false, false]; }
| '&' variable { $$ = Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Arg[$2, false, true]; }
expr { $$ = Node\Arg[$1, false, false]; }
| '&' variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
;
global_var_list:
@ -615,6 +625,7 @@ expr:
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
| T_PRINT expr { $$ = Expr\Print_[$2]; }
| T_YIELD { $$ = Expr\Yield_[null, null]; }
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
'{' inner_statement_list '}'
{ $$ = Expr\Closure[[static: false, byRef: $2, params: $4, uses: $6, returnType: $7, stmts: $9]]; }
@ -647,8 +658,14 @@ scalar_dereference:
/* alternative array syntax missing intentionally */
;
anonymous_class:
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, [type: 0, extends: $3, implements: $4, stmts: $6]], $2); }
new_expr:
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
| T_NEW anonymous_class
{ list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
;
lexical_vars:

View File

@ -23,7 +23,6 @@ class Autoloader
return;
}
ini_set('unserialize_callback_func', 'spl_autoload_call');
spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend);
self::$registered = true;
self::$runningOnPhp7 = version_compare(PHP_VERSION, '7.0-dev', '>=');
@ -36,17 +35,27 @@ class Autoloader
*/
static public function autoload($class) {
if (0 === strpos($class, 'PhpParser\\')) {
if (isset(self::$php7CompatAliases[$class])) {
if (!self::$runningOnPhp7) {
// Register aliases only on PHP 5.x, otherwise this call will fatal
class_alias(self::$php7CompatAliases[$class], $class);
if (isset(self::$php7AliasesOldToNew[$class])) {
if (self::$runningOnPhp7) {
return;
}
// Load the new class, alias will be registered afterwards
$class = self::$php7AliasesOldToNew[$class];
}
$fileName = dirname(__DIR__) . '/' . strtr($class, '\\', '/') . '.php';
if (file_exists($fileName)) {
require $fileName;
}
if (isset(self::$php7AliasesNewToOld[$class])) {
// New class name was used, register alias for old one, otherwise
// it won't be usable in "instanceof" and other non-autoloading places.
if (!self::$runningOnPhp7) {
class_alias($class, self::$php7AliasesNewToOld[$class]);
}
}
} else if (0 === strpos($class, 'PHPParser_')) {
if (isset(self::$nonNamespacedAliases[$class])) {
// Register all aliases at once to avoid dependency issues
@ -61,7 +70,7 @@ class Autoloader
}
}
private static $php7CompatAliases = array(
private static $php7AliasesOldToNew = array(
'PhpParser\Node\Expr\Cast\Bool' => 'PhpParser\Node\Expr\Cast\Bool_',
'PhpParser\Node\Expr\Cast\Int' => 'PhpParser\Node\Expr\Cast\Int_',
'PhpParser\Node\Expr\Cast\Object' => 'PhpParser\Node\Expr\Cast\Object_',
@ -69,6 +78,14 @@ class Autoloader
'PhpParser\Node\Scalar\String' => 'PhpParser\Node\Scalar\String_',
);
private static $php7AliasesNewToOld = array(
'PhpParser\Node\Expr\Cast\Bool_' => 'PhpParser\Node\Expr\Cast\Bool',
'PhpParser\Node\Expr\Cast\Int_' => 'PhpParser\Node\Expr\Cast\Int',
'PhpParser\Node\Expr\Cast\Object_' => 'PhpParser\Node\Expr\Cast\Object',
'PhpParser\Node\Expr\Cast\String_' => 'PhpParser\Node\Expr\Cast\String',
'PhpParser\Node\Scalar\String_' => 'PhpParser\Node\Scalar\String',
);
private static $nonNamespacedAliases = array(
'PHPParser_Builder' => 'PhpParser\Builder',
'PHPParser_BuilderAbstract' => 'PhpParser\BuilderAbstract',

View File

@ -9,6 +9,7 @@ use PhpParser\Node\Stmt;
class Trait_ extends Declaration
{
protected $name;
protected $properties = array();
protected $methods = array();
/**
@ -29,12 +30,15 @@ class Trait_ extends Declaration
*/
public function addStmt($stmt) {
$stmt = $this->normalizeNode($stmt);
if (!$stmt instanceof Stmt\ClassMethod) {
if ($stmt instanceof Stmt\Property) {
$this->properties[] = $stmt;
} else if ($stmt instanceof Stmt\ClassMethod) {
$this->methods[] = $stmt;
} else {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
$this->methods[] = $stmt;
return $this;
}
@ -44,6 +48,8 @@ class Trait_ extends Declaration
* @return Stmt\Trait_ The built interface node
*/
public function getNode() {
return new Stmt\Trait_($this->name, $this->methods, $this->attributes);
return new Stmt\Trait_(
$this->name, array_merge($this->properties, $this->methods), $this->attributes
);
}
}

View File

@ -36,8 +36,12 @@ class Use_ extends BuilderAbstract {
$this->alias = $alias;
return $this;
}
public function __call($method, $args) {
return call_user_func_array(array($this, $method . '_'), $args);
public function __call($name, $args) {
if (method_exists($this, $name . '_')) {
return call_user_func_array(array($this, $name . '_'), $args);
}
throw new \LogicException(sprintf('Method "%s" does not exist', $name));
}
/**

View File

@ -5,17 +5,22 @@ namespace PhpParser;
class Error extends \RuntimeException
{
protected $rawMessage;
protected $rawLine;
protected $attributes;
/**
* Creates an Exception signifying a parse error.
*
* @param string $message Error message
* @param int $line Error line in PHP file
* @param string $message Error message
* @param array|int $attributes Attributes of node/token where error occurred
* (or start line of error -- deprecated)
*/
public function __construct($message, $line = -1) {
public function __construct($message, $attributes = array()) {
$this->rawMessage = (string) $message;
$this->rawLine = (int) $line;
if (is_array($attributes)) {
$this->attributes = $attributes;
} else {
$this->attributes = array('startLine' => $attributes);
}
$this->updateMessage();
}
@ -28,6 +33,34 @@ class Error extends \RuntimeException
return $this->rawMessage;
}
/**
* Gets the line the error starts in.
*
* @return int Error start line
*/
public function getStartLine() {
return isset($this->attributes['startLine']) ? $this->attributes['startLine'] : -1;
}
/**
* Gets the line the error ends in.
*
* @return int Error end line
*/
public function getEndLine() {
return isset($this->attributes['endLine']) ? $this->attributes['endLine'] : -1;
}
/**
* Gets the attributes of the node/token the error occurred at.
*
* @return array
*/
public function getAttributes() {
return $this->attributes;
}
/**
* Sets the line of the PHP file the error occurred in.
*
@ -39,22 +72,65 @@ class Error extends \RuntimeException
}
/**
* Gets the error line in the PHP file.
* Sets the line the error starts in.
*
* @return int Error line in the PHP file
* @param int $line Error start line
*/
public function getRawLine() {
return $this->rawLine;
public function setStartLine($line) {
$this->attributes['startLine'] = (int) $line;
$this->updateMessage();
}
/**
* Sets the line of the PHP file the error occurred in.
* Returns whether the error has start and end column information.
*
* @param int $line Error line in the PHP file
* For column information enable the startFilePos and endFilePos in the lexer options.
*
* @return bool
*/
public function setRawLine($line) {
$this->rawLine = (int) $line;
$this->updateMessage();
public function hasColumnInfo() {
return isset($this->attributes['startFilePos']) && isset($this->attributes['endFilePos']);
}
/**
* Gets the start column (1-based) into the line where the error started.
*
* @param string $code Source code of the file
* @return int
*/
public function getStartColumn($code) {
if (!$this->hasColumnInfo()) {
throw new \RuntimeException('Error does not have column information');
}
return $this->toColumn($code, $this->attributes['startFilePos']);
}
/**
* Gets the end column (1-based) into the line where the error ended.
*
* @param string $code Source code of the file
* @return int
*/
public function getEndColumn($code) {
if (!$this->hasColumnInfo()) {
throw new \RuntimeException('Error does not have column information');
}
return $this->toColumn($code, $this->attributes['endFilePos']);
}
private function toColumn($code, $pos) {
if ($pos > strlen($code)) {
throw new \RuntimeException('Invalid position information');
}
$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
if (false === $lineStartPos) {
$lineStartPos = -1;
}
return $pos - $lineStartPos;
}
/**
@ -63,10 +139,20 @@ class Error extends \RuntimeException
protected function updateMessage() {
$this->message = $this->rawMessage;
if (-1 === $this->rawLine) {
if (-1 === $this->getStartLine()) {
$this->message .= ' on unknown line';
} else {
$this->message .= ' on line ' . $this->rawLine;
$this->message .= ' on line ' . $this->getStartLine();
}
}
}
/** @deprecated Use getStartLine() instead */
public function getRawLine() {
return $this->getStartLine();
}
/** @deprecated Use setStartLine() instead */
public function setRawLine($line) {
$this->setStartLine($line);
}
}

View File

@ -48,13 +48,15 @@ class Lexer
* @throws Error on lexing errors (unterminated comment or unexpected character)
*/
public function startLexing($code) {
$scream = ini_set('xdebug.scream', 0);
$scream = ini_set('xdebug.scream', '0');
$this->resetErrors();
$this->tokens = @token_get_all($code);
$this->handleErrors();
ini_set('xdebug.scream', $scream);
if (false !== $scream) {
ini_set('xdebug.scream', $scream);
}
$this->code = $code; // keep the code around for __halt_compiler() handling
$this->pos = -1;
@ -76,7 +78,7 @@ class Lexer
'~^Unterminated comment starting line ([0-9]+)$~',
$error['message'], $matches
)) {
throw new Error('Unterminated comment', $matches[1]);
throw new Error('Unterminated comment', (int) $matches[1]);
}
if (preg_match(
@ -121,8 +123,13 @@ class Lexer
$startAttributes = array();
$endAttributes = array();
while (isset($this->tokens[++$this->pos])) {
$token = $this->tokens[$this->pos];
while (1) {
if (isset($this->tokens[++$this->pos])) {
$token = $this->tokens[$this->pos];
} else {
// EOF token with ID 0
$token = "\0";
}
if (isset($this->usedAttributes['startTokenPos'])) {
$startAttributes['startTokenPos'] = $this->pos;
@ -190,10 +197,7 @@ class Lexer
}
}
$startAttributes['startLine'] = $this->line;
// 0 is the EOF token
return 0;
throw new \RuntimeException('Reached end of lexer loop');
}
/**
@ -216,23 +220,13 @@ class Lexer
* @return string Remaining text
*/
public function handleHaltCompiler() {
// get the length of the text before the T_HALT_COMPILER token
$textBefore = '';
for ($i = 0; $i <= $this->pos; ++$i) {
if (is_string($this->tokens[$i])) {
$textBefore .= $this->tokens[$i];
} else {
$textBefore .= $this->tokens[$i][1];
}
}
// text after T_HALT_COMPILER, still including ();
$textAfter = substr($this->code, strlen($textBefore));
$textAfter = substr($this->code, $this->filePos);
// ensure that it is followed by ();
// this simplifies the situation, by not allowing any comments
// in between of the tokens.
if (!preg_match('~\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
throw new Error('__HALT_COMPILER must be followed by "();"');
}
@ -282,6 +276,10 @@ class Lexer
if (defined('T_ONUMBER')) {
$tokenMap[T_ONUMBER] = Parser::T_DNUMBER;
}
// HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
if (defined('T_COMPILER_HALT_OFFSET')) {
$tokenMap[T_COMPILER_HALT_OFFSET] = Parser::T_STRING;
}
return $tokenMap;
}

View File

@ -12,11 +12,12 @@ class Emulative extends \PhpParser\Lexer
protected $newKeywords;
protected $inObjectAccess;
const T_ELLIPSIS = 1001;
const T_POW = 1002;
const T_POW_EQUAL = 1003;
const T_COALESCE = 1004;
const T_SPACESHIP = 1005;
const T_ELLIPSIS = 1001;
const T_POW = 1002;
const T_POW_EQUAL = 1003;
const T_COALESCE = 1004;
const T_SPACESHIP = 1005;
const T_YIELD_FROM = 1006;
const PHP_7_0 = '7.0.0dev';
const PHP_5_6 = '5.6.0rc1';
@ -53,6 +54,7 @@ class Emulative extends \PhpParser\Lexer
}
$this->tokenMap[self::T_COALESCE] = Parser::T_COALESCE;
$this->tokenMap[self::T_SPACESHIP] = Parser::T_SPACESHIP;
$this->tokenMap[self::T_YIELD_FROM] = Parser::T_YIELD_FROM;
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
return;
@ -91,6 +93,10 @@ class Emulative extends \PhpParser\Lexer
$code = str_replace('??', '~__EMU__COALESCE__~', $code);
$code = str_replace('<=>', '~__EMU__SPACESHIP__~', $code);
$code = preg_replace_callback('(yield[ \n\r\t]+from)', function($matches) {
// Encoding $0 in order to preserve exact whitespace
return '~__EMU__YIELDFROM__' . bin2hex($matches[0]) . '__~';
}, $code);
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
return $code;
@ -127,8 +133,9 @@ class Emulative extends \PhpParser\Lexer
if ('BINARY' === $matches[1]) {
// the binary number can either be an integer or a double, so return a LNUMBER
// or DNUMBER respectively
$isInt = is_int(bindec($matches[2]));
$replace = array(
array(is_int(bindec($matches[2])) ? T_LNUMBER : T_DNUMBER, $matches[2], $this->tokens[$i + 1][2])
array($isInt ? T_LNUMBER : T_DNUMBER, $matches[2], $this->tokens[$i + 1][2])
);
} else if ('ELLIPSIS' === $matches[1]) {
$replace = array(
@ -150,9 +157,13 @@ class Emulative extends \PhpParser\Lexer
$replace = array(
array(self::T_SPACESHIP, '<=>', $this->tokens[$i + 1][2]),
);
} else if ('YIELDFROM' === $matches[1]) {
$content = $this->hex2bin($matches[2]);
$replace = array(
array(self::T_YIELD_FROM, $content, $this->tokens[$i + 1][2] - substr_count($content, "\n"))
);
} else {
// just ignore all other __EMU__ sequences
continue;
throw new \RuntimeException('Invalid __EMU__ sequence');
}
array_splice($this->tokens, $i, 3, $replace);
@ -188,11 +199,18 @@ class Emulative extends \PhpParser\Lexer
return '??';
} else if ('SPACESHIP' === $matches[1]) {
return '<=>';
} else if ('YIELDFROM' === $matches[1]) {
return $this->hex2bin($matches[2]);
} else {
return $matches[0];
}
}
private function hex2bin($str) {
// TODO Drop when removing support for PHP 5.3
return pack('H*', $str);
}
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
$token = parent::getNextToken($value, $startAttributes, $endAttributes);
@ -203,11 +221,9 @@ class Emulative extends \PhpParser\Lexer
if (isset($this->newKeywords[strtolower($value)])) {
return $this->newKeywords[strtolower($value)];
}
// keep track of whether we currently are in an object access (after ->)
} elseif (Parser::T_OBJECT_OPERATOR === $token) {
$this->inObjectAccess = true;
} else {
$this->inObjectAccess = false;
// keep track of whether we currently are in an object access (after ->)
$this->inObjectAccess = Parser::T_OBJECT_OPERATOR === $token;
}
return $token;

View File

@ -4,8 +4,9 @@ namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;
class Closure extends Expr
class Closure extends Expr implements FunctionLike
{
/** @var bool Whether the closure is static */
public $static;
@ -15,7 +16,7 @@ class Closure extends Expr
public $params;
/** @var ClosureUse[] use()s */
public $uses;
/** @var null|string|Node\Name[] Return type */
/** @var null|string|Node\Name Return type */
public $returnType;
/** @var Node[] Statements */
public $stmts;
@ -45,4 +46,20 @@ class Closure extends Expr
public function getSubNodeNames() {
return array('static', 'byRef', 'params', 'uses', 'returnType', 'stmts');
}
public function returnsByRef() {
return $this->byRef;
}
public function getParams() {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
public function getStmts() {
return $this->stmts;
}
}

View File

@ -7,7 +7,7 @@ use PhpParser\Node\Expr;
class New_ extends Expr
{
/** @var Node\Name|Expr Class name */
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
public $class;
/** @var Node\Arg[] Arguments */
public $args;
@ -15,9 +15,9 @@ class New_ extends Expr
/**
* Constructs a function call node.
*
* @param Node\Name|Expr $class Class name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, array $args = array(), array $attributes = array()) {
parent::__construct(null, $attributes);

View File

@ -15,7 +15,7 @@ class ShellExec extends Expr
* @param array $parts Encapsed string array
* @param array $attributes Additional attributes
*/
public function __construct($parts, array $attributes = array()) {
public function __construct(array $parts, array $attributes = array()) {
parent::__construct(null, $attributes);
$this->parts = $parts;
}

View File

@ -0,0 +1,26 @@
<?php
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class YieldFrom extends Expr
{
/** @var Expr Expression to yield from */
public $expr;
/**
* Constructs an "yield from" node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = array()) {
parent::__construct(null, $attributes);
$this->expr = $expr;
}
public function getSubNodeNames() {
return array('expr');
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace PhpParser\Node;
use PhpParser\Node;
interface FunctionLike extends Node
{
/**
* Whether to return by reference
*
* @return bool
*/
public function returnsByRef();
/**
* List of parameters
*
* @return Node\Param[]
*/
public function getParams();
/**
* Get the declared return type or null
*
* @return null|string|Node\Name
*/
public function getReturnType();
/**
* The function body
*
* @return Node\Stmt[]
*/
public function getStmts();
}

View File

@ -28,7 +28,7 @@ class Param extends NodeAbstract
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
*/
public function __construct($name, $default = null, $type = null, $byRef = false, $variadic = false, array $attributes = array()) {
public function __construct($name, Expr $default = null, $type = null, $byRef = false, $variadic = false, array $attributes = array()) {
parent::__construct(null, $attributes);
$this->type = $type;
$this->byRef = $byRef;
@ -37,7 +37,7 @@ class Param extends NodeAbstract
$this->default = $default;
if ($variadic && null !== $default) {
throw new Error('Variadic parameter cannot have a default value');
throw new Error('Variadic parameter cannot have a default value', $default->getAttributes());
}
}

View File

@ -10,6 +10,11 @@ abstract class ClassLike extends Node\Stmt {
/** @var Node[] Statements */
public $stmts;
/**
* Gets all methods defined directly in this class/interface/trait
*
* @return ClassMethod[]
*/
public function getMethods() {
$methods = array();
foreach ($this->stmts as $stmt) {
@ -19,4 +24,21 @@ abstract class ClassLike extends Node\Stmt {
}
return $methods;
}
/**
* Gets method with the given name defined directly in this class/interface/trait.
*
* @param string $name Name of the method (compared case-insensitively)
*
* @return ClassMethod|null Method node or null if the method does not exist
*/
public function getMethod($name) {
$lowerName = strtolower($name);
foreach ($this->stmts as $stmt) {
if ($stmt instanceof ClassMethod && $lowerName === strtolower($stmt->name)) {
return $stmt;
}
}
return null;
}
}

View File

@ -3,9 +3,10 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Error;
class ClassMethod extends Node\Stmt
class ClassMethod extends Node\Stmt implements FunctionLike
{
/** @var int Type */
public $type;
@ -15,7 +16,7 @@ class ClassMethod extends Node\Stmt
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|string|Node\Name[] Return type */
/** @var null|string|Node\Name Return type */
public $returnType;
/** @var Node[] Statements */
public $stmts;
@ -57,8 +58,25 @@ class ClassMethod extends Node\Stmt
return array('type', 'byRef', 'name', 'params', 'returnType', 'stmts');
}
public function returnsByRef() {
return $this->byRef;
}
public function getParams() {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
public function getStmts() {
return $this->stmts;
}
public function isPublic() {
return ($this->type & Class_::MODIFIER_PUBLIC) !== 0 || $this->type === 0;
return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
}
public function isProtected() {

View File

@ -32,7 +32,7 @@ class Class_ extends ClassLike
/**
* Constructs a class node.
*
* @param string $name Name
* @param string|null $name Name
* @param array $subNodes Array of the following optional subnodes:
* 'type' => 0 : Type
* 'extends' => null : Name of extended class
@ -48,17 +48,23 @@ class Class_ extends ClassLike
$this->implements = isset($subNodes['implements']) ? $subNodes['implements'] : array();
$this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
if (isset(self::$specialNames[(string) $this->name])) {
if (null !== $this->name && isset(self::$specialNames[strtolower($this->name)])) {
throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
}
if (isset(self::$specialNames[(string) $this->extends])) {
throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->extends));
if (isset(self::$specialNames[strtolower($this->extends)])) {
throw new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $this->extends),
$this->extends->getAttributes()
);
}
foreach ($this->implements as $interface) {
if (isset(self::$specialNames[(string) $interface])) {
throw new Error(sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface));
if (isset(self::$specialNames[strtolower($interface)])) {
throw new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
);
}
}
}
@ -75,6 +81,10 @@ class Class_ extends ClassLike
return (bool) ($this->type & self::MODIFIER_FINAL);
}
public function isAnonymous() {
return null === $this->name;
}
/**
* @internal
*/

View File

@ -3,8 +3,9 @@
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
class Function_ extends Node\Stmt
class Function_ extends Node\Stmt implements FunctionLike
{
/** @var bool Whether function returns by reference */
public $byRef;
@ -12,7 +13,7 @@ class Function_ extends Node\Stmt
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|string|Node\Name[] Return type */
/** @var null|string|Node\Name Return type */
public $returnType;
/** @var Node[] Statements */
public $stmts;
@ -40,4 +41,20 @@ class Function_ extends Node\Stmt
public function getSubNodeNames() {
return array('byRef', 'name', 'params', 'returnType', 'stmts');
}
public function returnsByRef() {
return $this->byRef;
}
public function getParams() {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
public function getStmts() {
return $this->stmts;
}
}

View File

@ -31,13 +31,16 @@ class Interface_ extends ClassLike
$this->extends = isset($subNodes['extends']) ? $subNodes['extends'] : array();
$this->stmts = isset($subNodes['stmts']) ? $subNodes['stmts'] : array();
if (isset(self::$specialNames[(string) $this->name])) {
if (isset(self::$specialNames[strtolower($this->name)])) {
throw new Error(sprintf('Cannot use \'%s\' as class name as it is reserved', $this->name));
}
foreach ($this->extends as $interface) {
if (isset(self::$specialNames[(string) $interface])) {
throw new Error(sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface));
if (isset(self::$specialNames[strtolower($interface)])) {
throw new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
);
}
}
}

View File

@ -22,7 +22,7 @@ class Namespace_ extends Node\Stmt
* Constructs a namespace node.
*
* @param null|Node\Name $name Name
* @param Node[] $stmts Statements
* @param null|Node[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Name $name = null, $stmts = array(), array $attributes = array()) {
@ -30,14 +30,17 @@ class Namespace_ extends Node\Stmt
$this->name = $name;
$this->stmts = $stmts;
if (isset(self::$specialNames[(string) $this->name])) {
throw new Error(sprintf('Cannot use \'%s\' as namespace name', $this->name));
if (isset(self::$specialNames[strtolower($this->name)])) {
throw new Error(
sprintf('Cannot use \'%s\' as namespace name', $this->name),
$this->name->getAttributes()
);
}
if (null !== $this->stmts) {
foreach ($this->stmts as $stmt) {
if ($stmt instanceof self) {
throw new Error('Namespace declarations cannot be nested', $stmt->getLine());
throw new Error('Namespace declarations cannot be nested', $stmt->getAttributes());
}
}
}

View File

@ -38,7 +38,8 @@ class Property extends Node\Stmt
}
public function isPublic() {
return ($this->type & Class_::MODIFIER_PUBLIC) !== 0 || $this->type === 0;
return ($this->type & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->type & Class_::VISIBILITY_MODIFER_MASK) === 0;
}
public function isProtected() {

View File

@ -24,7 +24,7 @@ class UseUse extends Node\Stmt
$alias = $name->getLast();
}
if ('self' == $alias || 'parent' == $alias) {
if ('self' == strtolower($alias) || 'parent' == strtolower($alias)) {
throw new Error(sprintf(
'Cannot use %s as %s because \'%2$s\' is a special class name',
$name, $alias

View File

@ -37,7 +37,9 @@ class NameResolver extends NodeVisitorAbstract
$interface = $this->resolveClassName($interface);
}
$this->addNamespacedName($node);
if (null !== $node->name) {
$this->addNamespacedName($node);
}
} elseif ($node instanceof Stmt\Interface_) {
foreach ($node->extends as &$interface) {
$interface = $this->resolveClassName($interface);
@ -48,15 +50,11 @@ class NameResolver extends NodeVisitorAbstract
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\Function_) {
$this->addNamespacedName($node);
if ($node->returnType instanceof Name) {
$node->returnType = $this->resolveClassName($node->returnType);
}
$this->resolveSignature($node);
} elseif ($node instanceof Stmt\ClassMethod
|| $node instanceof Expr\Closure
) {
if ($node->returnType instanceof Name) {
$node->returnType = $this->resolveClassName($node->returnType);
}
$this->resolveSignature($node);
} elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) {
$this->addNamespacedName($const);
@ -83,7 +81,7 @@ class NameResolver extends NodeVisitorAbstract
$trait = $this->resolveClassName($trait);
}
foreach($node->adaptations as $adaptation) {
foreach ($node->adaptations as $adaptation) {
if (null !== $adaptation->trait) {
$adaptation->trait = $this->resolveClassName($adaptation->trait);
}
@ -95,10 +93,6 @@ class NameResolver extends NodeVisitorAbstract
}
}
} elseif ($node instanceof Node\Param
&& $node->type instanceof Name
) {
$node->type = $this->resolveClassName($node->type);
}
}
@ -138,9 +132,21 @@ class NameResolver extends NodeVisitorAbstract
$this->aliases[$type][$aliasName] = $use->name;
}
/** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
private function resolveSignature($node) {
foreach ($node->params as $param) {
if ($param->type instanceof Name) {
$param->type = $this->resolveClassName($param->type);
}
}
if ($node->returnType instanceof Name) {
$node->returnType = $this->resolveClassName($node->returnType);
}
}
protected function resolveClassName(Name $name) {
// don't resolve special class names
if (in_array(strtolower($name), array('self', 'parent', 'static'))) {
if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
if (!$name->isUnqualified()) {
throw new Error(
sprintf("'\\%s' is an invalid class name", $name->toString()),

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,8 @@ abstract class ParserAbstract
/** @var int Symbol number signifying an invalid token */
protected $invalidSymbol;
/** @var int Symbol number of error recovery token */
protected $errorSymbol;
/** @var int Action number signifying default action */
protected $defaultAction;
/** @var int Rule number signifying that an unexpected token was encountered */
@ -76,18 +78,43 @@ abstract class ParserAbstract
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;
/** @var array Semantic value stack (contains values of tokens and semantic action results) */
protected $semStack;
/** @var array[] Start attribute stack */
protected $startAttributeStack;
/** @var array End attributes of last *shifted* token */
protected $endAttributes;
/** @var bool Whether to throw on first error */
protected $throwOnError;
/** @var Error[] Errors collected during last parse */
protected $errors;
/**
* Creates a parser instance.
*
* @param Lexer $lexer A lexer
* @param array $options Options array. The boolean 'throwOnError' option determines whether an exception should be
* thrown on first error, or if the parser should try to continue parsing the remaining code
* and build a partial AST.
*/
public function __construct(Lexer $lexer) {
public function __construct(Lexer $lexer, array $options = array()) {
$this->lexer = $lexer;
$this->errors = array();
$this->throwOnError = isset($options['throwOnError']) ? $options['throwOnError'] : true;
}
/**
* Get array of errors that occurred during the last parse.
*
* This method may only return multiple errors if the 'throwOnError' option is disabled.
*
* @return Error[]
*/
public function getErrors() {
return $this->errors;
}
/**
@ -95,10 +122,12 @@ abstract class ParserAbstract
*
* @param string $code The source code to parse
*
* @return Node[] Array of statements
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
* unable to recover from an error).
*/
public function parse($code) {
$this->lexer->startLexing($code);
$this->errors = array();
// We start off with no lookahead-token
$symbol = self::SYMBOL_NONE;
@ -106,12 +135,13 @@ abstract class ParserAbstract
// 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();
$startAttributes = '*POISON';
$endAttributes = '*POISON';
$this->endAttributes = $endAttributes;
// In order to figure out the attributes for the starting token, we have to keep
// them in a stack
$attributeStack = array($startAttributes);
$this->startAttributeStack = array();
// Start off in the initial state and keep a stack of previous states
$state = 0;
@ -123,6 +153,8 @@ abstract class ParserAbstract
// Current position in the stack(s)
$this->stackPos = 0;
$errorState = 0;
for (;;) {
//$this->traceNewState($state, $symbol);
@ -134,7 +166,7 @@ abstract class ParserAbstract
// 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);
$tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);
// map the lexer token id to the internally used symbols
$symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
@ -148,7 +180,9 @@ abstract class ParserAbstract
));
}
$attributeStack[$this->stackPos] = $startAttributes;
// This is necessary to assign some meaningful attributes to /* empty */ productions. They'll get
// the attributes of the next token, even though they don't contain it themselves.
$this->startAttributeStack[$this->stackPos+1] = $startAttributes;
//$this->traceRead($symbol);
}
@ -171,14 +205,19 @@ abstract class ParserAbstract
//$this->traceShift($symbol);
++$this->stackPos;
$stateStack[$this->stackPos] = $state = $action;
$stateStack[$this->stackPos] = $state = $action;
$this->semStack[$this->stackPos] = $tokenValue;
$attributeStack[$this->stackPos] = $startAttributes;
$endAttributes = $nextEndAttributes;
$this->startAttributeStack[$this->stackPos] = $startAttributes;
$this->endAttributes = $endAttributes;
$symbol = self::SYMBOL_NONE;
if ($action < $this->YYNLSTATES)
if ($errorState) {
--$errorState;
}
if ($action < $this->YYNLSTATES) {
continue;
}
/* $yyn >= YYNLSTATES means shift-and-reduce */
$rule = $action - $this->YYNLSTATES;
@ -200,16 +239,19 @@ abstract class ParserAbstract
//$this->traceReduce($rule);
try {
$this->{'reduceRule' . $rule}(
$attributeStack[$this->stackPos - $this->ruleToLength[$rule]]
+ $endAttributes
);
$this->{'reduceRule' . $rule}();
} catch (Error $e) {
if (-1 === $e->getRawLine() && isset($startAttributes['startLine'])) {
$e->setRawLine($startAttributes['startLine']);
if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
$e->setStartLine($startAttributes['startLine']);
}
throw $e;
$this->errors[] = $e;
if ($this->throwOnError) {
throw $e;
} else {
// Currently can't recover from "special" errors
return null;
}
}
/* Goto - shift nonterminal */
@ -225,27 +267,72 @@ abstract class ParserAbstract
++$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 = '';
}
switch ($errorState) {
case 0:
$msg = $this->getErrorMessage($symbol, $state);
$error = new Error($msg, $startAttributes + $endAttributes);
$this->errors[] = $error;
if ($this->throwOnError) {
throw $error;
}
// Break missing intentionally
case 1:
case 2:
$errorState = 3;
throw new Error(
'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString,
$startAttributes['startLine']
);
// Pop until error-expecting state uncovered
while (!(
(($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
|| ($state < $this->YY2TBLSTATE
&& ($idx = $this->actionBase[$state + $this->YYNLSTATES] + $this->errorSymbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] == $this->errorSymbol)
) || ($action = $this->action[$idx]) == $this->defaultAction) { // Not totally sure about this
if ($this->stackPos <= 0) {
// Could not recover from error
return null;
}
$state = $stateStack[--$this->stackPos];
//$this->tracePop($state);
}
//$this->traceShift($this->errorSymbol);
$stateStack[++$this->stackPos] = $state = $action;
break;
case 3:
if ($symbol === 0) {
// Reached EOF without recovering from error
return null;
}
//$this->traceDiscard($symbol);
$symbol = self::SYMBOL_NONE;
break 2;
}
}
if ($state < $this->YYNLSTATES) {
break;
}
/* >= YYNLSTATES means shift-and-reduce */
$rule = $state - $this->YYNLSTATES;
}
}
throw new \RuntimeException('Reached end of parser loop');
}
protected function getErrorMessage($symbol, $state) {
$expectedString = '';
if ($expected = $this->getExpectedTokens($state)) {
$expectedString = ', expecting ' . implode(' or ', $expected);
}
return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
}
protected function getExpectedTokens($state) {
@ -277,6 +364,7 @@ abstract class ParserAbstract
* 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";
@ -298,6 +386,15 @@ abstract class ParserAbstract
echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
}
protected function tracePop($state) {
echo '% Recovering, uncovered state ' . $state . "\n";
}
protected function traceDiscard($symbol) {
echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
}
*/
/*
* Helper functions invoked by semantic actions
*/

View File

@ -96,7 +96,10 @@ class Standard extends PrettyPrinterAbstract
}
public function pScalar_DNumber(Scalar\DNumber $node) {
$stringValue = (string) $node->value;
$stringValue = sprintf('%.16G', $node->value);
if ($node->value !== (double) $stringValue) {
$stringValue = sprintf('%.17G', $node->value);
}
// ensure that number is really printed as float
return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue;
@ -312,6 +315,14 @@ class Standard extends PrettyPrinterAbstract
return $this->pPrefixOp('Expr_ErrorSuppress', '@', $node->expr);
}
public function pExpr_YieldFrom(Expr\YieldFrom $node) {
return $this->pPrefixOp('Expr_YieldFrom', 'yield from ', $node->expr);
}
public function pExpr_Print(Expr\Print_ $node) {
return $this->pPrefixOp('Expr_Print', 'print ', $node->expr);
}
// Casts
public function pExpr_Cast_Int(Cast\Int_ $node) {
@ -372,10 +383,6 @@ class Standard extends PrettyPrinterAbstract
return 'isset(' . $this->pCommaSeparated($node->vars) . ')';
}
public function pExpr_Print(Expr\Print_ $node) {
return 'print ' . $this->p($node->expr);
}
public function pExpr_Eval(Expr\Eval_ $node) {
return 'eval(' . $this->p($node->expr) . ')';
}
@ -462,6 +469,10 @@ class Standard extends PrettyPrinterAbstract
}
public function pExpr_New(Expr\New_ $node) {
if ($node->class instanceof Stmt\Class_) {
$args = $node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : '';
return 'new ' . $this->pClassCommon($node->class, $args);
}
return 'new ' . $this->p($node->class) . '(' . $this->pCommaSeparated($node->args) . ')';
}
@ -523,11 +534,7 @@ class Standard extends PrettyPrinterAbstract
}
public function pStmt_Class(Stmt\Class_ $node) {
return $this->pModifiers($node->type)
. 'class ' . $node->name
. (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
. "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
return $this->pClassCommon($node, ' ' . $node->name);
}
public function pStmt_Trait(Stmt\Trait_ $node) {
@ -725,6 +732,14 @@ class Standard extends PrettyPrinterAbstract
return is_string($node) ? $node : $this->p($node);
}
protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) {
return $this->pModifiers($node->type)
. 'class' . $afterClassToken
. (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
. "\n" . '{' . $this->pStmts($node->stmts) . "\n" . '}';
}
/** @internal */
public function pObjectProperty($node) {
if ($node instanceof Expr) {

View File

@ -66,6 +66,8 @@ abstract class PrettyPrinterAbstract
'Expr_AssignOp_ShiftLeft' => array(160, 1),
'Expr_AssignOp_ShiftRight' => array(160, 1),
'Expr_AssignOp_Pow' => array(160, 1),
'Expr_YieldFrom' => array(165, 1),
'Expr_Print' => array(168, 1),
'Expr_BinaryOp_LogicalAnd' => array(170, -1),
'Expr_BinaryOp_LogicalXor' => array(180, -1),
'Expr_BinaryOp_LogicalOr' => array(190, -1),

View File

@ -57,7 +57,7 @@ class XML implements Serializer
} elseif ($node instanceof Comment) {
$this->writer->startElement('comment');
$this->writer->writeAttribute('isDocComment', $node instanceof Comment\Doc ? 'true' : 'false');
$this->writer->writeAttribute('line', $node->getLine());
$this->writer->writeAttribute('line', (string) $node->getLine());
$this->writer->text($node->getText());
$this->writer->endElement();
} elseif (is_array($node)) {
@ -69,9 +69,10 @@ class XML implements Serializer
} elseif (is_string($node)) {
$this->writer->writeElement('scalar:string', $node);
} elseif (is_int($node)) {
$this->writer->writeElement('scalar:int', $node);
$this->writer->writeElement('scalar:int', (string) $node);
} elseif (is_float($node)) {
$this->writer->writeElement('scalar:float', $node);
// TODO Higher precision conversion?
$this->writer->writeElement('scalar:float', (string) $node);
} elseif (true === $node) {
$this->writer->writeElement('scalar:true');
} elseif (false === $node) {
@ -82,4 +83,4 @@ class XML implements Serializer
throw new \InvalidArgumentException('Unexpected node type');
}
}
}
}

View File

@ -9,7 +9,7 @@ interface Unserializer
*
* @param string $string Serialized string
*
* @return array Statements
* @return mixed Node tree
*/
public function unserialize($string);
}

View File

@ -102,11 +102,7 @@ class XML implements Unserializer
case 'string':
return $this->reader->readString();
case 'int':
$text = $this->reader->readString();
if (false === $int = filter_var($text, FILTER_VALIDATE_INT)) {
throw new DomainException(sprintf('"%s" is not a valid integer', $text));
}
return $int;
return $this->parseInt($this->reader->readString());
case 'float':
$text = $this->reader->readString();
if (false === $float = filter_var($text, FILTER_VALIDATE_FLOAT)) {
@ -125,6 +121,13 @@ class XML implements Unserializer
}
}
private function parseInt($text) {
if (false === $int = filter_var($text, FILTER_VALIDATE_INT)) {
throw new DomainException(sprintf('"%s" is not a valid integer', $text));
}
return $int;
}
protected function readComment() {
$className = $this->reader->getAttribute('isDocComment') === 'true'
? 'PhpParser\Comment\Doc'
@ -132,7 +135,7 @@ class XML implements Unserializer
;
return new $className(
$this->reader->readString(),
$this->reader->getAttribute('line')
$this->parseInt($this->reader->getAttribute('line'))
);
}

View File

@ -23,8 +23,9 @@ class AutoloaderTest extends \PHPUnit_Framework_TestCase {
$this->markTestSkipped('Cannot create aliases to reserved names on PHP 7');
}
$this->assertInstanceof('PhpParser\Node\Expr\Cast\Bool_', new Expr\Cast\Bool(new Expr\Variable('foo')));
$this->assertInstanceof('PhpParser\Node\Expr\Cast\Int_', new Expr\Cast\Int(new Expr\Variable('foo')));
$this->assertTrue(new Expr\Cast\Bool_(new Expr\Variable('foo')) instanceof Expr\Cast\Bool);
$this->assertTrue(new Expr\Cast\Int_(new Expr\Variable('foo')) instanceof Expr\Cast\Int);
$this->assertInstanceof('PhpParser\Node\Expr\Cast\Object_', new Expr\Cast\Object(new Expr\Variable('foo')));
$this->assertInstanceof('PhpParser\Node\Expr\Cast\String_', new Expr\Cast\String(new Expr\Variable('foo')));
$this->assertInstanceof('PhpParser\Node\Scalar\String_', new Scalar\String('foobar'));

View File

@ -47,10 +47,10 @@ class InterfaceTest extends \PHPUnit_Framework_TestCase
public function testAddConst() {
$const = new Stmt\ClassConst(array(
new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458))
new Node\Const_('SPEED_OF_LIGHT', new DNumber(299792458.0))
));
$contract = $this->builder->addStmt($const)->getNode();
$this->assertSame(299792458, $contract->stmts[0]->consts[0]->value->value);
$this->assertSame(299792458.0, $contract->stmts[0]->consts[0]->value->value);
}
public function testOrder() {

View File

@ -17,13 +17,17 @@ class TraitTest extends \PHPUnit_Framework_TestCase
$method1 = new Stmt\ClassMethod('test1');
$method2 = new Stmt\ClassMethod('test2');
$method3 = new Stmt\ClassMethod('test3');
$prop = new Stmt\Property(Stmt\Class_::MODIFIER_PUBLIC, array(
new Stmt\PropertyProperty('test')
));
$trait = $this->createTraitBuilder('TestTrait')
->setDocComment('/** Nice trait */')
->addStmt($method1)
->addStmts(array($method2, $method3))
->addStmt($prop)
->getNode();
$this->assertEquals(new Stmt\Trait_('TestTrait', array(
$method1, $method2, $method3
$prop, $method1, $method2, $method3
), array(
'comments' => array(
new Comment\Doc('/** Nice trait */')

View File

@ -26,4 +26,10 @@ class UseTest extends \PHPUnit_Framework_TestCase
new Stmt\UseUse(new Name('foo\bar'), 'foo')
), Stmt\Use_::TYPE_FUNCTION), $node);
}
public function testNonExistingMethod() {
$this->setExpectedException('LogicException', 'Method "foo" does not exist');
$builder = $this->createUseBuilder('Test');
$builder->foo();
}
}

View File

@ -28,6 +28,12 @@ class BuilderFactoryTest extends \PHPUnit_Framework_TestCase
);
}
public function testNonExistingMethod() {
$this->setExpectedException('LogicException', 'Method "foo" does not exist');
$factory = new BuilderFactory();
$factory->foo();
}
public function testIntegration() {
$factory = new BuilderFactory;
$node = $factory->namespace('Name\Space')

View File

@ -11,8 +11,8 @@ abstract class CodeTestAbstract extends \PHPUnit_Framework_TestCase
$tests = array();
foreach ($it as $file) {
// read file
$fileContents = file_get_contents($file);
$fileName = realpath($file->getPathname());
$fileContents = file_get_contents($fileName);
// evaluate @@{expr}@@ expressions
$fileContents = preg_replace_callback(
@ -25,7 +25,6 @@ abstract class CodeTestAbstract extends \PHPUnit_Framework_TestCase
$parts = array_map('trim', explode('-----', $fileContents));
// first part is the name
$fileName = realpath($file->getPathname());
$name = array_shift($parts) . ' (' . $fileName . ')';
// multiple sections possible with always two forming a pair

View File

@ -5,9 +5,16 @@ namespace PhpParser;
class ErrorTest extends \PHPUnit_Framework_TestCase
{
public function testConstruct() {
$error = new Error('Some error', 10);
$attributes = array(
'startLine' => 10,
'endLine' => 11,
);
$error = new Error('Some error', $attributes);
$this->assertSame('Some error', $error->getRawMessage());
$this->assertSame($attributes, $error->getAttributes());
$this->assertSame(10, $error->getStartLine());
$this->assertSame(11, $error->getEndLine());
$this->assertSame(10, $error->getRawLine());
$this->assertSame('Some error on line 10', $error->getMessage());
@ -19,17 +26,87 @@ class ErrorTest extends \PHPUnit_Framework_TestCase
*/
public function testSetMessageAndLine(Error $error) {
$error->setRawMessage('Some other error');
$error->setRawLine(15);
$this->assertSame('Some other error', $error->getRawMessage());
$this->assertSame(15, $error->getRawLine());
$error->setStartLine(15);
$this->assertSame(15, $error->getStartLine());
$this->assertSame('Some other error on line 15', $error->getMessage());
$error->setRawLine(17);
$this->assertSame(17, $error->getRawLine());
$this->assertSame('Some other error on line 17', $error->getMessage());
}
public function testUnknownLine() {
$error = new Error('Some error');
$this->assertSame(-1, $error->getStartLine());
$this->assertSame(-1, $error->getEndLine());
$this->assertSame(-1, $error->getRawLine());
$this->assertSame('Some error on unknown line', $error->getMessage());
}
}
/** @dataProvider provideTestColumnInfo */
public function testColumnInfo($code, $startPos, $endPos, $startColumn, $endColumn) {
$error = new Error('Some error', array(
'startFilePos' => $startPos,
'endFilePos' => $endPos,
));
$this->assertSame(true, $error->hasColumnInfo());
$this->assertSame($startColumn, $error->getStartColumn($code));
$this->assertSame($endColumn, $error->getEndColumn($code));
}
public function provideTestColumnInfo() {
return array(
// Error at "bar"
array("<?php foo bar baz", 10, 12, 11, 13),
array("<?php\nfoo bar baz", 10, 12, 5, 7),
array("<?php foo\nbar baz", 10, 12, 1, 3),
array("<?php foo bar\nbaz", 10, 12, 11, 13),
array("<?php\r\nfoo bar baz", 11, 13, 5, 7),
// Error at "baz"
array("<?php foo bar baz", 14, 16, 15, 17),
array("<?php foo bar\nbaz", 14, 16, 1, 3),
// Error at string literal
array("<?php foo 'bar\nbaz' xyz", 10, 18, 11, 4),
array("<?php\nfoo 'bar\nbaz' xyz", 10, 18, 5, 4),
array("<?php foo\n'\nbarbaz\n'\nxyz", 10, 19, 1, 1),
// Error over full string
array("<?php", 0, 4, 1, 5),
array("<?\nphp", 0, 5, 1, 3),
);
}
public function testNoColumnInfo() {
$error = new Error('Some error', 3);
$this->assertSame(false, $error->hasColumnInfo());
try {
$error->getStartColumn('');
$this->fail('Expected RuntimeException');
} catch (\RuntimeException $e) {
$this->assertSame('Error does not have column information', $e->getMessage());
}
try {
$error->getEndColumn('');
$this->fail('Expected RuntimeException');
} catch (\RuntimeException $e) {
$this->assertSame('Error does not have column information', $e->getMessage());
}
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Invalid position information
*/
public function testInvalidPosInfo() {
$error = new Error('Some error', array(
'startFilePos' => 10,
'endFilePos' => 11,
));
$error->getStartColumn('code');
}
}

View File

@ -87,6 +87,12 @@ class EmulativeTest extends LexerTest
public function provideTestLexNewFeatures() {
return array(
array('yield from', array(
array(Parser::T_YIELD_FROM, 'yield from'),
)),
array("yield\r\nfrom", array(
array(Parser::T_YIELD_FROM, "yield\r\nfrom"),
)),
array('...', array(
array(Parser::T_ELLIPSIS, '...'),
)),

View File

@ -215,6 +215,18 @@ class LexerTest extends \PHPUnit_Framework_TestCase
);
}
/**
* @expectedException \PhpParser\Error
* @expectedExceptionMessage __HALT_COMPILER must be followed by "();"
*/
public function testHandleHaltCompilerError() {
$lexer = $this->getLexer();
$lexer->startLexing('<?php ... __halt_compiler invalid ();');
while (Parser::T_HALT_COMPILER !== $lexer->getNextToken());
$lexer->handleHaltCompiler();
}
public function testGetTokens() {
$code = '<?php "a";' . "\n" . '// foo' . "\n" . '"b";';
$expectedTokens = array(

View File

@ -36,4 +36,28 @@ class ClassMethodTest extends \PHPUnit_Framework_TestCase
array('static'),
);
}
/**
* Checks that implicit public modifier detection for method is working
*
* @dataProvider implicitPublicModifiers
*
* @param integer $modifier Node type modifier
*/
public function testImplicitPublic($modifier)
{
$node = new ClassMethod('foo', array(
'type' => constant('PhpParser\Node\Stmt\Class_::MODIFIER_' . strtoupper($modifier))
));
$this->assertTrue($node->isPublic(), 'Node should be implicitly public');
}
public function implicitPublicModifiers() {
return array(
array('abstract'),
array('final'),
array('static'),
);
}
}

View File

@ -39,4 +39,21 @@ class ClassTest extends \PHPUnit_Framework_TestCase
$this->assertSame($methods, $class->getMethods());
}
public function testGetMethod() {
$methodConstruct = new ClassMethod('__CONSTRUCT');
$methodTest = new ClassMethod('test');
$class = new Class_('Foo', array(
'stmts' => array(
new ClassConst(array()),
$methodConstruct,
new Property(0, array()),
$methodTest,
)
));
$this->assertSame($methodConstruct, $class->getMethod('__construct'));
$this->assertSame($methodTest, $class->getMethod('test'));
$this->assertNull($class->getMethod('nonExisting'));
}
}

View File

@ -25,6 +25,14 @@ class PropertyTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($node->isStatic());
}
public function testStaticImplicitlyPublic() {
$node = new Property(Class_::MODIFIER_STATIC, array());
$this->assertTrue($node->isPublic());
$this->assertFalse($node->isProtected());
$this->assertFalse($node->isPrivate());
$this->assertTrue($node->isStatic());
}
public function provideModifiers() {
return array(
array('public'),

View File

@ -257,6 +257,7 @@ EOC;
new Stmt\Const_(array(
new Node\Const_('D', new Node\Scalar\String_('E'))
)),
new Expr\New_(new Stmt\Class_(null)),
));
$traverser = new PhpParser\NodeTraverser;
@ -268,10 +269,12 @@ EOC;
$this->assertSame('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
$this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
$this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[4]->class);
$this->assertSame('A', (string) $stmts[1]->stmts[0]->namespacedName);
$this->assertSame('B', (string) $stmts[1]->stmts[1]->namespacedName);
$this->assertSame('C', (string) $stmts[1]->stmts[2]->namespacedName);
$this->assertSame('D', (string) $stmts[1]->stmts[3]->consts[0]->namespacedName);
$this->assertObjectNotHasAttribute('namespacedName', $stmts[1]->stmts[4]->class);
}
public function testAddTraitNamespacedName() {

View File

@ -11,39 +11,59 @@ class ParserTest extends CodeTestAbstract
/**
* @dataProvider provideTestParse
*/
public function testParse($name, $code, $dump) {
$parser = new Parser(new Lexer\Emulative);
$dumper = new NodeDumper;
public function testParse($name, $code, $expected) {
$lexer = new Lexer\Emulative(array('usedAttributes' => array(
'startLine', 'endLine', 'startFilePos', 'endFilePos'
)));
$parser = new Parser($lexer, array(
'throwOnError' => false,
));
$stmts = $parser->parse($code);
$this->assertSame(
$this->canonicalize($dump),
$this->canonicalize($dumper->dump($stmts)),
$name
);
$errors = $parser->getErrors();
$output = '';
foreach ($errors as $error) {
$output .= $this->formatErrorMessage($error, $code) . "\n";
}
if (null !== $stmts) {
$dumper = new NodeDumper;
$output .= $dumper->dump($stmts);
}
$this->assertSame($this->canonicalize($expected), $this->canonicalize($output), $name);
}
public function provideTestParse() {
return $this->getTests(__DIR__ . '/../code/parser', 'test');
}
/**
* @dataProvider provideTestParseFail
*/
public function testParseFail($name, $code, $msg) {
$parser = new Parser(new Lexer\Emulative);
try {
$parser->parse($code);
$this->fail(sprintf('"%s": Expected Error', $name));
} catch (Error $e) {
$this->assertSame($msg, $e->getMessage(), $name);
private function formatErrorMessage(Error $e, $code) {
if ($e->hasColumnInfo()) {
return $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
} else {
return $e->getMessage();
}
}
public function provideTestParseFail() {
return $this->getTests(__DIR__ . '/../code/parser', 'test-fail');
/**
* @expectedException \PhpParser\Error
* @expectedExceptionMessage Syntax error, unexpected EOF on line 1
*/
public function testParserThrowsSyntaxError() {
$parser = new Parser(new Lexer());
$parser->parse('<?php foo');
}
/**
* @expectedException \PhpParser\Error
* @expectedExceptionMessage Cannot use foo as self because 'self' is a special class name on line 1
*/
public function testParserThrowsSpecialError() {
$parser = new Parser(new Lexer());
$parser->parse('<?php use foo as self;');
}
public function testAttributeAssignment() {
@ -124,19 +144,6 @@ EOC;
$parser = new Parser($lexer);
$parser->parse('dummy');
}
public function testInvalidOctals() {
if (version_compare(PHP_VERSION, '7.0-dev', '>=')) {
$this->markTestSkipped('Cannot parse invalid octal numbers on PHP 7');
}
$parser = new Parser(new Lexer);
$stmts = $parser->parse('<?php 0787; 0177777777777777777777787;');
$this->assertInstanceof('PhpParser\Node\Scalar\LNumber', $stmts[0]);
$this->assertInstanceof('PhpParser\Node\Scalar\DNumber', $stmts[1]);
$this->assertSame(7, $stmts[0]->value);
$this->assertSame(0xFFFFFFFFFFFFFFFF, $stmts[1]->value);
}
}
class InvalidTokenLexer extends Lexer {

View File

@ -0,0 +1,13 @@
Error positions
-----
<?php foo
-----
Syntax error, unexpected EOF from 1:10 to 1:10
array(
)
-----
<?php foo /* bar */
-----
Syntax error, unexpected EOF from 1:20 to 1:20
array(
)

View File

@ -0,0 +1,186 @@
Error recovery
-----
<?php
foo()
bar()
baz()
-----
Syntax error, unexpected T_STRING from 4:1 to 4:3
Syntax error, unexpected T_STRING from 5:1 to 5:3
Syntax error, unexpected EOF from 5:6 to 5:6
array(
)
-----
<?php
foo()
bar();
baz();
-----
Syntax error, unexpected T_STRING from 4:1 to 4:3
array(
0: Expr_FuncCall(
name: Name(
parts: array(
0: bar
)
)
args: array(
)
)
1: Expr_FuncCall(
name: Name(
parts: array(
0: baz
)
)
args: array(
)
)
)
-----
<?php
foo();
bar()
baz();
-----
Syntax error, unexpected T_STRING from 5:1 to 5:3
array(
0: Expr_FuncCall(
name: Name(
parts: array(
0: foo
)
)
args: array(
)
)
1: Expr_FuncCall(
name: Name(
parts: array(
0: baz
)
)
args: array(
)
)
)
-----
<?php
abc;
1 + ;
-----
Syntax error, unexpected ';' from 3:5 to 3:5
array(
0: Expr_ConstFetch(
name: Name(
parts: array(
0: abc
)
)
)
)
-----
<?php
function test() {
1 +
}
-----
Syntax error, unexpected '}' from 4:1 to 4:1
array(
0: Stmt_Function(
byRef: false
name: test
params: array(
)
returnType: null
stmts: array(
)
)
)
-----
<?php
$i = 0;
while
$j = 1;
$k = 2;
-----
Syntax error, unexpected T_VARIABLE, expecting '(' from 6:1 to 6:2
array(
0: Expr_Assign(
var: Expr_Variable(
name: i
)
expr: Scalar_LNumber(
value: 0
)
)
1: Expr_Assign(
var: Expr_Variable(
name: j
)
expr: Scalar_LNumber(
value: 1
)
)
2: Expr_Assign(
var: Expr_Variable(
name: k
)
expr: Scalar_LNumber(
value: 2
)
)
)
-----
<?php
$i = 0;
while () {
$j = 1;
}
$k = 2;
// The output here drops the loop - would require Error node to handle this
-----
Syntax error, unexpected ')' from 4:8 to 4:8
array(
0: Expr_Assign(
var: Expr_Variable(
name: i
)
expr: Scalar_LNumber(
value: 0
)
)
1: Expr_Assign(
var: Expr_Variable(
name: j
)
expr: Scalar_LNumber(
value: 1
)
)
2: Expr_Assign(
var: Expr_Variable(
name: k
)
expr: Scalar_LNumber(
value: 2
)
)
)
-----
<?php
// Can't recover this yet, as the '}' for the inner_statement_list
// is always required.
$i = 0;
while (true) {
$i = 1;
$i = 2;
-----
Syntax error, unexpected EOF from 8:12 to 8:12

View File

@ -0,0 +1,8 @@
New without a class
-----
<?php
new;
-----
Syntax error, unexpected ';' from 2:4 to 2:4
array(
)

View File

@ -1,6 +0,0 @@
New without a class
-----
<?php
new;
-----
Syntax error, unexpected ';' on line 2

View File

@ -18,6 +18,7 @@ Different float syntaxes
18446744073709551615;
0xFFFFFFFFFFFFFFFF;
01777777777777777777777;
0177777777777777777777787;
0b1111111111111111111111111111111111111111111111111111111111111111;
-----
array(
@ -63,4 +64,7 @@ array(
13: Scalar_DNumber(
value: @@{ 0xFFFFFFFFFFFFFFFF }@@
)
)
14: Scalar_DNumber(
value: @@{ 0xFFFFFFFFFFFFFFFF }@@
)
)

View File

@ -10,6 +10,7 @@ Different integer syntaxes
0xfff;
0XfFf;
0777;
0787;
0b111000111000;
-----
array(
@ -38,6 +39,9 @@ array(
value: 511
)
8: Scalar_LNumber(
value: 7
)
9: Scalar_LNumber(
value: 3640
)
)
)

View File

@ -0,0 +1,194 @@
Anonymous classes
-----
<?php
new class {
public function test() {}
};
new class extends A implements B, C {};
new class() {
public $foo;
};
new class($a, $b) extends A {
use T;
};
class A {
public function test() {
return new class($this) extends A {
const A = 'B';
};
}
}
-----
array(
0: Expr_New(
class: Stmt_Class(
type: 0
name: null
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
type: 1
byRef: false
name: test
params: array(
)
returnType: null
stmts: array(
)
)
)
)
args: array(
)
)
1: Expr_New(
class: Stmt_Class(
type: 0
name: null
extends: Name(
parts: array(
0: A
)
)
implements: array(
0: Name(
parts: array(
0: B
)
)
1: Name(
parts: array(
0: C
)
)
)
stmts: array(
)
)
args: array(
)
)
2: Expr_New(
class: Stmt_Class(
type: 0
name: null
extends: null
implements: array(
)
stmts: array(
0: Stmt_Property(
type: 1
props: array(
0: Stmt_PropertyProperty(
name: foo
default: null
)
)
)
)
)
args: array(
)
)
3: Expr_New(
class: Stmt_Class(
type: 0
name: null
extends: Name(
parts: array(
0: A
)
)
implements: array(
)
stmts: array(
0: Stmt_TraitUse(
traits: array(
0: Name(
parts: array(
0: T
)
)
)
adaptations: array(
)
)
)
)
args: array(
0: Arg(
value: Expr_Variable(
name: a
)
byRef: false
unpack: false
)
1: Arg(
value: Expr_Variable(
name: b
)
byRef: false
unpack: false
)
)
)
4: Stmt_Class(
type: 0
name: A
extends: null
implements: array(
)
stmts: array(
0: Stmt_ClassMethod(
type: 1
byRef: false
name: test
params: array(
)
returnType: null
stmts: array(
0: Stmt_Return(
expr: Expr_New(
class: Stmt_Class(
type: 0
name: null
extends: Name(
parts: array(
0: A
)
)
implements: array(
)
stmts: array(
0: Stmt_ClassConst(
consts: array(
0: Const(
name: A
value: Scalar_String(
value: B
)
)
)
)
)
)
args: array(
0: Arg(
value: Expr_Variable(
name: this
)
byRef: false
unpack: false
)
)
)
)
)
)
)
)
)

View File

@ -25,8 +25,20 @@ Multiple final modifiers are not allowed on line 1
Cannot use the final modifier on an abstract class member on line 1
-----
<?php abstract final class A { }
// Type in the partial parse could conceivably be any of 0, 16 or 32
-----
Syntax error, unexpected T_FINAL, expecting T_CLASS on line 1
Syntax error, unexpected T_FINAL, expecting T_CLASS from 1:16 to 1:20
array(
0: Stmt_Class(
type: 32
name: A
extends: null
implements: array(
)
stmts: array(
)
)
)
-----
<?php class A { abstract $a; }
-----

View File

@ -0,0 +1,71 @@
Invalid class name
-----
<?php class self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
-----
<?php class PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved on line 1
-----
<?php class static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:13 to 1:18
array(
)
-----
<?php class A extends self {}
-----
Cannot use 'self' as class name as it is reserved from 1:23 to 1:26
-----
<?php class A extends PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved from 1:23 to 1:28
-----
<?php class A extends static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR from 1:23 to 1:28
array(
)
-----
<?php class A implements self {}
-----
Cannot use 'self' as interface name as it is reserved from 1:26 to 1:29
-----
<?php class A implements PARENT {}
-----
Cannot use 'PARENT' as interface name as it is reserved from 1:26 to 1:31
-----
<?php class A implements static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR from 1:26 to 1:31
array(
)
-----
<?php interface self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
-----
<?php interface PARENT {}
-----
Cannot use 'PARENT' as class name as it is reserved on line 1
-----
<?php interface static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:17 to 1:22
array(
)
-----
<?php interface A extends self {}
-----
Cannot use 'self' as interface name as it is reserved from 1:27 to 1:30
-----
<?php interface A extends PARENT {}
-----
Cannot use 'PARENT' as interface name as it is reserved from 1:27 to 1:32
-----
<?php interface A extends static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR from 1:27 to 1:32
array(
)

View File

@ -1,61 +0,0 @@
Invalid class name
-----
<?php class self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
-----
<?php class parent {}
-----
Cannot use 'parent' as class name as it is reserved on line 1
-----
<?php class static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING on line 1
-----
<?php class A extends self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
-----
<?php class A extends parent {}
-----
Cannot use 'parent' as class name as it is reserved on line 1
-----
<?php class A extends static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR on line 1
-----
<?php class A implements self {}
-----
Cannot use 'self' as interface name as it is reserved on line 1
-----
<?php class A implements parent {}
-----
Cannot use 'parent' as interface name as it is reserved on line 1
-----
<?php class A implements static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR on line 1
-----
<?php interface self {}
-----
Cannot use 'self' as class name as it is reserved on line 1
-----
<?php interface parent {}
-----
Cannot use 'parent' as class name as it is reserved on line 1
-----
<?php interface static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING on line 1
-----
<?php interface A extends self {}
-----
Cannot use 'self' as interface name as it is reserved on line 1
-----
<?php interface A extends parent {}
-----
Cannot use 'parent' as interface name as it is reserved on line 1
-----
<?php interface A extends static {}
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NAMESPACE or T_NS_SEPARATOR on line 1

View File

@ -1,4 +1,4 @@
Generators (yield expression
Generators (yield expression)
-----
<?php
@ -25,6 +25,10 @@ function gen() {
func(yield $foo);
$foo->func(yield $foo);
new Foo(yield $foo);
yield from $foo;
yield from $foo and yield from $bar;
yield from $foo + $bar;
}
-----
array(
@ -226,6 +230,33 @@ array(
)
)
)
15: Expr_YieldFrom(
expr: Expr_Variable(
name: foo
)
)
16: Expr_BinaryOp_LogicalAnd(
left: Expr_YieldFrom(
expr: Expr_Variable(
name: foo
)
)
right: Expr_YieldFrom(
expr: Expr_Variable(
name: bar
)
)
)
17: Expr_YieldFrom(
expr: Expr_BinaryOp_Plus(
left: Expr_Variable(
name: foo
)
right: Expr_Variable(
name: bar
)
)
)
)
)
)

View File

@ -3,4 +3,4 @@ Invalid variadic function
<?php
function foo(...$foo = []) {}
-----
Variadic parameter cannot have a default value on line 2
Variadic parameter cannot have a default value from 2:24 to 2:25

View File

@ -0,0 +1,34 @@
Use of __HALT_COMPILER_OFFSET__ constant
-----
<?php
var_dump(__HALT_COMPILER_OFFSET__);
__halt_compiler();
Foo
-----
array(
0: Expr_FuncCall(
name: Name(
parts: array(
0: var_dump
)
)
args: array(
0: Arg(
value: Expr_ConstFetch(
name: Name(
parts: array(
0: __HALT_COMPILER_OFFSET__
)
)
)
byRef: false
unpack: false
)
)
)
1: Stmt_HaltCompiler(
remaining:
Foo
)
)

View File

@ -5,4 +5,4 @@ if (true) {
__halt_compiler();
}
-----
__HALT_COMPILER() can only be used from the outermost scope on line 3
__HALT_COMPILER() can only be used from the outermost scope from 3:5 to 3:19

View File

@ -0,0 +1,29 @@
Invalid namespace names
-----
<?php namespace self;
-----
Cannot use 'self' as namespace name from 1:17 to 1:20
-----
<?php namespace PARENT;
-----
Cannot use 'PARENT' as namespace name from 1:17 to 1:22
-----
<?php namespace static;
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NS_SEPARATOR or '{' from 1:17 to 1:22
array(
)
-----
<?php use A as self;
-----
Cannot use A as self because 'self' is a special class name on line 1
-----
<?php use B as PARENT;
-----
Cannot use B as PARENT because 'PARENT' is a special class name on line 1
-----
<?php use C as static;
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:16 to 1:21
array(
)

View File

@ -1,25 +0,0 @@
Invalid namespace names
-----
<?php namespace self;
-----
Cannot use 'self' as namespace name on line 1
-----
<?php namespace parent;
-----
Cannot use 'parent' as namespace name on line 1
-----
<?php namespace static;
-----
Syntax error, unexpected T_STATIC, expecting T_STRING or T_NS_SEPARATOR or '{' on line 1
-----
<?php use A as self;
-----
Cannot use A as self because 'self' is a special class name on line 1
-----
<?php use B as parent;
-----
Cannot use B as parent because 'parent' is a special class name on line 1
-----
<?php use C as static;
-----
Syntax error, unexpected T_STATIC, expecting T_STRING on line 1

View File

@ -7,4 +7,4 @@ namespace A {
}
}
-----
Namespace declarations cannot be nested on line 3
Namespace declarations cannot be nested from 3:5 to 5:5

View File

@ -0,0 +1,27 @@
Anonymous classes
-----
<?php
new class {};
new class extends A implements B, C {};
new class($a) extends A {
private $a;
public function __construct($a) {
$this->a = $a;
}
};
-----
new class
{
};
new class extends A implements B, C
{
};
new class($a) extends A
{
private $a;
public function __construct($a)
{
$this->a = $a;
}
};

View File

@ -37,6 +37,8 @@ FALSE;
1e1000;
1E-100;
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
378282246310005.0;
10000000000000002.0;
// strings (normalized to single quoted)
'a';
@ -112,6 +114,8 @@ FALSE;
INF;
1.0E-100;
1.0E+84;
378282246310005.0;
10000000000000002.0;
// strings (normalized to single quoted)
'a';
'a

View File

@ -33,6 +33,11 @@ $a ** $b ** $c;
($a ** $b) ** $c;
-1 ** 2;
yield from $a and yield from $b;
yield from ($a and yield from $b);
print ($a and print $b);
// The following will currently add unnecessary parentheses, because the pretty printer is not aware that assignment
// and incdec only work on variables.
!$a = $b;
@ -62,6 +67,9 @@ $a + $b++;
$a ** $b ** $c;
($a ** $b) ** $c;
-1 ** 2;
yield from $a and yield from $b;
yield from ($a and yield from $b);
print ($a and print $b);
// The following will currently add unnecessary parentheses, because the pretty printer is not aware that assignment
// and incdec only work on variables.
!($a = $b);