Parser
Phasis udostępnia swój parser JavaScript jako samodzielny PHP API. Możesz parsować tekst źródłowy do typowanego AST, przechodzić po nim i eksportować je jako tablice w formacie ESTree lub JSON bez tworzenia Engine ani wykonywania czegokolwiek.
parseSource
public static function parseSource(string $source, string $sourceType = 'script'): Program
Jednopunktowe wejście API w Phasis\Parser\Parser. Parsuje $source i zwraca węzeł główny Phasis\Ast\Program.
use Phasis\Parser\Parser;
$program = Parser::parseSource('const x = [1, 2].map(n => n * 2);');
$module = Parser::parseSource('import { x } from "./x.js"; export default x;', 'module');
$sourceType wybiera cel gramatyki, nazwany tak samo jak ta opcja w parserach JS:
| Wartość | Cel |
|---|---|
script |
klasyczny skrypt. Konstrukcje najwyższego poziomu import/export są błędami składni, await jest identyfikatorem |
module |
moduł ES. Domyślnie działa tryb ścisły, parsowane są import/export oraz await najwyższego poziomu |
Każda inna wartość powoduje zgłoszenie \InvalidArgumentException.
Aby uzyskać większą kontrolę (wstępnie włączyć tryb ścisły dla kontekstu bezpośredniego eval, odczytać dyrektywę //# sourceURL), utwórz parser bezpośrednio: new Parser($source) oraz setModuleMode(), setStrictMode() i parse().
Węzły
Każda klasa AST rozszerza Phasis\Ast\Node i znajduje się pod Phasis\Ast\{Declaration, Expression, Pattern, Statement}. Węzły są zwykłymi obiektami tylko do odczytu:
$node->type()zwraca ciąg znaków typu węzła, będący nazwą bazową klasy ('BinaryExpression','ArrowFunction', ...).$node->locationtoPhasis\Lexer\SourceLocationzline(od 1),column(od 0) ioffset(przesunięciem w bajtach). Zapisane są tylko pozycje początkowe; węzły nie zawierają przesunięcia końcowego.- Wszystkie pozostałe pola publiczne to elementy podrzędne i atrybuty węzła (
$binary->left,$binary->operator,$binary->right).
Większość nazw typów odpowiada ESTree. Różnice:
| Typ Phasis | Typ ESTree |
|---|---|
ArrowFunction |
ArrowFunctionExpression |
TaggedTemplate |
TaggedTemplateExpression |
ClassMethod |
MethodDefinition |
ClassProperty |
PropertyDefinition |
AssignmentProperty |
Property (wewnątrz ObjectPattern) |
ExportDeclaration |
ExportNamedDeclaration / ExportDefaultDeclaration / ExportAllDeclaration |
ImportSpecifier |
ImportSpecifier / ImportDefaultSpecifier / ImportNamespaceSpecifier |
Różni się również kilka struktur: treści klas są zwykłą tablicą elementów w węźle klasy (bez opakowującego ClassBody), nazwy importu/eksportu, etykiety instrukcji i nazwy FunctionExpression są przechowywane jako ciągi znaków, a nie zagnieżdżone węzły Identifier, a super jest parsowane jako Identifier o nazwie super (nie ma klasy węzła Super). Phasis\Ast\EstreeSerializer przekształca to wszystko do formatu ESTree.
Błędy składni
Nieprawidłowe dane wejściowe powodują zgłoszenie Phasis\Exceptions\SyntaxError (zwykle jego podklasy Phasis\Parser\ParseError). Komunikat wyjątku kończy się pozycją, a ustrukturyzowana lokalizacja jest dostępna w wyjątku:
use Phasis\Exceptions\SyntaxError;
use Phasis\Parser\Parser;
try {
Parser::parseSource("const a = 1;\nconst b = ;");
} catch (SyntaxError $e) {
echo $e->getMessage(); // Unexpected token (got ; ";") at line 2, column 10
echo $e->location->line; // 2
echo $e->location->column; // 10
echo $e->location->offset; // 23
}
Walker
Phasis\Ast\Walker przechodzi przez drzewo w głąb, wywołując funkcje zwrotne przy wejściu i wyjściu, zgodnie ze stylem estraverse:
public static function walk(Node $node, callable $enter, ?callable $leave = null): void
Obie funkcje zwrotne otrzymują (Node $node, ?Node $parent). Zwrócenie Walker::SKIP z $enter pomija elementy podrzędne węzła; funkcja $leave dla samego węzła nadal zostanie wywołana.
use Phasis\Ast\Expression\Identifier;
use Phasis\Ast\Node;
use Phasis\Ast\Walker;
$names = [];
Walker::walk($program, function (Node $node) use (&$names) {
if ($node instanceof Identifier) {
$names[] = $node->name;
}
if ($node instanceof \Phasis\Ast\Expression\ArrowFunction) {
return Walker::SKIP;
}
});
Przechodzenie odbywa się tylko do odczytu. Węzły są obiektami tylko do odczytu, więc nie można ich zastępować ani modyfikować.
Serializator
Phasis\Ast\Serializer konwertuje typowane AST do tablic lub JSON, używając nazw typów i układu pól Phasis oraz usuwając wewnętrzne pola pamięci podręcznej parsera:
public static function toArray(Node $node): array
public static function toJson(Node $node, bool $pretty = false): string
To format wyświetlany przez bin/phasis --ast.
EstreeSerializer
Phasis\Ast\EstreeSerializer konwertuje typowane AST zamiast tego do tablic w formacie ESTree: używa nazw typów i kolejności pól ESTree (zgodnie z powyższą tabelą różnic), tworzy syntetyczne węzły ClassBody / specifier / source-Literal oraz dodaje przesunięcie w bajtach start do każdego węzła sparsowanego ze źródła. Nie ma przesunięć end, ponieważ węzły przechowują tylko pozycje początkowe, a węzły ciągów znaków Literal nie zawierają tekstu raw.
public static function toArray(Node $node): array
public static function toJson(Node $node, bool $pretty = false): string
public static function summarize(Node $node): string
summarize() generuje deterministyczne, jednowierszowe podsumowanie każdego węzła (typ oraz atrybuty name= / value= / op= / kind=, wcięcie dwoma spacjami na poziom głębokości). Format jest identyczny bajt po bajcie z podsumowaniem AST generowanym przez fixture acorn w tests/Popular/acorn, więc ten sam oracle sprawdza zarówno ścieżkę parsera JS, jak i ten eksport PHP:
use Phasis\Ast\EstreeSerializer;
use Phasis\Parser\Parser;
echo EstreeSerializer::summarize(Parser::parseSource('let x = a + 1;'));
// Program
// VariableDeclaration kind=let
// VariableDeclarator
// Identifier name=x
// BinaryExpression op=+
// Identifier name=a
// Literal value=1
CLI
bin/phasis --ast script.js parsuje plik (cel skryptu) i wyświetla zrzut Serializer JSON z wcięciem dwoma spacjami. Pełny opis flag znajduje się w CLI.