Vai al contenuto

Parser

Phasis espone il proprio parser JavaScript come PHP API autonomo. Puoi analizzare il testo sorgente in un AST tipizzato, attraversarlo ed esportarlo come array con struttura ESTree o JSON senza creare un Engine né eseguire alcunché.

parseSource

PHP
public static function parseSource(string $source, string $sourceType = 'script'): Program

Il punto di ingresso con una sola chiamata di Phasis\Parser\Parser. Analizza $source e restituisce il nodo radice 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 seleziona l'obiettivo grammaticale, denominato come la stessa opzione nei parser JS:

Valore Obiettivo
script Script classico. import/export di primo livello sono errori di sintassi, await è un identificatore
module Modulo ES. Modalità rigorosa implicita; import/export e await di primo livello vengono analizzati

Qualsiasi altro valore genera \InvalidArgumentException.

Per un controllo più preciso (attivazione preventiva della modalità rigorosa per un contesto direct-eval, lettura del pragma //# sourceURL), crea direttamente il parser: new Parser($source) più setModuleMode(), setStrictMode() e parse().

Nodi

Ogni classe AST estende Phasis\Ast\Node e si trova sotto Phasis\Ast\{Declaration, Expression, Pattern, Statement}. I nodi sono semplici oggetti valore in sola lettura:

  • $node->type() restituisce la stringa del tipo del nodo, che corrisponde al nome della classe senza namespace ('BinaryExpression', 'ArrowFunction', ...).
  • $node->location è un Phasis\Lexer\SourceLocation con line (a partire da 1), column (a partire da 0) e offset (offset in byte). Vengono memorizzate solo le posizioni iniziali; i nodi non contengono un offset finale.
  • Tutti gli altri campi pubblici sono figli e attributi del nodo ($binary->left, $binary->operator, $binary->right).

La maggior parte dei nomi dei tipi corrisponde a ESTree. Le differenze:

Tipo Phasis Tipo ESTree
ArrowFunction ArrowFunctionExpression
TaggedTemplate TaggedTemplateExpression
ClassMethod MethodDefinition
ClassProperty PropertyDefinition
AssignmentProperty Property (all'interno di ObjectPattern)
ExportDeclaration ExportNamedDeclaration / ExportDefaultDeclaration / ExportAllDeclaration
ImportSpecifier ImportSpecifier / ImportDefaultSpecifier / ImportNamespaceSpecifier

Anche alcune strutture sono diverse: i corpi delle classi sono semplici array di elementi nel nodo di classe (senza wrapper ClassBody); i nomi di import/export, le etichette delle istruzioni e i nomi FunctionExpression sono stringhe anziché nodi Identifier annidati; inoltre super viene analizzato come Identifier denominato super (non esiste una classe di nodi Super). Phasis\Ast\EstreeSerializer rimodella tutto secondo il formato ESTree.

Errori di sintassi

Un input non valido genera Phasis\Exceptions\SyntaxError (di solito la sua sottoclasse Phasis\Parser\ParseError). Il messaggio dell'eccezione termina con la posizione, mentre la posizione strutturata è disponibile sull'eccezione:

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
}

Attraversamento

Phasis\Ast\Walker attraversa un albero in profondità, con callback di ingresso/uscita, secondo lo stile di estraverse:

PHP
public static function walk(Node $node, callable $enter, ?callable $leave = null): void

Entrambe le callback ricevono (Node $node, ?Node $parent). Se da $enter viene restituito Walker::SKIP, i nodi figli vengono ignorati, ma viene comunque eseguita la callback $leave del nodo stesso.

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;
    }
});

L'attraversamento è in sola lettura. I nodi sono oggetti valore di sola lettura, quindi non è possibile sostituirli o modificarli.

Serializzatore

Phasis\Ast\Serializer converte l’AST tipizzato in array o JSON usando i nomi dei tipi e la struttura dei campi di Phasis, rimuovendo i campi della cache interna del parser:

PHP
public static function toArray(Node $node): array
public static function toJson(Node $node, bool $pretty = false): string

Questo è il formato stampato da bin/phasis --ast.

EstreeSerializer

Phasis\Ast\EstreeSerializer converte invece l’AST tipizzato in array strutturati secondo ESTree: nomi dei tipi ESTree (come indicato nella tabella delle differenze sopra), nomi e ordine dei campi ESTree, nodi ClassBody / specifier / source-Literal sintetizzati e un offset in byte start su ogni nodo analizzato dal sorgente. Non ci sono offset end perché i nodi memorizzano solo le posizioni iniziali, e i nodi stringa Literal non contengono testo 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() genera un riepilogo deterministico, una riga per nodo (tipo più attributi name= / value= / op= / kind=, indentazione di due spazi per livello di profondità). Il formato è identico byte per byte al riepilogo AST prodotto dal fixture acorn in tests/Popular/acorn, quindi lo stesso oracle verifica sia il percorso del parser JS sia questa esportazione 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 analizza un file (script goal) e stampa il dump Serializer JSON con indentazione di due spazi. Per il riferimento completo ai flag, consultate CLI.