Analisador sintático
Phasis disponibiliza seu analisador JavaScript como um PHP API independente. Você pode analisar o texto-fonte em uma AST tipada, percorrê-la e exportá-la como arrays no formato ESTree ou JSON, sem construir um Engine nem executar nada.
parseSource
public static function parseSource(string $source, string $sourceType = 'script'): Program
O ponto de entrada de chamada única em Phasis\Parser\Parser. Analisa $source e retorna o nó raiz Phasis\Ast\Program.
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 seleciona o objetivo gramatical, com nomes correspondentes à mesma opção nos analisadores JS:
| Valor | Objetivo |
|---|---|
script |
Script clássico. import/export no nível superior são erros de sintaxe; await é um identificador |
module |
Módulo ES. Modo estrito implícito; import/export e await no nível superior são analisados |
Qualquer outro valor lança \InvalidArgumentException.
Para um controle mais preciso (ativar previamente o modo estrito para um contexto de eval direto ou ler a pragma //# sourceURL), construa o analisador diretamente: new Parser($source) mais setModuleMode(), setStrictMode() e parse().
Nós
Toda classe AST estende Phasis\Ast\Node e fica em Phasis\Ast\{Declaration, Expression, Pattern, Statement}. Os nós são objetos de valor somente para leitura:
$node->type()retorna a string do tipo do nó, que corresponde ao nome da classe sem o namespace ('BinaryExpression','ArrowFunction', ...).$node->locationé umPhasis\Lexer\SourceLocationcomline(baseado em 1),column(baseado em 0) eoffset(deslocamento em bytes). Somente as posições iniciais são armazenadas; os nós não têm deslocamento final.- Todos os outros campos públicos são filhos e atributos do nó (
$binary->left,$binary->operator,$binary->right).
A maioria dos nomes de tipo corresponde ao ESTree. Diferenças:
| Tipo Phasis | Tipo ESTree |
|---|---|
ArrowFunction |
ArrowFunctionExpression |
TaggedTemplate |
TaggedTemplateExpression |
ClassMethod |
MethodDefinition |
ClassProperty |
PropertyDefinition |
AssignmentProperty |
Property (dentro de ObjectPattern) |
ExportDeclaration |
ExportNamedDeclaration / ExportDefaultDeclaration / ExportAllDeclaration |
ImportSpecifier |
ImportSpecifier / ImportDefaultSpecifier / ImportNamespaceSpecifier |
Algumas formas também diferem: os corpos de classe são arrays simples de elementos no nó da classe (sem wrapper ClassBody); nomes de importação/exportação, rótulos de instruções e nomes FunctionExpression são armazenados como strings, e não como nós Identifier aninhados; e super é analisado como um Identifier denominado super (não existe uma classe de nó Super). Phasis\Ast\EstreeSerializer reformata tudo isso para o formato ESTree.
Erros de sintaxe
Uma entrada inválida lança Phasis\Exceptions\SyntaxError (geralmente sua subclasse Phasis\Parser\ParseError). A mensagem da exceção termina com a posição, e a localização estruturada está disponível na exceção:
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
}
Percursor
Phasis\Ast\Walker percorre uma árvore em profundidade, com callbacks de entrada e saída, no estilo do estraverse:
public static function walk(Node $node, callable $enter, ?callable $leave = null): void
Ambos os callbacks recebem (Node $node, ?Node $parent). Retornar Walker::SKIP de $enter ignora os filhos do nó; ainda assim, o próprio $leave do nó é executado.
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;
}
});
O percurso é somente para leitura. Os nós são objetos de valor somente para leitura, portanto não há suporte para substituí-los ou modificá-los.
Serializador
Phasis\Ast\Serializer converte a AST tipada em arrays ou JSON usando os nomes de tipo e a disposição de campos de Phasis, removendo os campos de cache internos do parser:
public static function toArray(Node $node): array
public static function toJson(Node $node, bool $pretty = false): string
Este é o formato que bin/phasis --ast imprime.
EstreeSerializer
Phasis\Ast\EstreeSerializer converte a AST tipada em arrays no formato ESTree: nomes de tipo ESTree (conforme a tabela de divergências acima), nomes e ordenação de campos ESTree, nós ClassBody / especificador / origem-Literal sintetizados e um deslocamento em bytes start em cada nó analisado a partir da origem. Não há deslocamentos end, pois os nós armazenam apenas posições iniciais, e os nós de string Literal não contêm texto raw.
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() emite um resumo determinístico, com uma linha por nó (tipo mais atributos name= / value= / op= / kind=, indentação de dois espaços por nível). O formato é idêntico byte a byte ao resumo da AST produzido pelo fixture do acorn em tests/Popular/acorn, portanto o mesmo oráculo verifica tanto o caminho do parser JS quanto esta exportação 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 analisa um arquivo (objetivo de script) e imprime o despejo Serializer JSON com indentação de dois espaços. Consulte CLI para ver a referência completa das opções.