Arquitectura
Phasis es un recorrido de árbol de cuatro etapas con una máquina virtual de bytecode oportunista. El flujo de datos es:
JS text
│
▼
┌──────────┐
│ Lexer │ src/Lexer/
└────┬─────┘
▼ tokens
┌──────────┐
│ Parser │ src/Parser/
└────┬─────┘
▼ AST nodes
┌──────────────┐
│ Interpreter │ src/Runtime/
│ + Bytecode │ src/Bytecode/
│ VM │
└────┬─────────┘
▼ JsValue
PHP host
Cada etapa es una clase PHP con una interfaz pequeña que consume la etapa siguiente. No hay paso de enlazado: el análisis sintáctico produce un AST que el intérprete recorre directamente.
Analizador léxico
src/Lexer/Lexer.php convierte el texto fuente en un flujo de instancias Token. Cada token es una estructura de solo lectura: tipo (enumeración TokenType, ~80 casos), valor, línea y columna.
El analizador léxico procesa:
- Literales numéricos: decimales, hexadecimales (
0x), octales (0o), binarios (0b), exponenciales y separadores numéricos. - Literales de cadena: comillas simples y dobles, todas las secuencias de escape y literales de plantilla con expresiones anidadas.
- Literales de expresiones regulares: tokenización
/pattern/flagscompleta, que detecta cuándo/debe interpretarse como expresión regular o como división. - Identificadores, palabras clave y signos de puntuación.
- Comentarios de línea y de bloque, preservados como elementos triviales.
- Fuente Unicode: bytes UTF-8 interpretados como fuente UTF-16 JS.
El resultado es un generador que produce tokens de forma diferida. El analizador sintáctico los consume con un token de anticipación.
Analizador
src/Parser/Parser.php es un analizador sintáctico Pratt escrito a mano. La precedencia de las expresiones está en src/Parser/Precedence.php. Las instrucciones tienen su propio análisis descendente recursivo.
Produce nodos AST inmutables a partir de src/Ast/. Cada nodo contiene su ubicación en el código fuente para los seguimientos de pila y los informes de errores.
Estructuras destacadas:
- Expresión:
Literal,Identifier,BinaryExpression,UnaryExpression,AssignmentExpression,CallExpression,MemberExpression,ArrowFunction,ObjectExpression,ArrayExpression,ConditionalExpression,TemplateLiteral,SpreadElement,NewExpression,ThisExpression,SequenceExpression,TaggedTemplate,ClassExpression,YieldExpression,AwaitExpression. - Instrucción:
BlockStatement,IfStatement,ForStatement,ForInStatement,ForOfStatement,WhileStatement,DoWhileStatement,SwitchStatement,ReturnStatement,ThrowStatement,TryStatement,BreakStatement,ContinueStatement,ExpressionStatement. - Declaración:
VariableDeclaration,FunctionDeclaration,ClassDeclaration,ImportDeclaration. - Patrón (desestructuración):
ArrayPattern,ObjectPattern,RestElement,AssignmentPattern.
La inserción automática de punto y coma se implementa en el analizador sintáctico (no en el léxico), según la especificación.
Intérprete
src/Runtime/Interpreter.php recorre el AST. Visita cada nodo y devuelve un JsValue (o un Completion para las instrucciones que cambian el flujo de control).
Clases de apoyo principales:
Environment(src/Runtime/Environment.php): cadena de ámbitos léxicos. Cada llamada a función crea un entorno que apunta a su padre léxico.varse eleva al ámbito de la función;let/consttienen ámbito de bloque y una zona muerta temporal (TDZ).CallStack(src/Runtime/CallStack.php): pila explícita con límite de profundidad. Las llamadas a funciones y métodos apilan marcos; las excepciones los desenrollan.Completion(src/Runtime/Completion.php): unión discriminada denormal,return,throw,break,continue, más un valor y una etiqueta opcionales.Reference(src/Runtime/Reference.php): tipo Reference de la especificación para evaluar el lado izquierdo en asignaciones ydelete.
Valores
Los valores JavaScript son objetos PHP bajo src/Value/. Cada uno implementa la interfaz JsValue y contiene sus propios métodos de conversión de tipo (toNumber(), toString(), toBoolean()).
| Clase | Tipo JS |
|---|---|
JsUndefined |
undefined (singleton) |
JsNull |
null (singleton) |
JsBoolean |
true / false |
JsNumber |
double IEEE 754; conserva NaN, ±Infinity, ±0 |
JsString |
semántica de cadenas UTF-16 |
JsObject |
objeto base con mapa de propiedades y cadena de prototipos |
JsArray |
amplía JsObject con seguimiento de longitud |
JsFunction |
cierre con Environment capturado |
JsSymbol |
primitivo Symbol |
JsBigInt |
precisión arbitraria mediante bcmath |
JsProxy |
los 13 interceptores, revocables |
JsPromise |
ejecutor síncrono + cola de microtareas |
JsGenerator |
modo dual: instantánea de marco (cuerpos compilados a bytecode; véase Bytecode VM § Generadores con instantánea de marco) con alternativa de Fiber PHP para yield*, try-with-yield y generadores asíncronos |
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry |
colecciones |
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray |
datos binarios |
El manejo de números es la mayor fuente histórica de errores: NaN !== NaN debe ser verdadero, -0 === 0 debe ser verdadero, y los casos límite de ToNumber requieren precisión a nivel de bits. La clase Spec/TypeConversion implementa literalmente los algoritmos de la especificación y se ha probado exhaustivamente.
Biblioteca integrada
src/BuiltIn/ proporciona todos los objetos de la biblioteca estándar: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, además de los paquetes 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 y el bucle de eventos (setTimeout, setInterval, queueMicrotask, AsyncContext).
Cada función integrada es un método estático install() de una clase. Engine las invoca en orden de dependencias durante la construcción. Cada una define su cadena de prototipos, instala métodos como instancias JsFunction envueltas en cierres PHP y expone el constructor como global.
Puente PHP↔JS
La conversión de valores entre ambos lados se realiza en dos lugares:
Phasis\Interop\PhpToJs::convert(): valor PHP aJsValue, usado porsetGlobal(), los argumentos decall()y los valores devueltos por funciones del host. Una función invocable PHP se convierte en unJsFunctioncuyo cuerpo la llama.PhpToJs::toPhp():JsValuea PHP para los argumentos de una función del host.Engine::toPhp()(privado):JsValuea PHP para los resultados deeval()ycall().
No hay puente para objetos: los objetos PHP que no son invocables se convierten en undefined (issue #18). La clase JsToPhp de src/Bytecode/ no está relacionada con la interoperabilidad; es el transpilador descrito más abajo. Las reglas de conversión se documentan en Conversión de valores.
Gestión de errores
Un JS throw no capturado se convierte en un PHP Phasis\Exceptions\JsThrowable; los errores que genera el propio motor se presentan como Phasis\Exceptions\TypeError, RangeError o ReferenceError, todos subclases de Phasis\Exceptions\RuntimeError. Dentro del motor, un JS try / catch captura JsThrowable, cualquier RuntimeError y Phasis\Exceptions\SyntaxError. Las demás excepciones PHP, incluidas las generadas por funciones del host, no se convierten y se propagan directamente al llamador PHP (issue #20).
Hay dos límites: el máximo de la pila de llamadas (CallStack::maxDepth, 4,096 marcos, más un límite de 8,192 marcos para cierres transpilados) y maxLoopIterations, que se configura mediante Engine::setLimit() y solo aplica el recorrido del árbol (issue #19). Ambos generan Phasis\Exceptions\InternalError. No hay límites de longitud de cadena, tamaño de salida ni tiempo de ejecución.
Máquina virtual de bytecode
Las funciones de uso intensivo se compilan de dos maneras. JsToPhp (en src/Bytecode/) genera un cierre PHP para cuerpos con muchas operaciones numéricas o variables locales que el JIT de trazado de PHP puede compilar a código nativo; la canalización compila lecturas de new Ctor(args), obj.method(args) y this.member, lo que basta para que el patrón canónico de método de clase dentro de un bucle evite por completo la máquina virtual. Todo lo demás se compila a bytecode plano que consume VM::execute, con una pila de llamadas personalizada que mantiene cada llamada JS dentro de un único marco PHP y una caché en línea por PC para las lecturas de métodos de prototipo. Los cuerpos sencillos de generador también se compilan a bytecode, y Op::YIELD captura una instantánea del marco en el montón en lugar de pasar por Fiber PHP.
Cualquier estructura que el compilador no pueda compilar (e.g. with, try con un catch reasignado, yield*, generadores asíncronos) vuelve al recorrido del árbol. Consulte Máquina virtual de bytecode para conocer el mecanismo completo.