Zum Inhalt springen

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

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

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 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->location ist ein Phasis\Lexer\SourceLocation mit line (ab 1), column (ab 0) und offset (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:

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 durchläuft einen Baum in Tiefensuche mit Enter-/Leave-Callbacks, ähnlich wie estraverse:

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

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

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:

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

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() 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:

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