parent
e86b3f7b00
commit
5b7cdafdf4
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Json;
|
||||||
|
|
||||||
|
use Shared\Log;
|
||||||
|
|
||||||
|
class JsonSerializer
|
||||||
|
{
|
||||||
|
public static function serialize($data): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return json_encode($data);
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
// Gérer l'erreur ici, par exemple, journaliser l'exception
|
||||||
|
error_log('Erreur de sérialisation JSON : ' . $e->getMessage());
|
||||||
|
return ''; // Ou retournez une valeur par défaut, selon vos besoins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$finder = PhpCsFixer\Finder::create()
|
||||||
|
->exclude('PhpParser/Parser')
|
||||||
|
->in(__DIR__ . '/lib')
|
||||||
|
->in(__DIR__ . '/test')
|
||||||
|
->in(__DIR__ . '/grammar')
|
||||||
|
;
|
||||||
|
|
||||||
|
$config = new PhpCsFixer\Config();
|
||||||
|
return $config->setRiskyAllowed(true)
|
||||||
|
->setRules([
|
||||||
|
'@PSR12' => true,
|
||||||
|
// We use PSR12 with consistent brace placement.
|
||||||
|
'curly_braces_position' => [
|
||||||
|
'functions_opening_brace' => 'same_line',
|
||||||
|
'classes_opening_brace' => 'same_line',
|
||||||
|
],
|
||||||
|
// declare(strict_types=1) on the same line as <?php.
|
||||||
|
'blank_line_after_opening_tag' => false,
|
||||||
|
'declare_strict_types' => true,
|
||||||
|
// Keep argument formatting for now.
|
||||||
|
'method_argument_space' => ['on_multiline' => 'ignore'],
|
||||||
|
'phpdoc_align' => ['align' => 'left'],
|
||||||
|
'phpdoc_trim' => true,
|
||||||
|
'no_empty_phpdoc' => true,
|
||||||
|
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
|
||||||
|
'no_extra_blank_lines' => true,
|
||||||
|
])
|
||||||
|
->setFinder($finder)
|
||||||
|
;
|
@ -0,0 +1,10 @@
|
|||||||
|
.PHONY: phpstan php-cs-fixer
|
||||||
|
|
||||||
|
tools/vendor:
|
||||||
|
composer install -d tools
|
||||||
|
|
||||||
|
phpstan: tools/vendor
|
||||||
|
tools/vendor/bin/phpstan
|
||||||
|
|
||||||
|
php-cs-fixer: tools/vendor
|
||||||
|
tools/vendor/bin/php-cs-fixer fix
|
@ -1,30 +0,0 @@
|
|||||||
What do all those files mean?
|
|
||||||
=============================
|
|
||||||
|
|
||||||
* `php5.y`: PHP 5 grammar written in a pseudo language
|
|
||||||
* `php7.y`: PHP 7 grammar written in a pseudo language
|
|
||||||
* `tokens.y`: Tokens definition shared between PHP 5 and PHP 7 grammars
|
|
||||||
* `parser.template`: A `kmyacc` parser prototype file for PHP
|
|
||||||
* `tokens.template`: A `kmyacc` prototype file for the `Tokens` class
|
|
||||||
* `rebuildParsers.php`: Preprocesses the grammar and builds the parser using `kmyacc`
|
|
||||||
|
|
||||||
.phpy pseudo language
|
|
||||||
=====================
|
|
||||||
|
|
||||||
The `.y` file is a normal grammar in `kmyacc` (`yacc`) style, with some transformations
|
|
||||||
applied to it:
|
|
||||||
|
|
||||||
* Nodes are created using the syntax `Name[..., ...]`. This is transformed into
|
|
||||||
`new Name(..., ..., attributes())`
|
|
||||||
* Some function-like constructs are resolved (see `rebuildParsers.php` for a list)
|
|
||||||
|
|
||||||
Building the parser
|
|
||||||
===================
|
|
||||||
|
|
||||||
Run `php grammar/rebuildParsers.php` to rebuild the parsers. Additional options:
|
|
||||||
|
|
||||||
* The `KMYACC` environment variable can be used to specify an alternative `kmyacc` binary.
|
|
||||||
By default the `phpyacc` dev dependency will be used. To use the original `kmyacc`, you
|
|
||||||
need to compile [moriyoshi's fork](https://github.com/moriyoshi/kmyacc-forked).
|
|
||||||
* The `--debug` option enables emission of debug symbols and creates the `y.output` file.
|
|
||||||
* The `--keep-tmp-grammar` option preserves the preprocessed grammar file.
|
|
@ -1,106 +0,0 @@
|
|||||||
<?php
|
|
||||||
$meta #
|
|
||||||
#semval($) $this->semValue
|
|
||||||
#semval($,%t) $this->semValue
|
|
||||||
#semval(%n) $stackPos-(%l-%n)
|
|
||||||
#semval(%n,%t) $stackPos-(%l-%n)
|
|
||||||
|
|
||||||
namespace PhpParser\Parser;
|
|
||||||
|
|
||||||
use PhpParser\Error;
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\Node\Expr;
|
|
||||||
use PhpParser\Node\Name;
|
|
||||||
use PhpParser\Node\Scalar;
|
|
||||||
use PhpParser\Node\Stmt;
|
|
||||||
#include;
|
|
||||||
|
|
||||||
/* This is an automatically GENERATED file, which should not be manually edited.
|
|
||||||
* Instead edit one of the following:
|
|
||||||
* * the grammar files grammar/php5.y or grammar/php7.y
|
|
||||||
* * the skeleton file grammar/parser.template
|
|
||||||
* * the preprocessing script grammar/rebuildParsers.php
|
|
||||||
*/
|
|
||||||
class #(-p) extends \PhpParser\ParserAbstract
|
|
||||||
{
|
|
||||||
protected $tokenToSymbolMapSize = #(YYMAXLEX);
|
|
||||||
protected $actionTableSize = #(YYLAST);
|
|
||||||
protected $gotoTableSize = #(YYGLAST);
|
|
||||||
|
|
||||||
protected $invalidSymbol = #(YYBADCH);
|
|
||||||
protected $errorSymbol = #(YYINTERRTOK);
|
|
||||||
protected $defaultAction = #(YYDEFAULT);
|
|
||||||
protected $unexpectedTokenRule = #(YYUNEXPECTED);
|
|
||||||
|
|
||||||
protected $YY2TBLSTATE = #(YY2TBLSTATE);
|
|
||||||
protected $numNonLeafStates = #(YYNLSTATES);
|
|
||||||
|
|
||||||
protected $symbolToName = array(
|
|
||||||
#listvar terminals
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $tokenToSymbol = array(
|
|
||||||
#listvar yytranslate
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $action = array(
|
|
||||||
#listvar yyaction
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $actionCheck = array(
|
|
||||||
#listvar yycheck
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $actionBase = array(
|
|
||||||
#listvar yybase
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $actionDefault = array(
|
|
||||||
#listvar yydefault
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $goto = array(
|
|
||||||
#listvar yygoto
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $gotoCheck = array(
|
|
||||||
#listvar yygcheck
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $gotoBase = array(
|
|
||||||
#listvar yygbase
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $gotoDefault = array(
|
|
||||||
#listvar yygdefault
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $ruleToNonTerminal = array(
|
|
||||||
#listvar yylhs
|
|
||||||
);
|
|
||||||
|
|
||||||
protected $ruleToLength = array(
|
|
||||||
#listvar yylen
|
|
||||||
);
|
|
||||||
#if -t
|
|
||||||
|
|
||||||
protected $productions = array(
|
|
||||||
#production-strings;
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected function initReduceCallbacks() {
|
|
||||||
$this->reduceCallbacks = [
|
|
||||||
#reduce
|
|
||||||
%n => function ($stackPos) {
|
|
||||||
%b
|
|
||||||
},
|
|
||||||
#noact
|
|
||||||
%n => function ($stackPos) {
|
|
||||||
$this->semValue = $this->semStack[$stackPos];
|
|
||||||
},
|
|
||||||
#endreduce
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#tailcode;
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,184 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
///////////////////////////////
|
|
||||||
/// Utility regex constants ///
|
|
||||||
///////////////////////////////
|
|
||||||
|
|
||||||
const LIB = '(?(DEFINE)
|
|
||||||
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
|
|
||||||
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
|
|
||||||
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
|
|
||||||
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
|
|
||||||
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
|
|
||||||
)';
|
|
||||||
|
|
||||||
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]';
|
|
||||||
const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
|
|
||||||
|
|
||||||
///////////////////////////////
|
|
||||||
/// Preprocessing functions ///
|
|
||||||
///////////////////////////////
|
|
||||||
|
|
||||||
function preprocessGrammar($code) {
|
|
||||||
$code = resolveNodes($code);
|
|
||||||
$code = resolveMacros($code);
|
|
||||||
$code = resolveStackAccess($code);
|
|
||||||
|
|
||||||
return $code;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveNodes($code) {
|
|
||||||
return preg_replace_callback(
|
|
||||||
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
|
|
||||||
function($matches) {
|
|
||||||
// recurse
|
|
||||||
$matches['params'] = resolveNodes($matches['params']);
|
|
||||||
|
|
||||||
$params = magicSplit(
|
|
||||||
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
|
||||||
$matches['params']
|
|
||||||
);
|
|
||||||
|
|
||||||
$paramCode = '';
|
|
||||||
foreach ($params as $param) {
|
|
||||||
$paramCode .= $param . ', ';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
|
|
||||||
},
|
|
||||||
$code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveMacros($code) {
|
|
||||||
return preg_replace_callback(
|
|
||||||
'~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
|
|
||||||
function($matches) {
|
|
||||||
// recurse
|
|
||||||
$matches['args'] = resolveMacros($matches['args']);
|
|
||||||
|
|
||||||
$name = $matches['name'];
|
|
||||||
$args = magicSplit(
|
|
||||||
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
|
|
||||||
$matches['args']
|
|
||||||
);
|
|
||||||
|
|
||||||
if ('attributes' === $name) {
|
|
||||||
assertArgs(0, $args, $name);
|
|
||||||
return '$this->startAttributeStack[#1] + $this->endAttributes';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('stackAttributes' === $name) {
|
|
||||||
assertArgs(1, $args, $name);
|
|
||||||
return '$this->startAttributeStack[' . $args[0] . ']'
|
|
||||||
. ' + $this->endAttributeStack[' . $args[0] . ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('init' === $name) {
|
|
||||||
return '$$ = array(' . implode(', ', $args) . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('push' === $name) {
|
|
||||||
assertArgs(2, $args, $name);
|
|
||||||
|
|
||||||
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('pushNormalizing' === $name) {
|
|
||||||
assertArgs(2, $args, $name);
|
|
||||||
|
|
||||||
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
|
|
||||||
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('toArray' == $name) {
|
|
||||||
assertArgs(1, $args, $name);
|
|
||||||
|
|
||||||
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('parseVar' === $name) {
|
|
||||||
assertArgs(1, $args, $name);
|
|
||||||
|
|
||||||
return 'substr(' . $args[0] . ', 1)';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('parseEncapsed' === $name) {
|
|
||||||
assertArgs(3, $args, $name);
|
|
||||||
|
|
||||||
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
|
|
||||||
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('makeNop' === $name) {
|
|
||||||
assertArgs(3, $args, $name);
|
|
||||||
|
|
||||||
return '$startAttributes = ' . $args[1] . ';'
|
|
||||||
. ' if (isset($startAttributes[\'comments\']))'
|
|
||||||
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
|
|
||||||
. ' else { ' . $args[0] . ' = null; }';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('makeZeroLengthNop' == $name) {
|
|
||||||
assertArgs(2, $args, $name);
|
|
||||||
|
|
||||||
return '$startAttributes = ' . $args[1] . ';'
|
|
||||||
. ' if (isset($startAttributes[\'comments\']))'
|
|
||||||
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }'
|
|
||||||
. ' else { ' . $args[0] . ' = null; }';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('prependLeadingComments' === $name) {
|
|
||||||
assertArgs(1, $args, $name);
|
|
||||||
|
|
||||||
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
|
|
||||||
. 'if (!empty($attrs[\'comments\'])) {'
|
|
||||||
. '$stmts[0]->setAttribute(\'comments\', '
|
|
||||||
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $matches[0];
|
|
||||||
},
|
|
||||||
$code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function assertArgs($num, $args, $name) {
|
|
||||||
if ($num != count($args)) {
|
|
||||||
die('Wrong argument count for ' . $name . '().');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////
|
|
||||||
/// Regex helper functions ///
|
|
||||||
//////////////////////////////
|
|
||||||
|
|
||||||
function regex($regex) {
|
|
||||||
return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
|
|
||||||
}
|
|
||||||
|
|
||||||
function magicSplit($regex, $string) {
|
|
||||||
$pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
|
|
||||||
|
|
||||||
foreach ($pieces as &$piece) {
|
|
||||||
$piece = trim($piece);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($pieces === ['']) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $pieces;
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require __DIR__ . '/phpyLang.php';
|
|
||||||
|
|
||||||
$grammarFileToName = [
|
|
||||||
__DIR__ . '/php5.y' => 'Php5',
|
|
||||||
__DIR__ . '/php7.y' => 'Php7',
|
|
||||||
];
|
|
||||||
|
|
||||||
$tokensFile = __DIR__ . '/tokens.y';
|
|
||||||
$tokensTemplate = __DIR__ . '/tokens.template';
|
|
||||||
$skeletonFile = __DIR__ . '/parser.template';
|
|
||||||
$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy';
|
|
||||||
$tmpResultFile = __DIR__ . '/tmp_parser.php';
|
|
||||||
$resultDir = __DIR__ . '/../lib/PhpParser/Parser';
|
|
||||||
$tokensResultsFile = $resultDir . '/Tokens.php';
|
|
||||||
|
|
||||||
$kmyacc = getenv('KMYACC');
|
|
||||||
if (!$kmyacc) {
|
|
||||||
// Use phpyacc from dev dependencies by default.
|
|
||||||
$kmyacc = __DIR__ . '/../vendor/bin/phpyacc';
|
|
||||||
}
|
|
||||||
|
|
||||||
$options = array_flip($argv);
|
|
||||||
$optionDebug = isset($options['--debug']);
|
|
||||||
$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);
|
|
||||||
|
|
||||||
///////////////////
|
|
||||||
/// Main script ///
|
|
||||||
///////////////////
|
|
||||||
|
|
||||||
$tokens = file_get_contents($tokensFile);
|
|
||||||
|
|
||||||
foreach ($grammarFileToName as $grammarFile => $name) {
|
|
||||||
echo "Building temporary $name grammar file.\n";
|
|
||||||
|
|
||||||
$grammarCode = file_get_contents($grammarFile);
|
|
||||||
$grammarCode = str_replace('%tokens', $tokens, $grammarCode);
|
|
||||||
$grammarCode = preprocessGrammar($grammarCode);
|
|
||||||
|
|
||||||
file_put_contents($tmpGrammarFile, $grammarCode);
|
|
||||||
|
|
||||||
$additionalArgs = $optionDebug ? '-t -v' : '';
|
|
||||||
|
|
||||||
echo "Building $name parser.\n";
|
|
||||||
$output = execCmd("$kmyacc $additionalArgs -m $skeletonFile -p $name $tmpGrammarFile");
|
|
||||||
|
|
||||||
$resultCode = file_get_contents($tmpResultFile);
|
|
||||||
$resultCode = removeTrailingWhitespace($resultCode);
|
|
||||||
|
|
||||||
ensureDirExists($resultDir);
|
|
||||||
file_put_contents("$resultDir/$name.php", $resultCode);
|
|
||||||
unlink($tmpResultFile);
|
|
||||||
|
|
||||||
echo "Building token definition.\n";
|
|
||||||
$output = execCmd("$kmyacc -m $tokensTemplate $tmpGrammarFile");
|
|
||||||
rename($tmpResultFile, $tokensResultsFile);
|
|
||||||
|
|
||||||
if (!$optionKeepTmpGrammar) {
|
|
||||||
unlink($tmpGrammarFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////
|
|
||||||
/// Utility helper functions ///
|
|
||||||
////////////////////////////////
|
|
||||||
|
|
||||||
function ensureDirExists($dir) {
|
|
||||||
if (!is_dir($dir)) {
|
|
||||||
mkdir($dir, 0777, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function execCmd($cmd) {
|
|
||||||
$output = trim(shell_exec("$cmd 2>&1"));
|
|
||||||
if ($output !== "") {
|
|
||||||
echo "> " . $cmd . "\n";
|
|
||||||
echo $output;
|
|
||||||
}
|
|
||||||
return $output;
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
$meta #
|
|
||||||
#semval($) $this->semValue
|
|
||||||
#semval($,%t) $this->semValue
|
|
||||||
#semval(%n) $this->stackPos-(%l-%n)
|
|
||||||
#semval(%n,%t) $this->stackPos-(%l-%n)
|
|
||||||
|
|
||||||
namespace PhpParser\Parser;
|
|
||||||
#include;
|
|
||||||
|
|
||||||
/* GENERATED file based on grammar/tokens.y */
|
|
||||||
final class Tokens
|
|
||||||
{
|
|
||||||
#tokenval
|
|
||||||
const %s = %n;
|
|
||||||
#endtokenval
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for
|
|
||||||
* both. This is enforced by sharing this token file. */
|
|
||||||
|
|
||||||
%right T_THROW
|
|
||||||
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
|
|
||||||
%left ','
|
|
||||||
%left T_LOGICAL_OR
|
|
||||||
%left T_LOGICAL_XOR
|
|
||||||
%left T_LOGICAL_AND
|
|
||||||
%right T_PRINT
|
|
||||||
%right T_YIELD
|
|
||||||
%right T_DOUBLE_ARROW
|
|
||||||
%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 T_COALESCE_EQUAL
|
|
||||||
%left '?' ':'
|
|
||||||
%right T_COALESCE
|
|
||||||
%left T_BOOLEAN_OR
|
|
||||||
%left T_BOOLEAN_AND
|
|
||||||
%left '|'
|
|
||||||
%left '^'
|
|
||||||
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
|
||||||
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
|
|
||||||
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
|
|
||||||
%left T_SL T_SR
|
|
||||||
%left '+' '-' '.'
|
|
||||||
%left '*' '/' '%'
|
|
||||||
%right '!'
|
|
||||||
%nonassoc T_INSTANCEOF
|
|
||||||
%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
|
|
||||||
%right T_POW
|
|
||||||
%right '['
|
|
||||||
%nonassoc T_NEW T_CLONE
|
|
||||||
%token T_EXIT
|
|
||||||
%token T_IF
|
|
||||||
%left T_ELSEIF
|
|
||||||
%left T_ELSE
|
|
||||||
%left T_ENDIF
|
|
||||||
%token T_LNUMBER
|
|
||||||
%token T_DNUMBER
|
|
||||||
%token T_STRING
|
|
||||||
%token T_STRING_VARNAME
|
|
||||||
%token T_VARIABLE
|
|
||||||
%token T_NUM_STRING
|
|
||||||
%token T_INLINE_HTML
|
|
||||||
%token T_ENCAPSED_AND_WHITESPACE
|
|
||||||
%token T_CONSTANT_ENCAPSED_STRING
|
|
||||||
%token T_ECHO
|
|
||||||
%token T_DO
|
|
||||||
%token T_WHILE
|
|
||||||
%token T_ENDWHILE
|
|
||||||
%token T_FOR
|
|
||||||
%token T_ENDFOR
|
|
||||||
%token T_FOREACH
|
|
||||||
%token T_ENDFOREACH
|
|
||||||
%token T_DECLARE
|
|
||||||
%token T_ENDDECLARE
|
|
||||||
%token T_AS
|
|
||||||
%token T_SWITCH
|
|
||||||
%token T_MATCH
|
|
||||||
%token T_ENDSWITCH
|
|
||||||
%token T_CASE
|
|
||||||
%token T_DEFAULT
|
|
||||||
%token T_BREAK
|
|
||||||
%token T_CONTINUE
|
|
||||||
%token T_GOTO
|
|
||||||
%token T_FUNCTION
|
|
||||||
%token T_FN
|
|
||||||
%token T_CONST
|
|
||||||
%token T_RETURN
|
|
||||||
%token T_TRY
|
|
||||||
%token T_CATCH
|
|
||||||
%token T_FINALLY
|
|
||||||
%token T_THROW
|
|
||||||
%token T_USE
|
|
||||||
%token T_INSTEADOF
|
|
||||||
%token T_GLOBAL
|
|
||||||
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
|
|
||||||
%token T_VAR
|
|
||||||
%token T_UNSET
|
|
||||||
%token T_ISSET
|
|
||||||
%token T_EMPTY
|
|
||||||
%token T_HALT_COMPILER
|
|
||||||
%token T_CLASS
|
|
||||||
%token T_TRAIT
|
|
||||||
%token T_INTERFACE
|
|
||||||
%token T_ENUM
|
|
||||||
%token T_EXTENDS
|
|
||||||
%token T_IMPLEMENTS
|
|
||||||
%token T_OBJECT_OPERATOR
|
|
||||||
%token T_NULLSAFE_OBJECT_OPERATOR
|
|
||||||
%token T_DOUBLE_ARROW
|
|
||||||
%token T_LIST
|
|
||||||
%token T_ARRAY
|
|
||||||
%token T_CALLABLE
|
|
||||||
%token T_CLASS_C
|
|
||||||
%token T_TRAIT_C
|
|
||||||
%token T_METHOD_C
|
|
||||||
%token T_FUNC_C
|
|
||||||
%token T_LINE
|
|
||||||
%token T_FILE
|
|
||||||
%token T_START_HEREDOC
|
|
||||||
%token T_END_HEREDOC
|
|
||||||
%token T_DOLLAR_OPEN_CURLY_BRACES
|
|
||||||
%token T_CURLY_OPEN
|
|
||||||
%token T_PAAMAYIM_NEKUDOTAYIM
|
|
||||||
%token T_NAMESPACE
|
|
||||||
%token T_NS_C
|
|
||||||
%token T_DIR
|
|
||||||
%token T_NS_SEPARATOR
|
|
||||||
%token T_ELLIPSIS
|
|
||||||
%token T_NAME_FULLY_QUALIFIED
|
|
||||||
%token T_NAME_QUALIFIED
|
|
||||||
%token T_NAME_RELATIVE
|
|
||||||
%token T_ATTRIBUTE
|
|
||||||
%token T_ENUM
|
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace PhpParser;
|
namespace PhpParser;
|
||||||
|
|
||||||
class ConstExprEvaluationException extends \Exception
|
class ConstExprEvaluationException extends \Exception {
|
||||||
{}
|
}
|
||||||
|
@ -0,0 +1,237 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Internal;
|
||||||
|
|
||||||
|
if (\PHP_VERSION_ID >= 80000) {
|
||||||
|
class TokenPolyfill extends \PhpToken {
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a polyfill for the PhpToken class introduced in PHP 8.0. We do not actually polyfill
|
||||||
|
* PhpToken, because composer might end up picking a different polyfill implementation, which does
|
||||||
|
* not meet our requirements.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class TokenPolyfill {
|
||||||
|
/** @var int The ID of the token. Either a T_* constant of a character code < 256. */
|
||||||
|
public int $id;
|
||||||
|
/** @var string The textual content of the token. */
|
||||||
|
public string $text;
|
||||||
|
/** @var int The 1-based starting line of the token (or -1 if unknown). */
|
||||||
|
public int $line;
|
||||||
|
/** @var int The 0-based starting position of the token (or -1 if unknown). */
|
||||||
|
public int $pos;
|
||||||
|
|
||||||
|
/** @var array<int, bool> Tokens ignored by the PHP parser. */
|
||||||
|
private const IGNORABLE_TOKENS = [
|
||||||
|
\T_WHITESPACE => true,
|
||||||
|
\T_COMMENT => true,
|
||||||
|
\T_DOC_COMMENT => true,
|
||||||
|
\T_OPEN_TAG => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var array<int, bool> Tokens that may be part of a T_NAME_* identifier. */
|
||||||
|
private static array $identifierTokens;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Token with the given ID and text, as well optional line and position information.
|
||||||
|
*/
|
||||||
|
final public function __construct(int $id, string $text, int $line = -1, int $pos = -1) {
|
||||||
|
$this->id = $id;
|
||||||
|
$this->text = $text;
|
||||||
|
$this->line = $line;
|
||||||
|
$this->pos = $pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the token. For single-char tokens this will be the token character.
|
||||||
|
* Otherwise it will be a T_* style name, or null if the token ID is unknown.
|
||||||
|
*/
|
||||||
|
public function getTokenName(): ?string {
|
||||||
|
if ($this->id < 256) {
|
||||||
|
return \chr($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = token_name($this->id);
|
||||||
|
return $name === 'UNKNOWN' ? null : $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the token is of the given kind. The kind may be either an integer that matches
|
||||||
|
* the token ID, a string that matches the token text, or an array of integers/strings. In the
|
||||||
|
* latter case, the function returns true if any of the kinds in the array match.
|
||||||
|
*
|
||||||
|
* @param int|string|(int|string)[] $kind
|
||||||
|
*/
|
||||||
|
public function is($kind): bool {
|
||||||
|
if (\is_int($kind)) {
|
||||||
|
return $this->id === $kind;
|
||||||
|
}
|
||||||
|
if (\is_string($kind)) {
|
||||||
|
return $this->text === $kind;
|
||||||
|
}
|
||||||
|
if (\is_array($kind)) {
|
||||||
|
foreach ($kind as $entry) {
|
||||||
|
if (\is_int($entry)) {
|
||||||
|
if ($this->id === $entry) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} elseif (\is_string($entry)) {
|
||||||
|
if ($this->text === $entry) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \TypeError(
|
||||||
|
'Argument #1 ($kind) must only have elements of type string|int, ' .
|
||||||
|
gettype($entry) . ' given');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new \TypeError(
|
||||||
|
'Argument #1 ($kind) must be of type string|int|array, ' .gettype($kind) . ' given');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this token would be ignored by the PHP parser. Returns true for T_WHITESPACE,
|
||||||
|
* T_COMMENT, T_DOC_COMMENT and T_OPEN_TAG, and false for everything else.
|
||||||
|
*/
|
||||||
|
public function isIgnorable(): bool {
|
||||||
|
return isset(self::IGNORABLE_TOKENS[$this->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the textual content of the token.
|
||||||
|
*/
|
||||||
|
public function __toString(): string {
|
||||||
|
return $this->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokenize the given source code and return an array of tokens.
|
||||||
|
*
|
||||||
|
* This performs certain canonicalizations to match the PHP 8.0 token format:
|
||||||
|
* * Bad characters are represented using T_BAD_CHARACTER rather than omitted.
|
||||||
|
* * T_COMMENT does not include trailing newlines, instead the newline is part of a following
|
||||||
|
* T_WHITESPACE token.
|
||||||
|
* * Namespaced names are represented using T_NAME_* tokens.
|
||||||
|
*
|
||||||
|
* @return static[]
|
||||||
|
*/
|
||||||
|
public static function tokenize(string $code, int $flags = 0): array {
|
||||||
|
self::init();
|
||||||
|
|
||||||
|
$tokens = [];
|
||||||
|
$line = 1;
|
||||||
|
$pos = 0;
|
||||||
|
$origTokens = \token_get_all($code, $flags);
|
||||||
|
|
||||||
|
$numTokens = \count($origTokens);
|
||||||
|
for ($i = 0; $i < $numTokens; $i++) {
|
||||||
|
$token = $origTokens[$i];
|
||||||
|
if (\is_string($token)) {
|
||||||
|
if (\strlen($token) === 2) {
|
||||||
|
// b" and B" are tokenized as single-char tokens, even though they aren't.
|
||||||
|
$tokens[] = new static(\ord('"'), $token, $line, $pos);
|
||||||
|
$pos += 2;
|
||||||
|
} else {
|
||||||
|
$tokens[] = new static(\ord($token), $token, $line, $pos);
|
||||||
|
$pos++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$id = $token[0];
|
||||||
|
$text = $token[1];
|
||||||
|
|
||||||
|
// Emulate PHP 8.0 comment format, which does not include trailing whitespace anymore.
|
||||||
|
if ($id === \T_COMMENT && \substr($text, 0, 2) !== '/*' &&
|
||||||
|
\preg_match('/(\r\n|\n|\r)$/D', $text, $matches)
|
||||||
|
) {
|
||||||
|
$trailingNewline = $matches[0];
|
||||||
|
$text = \substr($text, 0, -\strlen($trailingNewline));
|
||||||
|
$tokens[] = new static($id, $text, $line, $pos);
|
||||||
|
$pos += \strlen($text);
|
||||||
|
|
||||||
|
if ($i + 1 < $numTokens && $origTokens[$i + 1][0] === \T_WHITESPACE) {
|
||||||
|
// Move trailing newline into following T_WHITESPACE token, if it already exists.
|
||||||
|
$origTokens[$i + 1][1] = $trailingNewline . $origTokens[$i + 1][1];
|
||||||
|
$origTokens[$i + 1][2]--;
|
||||||
|
} else {
|
||||||
|
// Otherwise, we need to create a new T_WHITESPACE token.
|
||||||
|
$tokens[] = new static(\T_WHITESPACE, $trailingNewline, $line, $pos);
|
||||||
|
$line++;
|
||||||
|
$pos += \strlen($trailingNewline);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emulate PHP 8.0 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and
|
||||||
|
// T_STRING into a single token.
|
||||||
|
if (($id === \T_NS_SEPARATOR || isset(self::$identifierTokens[$id]))) {
|
||||||
|
$newText = $text;
|
||||||
|
$lastWasSeparator = $id === \T_NS_SEPARATOR;
|
||||||
|
for ($j = $i + 1; $j < $numTokens; $j++) {
|
||||||
|
if ($lastWasSeparator) {
|
||||||
|
if (!isset(self::$identifierTokens[$origTokens[$j][0]])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$lastWasSeparator = false;
|
||||||
|
} else {
|
||||||
|
if ($origTokens[$j][0] !== \T_NS_SEPARATOR) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$lastWasSeparator = true;
|
||||||
|
}
|
||||||
|
$newText .= $origTokens[$j][1];
|
||||||
|
}
|
||||||
|
if ($lastWasSeparator) {
|
||||||
|
// Trailing separator is not part of the name.
|
||||||
|
$j--;
|
||||||
|
$newText = \substr($newText, 0, -1);
|
||||||
|
}
|
||||||
|
if ($j > $i + 1) {
|
||||||
|
if ($id === \T_NS_SEPARATOR) {
|
||||||
|
$id = \T_NAME_FULLY_QUALIFIED;
|
||||||
|
} elseif ($id === \T_NAMESPACE) {
|
||||||
|
$id = \T_NAME_RELATIVE;
|
||||||
|
} else {
|
||||||
|
$id = \T_NAME_QUALIFIED;
|
||||||
|
}
|
||||||
|
$tokens[] = new static($id, $newText, $line, $pos);
|
||||||
|
$pos += \strlen($newText);
|
||||||
|
$i = $j - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens[] = new static($id, $text, $line, $pos);
|
||||||
|
$line += \substr_count($text, "\n");
|
||||||
|
$pos += \strlen($text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialize private static state needed by tokenize(). */
|
||||||
|
private static function init(): void {
|
||||||
|
if (isset(self::$identifierTokens)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on semi_reserved production.
|
||||||
|
self::$identifierTokens = \array_fill_keys([
|
||||||
|
\T_STRING,
|
||||||
|
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY,
|
||||||
|
\T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND,
|
||||||
|
\T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE,
|
||||||
|
\T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH,
|
||||||
|
\T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO,
|
||||||
|
\T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT,
|
||||||
|
\T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS,
|
||||||
|
\T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN,
|
||||||
|
\T_MATCH,
|
||||||
|
], true);
|
||||||
|
}
|
||||||
|
}
|
@ -1,47 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace PhpParser\Lexer\TokenEmulator;
|
|
||||||
|
|
||||||
use PhpParser\Lexer\Emulative;
|
|
||||||
|
|
||||||
final class CoaleseEqualTokenEmulator extends TokenEmulator
|
|
||||||
{
|
|
||||||
public function getPhpVersion(): string
|
|
||||||
{
|
|
||||||
return Emulative::PHP_7_4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isEmulationNeeded(string $code): bool
|
|
||||||
{
|
|
||||||
return strpos($code, '??=') !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function emulate(string $code, array $tokens): array
|
|
||||||
{
|
|
||||||
// We need to manually iterate and manage a count because we'll change
|
|
||||||
// the tokens array on the way
|
|
||||||
$line = 1;
|
|
||||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
|
||||||
if (isset($tokens[$i + 1])) {
|
|
||||||
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
|
|
||||||
array_splice($tokens, $i, 2, [
|
|
||||||
[\T_COALESCE_EQUAL, '??=', $line]
|
|
||||||
]);
|
|
||||||
$c--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (\is_array($tokens[$i])) {
|
|
||||||
$line += substr_count($tokens[$i][1], "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reverseEmulate(string $code, array $tokens): array
|
|
||||||
{
|
|
||||||
// ??= was not valid code previously, don't bother.
|
|
||||||
return $tokens;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace PhpParser\Lexer\TokenEmulator;
|
|
||||||
|
|
||||||
use PhpParser\Lexer\Emulative;
|
|
||||||
|
|
||||||
final class FlexibleDocStringEmulator extends TokenEmulator
|
|
||||||
{
|
|
||||||
const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
|
|
||||||
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
|
|
||||||
(?:.*\r?\n)*?
|
|
||||||
(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
|
|
||||||
REGEX;
|
|
||||||
|
|
||||||
public function getPhpVersion(): string
|
|
||||||
{
|
|
||||||
return Emulative::PHP_7_3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isEmulationNeeded(string $code) : bool
|
|
||||||
{
|
|
||||||
return strpos($code, '<<<') !== false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function emulate(string $code, array $tokens): array
|
|
||||||
{
|
|
||||||
// Handled by preprocessing + fixup.
|
|
||||||
return $tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reverseEmulate(string $code, array $tokens): array
|
|
||||||
{
|
|
||||||
// Not supported.
|
|
||||||
return $tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function preprocessCode(string $code, array &$patches): string {
|
|
||||||
if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
|
|
||||||
// No heredoc/nowdoc found
|
|
||||||
return $code;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of how much we need to adjust string offsets due to the modifications we
|
|
||||||
// already made
|
|
||||||
$posDelta = 0;
|
|
||||||
foreach ($matches as $match) {
|
|
||||||
$indentation = $match['indentation'][0];
|
|
||||||
$indentationStart = $match['indentation'][1];
|
|
||||||
|
|
||||||
$separator = $match['separator'][0];
|
|
||||||
$separatorStart = $match['separator'][1];
|
|
||||||
|
|
||||||
if ($indentation === '' && $separator !== '') {
|
|
||||||
// Ordinary heredoc/nowdoc
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($indentation !== '') {
|
|
||||||
// Remove indentation
|
|
||||||
$indentationLen = strlen($indentation);
|
|
||||||
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
|
|
||||||
$patches[] = [$indentationStart + $posDelta, 'add', $indentation];
|
|
||||||
$posDelta -= $indentationLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($separator === '') {
|
|
||||||
// Insert newline as separator
|
|
||||||
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
|
|
||||||
$patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
|
|
||||||
$posDelta += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $code;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace PhpParser\Lexer\TokenEmulator;
|
|
||||||
|
|
||||||
use PhpParser\Lexer\Emulative;
|
|
||||||
|
|
||||||
final class FnTokenEmulator extends KeywordEmulator
|
|
||||||
{
|
|
||||||
public function getPhpVersion(): string
|
|
||||||
{
|
|
||||||
return Emulative::PHP_7_4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getKeywordString(): string
|
|
||||||
{
|
|
||||||
return 'fn';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getKeywordToken(): int
|
|
||||||
{
|
|
||||||
return \T_FN;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace PhpParser\Lexer\TokenEmulator;
|
|
||||||
|
|
||||||
use PhpParser\Lexer\Emulative;
|
|
||||||
|
|
||||||
final class NumericLiteralSeparatorEmulator extends TokenEmulator
|
|
||||||
{
|
|
||||||
const BIN = '(?:0b[01]+(?:_[01]+)*)';
|
|
||||||
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
|
|
||||||
const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
|
|
||||||
const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
|
|
||||||
const EXP = '(?:e[+-]?' . self::DEC . ')';
|
|
||||||
const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
|
|
||||||
const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
|
|
||||||
|
|
||||||
public function getPhpVersion(): string
|
|
||||||
{
|
|
||||||
return Emulative::PHP_7_4;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isEmulationNeeded(string $code) : bool
|
|
||||||
{
|
|
||||||
return preg_match('~[0-9]_[0-9]~', $code)
|
|
||||||
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function emulate(string $code, array $tokens): array
|
|
||||||
{
|
|
||||||
// We need to manually iterate and manage a count because we'll change
|
|
||||||
// the tokens array on the way
|
|
||||||
$codeOffset = 0;
|
|
||||||
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
|
|
||||||
$token = $tokens[$i];
|
|
||||||
$tokenLen = \strlen(\is_array($token) ? $token[1] : $token);
|
|
||||||
|
|
||||||
if ($token[0] !== T_LNUMBER && $token[0] !== T_DNUMBER) {
|
|
||||||
$codeOffset += $tokenLen;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$res = preg_match(self::NUMBER, $code, $matches, 0, $codeOffset);
|
|
||||||
assert($res, "No number at number token position");
|
|
||||||
|
|
||||||
$match = $matches[0];
|
|
||||||
$matchLen = \strlen($match);
|
|
||||||
if ($matchLen === $tokenLen) {
|
|
||||||
// Original token already holds the full number.
|
|
||||||
$codeOffset += $tokenLen;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tokenKind = $this->resolveIntegerOrFloatToken($match);
|
|
||||||
$newTokens = [[$tokenKind, $match, $token[2]]];
|
|
||||||
|
|
||||||
$numTokens = 1;
|
|
||||||
$len = $tokenLen;
|
|
||||||
while ($matchLen > $len) {
|
|
||||||
$nextToken = $tokens[$i + $numTokens];
|
|
||||||
$nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken;
|
|
||||||
$nextTokenLen = \strlen($nextTokenText);
|
|
||||||
|
|
||||||
$numTokens++;
|
|
||||||
if ($matchLen < $len + $nextTokenLen) {
|
|
||||||
// Split trailing characters into a partial token.
|
|
||||||
assert(is_array($nextToken), "Partial token should be an array token");
|
|
||||||
$partialText = substr($nextTokenText, $matchLen - $len);
|
|
||||||
$newTokens[] = [$nextToken[0], $partialText, $nextToken[2]];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$len += $nextTokenLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
array_splice($tokens, $i, $numTokens, $newTokens);
|
|
||||||
$c -= $numTokens - \count($newTokens);
|
|
||||||
$codeOffset += $matchLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function resolveIntegerOrFloatToken(string $str): int
|
|
||||||
{
|
|
||||||
$str = str_replace('_', '', $str);
|
|
||||||
|
|
||||||
if (stripos($str, '0b') === 0) {
|
|
||||||
$num = bindec($str);
|
|
||||||
} elseif (stripos($str, '0x') === 0) {
|
|
||||||
$num = hexdec($str);
|
|
||||||
} elseif (stripos($str, '0') === 0 && ctype_digit($str)) {
|
|
||||||
$num = octdec($str);
|
|
||||||
} else {
|
|
||||||
$num = +$str;
|
|
||||||
}
|
|
||||||
|
|
||||||
return is_float($num) ? T_DNUMBER : T_LNUMBER;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function reverseEmulate(string $code, array $tokens): array
|
|
||||||
{
|
|
||||||
// Numeric separators were not legal code previously, don't bother.
|
|
||||||
return $tokens;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,69 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifiers used (as a bit mask) by various flags subnodes, for example on classes, functions,
|
||||||
|
* properties and constants.
|
||||||
|
*/
|
||||||
|
final class Modifiers {
|
||||||
|
public const PUBLIC = 1;
|
||||||
|
public const PROTECTED = 2;
|
||||||
|
public const PRIVATE = 4;
|
||||||
|
public const STATIC = 8;
|
||||||
|
public const ABSTRACT = 16;
|
||||||
|
public const FINAL = 32;
|
||||||
|
public const READONLY = 64;
|
||||||
|
|
||||||
|
public const VISIBILITY_MASK = 1 | 2 | 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public static function verifyClassModifier(int $a, int $b): void {
|
||||||
|
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
|
||||||
|
throw new Error('Multiple abstract modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
|
||||||
|
throw new Error('Multiple final modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
|
||||||
|
throw new Error('Multiple readonly modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & 48 && $b & 48) {
|
||||||
|
throw new Error('Cannot use the final modifier on an abstract class');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public static function verifyModifier(int $a, int $b): void {
|
||||||
|
if ($a & Modifiers::VISIBILITY_MASK && $b & Modifiers::VISIBILITY_MASK) {
|
||||||
|
throw new Error('Multiple access type modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & Modifiers::ABSTRACT && $b & Modifiers::ABSTRACT) {
|
||||||
|
throw new Error('Multiple abstract modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & Modifiers::STATIC && $b & Modifiers::STATIC) {
|
||||||
|
throw new Error('Multiple static modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & Modifiers::FINAL && $b & Modifiers::FINAL) {
|
||||||
|
throw new Error('Multiple final modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & Modifiers::READONLY && $b & Modifiers::READONLY) {
|
||||||
|
throw new Error('Multiple readonly modifiers are not allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($a & 48 && $b & 48) {
|
||||||
|
throw new Error('Cannot use the final modifier on an abstract class member');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
|
class ArrayItem extends NodeAbstract {
|
||||||
|
/** @var null|Expr Key */
|
||||||
|
public ?Expr $key;
|
||||||
|
/** @var Expr Value */
|
||||||
|
public Expr $value;
|
||||||
|
/** @var bool Whether to assign by reference */
|
||||||
|
public bool $byRef;
|
||||||
|
/** @var bool Whether to unpack the argument */
|
||||||
|
public bool $unpack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an array item node.
|
||||||
|
*
|
||||||
|
* @param Expr $value Value
|
||||||
|
* @param null|Expr $key Key
|
||||||
|
* @param bool $byRef Whether to assign by reference
|
||||||
|
* @param array<string, mixed> $attributes Additional attributes
|
||||||
|
*/
|
||||||
|
public function __construct(Expr $value, ?Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
$this->key = $key;
|
||||||
|
$this->value = $value;
|
||||||
|
$this->byRef = $byRef;
|
||||||
|
$this->unpack = $unpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames(): array {
|
||||||
|
return ['key', 'value', 'byRef', 'unpack'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string {
|
||||||
|
return 'ArrayItem';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @deprecated compatibility alias
|
||||||
|
class_alias(ArrayItem::class, Expr\ArrayItem::class);
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
|
class ClosureUse extends NodeAbstract {
|
||||||
|
/** @var Expr\Variable Variable to use */
|
||||||
|
public Expr\Variable $var;
|
||||||
|
/** @var bool Whether to use by reference */
|
||||||
|
public bool $byRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a closure use node.
|
||||||
|
*
|
||||||
|
* @param Expr\Variable $var Variable to use
|
||||||
|
* @param bool $byRef Whether to use by reference
|
||||||
|
* @param array<string, mixed> $attributes Additional attributes
|
||||||
|
*/
|
||||||
|
public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) {
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
$this->var = $var;
|
||||||
|
$this->byRef = $byRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames(): array {
|
||||||
|
return ['var', 'byRef'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string {
|
||||||
|
return 'ClosureUse';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @deprecated compatibility alias
|
||||||
|
class_alias(ClosureUse::class, Expr\ClosureUse::class);
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PhpParser\Node;
|
||||||
|
|
||||||
|
use PhpParser\Node;
|
||||||
|
use PhpParser\NodeAbstract;
|
||||||
|
|
||||||
|
class DeclareItem extends NodeAbstract {
|
||||||
|
/** @var Node\Identifier Key */
|
||||||
|
public Identifier $key;
|
||||||
|
/** @var Node\Expr Value */
|
||||||
|
public Expr $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a declare key=>value pair node.
|
||||||
|
*
|
||||||
|
* @param string|Node\Identifier $key Key
|
||||||
|
* @param Node\Expr $value Value
|
||||||
|
* @param array<string, mixed> $attributes Additional attributes
|
||||||
|
*/
|
||||||
|
public function __construct($key, Node\Expr $value, array $attributes = []) {
|
||||||
|
$this->attributes = $attributes;
|
||||||
|
$this->key = \is_string($key) ? new Node\Identifier($key) : $key;
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubNodeNames(): array {
|
||||||
|
return ['key', 'value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string {
|
||||||
|
return 'DeclareItem';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @deprecated compatibility alias
|
||||||
|
class_alias(DeclareItem::class, Stmt\DeclareDeclare::class);
|
@ -1,41 +1,3 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace PhpParser\Node\Expr;
|
require __DIR__ . '/../ArrayItem.php';
|
||||||
|
|
||||||
use PhpParser\Node\Expr;
|
|
||||||
|
|
||||||
class ArrayItem extends Expr
|
|
||||||
{
|
|
||||||
/** @var null|Expr Key */
|
|
||||||
public $key;
|
|
||||||
/** @var Expr Value */
|
|
||||||
public $value;
|
|
||||||
/** @var bool Whether to assign by reference */
|
|
||||||
public $byRef;
|
|
||||||
/** @var bool Whether to unpack the argument */
|
|
||||||
public $unpack;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an array item node.
|
|
||||||
*
|
|
||||||
* @param Expr $value Value
|
|
||||||
* @param null|Expr $key Key
|
|
||||||
* @param bool $byRef Whether to assign by reference
|
|
||||||
* @param array $attributes Additional attributes
|
|
||||||
*/
|
|
||||||
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
|
|
||||||
$this->attributes = $attributes;
|
|
||||||
$this->key = $key;
|
|
||||||
$this->value = $value;
|
|
||||||
$this->byRef = $byRef;
|
|
||||||
$this->unpack = $unpack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSubNodeNames() : array {
|
|
||||||
return ['key', 'value', 'byRef', 'unpack'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getType() : string {
|
|
||||||
return 'Expr_ArrayItem';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue