Nette Documentation Preview

syntax
Uzly a sloty
************

.[perex]
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.


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ů |nodes].

Slot je obyčejná vlastnost, takže se čte i zapisuje přiřazením:

```php
$if = $file->findFirst(IfNode::class);
$if->ifKeyword;   // Token 'if'
$if->cond;        // ExpressionNode, třeba PropertyFetchNode
$if->body;        // StatementNode, u složených závorek BlockNode

$if->cond = $parser->parseExpression('$order->isPaid()');
```

Za přiřazením není magie, ale hook: uzel si novou hodnotu adoptuje, starou pustí a ohlásí to indexu tokenů. Co to znamená pro trivia kolem nahrazeného uzlu, říká stránka [Úpravy |mutation].

Jména slotů jedné třídy vypíše konstanta `Slots`, v pořadí zdrojáku:

```php
IfNode::Slots;   // ifKeyword, openParen, cond, closeParen, body, colon, stmts, elseifs, else, endKeyword, semicolon
```

Že jich je jedenáct, když `if` má pět, je alternativní syntaxí: `if (…): … endif;` používá tutéž třídu se sloty `colon`, `stmts`, `endKeyword` a `semicolon` místo `body`. Prázdné sloty se nikde neobjeví.

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.

```php
foreach ($class->getChildren() as $child) {
	// NodeList (attributes), ModifiersNode, Token 'class', IdentifierNode, Token '{', NodeList (members), Token '}'
}
```

Dvě pravidla, která šetří překvapení:

- **Tělo řídicí struktury je `StatementNode`**, ne nutně blok: `if ($a) return;` má v `body` přímo `ReturnNode`. Se složenými závorkami je to `BlockNode` se seznamem `stmts`.
- **`else if` je `ElseNode`, jehož tělo je `IfNode`**; `elseif` je `ElseIfNode`. 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 jeden `instanceof`.


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:

```php
$token->is(TokenKind::Variable);
$token->is('(', '[');
```

`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, i tam, kde PHP počítá do tokenu mezery: přetypování `( int )`, inline HTML, obsah heredocu.


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:

```php
$args->getItems();              // list<ArgumentNode|VariadicPlaceholderNode>
$args->getSeparators();         // list<Token>, čárky
$args->hasTrailingSeparator();  // koncová čárka
count($args);
$args->indexOf($item);
```

Položky seznamu jsou `protected(set)`: mění se jen jeho metodami (`append()`, `insert()`, `removeItem()`), aby seznam vždy věděl, co v něm je. 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
=====

`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, a to vlastnostmi:

```php
$name->text;        // 'App\Model\Order', jak je zapsané
$name->kind;        // NameKind::Unqualified, Qualified, FullyQualified, Relative
$name->parts;       // ['App', 'Model', 'Order']
$name->shortName;   // 'Order'
$name->role;        // NameRole::ClassLike, Function, Constant, Namespace podle místa ve stromu
$name->isKeyword();
$name->equals('App\Model\Order');
```

`text` je zapisovatelný, a to je nejkratší cesta, jak jméno přepsat: `$call->name->text = 'count'` přetokenizuje jméno na nové a nechá kolem něj všechno, jak bylo.

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 |analyses].

`IdentifierNode` je jméno členu, návěští, hooku nebo aliasu (`->name`, `::CONST`, `as Alias`), také jeden token, i když je to rezervované slovo. Slot, který drží jméno, se jmenuje `name`, takže text typu se čte `$type->name->text`.


Skaláry
=======

Literály si drží text tak, jak byl zapsán, a hodnotu, kterou znamenají, dávají jako vlastnost:

```php
$int->token->text;   // '0o755'
$int->value;         // 493
$int->base;          // 8

$string->token->text;   // '"a\nb"'
$string->value;         // "a\nb" se skutečným koncem řádku
$string->quote;         // '"'
```

Zápis hodnoty je metoda, protože bere dvě věci najednou: hodnotu a případně uvozovku, kterou se má zapsat.

```php
$string->setValue("x'y", "'");   // 'x\'y'
```

Heredoc odpoví `hasInterpolation()` dřív, než se dá jeho `value` číst; interpolovaný řetězec je vlastní uzel se svými částmi.


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.

Uzly a sloty

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.

Sloty

Každá konstrukce jazyka má svou třídu uzlu a každý uzel má sloty pojmenované podle role, ne podle textu. IfNodeifKeyword, openParen, cond, closeParen a body; TernaryNodecond, 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ů.

Slot je obyčejná vlastnost, takže se čte i zapisuje přiřazením:

$if = $file->findFirst(IfNode::class);
$if->ifKeyword;   // Token 'if'
$if->cond;        // ExpressionNode, třeba PropertyFetchNode
$if->body;        // StatementNode, u složených závorek BlockNode

$if->cond = $parser->parseExpression('$order->isPaid()');

Za přiřazením není magie, ale hook: uzel si novou hodnotu adoptuje, starou pustí a ohlásí to indexu tokenů. Co to znamená pro trivia kolem nahrazeného uzlu, říká stránka Úpravy.

Jména slotů jedné třídy vypíše konstanta Slots, v pořadí zdrojáku:

IfNode::Slots;   // ifKeyword, openParen, cond, closeParen, body, colon, stmts, elseifs, else, endKeyword, semicolon

Že jich je jedenáct, když if má pět, je alternativní syntaxí: if (…): … endif; používá tutéž třídu se sloty colon, stmts, endKeyword a semicolon místo body. Prázdné sloty se nikde neobjeví.

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 '}'
}

Dvě pravidla, která šetří překvapení:

  • Tělo řídicí struktury je StatementNode, ne nutně blok: if ($a) return; má v body přímo ReturnNode. Se složenými závorkami je to BlockNode se seznamem stmts.
  • else if je ElseNode, jehož tělo je IfNode; elseif je ElseIfNode. 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 jeden instanceof.

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('(', '[');

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, i tam, kde PHP počítá do tokenu mezery: přetypování ( int ), inline HTML, obsah heredocu.

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);

Položky seznamu jsou protected(set): mění se jen jeho metodami (append(), insert(), removeItem()), aby seznam vždy věděl, co v něm je. 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

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, a to vlastnostmi:

$name->text;        // 'App\Model\Order', jak je zapsané
$name->kind;        // NameKind::Unqualified, Qualified, FullyQualified, Relative
$name->parts;       // ['App', 'Model', 'Order']
$name->shortName;   // 'Order'
$name->role;        // NameRole::ClassLike, Function, Constant, Namespace podle místa ve stromu
$name->isKeyword();
$name->equals('App\Model\Order');

text je zapisovatelný, a to je nejkratší cesta, jak jméno přepsat: $call->name->text = 'count' přetokenizuje jméno na nové a nechá kolem něj všechno, jak bylo.

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. Slot, který drží jméno, se jmenuje name, takže text typu se čte $type->name->text.

Skaláry

Literály si drží text tak, jak byl zapsán, a hodnotu, kterou znamenají, dávají jako vlastnost:

$int->token->text;   // '0o755'
$int->value;         // 493
$int->base;          // 8

$string->token->text;   // '"a\nb"'
$string->value;         // "a\nb" se skutečným koncem řádku
$string->quote;         // '"'

Zápis hodnoty je metoda, protože bere dvě věci najednou: hodnotu a případně uvozovku, kterou se má zapsat.

$string->setValue("x'y", "'");   // 'x\'y'

Heredoc odpoví hasInterpolation() dřív, než se dá jeho value číst; interpolovaný řetězec je vlastní uzel se svými částmi.

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.