support qualified names
This commit is contained in:
Родитель
9e09c55c6c
Коммит
79427d7055
|
@ -10,7 +10,6 @@ class DelimitedList extends Node {
|
|||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::DelimitedList);
|
||||
$this->children = array();
|
||||
}
|
||||
|
||||
public function getValues() {
|
||||
|
@ -23,6 +22,12 @@ class DelimitedList extends Node {
|
|||
}
|
||||
|
||||
public function addToken($node) {
|
||||
if ($node === null) {
|
||||
return;
|
||||
}
|
||||
if (!isset($this->children)) {
|
||||
$this->children = array();
|
||||
}
|
||||
array_push($this->children, $node);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@ class MethodNode extends Node {
|
|||
public $parameters;
|
||||
/** @var Token */
|
||||
public $closeParen;
|
||||
/** @var Token */
|
||||
public $colonToken;
|
||||
/** @var null | Name */
|
||||
public $returnTypeOpt;
|
||||
/** @var null | MethodBlockNode */
|
||||
|
|
|
@ -60,6 +60,18 @@ class Node implements \JsonSerializable {
|
|||
return $result;
|
||||
}
|
||||
|
||||
public function getChildrenKvPairs() {
|
||||
$result = array();
|
||||
foreach (call_user_func('get_object_vars', $this) as $i=>$val) {
|
||||
if ($i === "parent" || $i == "kind") {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[$i] = $val;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getStart() {
|
||||
$child = $this->getChildren()[0];
|
||||
if ($child instanceof Node) {
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class QualifiedName extends Node {
|
||||
/** @var Token */
|
||||
public $globalSpecifier; // \_opt
|
||||
/** @var Node */
|
||||
public $relativeSpecifier; // namespace\
|
||||
|
||||
/** @var DelimitedList */
|
||||
public $nameParts;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::QualifiedName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace PhpParser\Node;
|
||||
use PhpParser\NodeKind;
|
||||
use PhpParser\Token;
|
||||
|
||||
class RelativeSpecifier extends Node {
|
||||
/** @var Token */
|
||||
public $namespaceKeyword;
|
||||
|
||||
/** @var Token */
|
||||
public $backslash;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(NodeKind::RelativeSpecifier);
|
||||
}
|
||||
}
|
|
@ -18,4 +18,6 @@ class NodeKind {
|
|||
const Expression = 12;
|
||||
const Name = 13;
|
||||
const ParameterNode = 14;
|
||||
const QualifiedName = 15;
|
||||
const RelativeSpecifier = 16;
|
||||
}
|
108
parser.php
108
parser.php
|
@ -4,6 +4,7 @@
|
|||
|
||||
namespace PhpParser;
|
||||
|
||||
|
||||
// TODO make this less hacky
|
||||
spl_autoload_register(function ($class) {
|
||||
if (file_exists($filepath = __DIR__ . "/Node/" . basename($class) . ".php")) {
|
||||
|
@ -22,6 +23,8 @@ use PhpParser\Node\MethodBlockNode;
|
|||
use PhpParser\Node\MethodNode;
|
||||
use PhpParser\Node\Node;
|
||||
use PhpParser\Node\Parameter;
|
||||
use PhpParser\Node\QualifiedName;
|
||||
use PhpParser\Node\RelativeSpecifier;
|
||||
use PhpParser\Node\SourceFile;
|
||||
use PhpParser\Node\TemplateExpressionNode;
|
||||
|
||||
|
@ -249,10 +252,14 @@ class Parser {
|
|||
function parseClassElement() {
|
||||
return function($parentNode) {
|
||||
switch($this->getCurrentToken()->kind) {
|
||||
case TokenKind::PrivateKeyword:
|
||||
case TokenKind::PublicKeyword:
|
||||
case TokenKind::ProtectedKeyword:
|
||||
|
||||
case (TokenKind::FunctionKeyword):
|
||||
return $this->parseMethodDeclaration($parentNode);
|
||||
default:
|
||||
$token = $this->getCurrentToken();
|
||||
$token = $this->getCurrentToken(); // TODO new unsupported token
|
||||
$this->advanceToken();
|
||||
return $token;
|
||||
}
|
||||
|
@ -263,7 +270,7 @@ class Parser {
|
|||
return function() {
|
||||
switch($this->getCurrentToken()->kind) {
|
||||
default:
|
||||
$token = $this->getCurrentToken();
|
||||
$token = $this->getCurrentToken(); // TODO new unsupported token
|
||||
$this->advanceToken();
|
||||
return $token;
|
||||
}
|
||||
|
@ -297,10 +304,15 @@ class Parser {
|
|||
$node->byRefToken = $this->eatOptional(TokenKind::AmpersandToken);
|
||||
$node->name = $this->eat(TokenKind::Name);
|
||||
$node->openParen = $this->eat(TokenKind::OpenParenToken);
|
||||
$node->parameters = $this->parseDelimitedList(TokenKind::CommaToken, $this->parseParameterFn(), $node);
|
||||
$node->parameters = $this->parseDelimitedList(TokenKind::CommaToken, $this->isParameterStartFn(), $this->parseParameterFn(), $node);
|
||||
$node->closeParen = $this->eat(TokenKind::CloseParenToken);
|
||||
$node->colonToken = $this->eatOptional(TokenKind::ColonToken);
|
||||
$node->returnTypeOpt = $this->eatOptional(TokenKind::Name);
|
||||
$node->returnTypeOpt = $this->parseQualifiedName($node);
|
||||
if (!isset($node->returnTypeOpt)) {
|
||||
$node->returnTypeOpt = $this->eatOptional(
|
||||
TokenKind::ArrayKeyword, TokenKind::CallableKeyword, TokenKind::BoolReservedWord,
|
||||
TokenKind::FloatReservedWord, TokenKind::IntReservedWord, TokenKind::StringReservedWord);
|
||||
}
|
||||
$node->compoundStatement = $this->parseMethodBlock($node);
|
||||
$node->parent = $parentNode;
|
||||
return $node;
|
||||
|
@ -310,13 +322,15 @@ class Parser {
|
|||
return function ($parentNode) {
|
||||
$token = $this->getCurrentToken();
|
||||
echo json_encode($token);
|
||||
if ($this->isParameterStart($token)) {
|
||||
// if ($isParameterStartFn($token)) {
|
||||
$node = new Parameter();
|
||||
$node->parent = $parentNode;
|
||||
$node->typeOpt = $this->eatOptional(
|
||||
$node->typeOpt = $this->parseQualifiedName($node);
|
||||
if (!isset($node->typeOpt)) {
|
||||
$node->typeOpt = $this->eatOptional(
|
||||
TokenKind::ArrayKeyword, TokenKind::CallableKeyword, TokenKind::BoolReservedWord,
|
||||
TokenKind::FloatReservedWord, TokenKind::IntReservedWord, TokenKind::StringReservedWord,
|
||||
TokenKind::Name, TokenKind::QualifiedName);
|
||||
TokenKind::FloatReservedWord, TokenKind::IntReservedWord, TokenKind::StringReservedWord);
|
||||
}
|
||||
$node->byRefToken = $this->eatOptional(TokenKind::AmpersandToken);
|
||||
$node->variableName = $this->eat(TokenKind::VariableName);
|
||||
$node->equalsToken = $this->eatOptional(TokenKind::EqualsEqualsEqualsToken);
|
||||
|
@ -324,8 +338,8 @@ class Parser {
|
|||
$node->default = $this->parseConstantExpression($node);
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
return null;
|
||||
// }
|
||||
// return null;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -629,23 +643,29 @@ class Parser {
|
|||
return $modifiers;
|
||||
}
|
||||
|
||||
private function isParameterStart($token) {
|
||||
switch ($token->kind) {
|
||||
case TokenKind::ArrayKeyword:
|
||||
case TokenKind::CallableKeyword:
|
||||
case TokenKind::Name: // TODO spec doesn't mention Name, but I believe it's valid
|
||||
case TokenKind::QualifiedName: // http://php.net/manual/en/language.namespaces.rules.php
|
||||
case TokenKind::BoolReservedWord:
|
||||
case TokenKind::FloatReservedWord:
|
||||
case TokenKind::IntReservedWord:
|
||||
case TokenKind::StringReservedWord:
|
||||
private function isParameterStartFn() {
|
||||
return function($token) {
|
||||
switch ($token->kind) {
|
||||
case TokenKind::ArrayKeyword:
|
||||
case TokenKind::CallableKeyword:
|
||||
|
||||
case TokenKind::AmpersandToken:
|
||||
case TokenKind::Name: // http://php.net/manual/en/language.namespaces.rules.php
|
||||
case TokenKind::BackslashToken:
|
||||
case TokenKind::NamespaceKeyword:
|
||||
// All of these can be the start of a qualified name
|
||||
|
||||
case TokenKind::VariableName:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case TokenKind::BoolReservedWord:
|
||||
case TokenKind::FloatReservedWord:
|
||||
case TokenKind::IntReservedWord:
|
||||
case TokenKind::StringReservedWord:
|
||||
|
||||
case TokenKind::AmpersandToken:
|
||||
|
||||
case TokenKind::VariableName:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
private function parseConstantExpression($parentNode) {
|
||||
|
@ -657,10 +677,10 @@ class Parser {
|
|||
return $node;
|
||||
}
|
||||
|
||||
private function parseDelimitedList($delimeter, $parseParameterFn, $parentNode) {
|
||||
private function parseDelimitedList($delimeter, $isParameterStartFn, $parseParameterFn, $parentNode) {
|
||||
$node = new DelimitedList();
|
||||
$token = $this->getCurrentToken();
|
||||
while ($this->isParameterStart($token)) {
|
||||
while ($isParameterStartFn($token)) {
|
||||
echo 'hi';
|
||||
$node->addToken($parseParameterFn($node));
|
||||
$node->addToken($this->eatOptional($delimeter));
|
||||
|
@ -670,6 +690,38 @@ class Parser {
|
|||
$node->parent = $parentNode;
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function parseQualifiedName($parentNode) {
|
||||
$node = new QualifiedName();
|
||||
$node->parent = $parentNode;
|
||||
$node->relativeSpecifier = $this->parseRelativeSpecifier($node);
|
||||
if (!isset($node->relativeSpecifier)) {
|
||||
$node->globalSpecifier = $this->eatOptional(TokenKind::BackslashToken);
|
||||
}
|
||||
$node->nameParts =
|
||||
$this->parseDelimitedList(
|
||||
TokenKind::BackslashToken,
|
||||
function ($token) {
|
||||
return $token->kind === TokenKind::Name;
|
||||
},
|
||||
function ($parentNode) {
|
||||
return $this->eat(TokenKind::Name);
|
||||
}, $node);
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function parseRelativeSpecifier($parentNode) {
|
||||
$node = new RelativeSpecifier();
|
||||
$node->parent = $parentNode;
|
||||
$node->namespaceKeyword = $this->eatOptional(TokenKind::NamespaceKeyword);
|
||||
if ($node->namespaceKeyword !== null) {
|
||||
$node->backslash = $this->eat(TokenKind::BackslashToken);
|
||||
}
|
||||
if (isset($node->backslash)) {
|
||||
return $node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class ParseContext {
|
||||
|
@ -677,4 +729,4 @@ class ParseContext {
|
|||
const BlockStatements = 1;
|
||||
const ClassMembers = 2;
|
||||
const Count = 3;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,9 +56,10 @@ class ParserInvariantsTest extends LexerInvariantsTest {
|
|||
|
||||
foreach ($sourceFileNode->getAllChildren() as $child) {
|
||||
if ($child instanceof Node) {
|
||||
$encode = json_encode($child);
|
||||
$this->assertGreaterThanOrEqual(
|
||||
1, count($child->getChildren()),
|
||||
"Invariant: All Nodes have at least one child."
|
||||
"Invariant: All Nodes have at least one child. $encode"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
class A {
|
||||
public static function Blah () {
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
" hello456-$there+123"
|
|
@ -0,0 +1,6 @@
|
|||
class A {
|
||||
public static function Blah () {
|
||||
}
|
||||
|
||||
abstract foo ()
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
" hello456-$there+123"
|
|
@ -0,0 +1,5 @@
|
|||
class A {
|
||||
public function boo($awesome, $sauce) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
" hello456-$there+123"
|
|
@ -0,0 +1,6 @@
|
|||
class A {
|
||||
public function boo($awesome, $sauce) : int {
|
||||
;
|
||||
;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
class A {
|
||||
public function boo($awesome, $sauce) : int {
|
||||
;
|
||||
;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Hello {
|
||||
function A() {
|
||||
echo "hello";
|
||||
}
|
||||
|
||||
function B() {
|
||||
namespace\A();
|
||||
}
|
||||
|
||||
namespace\B();
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Hello {
|
||||
function A() {
|
||||
echo "hello";
|
||||
}
|
||||
|
||||
function B() {
|
||||
namespace\A();
|
||||
}
|
||||
|
||||
namespace\B();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
class A {
|
||||
function A () : \Exception {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
class A {
|
||||
function A () : \Exception {
|
||||
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче