Pular para o conteúdo

Arquitetura

Phasis é um interpretador de árvore em quatro etapas com uma VM de bytecode oportunista. O fluxo de dados é:

Código
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 / const tê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 de normal, 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 e delete.

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 para JsValue, usado por setGlobal(), argumentos de call() e valores retornados por funções do host. Uma função chamável PHP se torna um JsFunction cujo corpo a chama.
  • PhpToJs::toPhp() — de JsValue para PHP nos argumentos de uma função do host.
  • Engine::toPhp() (privado) — de JsValue para PHP nos resultados de eval() e call().

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.