Nette Documentation Preview

syntax
Parsování a tisk
****************

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

```php
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:

```php
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:

```php
$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 |php-syntax-mutation]. Fragment, který není tím, čím má být (dva příkazy místo jednoho, výraz místo typu), skončí `ParseException`.


Tisk
====

```php
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:

```php
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í.

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í.