Strom
Uzly a sloty pojmenované rolí, tokeny, seznamy s oddělovači a jména: jak je strom postavený a kde najít, co který uzel nese.
Uzly a sloty
Každá konstrukce jazyka má svou třídu uzlu a každý uzel má sloty pojmenované podle role, ne podle textu.
IfNode má ifKeyword, openParen, cond, closeParen a
body; TernaryNode má cond, question, if, colon a
else. Párové oddělovače se vždy jmenují open* a close*, seznamy jsou v množném
čísle (stmts, args, members), slot, který může být prázdný, je ?.
Všechny třídy leží v PhpSyntax\Nodes a úplný seznam se sloty je v referenci uzlů.
$if = $file->getDescendants(IfNode::class)[0];
$if->ifKeyword; // Token 'if'
$if->cond; // ExpressionNode, třeba PropertyFetchNode
$if->body; // StatementNode, u složených závorek BlockNode
Sloty jsou veřejné vlastnosti ke čtení; zapisuje se do nich přes settery (setCond()), aby strom věděl
o změně. Proč a jak, říká stránka Úpravy.
Děti uzlu vrací getChildren() v pořadí zdrojáku, prázdné sloty vynechané. Je to jediná cesta k dětem:
uzel není iterovatelný, aby existoval jeden způsob a byl jím obyčejné pole.
foreach ($class->getChildren() as $child) {
// NodeList (attributes), ModifiersNode, Token 'class', IdentifierNode, Token '{', NodeList (members), Token '}'
}
Tři pravidla, která šetří překvapení:
- Tělo řídicí struktury je
StatementNode, ne nutně blok:if ($a) return;má vbodypřímoReturnNode. Se složenými závorkami je toBlockNodese seznamemstmts. else ifjeElseNode, jehož tělo jeIfNode;elseifjeElseIfNode. Alternativní syntaxe (if: … endif;) používá tytéž třídy se slotycolon,stmts,endKeywordasemicolonmístobody.- Třídy, rozhraní, traity a výčty jsou různé uzly (
ClassNode,InterfaceNode,TraitNode,EnumNode), ale všechny i s anonymní třídou implementujíClassLikeNode, takže „je to nějaká třída“ je jedeninstanceof.
Tokeny
Listy stromu jsou Token: druh (kind), text a dvě pole trivia, úvodní a koncová. Druh je buď
konstanta TokenKind (TokenKind::Variable, TokenKind::DoubleArrow), nebo u jednoznakových
tokenů ordinální hodnota znaku; is() se ptá pohodlně na obojí a přijme i text operátoru:
$token->is(TokenKind::Variable);
$token->is('(', '[');
$token->is(TokenKind::DoubleArrow);
is() s textem nikdy nematchne obsah řetězce, jen operátory a interpunkci, takže
$token->is('(') je bezpečné i pro token řetězce '('. Text tokenu je přesně to, co stálo ve
zdrojáku: IntegerNode má v tokenu 0x1F, ne 31, a přetypování ( int ) i s mezerami
uvnitř, protože PHP je počítá do tokenu.
Seznamy
NodeList<T> je seznam bez oddělovačů (příkazy, členy třídy, skupiny atributů),
SeparatedNodeList<T> seznam s oddělovači mezi položkami (parametry, argumenty, položky pole, importy),
který si pamatuje i oddělovače a případnou koncovou čárku:
$args->getItems(); // list<ArgumentNode|VariadicPlaceholderNode>
$args->getSeparators(); // list<Token>, čárky
$args->hasTrailingSeparator(); // koncová čárka
count($args);
$args->indexOf($item);
Mezera mezi dvěma čárkami destrukturace ([, $b] = …) je EmptyArrayItemNode, aby seznam měl na
každém místě položku. Modifikátory (public static readonly) drží ModifiersNode v pořadí
zdrojáku.
Jména a identifikátory
NameNode je jméno třídy, funkce, konstanty nebo jmenného prostoru; je to jeden token, včetně klíčových
slov, která gramatika na místě jména připouští (static, array, readonly). Umí
o sobě říct víc než text:
$name->getName(); // 'App\Model\Order' bez úvodního zpětného lomítka
$name->getKind(); // NameKind::Unqualified, Qualified, FullyQualified, Relative
$name->getParts(); // ['App', 'Model', 'Order']
$name->getRole(); // NameRole::ClassLike, Function, Constant, Namespace podle místa ve stromu
$name->isKeyword(); // jméno, které je zároveň klíčové slovo
Role se odvozuje z místa ve stromu: totéž Foo je třída za new, funkce před ( a
konstanta jinde. Co jméno znamená doopravdy, tedy včetně importů a jmenného prostoru, říká až analýza
NameResolver, viz Analýzy.
IdentifierNode je jméno členu, návěští, hooku nebo aliasu (->name, ::CONST,
as Alias), také jeden token, i když je to rezervované slovo.
Kořen
FileNode je kořen: stmts se všemi příkazy souboru, přičemž příkazy za
namespace App; jsou vnořené do NamespaceNode, takže každý jmenný prostor má své příkazy jako
děti. Poslední token souboru je vždy EndOfFile s prázdným textem, na kterém visí, co je za posledním
příkazem. FileNode::$revision roste s každou změnou stromu; hodí se k tomu poznat, že se něco změnilo, ne
k počítání změn.