Перейти до вмісту

Парсер

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 класичний 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 fixture у 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 розбирає файл (у режимі script) і виводить дамп Serializer JSON з відступом у два пробіли. Повний опис прапорців див. у CLIдовіднику.