compiler
Compiles JavaScript source (parsed by `zerojs.parser`) to Luau source that executes against the same realm value model as the interpreter (`zerojs.runtime`). Objects, prototypes, the stdlib, promises, typed arrays and the bridge are shared between tiers, so compiled and interpret…
compiler
Compiles JavaScript source (parsed by zerojs.parser) to Luau source that
executes against the same realm value model as the interpreter
(zerojs.runtime). Objects, prototypes, the stdlib, promises, typed arrays
and the bridge are shared between tiers, so compiled and interpreted code
interoperate freely on one VM: bindings persist across runs, either tier can
call functions the other created, and host marshaling behaves identically.
The speed comes from eliminating the interpreter's per-node dispatch and
environment lookups: JS statements become plain Luau control flow, locals
become Luau locals, and array element access rides the runtime's dense
integer-indexed storage (rt.iget/rt.iset).
Surface:
compile(source, sourceName?) -> (luauSource?, err?)— translate a program.load(source, sourceName?) -> (chunk?, err?)— translate and load; the chunk takes the runtime companion as its argument.makeRtc(rt, globalObj, globalEnv?)— build the runtime companion binding the JS-semantics operations (operators, member access, calls, classes, destructuring, async/generators, step budgets) to a realm.runOn(rt, globalObj, source, sourceName?)— compile and run against a realm, returning the JS completion value.
Coverage spans the language the parser accepts: expressions and operators
with JS coercion semantics, control flow (including switch fall-through and
labeled-free break/continue), functions, closures, arrow functions with
lexical this, arguments, destructuring with defaults and rest, template
literals, regex literals, classes (inheritance, super, accessors, fields,
static members), try/catch/finally across loop and return boundaries,
async/await on the VM event loop, and generators. A construct outside the
covered set raises compile bail: <node>; zerojs.api's auto engine then
runs that program on the interpreter and records the bail.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2strict module zerojs.compiler Compiles a JavaScript AST (zerojs.parser) to Luau source that executes against the SAME realm value model as the interpreter (zerojs.runtime). Objects, prototypes, the stdlib, typed arrays and the bridge are shared, so compiled code interoperates with interpreted code and host marshaling with no second value model. Speed comes from eliminating per-node dispatch and environment lookups, plus integer-indexed array fast paths (rt.iget/iset). `compile` returns Luau source; `load` returns a chunk taking the runtime companion `rtc` (built by `makeRtc` from a realm + its global object). A construct outside the covered subset raises "compile bail: <node>" so the caller can fall back to the interpreter for that program.
makeRtc(rt: any, globalObj: any, globalEnv: any?) → any
Runtime companion. `makeRtc(rt, globalObj)` binds the JS-semantics operations compiled code calls into to a specific realm. JS values ARE realm values: undefined = values.undefined, null = values.null, numbers/strings/booleans native, objects = JSObject. Luau nil is never a JS value. `globalEnv` is the interpreter Env whose `vars` map holds the script-scope cells (top-level var/let/const/function bindings). Compiled code reads and writes the SAME cells the interpreter uses, so bindings persist across runs and across engine tiers on one VM. When absent, a private cell table is created (standalone use).
| arg | type | description |
|---|---|---|
| rt | any | |
| globalObj | any | |
| globalEnv | any? |
toUint32(v: any) → number
| arg | type | description |
|---|---|---|
| v | any |
toInt32(v: any) → number
| arg | type | description |
|---|---|---|
| v | any |
truthy(v: any) → boolean
Every hot operation opens with a native-type fast path: the overwhelming majority of dynamic operands are already numbers/booleans/strings, and the full abstract-operation path only runs for objects and mixed types.
| arg | type | description |
|---|---|---|
| v | any |
add(l: any, r: any) → any
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
sub(l: any, r: any) → any
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
mul(l: any, r: any) → any
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
div(l: any, r: any) → any
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
mod(l: any, r: any) → any return math
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
pow(l: any, r: any) → any return toNumber
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
band(l: any, r: any) → any return toInt32
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
bor(l: any, r: any) → any return toInt32
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
bxor(l: any, r: any) → any return toInt32
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
shl(l: any, r: any) → any return toInt32
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
shr(l: any, r: any) → any return toInt32
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
ushr(l: any, r: any) → any return toUint32
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
seq(l: any, r: any) → boolean
Primitive same-type equality is native (NaN ~= NaN holds in both languages); everything else takes the abstract operation.
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
sneq(l: any, r: any) → boolean return not rtc
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
eq(l: any, r: any) → boolean
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
neq(l: any, r: any) → boolean return not rtc
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
relational(op: string, l: any, r: any) → boolean
| arg | type | description |
|---|---|---|
| op | string | |
| l | any | |
| r | any |
lt(l: any, r: any) → boolean return relational
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
gt(l: any, r: any) → boolean return relational
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
le(l: any, r: any) → boolean return relational
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
ge(l: any, r: any) → boolean return relational
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
instanceOf(l: any, r: any) → boolean return rt
| arg | type | description |
|---|---|---|
| l | any | |
| r | any |
inOp(key: any, obj: any) → boolean
| arg | type | description |
|---|---|---|
| key | any | |
| obj | any |
imul(a: any, b: any) → number
Math.imul: exact low-32-bit product (double multiplication loses precision past 2^53, so the multiply splits at 16 bits).
| arg | type | description |
|---|---|---|
| a | any | |
| b | any |
max2(a: any, b: any) → number
Two-argument Math.max/min with JS NaN propagation.
| arg | type | description |
|---|---|---|
| a | any | |
| b | any |
min2(a: any, b: any) → number
| arg | type | description |
|---|---|---|
| a | any | |
| b | any |
neg(v: any) → number return
| arg | type | description |
|---|---|---|
| v | any |
pos(v: any) → number return toNumber
| arg | type | description |
|---|---|---|
| v | any |
bnot(v: any) → number return toInt32
| arg | type | description |
|---|---|---|
| v | any |
lnot(v: any) → boolean return not rt
| arg | type | description |
|---|---|---|
| v | any |
typeof(v: any) → string return rt
| arg | type | description |
|---|---|---|
| v | any |
get(obj: any, key: string) → any
Member read mirroring the interpreter's getMember: nullish guard, string indexing / length / StringProto, Number/Boolean protos, else realm get. An own data property on a plain object is the overwhelmingly common case, so it resolves in two table reads before the full dispatch.
| arg | type | description |
|---|---|---|
| obj | any | |
| key | string |
iget(obj: any, idx: number) → any
Integer-indexed read: strings index characters, arrays hit the dense fast path, everything else routes through the string-key member read.
| arg | type | description |
|---|---|---|
| obj | any | |
| idx | number |
getc(obj: any, key: any) → any
Computed read: a numeric key takes the integer path, otherwise the key is coerced to a property key string.
| arg | type | description |
|---|---|---|
| obj | any | |
| key | any |
set(obj: any, key: string, v: any) → any
| arg | type | description |
|---|---|---|
| obj | any | |
| key | string | |
| v | any |
iset(obj: any, idx: number, v: any) → any
| arg | type | description |
|---|---|---|
| obj | any | |
| idx | number | |
| v | any |
setc(obj: any, key: any, v: any) → any
| arg | type | description |
|---|---|---|
| obj | any | |
| key | any | |
| v | any |
gget(name: string) → any
Unresolved-name access, mirroring the interpreter's lookupVar/assignVar: script-scope cells shadow the global object; a read of a name that is in neither is a ReferenceError; an assignment to one creates a global-object property.
| arg | type | description |
|---|---|---|
| name | string |
gset(name: string, v: any) → any
| arg | type | description |
|---|---|---|
| name | string | |
| v | any |
gtypeof(name: string) → string
`typeof x` on an undeclared global is "undefined", not a ReferenceError.
| arg | type | description |
|---|---|---|
| name | string |
constErr( ) → void
Script-scope declaration: `var` keeps an existing cell (redeclaration is a no-op); lexical kinds bind a fresh cell.
gdeclare(name: string, kind: string) → void
| arg | type | description |
|---|---|---|
| name | string | |
| kind | string |
ginit(name: string, v: any) → any
Declaration initializer: writes the cell directly, bypassing the const assignment guard (a const's own initialization is not an assignment).
| arg | type | description |
|---|---|---|
| name | string | |
| v | any |
budget(n: number?, limit: number?, yieldMode: boolean?) → any
| arg | type | description |
|---|---|---|
| n | number? | |
| limit | number? | |
| yieldMode | boolean? |
budgetRestore(prev: any) → void
| arg | type | description |
|---|---|---|
| prev | any |
budgetState( ) → any
The calling coroutine's budget state, seeding the plain guard when absent — loop back-edges fetch it once and decrement inline.
tickPark(st: any) → void
Slow path for an exhausted inline-decremented budget: raise, or park and refill under yield mode.
| arg | type | description |
|---|---|---|
| st | any |
tick(n: number) → void
| arg | type | description |
|---|---|---|
| n | number |
isCallable(fn: any) → boolean
| arg | type | description |
|---|---|---|
| fn | any |
call(fn: any, thisVal: any, args: { any }) → any
| arg | type | description |
|---|---|---|
| fn | any | |
| thisVal | any | |
| args | { any } |
mcall(obj: any, key: string, args: { any }) → any
| arg | type | description |
|---|---|---|
| obj | any | |
| key | string | |
| args | { any } |
mcallc(obj: any, key: any, args: { any }) → any
| arg | type | description |
|---|---|---|
| obj | any | |
| key | any | |
| args | { any } |
callv(fn: any, thisVal: any, ...: ?) → any
Vararg calling convention: arguments travel as Luau varargs instead of a per-call table. A compiled callee exposes its raw closure on `__direct` (signature (this, p1..pK)) and the varargs pass straight through — Luau drops extras and leaves missing params nil, which the callee head converts to undefined. Only a non-direct callee (host, interpreter, generator/async wrapper) materializes the args table.
| arg | type | description |
|---|---|---|
| fn | any | |
| thisVal | any | |
| ... | ? |
mcallv(obj: any, key: string, ...: ?) → any
| arg | type | description |
|---|---|---|
| obj | any | |
| key | string | |
| ... | ? |
mcallcv(obj: any, key: any, ...: ?) → any
| arg | type | description |
|---|---|---|
| obj | any | |
| key | any | |
| ... | ? |
newv(ctor: any, ...: ?) → any
Vararg `new`: a class constructor carrying `__directCtor` constructs in one call with no args table; everything else funnels into rtc.new (the table materializes only on that fallback).
| arg | type | description |
|---|---|---|
| ctor | any | |
| ... | ? |
new(ctor: any, args: { any }) → any
| arg | type | description |
|---|---|---|
| ctor | any | |
| args | { any } |
func(impl: (any, { any }) → void
Wrap a compiled Luau closure (signature impl(thisVal, args)) as a JS function object living in the realm. A call arriving on a thread with no budget state (a timer or host callback outside any run) seeds a plain guard: a runaway callback raises RangeError like the interpreter's synchronous bodies. Parking belongs exclusively to loop-spawned async bodies — a synchronous callback runs inline on the thread that pumps the timers, and parking there would suspend the pump its own resume depends on.
| arg | type | description |
|---|---|---|
| impl | (any, { any } |
call(_rt: any, thisVal: any, argList: { any }) → void
| arg | type | description |
|---|---|---|
| _rt | any | |
| thisVal | any | |
| argList | { any } |
funcD(direct: any, name: string, length: number, nparams: number) → any
Wrap a direct-convention compiled closure (signature (this, p1..pK)). Compiled call sites dispatch `__direct` with varargs — no args table, no budget check: direct dispatch is only reachable from inside compiled code, whose thread was seeded at its entry boundary. The `call` slot is that boundary: host, interpreter, stdlib, and timer callers arrive here with an args table, get the budget seed, and the list unpacks into the direct closure. `nparams` is the declared parameter count: the unpack width. A direct closure can observe at most its declared parameters (no `arguments`, no rest), and a fixed width stays correct when a caller-built list carries nil holes for skipped arguments (`#` would truncate there).
| arg | type | description |
|---|---|---|
| direct | any | |
| name | string | |
| length | number | |
| nparams | number |
call(_rt: any, thisVal: any, argList: { any }) → void
| arg | type | description |
|---|---|---|
| _rt | any | |
| thisVal | any | |
| argList | { any } |
arg(args: { any }, i: number) → any
| arg | type | description |
|---|---|---|
| args | { any } | |
| i | number |
arr(elems: { any }) → any
| arg | type | description |
|---|---|---|
| elems | { any } |
arrFin(arr: any, n: number) → void
Array-literal length finalizer: trailing holes extend the length past the last written index without creating properties.
| arg | type | description |
|---|---|---|
| arr | any | |
| n | number |
obj( ) → any
objLit(vals: any, keyList: { string }) → any
A plain object literal in one construction: `vals` becomes the props table directly and `keyList` the insertion-ordered keys (duplicates already collapsed at compile time). A value that is undefined simply has no vals entry while its key stays listed — present-with-undefined.
| arg | type | description |
|---|---|---|
| vals | any | |
| keyList | { string } |
throw(v: any) → void
| arg | type | description |
|---|---|---|
| v | any |
isJsThrow(e: any) → void
| arg | type | description |
|---|---|---|
| e | any |
spread(v: any) → void
Expand an iterable into a Luau array of JS values (array spread, call spread, and array destructuring share this).
| arg | type | description |
|---|---|---|
| v | any |
dguard(v: any) → void
Destructuring support, mirroring the interpreter's bindPattern. A nullish destructuring source is a TypeError.
| arg | type | description |
|---|---|---|
| v | any |
relem(source: any, index: number) → any
One element of a destructured source: array/object by index, string by character, anything else undefined.
| arg | type | description |
|---|---|---|
| source | any | |
| index | number |
dget(source: any, key: string) → any
One property of a destructured source (string keys resolve through the member read so string sources get their prototype methods).
| arg | type | description |
|---|---|---|
| source | any | |
| key | string |
restArr(v: any, start: number) → any
Array-rest: the elements of `v` from index `start` on, as a new array.
| arg | type | description |
|---|---|---|
| v | any | |
| start | number |
restObj(v: any, consumed: { [string]: boolean }) → any
Object-rest: the enumerable own keys of `v` not in `consumed`.
| arg | type | description |
|---|---|---|
| v | any | |
| consumed | { [string]: boolean } |
restArgs(args: { any }, start: number) → any
Rest parameter: the tail of the call's argument list from `start` (1-based).
| arg | type | description |
|---|---|---|
| args | { any } | |
| start | number |
isCallableObj(v: any) → boolean
| arg | type | description |
|---|---|---|
| v | any |
classCtx(name: string, superVal: any) → any
Validate the heritage clause and build the class context: prototype object, super constructor, derived flag.
| arg | type | description |
|---|---|---|
| name | string | |
| superVal | any |
runFields(ctx: any, instance: any) → void
Run the instance field initializers against a revealed instance.
| arg | type | description |
|---|---|---|
| ctx | any | |
| instance | any |
classBuild(ctx: any, ctorImpl: any, ctorLen: number, fields: any, directN: number?) → any
Build the constructor function object and wire the prototype chain. `ctorImpl(this, args)` is the compiled constructor body (nil for the default constructor); in a derived class it receives rtc.TDZ as `this` and must reveal it via super(). `directN` marks a positional-convention constructor body: the impl's signature is (this, p1..pN) instead of (this, argList). Only base (non-derived) constructors take it; classBuild then exposes `__directCtor` so rtc.newv constructs with zero intermediate tables.
| arg | type | description |
|---|---|---|
| ctx | any | |
| ctorImpl | any | |
| ctorLen | number | |
| fields | any | |
| directN | number? |
call(_rt2: any, _thisVal: any, _args: { any }) → void
| arg | type | description |
|---|---|---|
| _rt2 | any | |
| _thisVal | any | |
| _args | { any } |
construct(_rt2: any, args: { any }, _newTarget: any) → void
| arg | type | description |
|---|---|---|
| _rt2 | any | |
| args | { any } | |
| _newTarget | any |
superNew(ctx: any, args: { any }) → any
super(...) inside a derived constructor: construct through the super constructor, adopt the instance under this class's prototype, run the field initializers, and reveal it as `this`.
| arg | type | description |
|---|---|---|
| ctx | any | |
| args | { any } |
superGet(ctx: any, static: boolean, key: string) → any
super.<key> read: through the home object's parent with the current `this` untouched. Instance members resolve through the prototype's parent; static members through the super constructor.
| arg | type | description |
|---|---|---|
| ctx | any | |
| static | boolean | |
| key | string |
superCall(ctx: any, static: boolean, key: string, thisVal: any, args: { any }) → any
| arg | type | description |
|---|---|---|
| ctx | any | |
| static | boolean | |
| key | string | |
| thisVal | any | |
| args | { any } |
thisChk(v: any) → any
Guard a `this` read in a derived constructor before super() has run.
| arg | type | description |
|---|---|---|
| v | any |
ctorRet(v: any, thisVal: any) → any
An explicit `return v` from a derived constructor: an object result overrides; anything else yields the revealed `this`.
| arg | type | description |
|---|---|---|
| v | any | |
| thisVal | any |
installAccessor(target: any, key: string, half: string, fn: any) → void
Install one accessor half (a JS function) onto a target, merging with an existing accessor entry for the other half.
| arg | type | description |
|---|---|---|
| target | any | |
| key | string | |
| half | string | |
| fn | any |
ctorFastW(instance: any) → boolean
Constructor write fast-path eligibility, sampled once at constructor entry: true when no object on the instance's prototype chain owns an accessor. Field writes emitted under this flag also compare rt.accGen against its entry sample, so any accessor install or prototype mutation mid-body reroutes the remaining writes through rt.set.
| arg | type | description |
|---|---|---|
| instance | any |
classMethod(ctx: any, static: boolean, key: string, fn: any) → void
| arg | type | description |
|---|---|---|
| ctx | any | |
| static | boolean | |
| key | string | |
| fn | any |
classAcc(ctx: any, static: boolean, key: string, half: string, fn: any) → void
| arg | type | description |
|---|---|---|
| ctx | any | |
| static | boolean | |
| key | string | |
| half | string | |
| fn | any |
classSField(ctx: any, key: string, init: any) → void
| arg | type | description |
|---|---|---|
| ctx | any | |
| key | string | |
| init | any |
objAcc(obj: any, key: string, half: string, fn: any) → void
Object-literal accessor property.
| arg | type | description |
|---|---|---|
| obj | any | |
| key | string | |
| half | string | |
| fn | any |
await(v: any) → any
Async functions and generators. Compiled bodies are ordinary Luau, so they suspend with coroutine.yield exactly like the interpreter's async machinery, sharing the same VM loop and promise module. Await: resolve the operand to a promise (one microtask tick even for non-thenables, per spec) and suspend this coroutine until settlement.
| arg | type | description |
|---|---|---|
| v | any |
asyncFunc(impl: (any, { any }) → void
An async call runs its body in a loop-spawned coroutine and returns a result promise: the synchronous prefix runs during the call, the body suspends at its first await, a JS throw becomes the rejection, and a returned value or thenable is adopted.
| arg | type | description |
|---|---|---|
| impl | (any, { any } |
call(_rt2: any, thisVal: any, args: { any }) → void
| arg | type | description |
|---|---|---|
| _rt2 | any | |
| thisVal | any | |
| args | { any } |
yield(v: any) → any
yield / yield*: runs on the generator's coroutine; the value sent into the matching next() becomes the expression's result.
| arg | type | description |
|---|---|---|
| v | any |
yieldStar(v: any) → any
| arg | type | description |
|---|---|---|
| v | any |
genFunc(impl: (any, { any }) → void
A generator call builds a suspended body on its own coroutine; next() resumes it to the next yield. The generator object carries next/return and registers with the realm's iterator protocol.
| arg | type | description |
|---|---|---|
| impl | (any, { any } |
call(_rt2: any, thisVal: any, args: { any }) → void
| arg | type | description |
|---|---|---|
| _rt2 | any | |
| thisVal | any | |
| args | { any } |
step(sendVal: any) → void
| arg | type | description |
|---|---|---|
| sendVal | any |
call(_rt3: any, _this3: any, nargs: { any }) → void
| arg | type | description |
|---|---|---|
| _rt3 | any | |
| _this3 | any | |
| nargs | { any } |
call(_rt3: any, _this3: any, nargs: { any }) → void
| arg | type | description |
|---|---|---|
| _rt3 | any | |
| _this3 | any | |
| nargs | { any } |
importCall(spec: any) → any
import() expression: resolve through the realm's dynamic-import hook and hand back a settled promise of the namespace (module bodies evaluate synchronously, so the promise is born resolved or rejected).
| arg | type | description |
|---|---|---|
| spec | any |
importMeta(url: string) → any
import.meta: an object carrying the module's url.
| arg | type | description |
|---|---|---|
| url | string |
regex(pattern: string, flags: string) → any
A regex literal constructs a fresh RegExp through the realm's RegExp constructor, so each evaluation yields an independent object with its own lastIndex.
| arg | type | description |
|---|---|---|
| pattern | string | |
| flags | string |
iter(v: any) → void
for-of driver: returns a step function yielding (hasValue, value). Arrays iterate by live index (length re-read each step), mirroring the interpreter's array-first iteration; strings yield characters; anything else consults the realm's iterator registry.
| arg | type | description |
|---|---|---|
| v | any |
keys(v: any) → void
for-in driver: enumerable keys of the object and its prototype chain.
| arg | type | description |
|---|---|---|
| v | any |
jsName(name: string) → string
| arg | type | description |
|---|---|---|
| name | string |
propIsPlain(name: string) → boolean
| arg | type | description |
|---|---|---|
| name | string |
bail(node: any, why: string?) → never
| arg | type | description |
|---|---|---|
| node | any | |
| why | string? |
isSimpleNode(node: any) → boolean
A node whose evaluation is order-independent and side-effect-free, safe to inline into an operand position without a temp.
| arg | type | description |
|---|---|---|
| node | any |
newGen( ) → any
emit(self: any, line: string) → void
| arg | type | description |
|---|---|---|
| self | any | |
| line | string |
newTemp(self: any) → string
Transient temp from the function's register pool. Pool slots are declared once at the function head and reassigned; the counter resets at each statement, so a statement's operand temps recycle the same few registers instead of exhausting Luau's 200-local limit on large bodies. A pool value never outlives its statement's own emission.
| arg | type | description |
|---|---|---|
| self | any |
persistTemp(self: any) → string
Construct-persistent temp: a dedicated local whose value survives across nested statement execution (loop step functions, switch state, try results). Never recycled.
| arg | type | description |
|---|---|---|
| self | any |
markPoolSlot(self: any) → void
Record where the pool declaration belongs (after the function preamble); `finishPool` inserts it once the body is generated and the high-water mark is known.
| arg | type | description |
|---|---|---|
| self | any |
finishPool(self: any) → void
| arg | type | description |
|---|---|---|
| self | any |
pushScope(self: any) → void
| arg | type | description |
|---|---|---|
| self | any |
popScope(self: any) → void
| arg | type | description |
|---|---|---|
| self | any |
declare(self: any, name: string, kind: string?) → void
`kind` distinguishes const bindings (assignment traps) from mutable ones.
| arg | type | description |
|---|---|---|
| self | any | |
| name | string | |
| kind | string? |
isConstLocal(self: any, name: string) → boolean
Is the nearest declaration of `name` a const binding?
| arg | type | description |
|---|---|---|
| self | any | |
| name | string |
isLocal(self: any, name: string) → boolean
| arg | type | description |
|---|---|---|
| self | any | |
| name | string |
currentScopeHas(self: any, name: string) → boolean
Declared in the innermost scope specifically — lexical declarations shadow an outer binding by emitting a fresh Luau local, so the check that guards redeclaration must not look past the current scope.
| arg | type | description |
|---|---|---|
| self | any | |
| name | string |
scanPatternNames(pat: any, out: { string }) → void
Hoisting scan: collect var + function-declaration names reachable in a function/program body without descending into nested functions.
| arg | type | description |
|---|---|---|
| pat | any | |
| out | { string } |
scanVarsList(body: { any }, out: { string }) → void
| arg | type | description |
|---|---|---|
| body | { any } | |
| out | { string } |
scanVarsStmt(stmt: any, out: { string }) → void
| arg | type | description |
|---|---|---|
| stmt | any | |
| out | { string } |
numericExpr(node: any, numVars: { [string]: boolean }) → boolean
| arg | type | description |
|---|---|---|
| node | any | |
| numVars | { [string]: boolean } |
inferTypes(body: { any }) → void
Scan one function body (not descending into nested function bodies except to spot assignments that capture and mutate outer names) and return (numVars, arrVars): the names provably number- and array-typed throughout.
| arg | type | description |
|---|---|---|
| body | { any } |
demote(name: string) → void
| arg | type | description |
|---|---|---|
| name | string |
recordAssign(name: string, rhs: any) → void
| arg | type | description |
|---|---|---|
| name | string | |
| rhs | any |
scanNestedFn(node: any) → void
Inside a nested function only mutations of outer names matter.
| arg | type | description |
|---|---|---|
| node | any |
scanExpr(node: any) → void
| arg | type | description |
|---|---|---|
| node | any |
scanStmt(node: any) → void
| arg | type | description |
|---|---|---|
| node | any |
referencesArguments(node: any) → boolean
Does a function body reference `arguments`? Arrows inherit the outer binding, so the scan descends into arrow functions but not into nested non-arrow functions (which bind their own).
| arg | type | description |
|---|---|---|
| node | any |
directEligible(node: any) → boolean
Shared direct-convention parameter eligibility: no rest param, no duplicate plain names, a bounded count, and no `arguments` reference in a non-arrow body. Used by function emission and by class emission when deciding whether a constructor body can take the positional convention.
| arg | type | description |
|---|---|---|
| node | any |
scanLexical(body: { any }, out: { string }) → void
Direct block-level lexical declarations (let/const/class/function names) that become locals scoped to this block.
| arg | type | description |
|---|---|---|
| body | { any } | |
| out | { string } |
expr(self: any, node: any) → string
Expressions Compile a node to a Luau expression string, emitting any needed preceding statements. Guarantees left-to-right evaluation for callers that sequence operands through `operand`.
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
operand(self: any, node: any) → string
Evaluate a node into a simple operand (inline for trivial nodes, otherwise a hoisted temp) so an ordered operand list keeps left-to-right evaluation.
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
arrayLit(self: any, node: any) → string
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
objectLit(self: any, node: any) → string
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
rtNumKey(n: number) → string
| arg | type | description |
|---|---|---|
| n | number |
isSuperNode(node: any) → boolean
| arg | type | description |
|---|---|---|
| node | any |
emitSetNamed(self: any, obj: string, name: string, v: string) → void
Emit a named member write: an overwrite of an existing own data property lands directly in the props slot with zero calls (own presence shadows any proto setter); everything else — new keys, accessors, frozen/proxy/array/ typed-array receivers, primitives — takes the full runtime write.
| arg | type | description |
|---|---|---|
| self | any | |
| obj | string | |
| name | string | |
| v | string |
memberGet(self: any, node: any) → string
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
argList(self: any, args: { any }) → string
Build a Luau table-constructor of the argument values, expanding spreads.
| arg | type | description |
|---|---|---|
| self | any | |
| args | { any } |
argParts(self: any, args: { any }) → string
Arguments as a bare operand list for the vararg calling convention. Returns nil when the call needs the table form (spread, or a list big enough to pressure the register file).
| arg | type | description |
|---|---|---|
| self | any | |
| args | { any } |
call(self: any, node: any) → string
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
isNum(self: any, node: any) → boolean
Is this expression provably number-typed under the current function's inferred locals?
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
isArr(self: any, node: any) → boolean
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
boolTest(self: any, node: any) → string
Compile a test-position expression to a Luau boolean: comparisons and logical not are already boolean; everything else goes through rtc.truthy.
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
binary(self: any, node: any) → string
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
numGuard( ) → string
operand() results are re-read-safe (literals, locals, temps; globals hoist), so the guard can reference them twice. A side that is a number literal or a proven-number expression needs no runtime check.
emitArmInto(self: any, node: any, dest: string) → void
Emit an arm's value into an existing destination temp. A Cond or Logical in arm position threads the destination down instead of allocating its own temp, so right-nested ternary/guard chains cost one register per chain. The arm's scratch temps die once the result lands, so the counter rewinds.
| arg | type | description |
|---|---|---|
| self | any | |
| node | any | |
| dest | string |
logicalInto(self: any, node: any, dest: string) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any | |
| dest | string |
condInto(self: any, node: any, dest: string) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any | |
| dest | string |
logical(self: any, node: any) → string
Short-circuit operators compile to a temp filled by if-statements, so the right operand is only evaluated when needed and no closure is allocated.
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
cond(self: any, node: any) → string
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
unary(self: any, node: any) → string
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
assign(self: any, node: any) → string
Assignment as an expression: returns the assigned value.
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
patBind(self: any, pat: any, srcT: string, mode: string) → void
Recursive destructuring emission. `srcT` is a Luau temp holding the current source value; `mode` picks the leaf binding form: "local" — declare (or assign an already-declared) Luau local "gvar" — program-level var: assign the hoisted script-scope cell "ginit" — program-level let/const: initialize the script-scope cell "assign" — plain assignment (locals, globals, and member targets)
| arg | type | description |
|---|---|---|
| self | any | |
| pat | any | |
| srcT | string | |
| mode | string |
resolveLocal(self: any, name: string) → void
Resolve a name to its storage: the scope index it binds in (nearest-first) and its kind, or nil when unbound (a global reference).
| arg | type | description |
|---|---|---|
| self | any | |
| name | string |
varRef(self: any, name: string) → string
The Luau expression for a bound name. A module compiled in spill mode keeps its module-scope bindings in the `_L` table (one upvalue for every nested function, no per-binding local/upvalue registers); everything else is a plain local.
| arg | type | description |
|---|---|---|
| self | any | |
| name | string |
identRef(self: any, name: string) → string
| arg | type | description |
|---|---|---|
| self | any | |
| name | string |
emitDenseWrite(self: any, obj: string, key: string, v: string) → void
Inline dense-array element write for a proven array binding and numeric index: overwrites hit the slot, appends push the dense boundary and bump the length property (always present on the key list from newArray on); gaps and non-integer indices take the runtime write.
| arg | type | description |
|---|---|---|
| self | any | |
| obj | string | |
| key | string | |
| v | string |
emitAssignIdent(self: any, name: string, valueExpr: string) → void
| arg | type | description |
|---|---|---|
| self | any | |
| name | string | |
| valueExpr | string |
update(self: any, node: any, asExpr: boolean) → string
++/-- : returns old value (postfix) or new value (prefix).
| arg | type | description |
|---|---|---|
| self | any | |
| node | any | |
| asExpr | boolean |
template(self: any, node: any) → string
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
funcExpr(self: any, node: any, opts: any?) → string
opts.classCtx = { ctxT, static } enables super resolution in the body; opts.ctor = { ctxT, derived } marks a constructor body (this-TDZ guards, return-through-ctorRet, super() revealing `this`); opts.raw emits a plain Luau closure instead of a realm function object (constructor impls).
| arg | type | description |
|---|---|---|
| self | any | |
| node | any | |
| opts | any? |
classExpr(self: any, node: any) → string
Compile a class (declaration or expression) to an IIFE producing the constructor. Member bodies compile as realm functions with the class context bound for super resolution; the constructor compiles as a raw closure invoked by the runtime's construct path.
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
memberKeyExpr(member: any) → string
| arg | type | description |
|---|---|---|
| member | any |
fieldInit(valueNode: any, static: boolean) → string
A field initializer compiles to a plain closure over `this`, with the class context available for super and the class-name binding captured.
| arg | type | description |
|---|---|---|
| valueNode | any | |
| static | boolean |
stmt(self: any, node: any) → void
Statements
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
varDecl(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
funcDecl(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
exprStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
discardExpr(self: any, e: any) → void
Evaluate an expression for effect only. A call compiles to a bare Luau call statement; anything else assigns a recycled pool slot (a fresh `local _` per statement would exhaust the register file on large bodies).
| arg | type | description |
|---|---|---|
| self | any | |
| e | any |
assignEffect(self: any, node: any) → void
Assignment executed for side effect (statement position).
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
ifStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
countNodes(node: any) → number
Approximate interpreter step count of a subtree: one step per AST node, excluding nested function bodies (their steps are consumed when called). Loop back-edges tick this amount so the step budget trips at roughly the same script size on both tiers.
| arg | type | description |
|---|---|---|
| node | any |
hasDirectContinue(node: any) → boolean
Does this statement subtree contain a Continue that would bind to the loop being compiled (not to a nested loop, and not inside a nested function)?
| arg | type | description |
|---|---|---|
| node | any |
noComp(self: any, fn: () → void
Loop bodies do not feed the program completion value (the interpreter's While/DoWhile/For all complete with no value); clear the accumulator while compiling one.
| arg | type | description |
|---|---|---|
| self | any | |
| fn | ( |
emitLoopBudget(self: any, cost: number) → string
The loop's budget back-edge: the coroutine's state is fetched once ahead of the loop, and each iteration decrements inline with a rare slow call.
| arg | type | description |
|---|---|---|
| self | any | |
| cost | number |
whileStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
loopBodyWithTail(self: any, body: any, emitTail: () → void
A loop whose Luau body has trailing statements after the JS body (a for update, a do-while test) cannot use a bare Luau `continue` — it would skip them. When the JS body continues directly, wrap it in a one-shot repeat so continue compiles to breaking the wrapper, landing on the trailing code.
| arg | type | description |
|---|---|---|
| self | any | |
| body | any | |
| emitTail | ( |
doWhileStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
forStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
stmtForUpdate(self: any, expr: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| expr | any |
forOfStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
forInStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
bindForHead(self: any, left: any, valueTmp: string) → void
Bind the loop variable of a for-of/for-in head to the Luau temp holding the current value/key.
| arg | type | description |
|---|---|---|
| self | any | |
| left | any | |
| valueTmp | string |
tryStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
switchStmt(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
stmtOrBlock(self: any, node: any) → void
| arg | type | description |
|---|---|---|
| self | any | |
| node | any |
blockBody(self: any, body: { any }) → boolean
A block body: pre-declare its lexical bindings, initialize function declarations (hoisted: callable before their source position), then compile the remaining statements. Compilation stops after a Luau control terminator (return/break/continue) — Luau forbids statements after one in a block, and the dropped JS statements are unreachable. Returns true when the emitted block ended in a terminator.
| arg | type | description |
|---|---|---|
| self | any | |
| body | { any } |
block(self: any, body: { any }) → boolean
| arg | type | description |
|---|---|---|
| self | any | |
| body | { any } |
compile(source: string, sourceName: string?) → void
Entry points
| arg | type | description |
|---|---|---|
| source | string | |
| sourceName | string? |
collectDeclNames(decl: any, out: { string }) → void
Collect the bound names of a declaration statement (VarDecl patterns, function/class ids) — the names an `export <decl>` exposes.
| arg | type | description |
|---|---|---|
| decl | any | |
| out | { string } |
compileModule(source: string, id: string, importNames: { string }, parsedAst: any?) → void
Compile a module body to a Luau chunk. The chunk takes (rtc, imports) — `imports` maps local import names to their resolved values — and returns { exports = { exportedName = value }, hasDefault, default }: the same completion snapshot the interpreter's evalModuleBody builds. Module-level bindings are chunk locals (module scope dies with the module), so the function-level type inference applies to module code. `parsedAst` (when the caller already parsed the module) skips the re-parse.
| arg | type | description |
|---|---|---|
| source | string | |
| id | string | |
| importNames | { string } | |
| parsedAst | any? |
loadChunk(luau: string, name: string) → void
| arg | type | description |
|---|---|---|
| luau | string | |
| name | string |
loadModule(source: string, id: string, importNames: { string }, parsedAst: any?) → void
Compile and load a module body; the chunk takes (rtc, imports).
| arg | type | description |
|---|---|---|
| source | string | |
| id | string | |
| importNames | { string } | |
| parsedAst | any? |
load(source: string, sourceName: string?) → void
Compile and load: returns a chunk taking the runtime companion `rtc`.
| arg | type | description |
|---|---|---|
| source | string | |
| sourceName | string? |
runOn(rt: any, globalObj: any, source: string, sourceName: string?) → any
Convenience: compile `source` and run it against realm `rt` + `globalObj`, returning the JS completion value (not marshaled).
| arg | type | description |
|---|---|---|
| rt | any | |
| globalObj | any | |
| source | string | |
| sourceName | string? |
Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Showing up to 3 levels deep. Open a part to see what it holds further down.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
These counts cover only the parts this page shows. Problems on the parts it leaves out are not counted here.
Inside
1 problem across 1 itemagent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.