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
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.
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è unPhasis\Lexer\SourceLocationconline(a partire da 1),column(a partire da 0) eoffset(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:
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:
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.
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:
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.
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:
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.