These instructions define the runtime and transpiler contracts for this repository. Preserve PHP semantics while making generated JavaScript the most direct, efficient representation of the source PHP. When a design choice is not specified here or by the user, ask before choosing behavior.
- PHP code retains PHP semantics, while generated JavaScript uses native asynchronous execution. Generated PHP functions, methods, constructors, and registered runtime/extension functions are
asyncand returnPromise<any>. - Runtime and extension functions accept
PHPReferencearguments. Their resolved results may be ordinary PHP values orPHPReferenceobjects; do not wrap every return value merely to satisfy the promise contract. PHPReference.get()andPHPReference.set(value)are asynchronous and return promises. Access values withawait reference.get()and mutate them withawait reference.set(value).- All PHP-callable runtime and extension functions are async and return promises before registration; registration does not wrap functions. All function, class, and constant registry names are lowercase plain strings, and
encodePhpIdentifieris only for emitted JavaScript identifiers. - Filesystem operations are asynchronous throughout the engine, runtime, extensions, compiler cache, and CLI. Do not use synchronous filesystem APIs.
JSTranspiler.transpile()is asynchronous because its cache I/O is asynchronous. PHPVariablerepresents mutable PHP state. Engine-created literals usePHPLiteral; generated scripts may hoist static string call arguments into reusableBasicLiteralinstances branded withSymbol.for("php.reference"). Both reject writes withLiterals cannot be changed.- PHP by-reference variables and parameters share the same
PHPReferenceobject in all scopes that refer to that variable. Do not introduce reference-forwarding chains orbindRef,unbindRef,isReference, orPHPReference.callAPIs. - PHP method/function arguments remain references so by-reference parameters can share identity. Do not unwrap an argument before invocation unless PHP semantics require a value.
- Transpile PHP classes to native JavaScript classes. Emit PHP static methods as JavaScript
staticmethods and PHP instance methods as JavaScript instance methods. Keep methods on their native class/prototype locations; call the correct method directly. - Emit PHP variables as
$-prefixed JavaScript bindings. PHP instance and static properties are stored directly on their JavaScript object/class using$-prefixed names. Every property value is aPHPReference. Methods do not use the$prefix. - Never emit a native JavaScript class
constructorfor a PHP constructor. Emit an async__construct(ctx, ...args)instance method. Instantiate PHP classes only through the static async method keyed bySYMBOL_PHP_NEW; never transpile PHP instantiation tonew ClassName(...). - The
[SYMBOL_PHP_NEW]factory creates an object without invoking a native constructor, initializes its PHP property references through[SYMBOL_PHP_INIT], invokes__constructunconditionally, and returns the instance. UseObject.create(this.prototype)where needed to retain native JavaScript inheritance. - Generate direct property and method operations whenever possible:
await object.$property.get(),await object.$property.set(value), andawait object.method(ctx, ...args). Do not route ordinary declared-property or direct method accesses throughPHPContextlookup helpers. - PHP
parent::method()calls use native JavaScriptsuper.method(ctx, ...args)in the matching static or instance context. Do not probe a class prototype as a fallback for static methods or probe a constructor as a fallback for instance methods.
- Classes with instance magic methods are returned as JavaScript
Proxyobjects from[SYMBOL_PHP_NEW]. Classes without instance magic methods return raw instances without proxy overhead. - The instance proxy distinguishes property keys from method keys by the leading
$. For a$property key, it returns the existing property reference or aPHPPropertyReferencethat performs the magic operation asynchronously. For a non-$key, it returns the native method or a generated async__calldispatcher. - JavaScript Proxy traps are synchronous. Do not await PHP magic methods inside a trap. Async
PHPPropertyReferenceoperations perform__get,__set,__isset, and__unsetas appropriate. isset($object->property)andunset($object->property)use async operations on the property reference, allowing__issetand__unsetto run. Preserve PHP null and unset semantics for declared properties.- Store a class constructor behind a class-level JavaScript
Proxyonly when the class defines__callStatic. The proxy returns a generated async dispatcher for missing static methods; ordinary static methods and classes remain direct. - The factory and initializer are engine internals, not string-named PHP methods: declare and invoke them only as
[SYMBOL_PHP_NEW]and[SYMBOL_PHP_INIT]symbol-keyed static methods. Use these symbols for PHP class inheritance/initialization and all built-in/extension class factories as well as transpiled classes. - Magic dispatchers pass PHP arguments and the original PHP method/property name with PHP-compatible value/reference semantics. Keep generated dispatch as direct as possible; restrict dynamic name conversion to genuinely dynamic names.
- Use one shared, deterministic PHP-to-JavaScript identifier converter for generated JavaScript identifiers and property keys. Never encode PHP function, class, or constant names when registering or looking them up in runtime Records; those registry keys are already lowercase PHP names and remain plain strings.
- Preserve ASCII letters, digits, and underscores. Encode every other UTF-8 byte as
$HHusing uppercase hexadecimal, including literal$bytes within the name. Preserve the leading$sigil for PHP variable/property names; encode any literal$in the name. The encoding must be collision-free and produce valid JavaScript identifiers/property keys; account for JavaScript-special names such asconstructor. - Lowercase PHP's case-insensitive identifiers at transpile time: class names, namespaces, functions, methods, and constants. Preserve case-sensitive variable and property names.
- Store namespaced class and function registry keys with lowercase namespace/name segments joined by
$$(for example,Runtime\One\Itembecomesruntime$$one$$item); nested namespaces use one$$between every segment. Generated JavaScript identifiers preserve these separators while encoding each segment independently. Keep original backslash-separated spelling and casing in symbol metadata for reflection and diagnostics. - Retain original source casing in symbol-backed metadata when needed for reflection, magic-method arguments, and PHP diagnostics. Do not carry duplicate runtime strings when casing can be recovered from metadata or the reversible encoding.
- Store engine-owned class and reflection metadata using exported
Symbolconstants, not string properties such as__php_properties,__php_constants, or__php_parent. This includes class name, property/method/constant metadata, parent class, implemented interfaces, and magic-method flags.SYMBOL_PHP_PROPERTIES,SYMBOL_PHP_METHODS, andSYMBOL_PHP_CONSTANTSvalues are Records/object literals, neverMapinstances. Actual PHP properties and methods stay directly on the JavaScript class/object under their emitted names. - Do not use JavaScript
MaporWeakMapanywhere in the codebase. Use null-prototype Records for string-keyed collections, arrays for identity-keyed collections, andSetwhere membership semantics are needed. - Function and method reflection metadata uses
SYMBOL_PHP_METAexclusively. Do not attach or read a string-namedphpMetaproperty. If symbol metadata is unavailable, reflection may fall back to parsing the JavaScript function source.
- Generated output should look like a direct JavaScript representation of the PHP program. Avoid redundant closures, dynamic dispatch checks, prototype probing, repeated case conversion, metadata scans, context helpers, and other per-operation overhead when direct JavaScript suffices.
- Resolve cross-file function and class registry references lazily at first executed use so preceding
require/includestatements can register them; cache successful lookups within their transpile scope. - Analyze each function, method, and closure body before emitting scope management. Emit
pushScope/popScopeonly for PHP scope-dependent behavior (such as globals/statics, variable variables, includes/eval, scope inspection/extraction, offset helpers, or argument introspection); otherwise use localPHPVariables or direct$arg_*arguments. EmitargPHPVaronly when PHP reference identity, mutable by-value parameter copies, or context-visible parameters require it. Useconstfor stable bindings andletonly when the binding is reassigned. - Hoist static string arguments to PHP calls into module-level immutable
BasicLiteralconstants, reusing each value within the compiled script. Keep literals created by engine-internal operations asPHPLiteral. - Do not emit loop tick counters,
checkLoop()calls, cooperative yielding, PHP-engine call-stack tracking, or per-call debug instrumentation. Trusted PHP that loops indefinitely behaves like JavaScript that loops indefinitely. - Do not block Node.js with synchronous filesystem or password-hashing APIs; use their asynchronous equivalents.
- Remove
JSPHP_DEBUG, its environment checks, and internal debug-only logging. Retain actual PHP error handling, PHP'serror_log()function, and diagnostics required to report real failures. - Generate source maps and use source-map-mapped native JavaScript error stacks when the active entrypoint has already installed
source-map-support. Do not install or importsource-map-supportglobally from the engine.debug_backtrace()parses native JavaScriptErrorframes as-is when mapping support is absent; do not maintain a PHP call stack to construct traces. - Native JavaScript errors caught by PHP
try/catchare wrapped as PHPErrorinstances. Preserve PHP error classes, error handlers, and source locations without adding hot-path tracking. - Increment
PHPEngine.REVISIONwhenever runtime or transpiler changes can invalidate compiled-code caches.
PHPEngineandPHPContextkeepinternalVarsinvisible to PHP userland ($GLOBALS,get_defined_vars(), and reflection).- Output buffering supports nested buffers and flushes remaining output and shutdown functions at the established top-level
require/evallifecycle boundaries. - HTTP response functions use the
PHPResponseattached toPHPContext. Preserve CLI/server distinctions and existing warning/error behavior. - Keep case-insensitive PHP lookup behavior for user-supplied names in functions such as
define,defined,class_exists,function_exists,constant, andis_callable; preserve user-provided casing in diagnostics. - Function, class, and constant registry names are already lowercase before registration. Do not call
toLowerCase()orencodePhpIdentifier()for registry keys. PHPEngine.registerFunction(name, fn)directly assignsthis.functions[name] = fn;registerFunctions(functions)usesObject.assign(this.functions, functions). These accept already-async functions and do not wrap or modify metadata.PHPEngine.registerClass(name, value)directly assignsthis.classes[name] = value;registerClasses(classes)usesObject.assign(this.classes, classes).- Preserve Node.js interoperability through the
nodejsextension and its dedicated proxy symbols. - Maintain WordPress and MySQL compatibility, including installation, administrator access, dashboard/theme rendering,
mysqlioperations, and the end-to-end WordPress test. - Preserve the CLI, FastCGI, and HTTP server binaries and their documented behavior.
- Follow the existing TypeScript, generated JavaScript/declaration/source-map, formatting, and test conventions. Keep generated artifacts in sync using the repository build.
- Add focused regression tests for changes to references, name conversion, native class emission, magic instance/static dispatch, reflection, and source-mapped stack traces.
- Run the smallest relevant tests first, then the WordPress end-to-end test when changing runtime, class, or transpiler behavior. Run the build/type check after source changes.
- Do not revert or stage unrelated work in a dirty worktree.