PhpSyntax
Bezztrátový konkrétní syntaktický strom PHP bez jediné závislosti: každý token, každá mezera i každý komentář má své místo a vytištěný strom je původní soubor bajt po bajtu. Kdy ho použít a kdy sáhnout po php-parseru.
Strom, který nic nezahodí
Parser, na kterém DressCode stojí, se jmenuje PhpSyntax a je napsaný tak, aby šel použít i bez DressCode: nemá žádnou
závislost, ani na zbytku balíku, a hlídá to statická analýza. Dnes je součástí balíku dresscode/dresscode
jako namespace PhpSyntax; samostatný balík je otázka času, ne návrhu.
Rozdíl proti php-parseru, který znáte z PHPStanu nebo Rectoru, je v tom, co strom drží. Abstraktní syntaktický strom zahazuje, co pro význam programu není podstatné: mezery, prázdné řádky, komentáře uvnitř výrazů, závorky navíc. Pro analýzu je to správně. Jakmile ale chcete kód změnit a vrátit zpátky, potřebujete i to, co AST zahodil, jinak přetisknete soubor podle svých pravidel místo podle autorových.
PhpSyntax staví konkrétní strom: každý token zdrojáku má v něm svůj slot (IfNode má
ifKeyword, openParen, cond, closeParen, body), a bílé znaky a
komentáře visí na tokenech jako trivia. Smlouva zní:
Printer::print($parser->parse($code)) === $code
Platí bajt po bajtu pro cokoli, co PHP přijme: BOM, hashbang, CR i CRLF, uzavírací tagy, inline HTML,
__halt_compiler(). Je to invariant, který drží testy nad korpusem, a je to zároveň nejjednodušší test, jaký
si nad vlastním kódem uděláte: parsuj, vytiskni, porovnej.
Kdy který
Obě knihovny mají své místo a je poctivé říct které.
php-parser, když potřebujete vědět, co kód dělá: typy, tok řízení, vyhodnocení konstantních výrazů, obrovský ekosystém nad ním. Také když potřebujete strom i z kódu se syntaktickou chybou: php-parser umí zotavení a vrátí částečný strom, PhpSyntax na chybě skončí výjimkou, protože jeho gramatika zotavení nemá.
PhpSyntax, když se chcete kódu dotknout a zbytek nechat: přejmenovat volání, doplnit argument, přesunout komentář, srovnat mezery, a dostat diff přesně tak velký jako změna. Formátovače, migrační skripty, generátory, které upravují existující soubory, nástroje na hromadné přepisy.
Na ukázku
use PhpSyntax\Nodes\Expression\FunctionCallNode;
use PhpSyntax\Parser\Parser;
use PhpSyntax\Printer;
$file = (new Parser)->parse(file_get_contents('Order.php'));
foreach ($file->getDescendants(FunctionCallNode::class) as $call) {
if ($call->name instanceof PhpSyntax\Nodes\NameNode && $call->name->getName() === 'sizeof') {
$call->name->token->setText('count');
}
}
file_put_contents('Order.php', Printer::print($file));
Přejmenovalo se každé volání sizeof, a to je také jediné, co se v souboru změnilo. Kde se pak strom
prochází, jak se mění a co ví o jménech, říkají další stránky: Parsování a
tisk, Strom, Trivia, Procházení, Úpravy, Analýzy a reference uzlů.
Výkon
Parser je generovaný LALR(1) automat nad gramatikou převzatou z php-parseru, tisk je pouhé spojení tokenů. Na běžném
stroji projde parse a tisk celého adresáře vendor/ s třemi sty soubory a 1,2 MB kódu za necelou sekundu, tedy
zhruba dvě milisekundy na soubor. Index pozic tokenů se staví líně a po úpravě se neobnovuje celý, takže cena změny
následované dotazem odpovídá vzdálenosti mezi nimi, ne velikosti souboru.