Arquitetura
Phasis é um interpretador de árvore em quatro etapas com uma VM de bytecode oportunista. O fluxo de dados é:
JS text
│
▼
┌──────────┐
│ Lexer │ src/Lexer/
└────┬─────┘
▼ tokens
┌──────────┐
│ Parser │ src/Parser/
└────┬─────┘
▼ AST nodes
┌──────────────┐
│ Interpreter │ src/Runtime/
│ + Bytecode │ src/Bytecode/
│ VM │
└────┬─────────┘
▼ JsValue
PHP host
Cada etapa é uma classe PHP com uma interface pequena, consumida pela etapa seguinte. Não há etapa de vinculação: a análise produz uma AST e o interpretador percorre-a diretamente.
Analisador léxico
src/Lexer/Lexer.php converte o texto-fonte em um fluxo de instâncias de Token. Cada token é uma estrutura somente para leitura: tipo (enum TokenType, cerca de 80 casos), valor, linha e coluna.
O analisador léxico trata de:
- Literais numéricos — decimais, hexadecimais (
0x), octais (0o), binários (0b), exponenciais e separadores numéricos. - Literais de string — aspas simples e duplas, todas as sequências de escape e literais de modelo com expressões aninhadas.
- Literais de expressões regulares — tokenização completa de
/pattern/flags, considerando quando uma/deve ser interpretada como expressão regular ou divisão. - Identificadores, palavras-chave e pontuação.
- Comentários de linha e de bloco, preservados como dados auxiliares.
- Código-fonte Unicode — bytes UTF-8 interpretados como o código-fonte UTF-16 de JS.
A saída é um gerador que produz tokens sob demanda. O analisador sintático os consome com uma antecipação de um token.
Analisador
src/Parser/Parser.php é um analisador Pratt escrito à mão. A precedência das expressões fica em src/Parser/Precedence.php. As instruções usam sua própria análise descendente recursiva.
Ele produz nós AST imutáveis a partir de src/Ast/. Cada nó contém sua localização no código-fonte para rastreamentos de pilha e relatórios de erros.
Formas notáveis:
- Expressão:
Literal,Identifier,BinaryExpression,UnaryExpression,AssignmentExpression,CallExpression,MemberExpression,ArrowFunction,ObjectExpression,ArrayExpression,ConditionalExpression,TemplateLiteral,SpreadElement,NewExpression,ThisExpression,SequenceExpression,TaggedTemplate,ClassExpression,YieldExpression,AwaitExpression. - Instrução:
BlockStatement,IfStatement,ForStatement,ForInStatement,ForOfStatement,WhileStatement,DoWhileStatement,SwitchStatement,ReturnStatement,ThrowStatement,TryStatement,BreakStatement,ContinueStatement,ExpressionStatement. - Declaração:
VariableDeclaration,FunctionDeclaration,ClassDeclaration,ImportDeclaration. - Padrão (desestruturação):
ArrayPattern,ObjectPattern,RestElement,AssignmentPattern.
A inserção automática de ponto e vírgula é implementada no analisador sintático (não no léxico), conforme a especificação.
Interpretador
src/Runtime/Interpreter.php percorre a AST. Ele visita cada nó e retorna um JsValue (ou um Completion para instruções que alteram o fluxo de controle).
Principais classes auxiliares:
Environment(src/Runtime/Environment.php) — cadeia de escopos léxicos. Cada chamada de função cria um novo ambiente ligado ao pai léxico.varé içado para o escopo da função;let/consttêm escopo de bloco e uma zona temporal morta (TDZ).CallStack(src/Runtime/CallStack.php) — pilha explícita com limite de profundidade. Chamadas de funções e métodos adicionam quadros; exceções os desempilham.Completion(src/Runtime/Completion.php) — união discriminada denormal,return,throw,break,continue, mais um valor e um rótulo opcionais.Reference(src/Runtime/Reference.php) — tipo Reference da especificação para avaliar o lado esquerdo em atribuições edelete.
Valores
Os valores JavaScript são objetos PHP sob src/Value/. Cada um implementa a interface JsValue e tem seus próprios métodos de conversão de tipo (toNumber(), toString(), toBoolean()).
| Classe | Tipo JS |
|---|---|
JsUndefined |
undefined (singleton) |
JsNull |
null (singleton) |
JsBoolean |
true / false |
JsNumber |
double IEEE 754; preserva NaN, ±Infinity, ±0 |
JsString |
semântica de strings UTF-16 |
JsObject |
objeto base com mapa de propriedades e cadeia de protótipos |
JsArray |
estende JsObject com controle de comprimento |
JsFunction |
fechamento com Environment capturado |
JsSymbol |
primitivo Symbol |
JsBigInt |
precisão arbitrária por meio de bcmath |
JsProxy |
todas as 13 interceptações, revogável |
JsPromise |
executor síncrono + fila de microtarefas |
JsGenerator |
modo duplo: instantâneo do quadro (corpos compilados em bytecode; consulte VM de bytecode § Geradores com instantâneo do quadro), com alternativa de Fiber PHP para yield*, try-with-yield e geradores assíncronos |
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry |
coleções |
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray |
dados binários |
O tratamento de números é, historicamente, a maior fonte de bugs: NaN !== NaN precisa ser verdadeiro, -0 === 0 precisa ser verdadeiro, e os casos extremos de ToNumber exigem precisão exata no nível dos bits. A classe Spec/TypeConversion implementa os algoritmos da especificação literalmente e foi testada exaustivamente.
Biblioteca integrada
src/BuiltIn/ fornece todos os objetos da biblioteca padrão: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, além dos pacotes Web Platform / Fetch (URL, Headers, Request, Response, Blob, FormData, Streams completo, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest e o loop de eventos (setTimeout, setInterval, queueMicrotask, AsyncContext).
Cada recurso integrado é um método estático install() de uma classe. Engine os chama em ordem de dependência durante a construção. Cada um define sua cadeia de protótipos, instala métodos como instâncias de JsFunction envolvidas por closures PHP e expõe o construtor como global.
Ponte PHP↔JS
A conversão de valores entre os dois lados acontece em dois pontos:
Phasis\Interop\PhpToJs::convert()— valor PHP paraJsValue, usado porsetGlobal(), argumentos decall()e valores retornados por funções do host. Uma função chamável PHP se torna umJsFunctioncujo corpo a chama.PhpToJs::toPhp()— deJsValuepara PHP nos argumentos de uma função do host.Engine::toPhp()(privado) — deJsValuepara PHP nos resultados deeval()ecall().
Não há ponte para objetos: objetos PHP que não são chamáveis são convertidos em undefined (problema #18). A classe JsToPhp em src/Bytecode/ não tem relação com interoperabilidade; ela é o transpiler descrito abaixo. As regras de conversão estão documentadas em Conversão de valores.
Tratamento de erros
Um JS throw não capturado se torna um PHP Phasis\Exceptions\JsThrowable; erros gerados pelo próprio mecanismo aparecem como Phasis\Exceptions\TypeError, RangeError ou ReferenceError, todos subclasses de Phasis\Exceptions\RuntimeError. Dentro do mecanismo, um JS try / catch captura JsThrowable, qualquer RuntimeError e Phasis\Exceptions\SyntaxError. Outras exceções PHP, incluindo as lançadas por funções do host, não são convertidas e se propagam diretamente ao chamador PHP (problema #20).
Há dois limites: o teto da pilha de chamadas (CallStack::maxDepth, 4,096 quadros, mais uma proteção de 8,192 quadros para closures transpiladas) e maxLoopIterations, definido por Engine::setLimit(), aplicado apenas pelo interpretador de árvore (problema #19). Ambos lançam Phasis\Exceptions\InternalError. Não há limites para o comprimento das strings, o tamanho da saída ou o tempo de execução.
VM de bytecode
Funções muito usadas são compiladas de duas formas. JsToPhp (em src/Bytecode/) gera uma closure PHP para corpos com muitos cálculos numéricos ou uso intenso de variáveis locais, que o JIT de rastreamento de PHP pode compilar para código nativo. O pipeline compila leituras de new Ctor(args), obj.method(args) e this.member, o que basta para que o padrão canônico de método de classe dentro de um loop ignore completamente a VM. Todo o restante é compilado para bytecode linear, consumido por VM::execute, com uma pilha de chamadas personalizada que mantém cada chamada JS dentro de um único quadro PHP e um cache inline por PC para leituras de métodos de protótipo. Corpos simples de geradores também são compilados para bytecode, com Op::YIELD capturando um instantâneo do quadro na heap em vez de atravessar a Fiber PHP.
Qualquer estrutura que o compilador não compile (e.g. with, try com um catch que redefine a ligação, yield*, geradores assíncronos) recorre ao interpretador de árvore. Consulte VM de bytecode para ver o mecanismo completo.