Przejdź do treści

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

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

PHP
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->location to Phasis\Lexer\SourceLocation z line (od 1), column (od 0) i offset (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:

PHP
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:

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

PHP
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:

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

PHP
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:

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.