Parsování a tisk
Parser::parse() a ParseException, syntaxe novějšího PHP na starším běhu, parsování fragmentů pro nové uzly a Printer, který nemá žádnou logiku.
Parsování souboru
use PhpSyntax\Parser\Parser;
$parser = new Parser;
$file = $parser->parse($code);
Výsledkem je FileNode, kořen stromu, s příkazy v $file->stmts. Jednu instanci parseru
můžete použít na libovolný počet souborů po sobě. Zpracuje cokoli, co přijme PHP: soubory s inline HTML, s několika
bloky <?php, s BOM i hashbangem na začátku, s __halt_compiler() a daty za ním.
Kód, který není platné PHP, skončí výjimkou ParseException s řádkem a offsetem místa, kde parser
přestal rozumět:
try {
$file = $parser->parse($code);
} catch (PhpSyntax\ParseException $e) {
echo "$e->originalLine: {$e->getMessage()}";
}
Částečný strom neexistuje: buď se soubor parsuje celý, nebo vůbec. Pro nástroj, který má kód měnit, je to správná vlastnost, protože nad neúplným stromem by změny nebyly bezpečné; pro editor, který chce něco ukázat i nad rozepsaným kódem, je to omezení, které je dobré znát dopředu.
Novější syntaxe na starším PHP
Parser rozumí syntaxi PHP 8.5 i tehdy, když běží na PHP 8.2. Tokenizér PHP starší verze novou syntaxi nezná, a tak
ji lexer napodobí: přepíše tokeny tam, kde by je novější PHP vydalo jinak (asymetrická viditelnost
private(set), operátor |>, přetypování (void), konstanta __PROPERTY__).
Děje se to samo podle verze interpretu a nic se nenastavuje. Naopak to neplatí: kód pro PHP 8.5 zůstane kódem pro PHP 8.5,
parser neříká nic o tom, na které verzi má projekt běžet.
Fragmenty
Když do stromu potřebujete vložit nový uzel, nestavíte ho z tokenů ručně. Napíšete ho jako kód a necháte parser, ať z něj udělá uzel:
$expr = $parser->parseExpression('$this->items[] = $item');
$stmt = $parser->parseStatement('return null;');
$type = $parser->parseType('?array');
$name = $parser->parseName('Nette\Utils\Strings');
Fragment je odpojený uzel bez rodiče a bez původních pozic, s prázdnými trivia na okrajích; kam a jak ho do stromu
vložit, říká stránka Úpravy. Fragment, který není tím, čím má být (dva
příkazy místo jednoho, výraz místo typu), skončí ParseException.
Tisk
use PhpSyntax\Printer;
$code = Printer::print($file);
$code = (string) $file;
Tisk nemá žádnou logiku: projde strom a spojí texty tokenů s jejich trivia. Proto platí, že vytištěný nezměněný strom je původní soubor bajt po bajtu, a proto po úpravě zůstane všechno, na co jste nesáhli, přesně tam, kde bylo. Tisknout lze i jednotlivý uzel, třeba výraz, který jste právě sestavili.
První test, který nad PhpSyntax stojí za to udělat, je round trip nad vlastním kódem:
foreach ($files as $path) {
$code = file_get_contents($path);
if (Printer::print($parser->parse($code)) !== $code) {
echo "$path\n";
}
}
Nad vendor/ běžného projektu nevypíše nic. Kdyby vypsal, je to chyba parseru a stojí za nahlášení.