Перейти к содержимому

Парсер

Phasis предоставляет свой парсер JavaScript как отдельный PHP API. Вы можете разобрать исходный текст в типизированный AST, обойти его и экспортировать как массивы в формате ESTree или JSON, не создавая Engine и ничего не выполняя.

parseSource

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

Единая точка входа для вызова Phasis\Parser\Parser. Разбирает $source и возвращает корневой узел 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 выбирает цель грамматики, названную так же, как соответствующая опция в парсерах JS:

Значение Цель
script классический скрипт. Верхнеуровневые import/export вызывают синтаксическую ошибку, await — идентификатор
module модуль ES. Неявно включён строгий режим; разбираются import/export и верхнеуровневый await

Любое другое значение вызывает \InvalidArgumentException.

Для более точного управления (например, предварительного включения строгого режима для контекста прямого eval и чтения директивы //# sourceURL) создайте парсер напрямую: new Parser($source) и setModuleMode(), setStrictMode() и parse().

Узлы

Каждый класс AST наследуется от Phasis\Ast\Node и находится в пространстве имён Phasis\Ast\{Declaration, Expression, Pattern, Statement}. Узлы — обычные объекты значений только для чтения:

  • $node->type() возвращает строку типа узла — базовое имя класса ('BinaryExpression', 'ArrowFunction', ...).
  • $node->location — это Phasis\Lexer\SourceLocation с line (с нумерацией от 1), column (с нумерацией от 0) и offset (смещение в байтах). Сохраняются только начальные позиции; у узлов нет конечного смещения.
  • Все остальные открытые поля — дочерние элементы и атрибуты узла ($binary->left, $binary->operator, $binary->right).

Большинство имён типов совпадает с ESTree. Отличия:

Тип Phasis Тип ESTree
ArrowFunction ArrowFunctionExpression
TaggedTemplate TaggedTemplateExpression
ClassMethod MethodDefinition
ClassProperty PropertyDefinition
AssignmentProperty Property (внутри ObjectPattern)
ExportDeclaration ExportNamedDeclaration / ExportDefaultDeclaration / ExportAllDeclaration
ImportSpecifier ImportSpecifier / ImportDefaultSpecifier / ImportNamespaceSpecifier

Некоторые формы тоже отличаются: тела классов представлены в узле класса обычным массивом элементов (без обёртки ClassBody); имена импортов и экспортов, метки операторов и имена FunctionExpression хранятся в виде строк, а не вложенных узлов Identifier; кроме того, super разбирается как именованный Identifier super (класса узла Super нет). Phasis\Ast\EstreeSerializer преобразует всё это в формат ESTree.

Синтаксические ошибки

При неверном вводе выбрасывается Phasis\Exceptions\SyntaxError (обычно его подкласс Phasis\Parser\ParseError). Сообщение об ошибке заканчивается позицией, а структурированное расположение доступно в самом исключении:

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
}

Обходчик

Phasis\Ast\Walker обходит дерево в глубину, вызывая обратные вызовы при входе в узел и выходе из него, как в estraverse:

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

Оба обратных вызова получают (Node $node, ?Node $parent). Возврат Walker::SKIP из $enter исключает дочерние элементы узла из обхода; сам узел всё равно вызывает $leave.

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

Обход выполняется только для чтения. Узлы доступны только для чтения, поэтому заменить или изменить их нельзя.

Сериализатор

Phasis\Ast\Serializer преобразует типизированное AST в массивы или JSON, используя имена типов и структуру полей Phasis; поля кэша парсера удаляются:

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

Это формат, который выводит bin/phasis --ast.

EstreeSerializer

Phasis\Ast\EstreeSerializer преобразует типизированное AST в массивы в форме ESTree: имена типов ESTree (согласно таблице отклонений выше), имена и порядок полей ESTree, синтезированные узлы ClassBody / specifier / source-Literal и смещение в байтах start для каждого узла, разобранного из исходного кода. Смещений end нет, поскольку узлы хранят только начальные позиции, а строковые узлы Literal не содержат текста 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() выводит детерминированную сводку: по одной строке на узел (тип и атрибуты name= / value= / op= / kind=, отступ в два пробела на уровень вложенности). Формат побайтно совпадает со сводкой AST, которую создаёт тестовый пример acorn в tests/Popular/acorn, поэтому один и тот же эталон проверяет оба пути парсера JS и этот экспорт 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 разбирает файл (цель — скрипт) и выводит дамп Serializer JSON с отступом в два пробела. Полный список флагов см. в справочнике по флагамCLI.