Skip to content

Parser

Phasis exposes its JavaScript parser as a standalone PHP API. You can parse source text into a typed AST, traverse it, and export it as ESTree-shaped arrays or JSON without constructing an Engine or executing anything.

parseSource

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

The one-call entry point on Phasis\Parser\Parser. Parses $source and returns the Phasis\Ast\Program root node.

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 selects the grammar goal, named after the same option in JS parsers:

Value Goal
script classic Script. Top-level import/export are syntax errors, await is an identifier
module ES Module. Implicitly strict, import/export and top-level await parse

Any other value throws \InvalidArgumentException.

For finer control (pre-seeding strict mode for a direct-eval context, reading the //# sourceURL pragma), construct the parser directly: new Parser($source) plus setModuleMode(), setStrictMode(), and parse().

Nodes

Every AST class extends Phasis\Ast\Node and lives under Phasis\Ast\{Declaration, Expression, Pattern, Statement}. Nodes are plain readonly value objects:

  • $node->type() returns the node's type string, which is the class basename ('BinaryExpression', 'ArrowFunction', ...).
  • $node->location is a Phasis\Lexer\SourceLocation with line (1-based), column (0-based), and offset (byte offset). Only start positions are stored; nodes carry no end offset.
  • All other public fields are the node's children and attributes ($binary->left, $binary->operator, $binary->right).

Most type names match ESTree. The deviations:

Phasis type ESTree type
ArrowFunction ArrowFunctionExpression
TaggedTemplate TaggedTemplateExpression
ClassMethod MethodDefinition
ClassProperty PropertyDefinition
AssignmentProperty Property (inside ObjectPattern)
ExportDeclaration ExportNamedDeclaration / ExportDefaultDeclaration / ExportAllDeclaration
ImportSpecifier ImportSpecifier / ImportDefaultSpecifier / ImportNamespaceSpecifier

A few shapes also differ: class bodies are a plain element array on the class node (no ClassBody wrapper), import/export names, statement labels, and FunctionExpression names are stored as strings rather than nested Identifier nodes, and super parses as an Identifier named super (there is no Super node class). Phasis\Ast\EstreeSerializer reshapes all of this to the ESTree form.

Syntax errors

Invalid input throws Phasis\Exceptions\SyntaxError (usually its subclass Phasis\Parser\ParseError). The exception message ends with the position, and the structured location is available on the exception:

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 traverses a tree depth-first with enter/leave callbacks, in the style of estraverse:

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

Both callbacks receive (Node $node, ?Node $parent). Returning Walker::SKIP from $enter prunes the node's children; the node's own $leave still fires.

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

Traversal is read-only. Nodes are readonly value objects, so there is no replace/mutate support.

Serializer

Phasis\Ast\Serializer converts the typed AST to arrays or JSON using the Phasis type names and field layout, with parser-internal cache fields stripped:

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

This is the format bin/phasis --ast prints.

EstreeSerializer

Phasis\Ast\EstreeSerializer converts the typed AST to ESTree-shaped arrays instead: ESTree type names (per the deviation table above), ESTree field names and ordering, synthesized ClassBody / specifier / source-Literal nodes, and a start byte offset on every node parsed from source. There are no end offsets because nodes only store start positions, and string Literal nodes carry no 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() emits a deterministic one-line-per-node summary (type plus name= / value= / op= / kind= attributes, two-space indent per depth level). The format is byte-identical to the AST summary the acorn fixture in tests/Popular/acorn produces, so the same oracle referees both the JS parser path and this 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 parses a file (script goal) and prints the Serializer JSON dump with two-space indentation. See CLI for the full flag reference.