Parser
Phasis stellt seinen JavaScript-Parser als eigenständiges PHP API bereit. Du kannst Quelltext in einen typisierten AST parsen, ihn durchlaufen und als ESTree-förmige Arrays oder JSON exportieren, ohne ein Engine zu erstellen oder etwas auszuführen.
parseSource
public static function parseSource(string $source, string $sourceType = 'script'): Program
Der Einstiegspunkt mit einem Aufruf auf Phasis\Parser\Parser. Er parst $source und gibt den Phasis\Ast\Program-Wurzelknoten zurück.
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 legt das Grammatikziel fest, benannt nach derselben Option in JS-Parsern:
| Wert | Ziel |
|---|---|
script |
klassisches Script. import/export auf oberster Ebene sind Syntaxfehler, await ist ein Bezeichner |
module |
ES-Modul. Implizit im Strict Mode; import/export und await auf oberster Ebene werden geparst |
Jeder andere Wert löst \InvalidArgumentException aus.
Für genauere Kontrolle (den Strict Mode für einen direkten Eval-Kontext vorab aktivieren, die //# sourceURL-Pragma auslesen) erstelle den Parser direkt: new Parser($source) sowie setModuleMode(), setStrictMode() und parse().
Knoten
Jede AST-Klasse erweitert Phasis\Ast\Node und befindet sich unter Phasis\Ast\{Declaration, Expression, Pattern, Statement}. Knoten sind einfache schreibgeschützte Wertobjekte:
$node->type()gibt den Typ-String des Knotens zurück, also den Klassennamen ohne Namespace ('BinaryExpression','ArrowFunction', ...).$node->locationist einPhasis\Lexer\SourceLocationmitline(ab 1),column(ab 0) undoffset(Byte-Offset). Gespeichert werden nur Startpositionen; Knoten enthalten keinen End-Offset.- Alle übrigen öffentlichen Felder sind die untergeordneten Knoten und Attribute des Knotens (
$binary->left,$binary->operator,$binary->right).
Die meisten Typnamen stimmen mit ESTree überein. Die Abweichungen:
| Phasis-Typ | ESTree-Typ |
|---|---|
ArrowFunction |
ArrowFunctionExpression |
TaggedTemplate |
TaggedTemplateExpression |
ClassMethod |
MethodDefinition |
ClassProperty |
PropertyDefinition |
AssignmentProperty |
Property (innerhalb von ObjectPattern) |
ExportDeclaration |
ExportNamedDeclaration / ExportDefaultDeclaration / ExportAllDeclaration |
ImportSpecifier |
ImportSpecifier / ImportDefaultSpecifier / ImportNamespaceSpecifier |
Einige Formen unterscheiden sich ebenfalls: Klassenkörper sind ein einfaches Element-Array am Klassenknoten (ohne ClassBody-Wrapper); Import-/Exportnamen, Anweisungsbezeichnungen und FunctionExpression-Namen werden als Strings statt als verschachtelte Identifier-Knoten gespeichert, und super wird als Identifier mit dem Namen super geparst (es gibt keine Super-Knotenklasse). Phasis\Ast\EstreeSerializer bringt all das in die ESTree-Form.
Syntaxfehler
Ungültige Eingaben lösen Phasis\Exceptions\SyntaxError aus (meist deren Unterklasse Phasis\Parser\ParseError). Die Fehlermeldung endet mit der Position; die strukturierte Ortsangabe ist über die Exception verfügbar:
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 durchläuft einen Baum in Tiefensuche mit Enter-/Leave-Callbacks, ähnlich wie estraverse:
public static function walk(Node $node, callable $enter, ?callable $leave = null): void
Beide Callbacks erhalten (Node $node, ?Node $parent). Wenn $enter Walker::SKIP zurückgibt, werden die Kindknoten des Knotens übersprungen; das eigene $leave des Knotens wird weiterhin aufgerufen.
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;
}
});
Der Durchlauf ist schreibgeschützt. Knoten sind schreibgeschützte Wertobjekte, daher ist kein Ersetzen oder Ändern möglich.
Serializer
Phasis\Ast\Serializer wandelt den typisierten AST in Arrays oder JSON um. Dabei verwendet es die Phasis-Typnamen und die Feldanordnung und entfernt parserinterne Cache-Felder:
public static function toArray(Node $node): array
public static function toJson(Node $node, bool $pretty = false): string
Dies ist das Format, das bin/phasis --ast ausgibt.
EstreeSerializer
Phasis\Ast\EstreeSerializer wandelt den typisierten AST stattdessen in ESTree-förmige Arrays um: mit ESTree-Typnamen (gemäß der obigen Abweichungstabelle), ESTree-Feldnamen und -Reihenfolge, erzeugten ClassBody-/Specifier-/Source-Literal-Knoten sowie einem start-Byte-Offset für jeden aus Quelltext geparsten Knoten. end-Offsets gibt es nicht, da Knoten nur Startpositionen speichern; String-Literal-Knoten enthalten keinen raw-Text.
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() gibt eine deterministische Zusammenfassung mit einer Zeile pro Knoten aus (Typ plus Attribute name= / value= / op= / kind=, Einrückung von zwei Leerzeichen pro Tiefenebene). Das Format ist bytegenau identisch mit der AST-Zusammenfassung, die das acorn-Fixture in tests/Popular/acorn erzeugt. Daher prüft dieselbe Referenz sowohl den JS-Parserpfad als auch diesen PHP-Export:
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 parst eine Datei (Script-Ziel) und gibt den Serializer-JSON-Dump mit zwei Leerzeichen Einrückung aus. Die vollständige Übersicht der Flags findest du unter CLI.