Phasis
Advanced

Architecture

Phasis is a four-stage tree walker with an opportunistic bytecode VM. The data path is:

JS text


┌──────────┐
│  Lexer   │  src/Lexer/
└────┬─────┘
     ▼ tokens
┌──────────┐
│  Parser  │  src/Parser/
└────┬─────┘
     ▼ AST nodes
┌──────────────┐
│ Interpreter  │  src/Runtime/
│  + Bytecode  │  src/Bytecode/
│      VM      │
└────┬─────────┘
     ▼ JsValue
PHP host

Each stage is a PHP class with a small surface that the next stage consumes. There's no link step — parsing produces an AST, the interpreter walks it directly.

Lexer

src/Lexer/Lexer.php converts source text into a stream of Token instances. Each token is a readonly struct: type (TokenType enum, ~80 cases), value, line, column.

The lexer handles:

  • Number literals — decimal, hex (0x), octal (0o), binary (0b), exponential, numeric separators.
  • String literals — single and double quotes, all escape sequences, template literals with nested expressions.
  • Regular expression literals — full /pattern/flags tokenisation with awareness of when a / should be regex vs division.
  • Identifiers, keywords, punctuation.
  • Comments (line and block), preserved as trivia.
  • Unicode source — UTF-8 bytes interpreted as the JS UTF-16 source.

Output is a generator that yields tokens lazily. The parser consumes them with one-token lookahead.

Parser

src/Parser/Parser.php is a hand-written Pratt parser. Expression precedence lives in src/Parser/Precedence.php. Statements have their own recursive descent.

It produces immutable AST nodes from src/Ast/. Every node carries its source location for stack traces and error reporting.

Notable shapes:

  • Expression: Literal, Identifier, BinaryExpression, UnaryExpression, AssignmentExpression, CallExpression, MemberExpression, ArrowFunction, ObjectExpression, ArrayExpression, ConditionalExpression, TemplateLiteral, SpreadElement, NewExpression, ThisExpression, SequenceExpression, TaggedTemplate, ClassExpression, YieldExpression, AwaitExpression.
  • Statement: BlockStatement, IfStatement, ForStatement, ForInStatement, ForOfStatement, WhileStatement, DoWhileStatement, SwitchStatement, ReturnStatement, ThrowStatement, TryStatement, BreakStatement, ContinueStatement, ExpressionStatement.
  • Declaration: VariableDeclaration, FunctionDeclaration, ClassDeclaration, ImportDeclaration.
  • Pattern (destructuring): ArrayPattern, ObjectPattern, RestElement, AssignmentPattern.

Automatic semicolon insertion is implemented in the parser (not the lexer) per spec.

Interpreter

src/Runtime/Interpreter.php is the AST walker. It visits each node and returns a JsValue (or a Completion for statements that change control flow).

Key supporting classes:

  • Environment (src/Runtime/Environment.php) — lexical scope chain. Each function call creates a new environment pointing to its lexical parent. var hoists to the function scope; let / const are block-scoped with a TDZ.
  • CallStack (src/Runtime/CallStack.php) — explicit stack with depth limit. Function and method calls push frames; exceptions unwind them.
  • Completion (src/Runtime/Completion.php) — discriminated union of normal, return, throw, break, continue plus an optional value and label.
  • Reference (src/Runtime/Reference.php) — spec Reference type for left-hand-side evaluation in assignments and delete.

Values

JavaScript values are PHP objects under src/Value/. Each one implements the JsValue interface and carries its own type-conversion methods (toNumber(), toString(), toBoolean()).

ClassJS type
JsUndefinedundefined (singleton)
JsNullnull (singleton)
JsBooleantrue / false
JsNumberIEEE 754 double; preserves NaN, ±Infinity, ±0
JsStringUTF-16 string semantics
JsObjectbase object with property map and prototype chain
JsArrayextends JsObject with length tracking
JsFunctionclosure with captured Environment
JsSymbolSymbol primitive
JsBigIntarbitrary precision via bcmath
JsProxyall 13 traps, revocable
JsPromisesynchronous executor + microtask queue
JsGeneratorPHP Fiber-backed
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistrycollections
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArraybinary data

Number handling is the single largest source of historical bugs: NaN !== NaN must be true, -0 === 0 must be true, ToNumber edge cases need exact bit-level care. The Spec/TypeConversion class implements the spec algorithms verbatim and is exhaustively tested.

Built-in library

src/BuiltIn/ provides every standard-library object: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, plus all the constructors and prototype methods for each.

Each built-in is a static install() method on a class. The Engine calls them in dependency order at construction. Each one defines its prototype chain, installs methods as JsFunction instances wrapped around PHP closures, and exposes the constructor as a global.

PHP↔JS bridge

src/Interop/ handles value conversion across the boundary.

  • PhpToJs::convert() — PHP value to JsValue.
  • JsToPhp::convert()JsValue to PHP value.
  • HostFunction — wraps a PHP callable as a JsFunction.
  • PhpBridge — wraps a PHP object with a custom property map.

Conversion rules are documented in Value conversion.

Error handling

JS throw becomes a PHP Phasis\Exceptions\JsThrowable. PHP exceptions thrown inside host functions become JS Error instances. The interpreter catches and re-throws across the boundary so each side sees the exception in its own idiom.

Resource limits (call depth, loop iterations, string length, output size, execution time) live on the Engine. When any limit is exceeded, the interpreter throws Phasis\Exceptions\InternalError.

Bytecode VM

For hot functions, the interpreter speculatively compiles to PHP closures (the "bytecode VM" in src/Bytecode/). When a function is called many times, JsToPhp emits a PHP closure that runs the function body directly without tree-walking. On any unhandled shape (a non-numeric assignment, a with statement, a try/catch with rebinding) it bails out to the tree-walker.

See Bytecode VM for the full mechanism.

On this page