UNPKG

wasmoon

Version:

A real lua VM with JS bindings made with webassembly

1,081 lines (1,067 loc) 152 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.wasmoon = {})); })(this, (function (exports) { 'use strict'; var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; exports.LuaReturn = void 0; (function (LuaReturn) { LuaReturn[LuaReturn["Ok"] = 0] = "Ok"; LuaReturn[LuaReturn["Yield"] = 1] = "Yield"; LuaReturn[LuaReturn["ErrorRun"] = 2] = "ErrorRun"; LuaReturn[LuaReturn["ErrorSyntax"] = 3] = "ErrorSyntax"; LuaReturn[LuaReturn["ErrorMem"] = 4] = "ErrorMem"; LuaReturn[LuaReturn["ErrorErr"] = 5] = "ErrorErr"; LuaReturn[LuaReturn["ErrorFile"] = 6] = "ErrorFile"; })(exports.LuaReturn || (exports.LuaReturn = {})); const PointerSize = 4; const LUA_MULTRET = -1; const LUAI_MAXSTACK = 1000000; const LUA_REGISTRYINDEX = -LUAI_MAXSTACK - 1000; exports.LuaType = void 0; (function (LuaType) { LuaType[LuaType["None"] = -1] = "None"; LuaType[LuaType["Nil"] = 0] = "Nil"; LuaType[LuaType["Boolean"] = 1] = "Boolean"; LuaType[LuaType["LightUserdata"] = 2] = "LightUserdata"; LuaType[LuaType["Number"] = 3] = "Number"; LuaType[LuaType["String"] = 4] = "String"; LuaType[LuaType["Table"] = 5] = "Table"; LuaType[LuaType["Function"] = 6] = "Function"; LuaType[LuaType["Userdata"] = 7] = "Userdata"; LuaType[LuaType["Thread"] = 8] = "Thread"; })(exports.LuaType || (exports.LuaType = {})); exports.LuaEventCodes = void 0; (function (LuaEventCodes) { LuaEventCodes[LuaEventCodes["Call"] = 0] = "Call"; LuaEventCodes[LuaEventCodes["Ret"] = 1] = "Ret"; LuaEventCodes[LuaEventCodes["Line"] = 2] = "Line"; LuaEventCodes[LuaEventCodes["Count"] = 3] = "Count"; LuaEventCodes[LuaEventCodes["TailCall"] = 4] = "TailCall"; })(exports.LuaEventCodes || (exports.LuaEventCodes = {})); exports.LuaEventMasks = void 0; (function (LuaEventMasks) { LuaEventMasks[LuaEventMasks["Call"] = 1] = "Call"; LuaEventMasks[LuaEventMasks["Ret"] = 2] = "Ret"; LuaEventMasks[LuaEventMasks["Line"] = 4] = "Line"; LuaEventMasks[LuaEventMasks["Count"] = 8] = "Count"; })(exports.LuaEventMasks || (exports.LuaEventMasks = {})); exports.LuaLibraries = void 0; (function (LuaLibraries) { LuaLibraries["Base"] = "_G"; LuaLibraries["Coroutine"] = "coroutine"; LuaLibraries["Table"] = "table"; LuaLibraries["IO"] = "io"; LuaLibraries["OS"] = "os"; LuaLibraries["String"] = "string"; LuaLibraries["UTF8"] = "utf8"; LuaLibraries["Math"] = "math"; LuaLibraries["Debug"] = "debug"; LuaLibraries["Package"] = "package"; })(exports.LuaLibraries || (exports.LuaLibraries = {})); class LuaTimeoutError extends Error { } class Decoration { constructor(target, options) { this.target = target; this.options = options; } } function decorate(target, options) { return new Decoration(target, options); } class Pointer extends Number { } class MultiReturn extends Array { } const INSTRUCTION_HOOK_COUNT = 1000; class Thread { constructor(lua, typeExtensions, address, parent) { this.closed = false; this.lua = lua; this.typeExtensions = typeExtensions; this.address = address; this.parent = parent; } newThread() { const address = this.lua.lua_newthread(this.address); if (!address) { throw new Error('lua_newthread returned a null pointer'); } return new Thread(this.lua, this.typeExtensions, address, this.parent || this); } resetThread() { this.assertOk(this.lua.lua_resetthread(this.address)); } loadString(luaCode, name) { const size = this.lua.module.lengthBytesUTF8(luaCode); const pointerSize = size + 1; const bufferPointer = this.lua.module._malloc(pointerSize); try { this.lua.module.stringToUTF8(luaCode, bufferPointer, pointerSize); this.assertOk(this.lua.luaL_loadbufferx(this.address, bufferPointer, size, name !== null && name !== void 0 ? name : bufferPointer, null)); } finally { this.lua.module._free(bufferPointer); } } loadFile(filename) { this.assertOk(this.lua.luaL_loadfilex(this.address, filename, null)); } resume(argCount = 0) { const dataPointer = this.lua.module._malloc(PointerSize); try { this.lua.module.setValue(dataPointer, 0, 'i32'); const luaResult = this.lua.lua_resume(this.address, null, argCount, dataPointer); return { result: luaResult, resultCount: this.lua.module.getValue(dataPointer, 'i32'), }; } finally { this.lua.module._free(dataPointer); } } getTop() { return this.lua.lua_gettop(this.address); } setTop(index) { this.lua.lua_settop(this.address, index); } remove(index) { return this.lua.lua_remove(this.address, index); } setField(index, name, value) { index = this.lua.lua_absindex(this.address, index); this.pushValue(value); this.lua.lua_setfield(this.address, index, name); } async run(argCount = 0, options) { const originalTimeout = this.timeout; try { if ((options === null || options === void 0 ? void 0 : options.timeout) !== undefined) { this.setTimeout(Date.now() + options.timeout); } let resumeResult = this.resume(argCount); while (resumeResult.result === exports.LuaReturn.Yield) { if (this.timeout && Date.now() > this.timeout) { if (resumeResult.resultCount > 0) { this.pop(resumeResult.resultCount); } throw new LuaTimeoutError(`thread timeout exceeded`); } if (resumeResult.resultCount > 0) { const lastValue = this.getValue(-1); this.pop(resumeResult.resultCount); if (lastValue === Promise.resolve(lastValue)) { await lastValue; } else { await new Promise((resolve) => setImmediate(resolve)); } } else { await new Promise((resolve) => setImmediate(resolve)); } resumeResult = this.resume(0); } this.assertOk(resumeResult.result); return this.getStackValues(); } finally { if ((options === null || options === void 0 ? void 0 : options.timeout) !== undefined) { this.setTimeout(originalTimeout); } } } runSync(argCount = 0) { const base = this.getTop() - argCount - 1; this.assertOk(this.lua.lua_pcallk(this.address, argCount, LUA_MULTRET, 0, 0, null)); return this.getStackValues(base); } pop(count = 1) { this.lua.lua_pop(this.address, count); } call(name, ...args) { const type = this.lua.lua_getglobal(this.address, name); if (type !== exports.LuaType.Function) { throw new Error(`A function of type '${type}' was pushed, expected is ${exports.LuaType.Function}`); } for (const arg of args) { this.pushValue(arg); } const base = this.getTop() - args.length - 1; this.lua.lua_callk(this.address, args.length, LUA_MULTRET, 0, null); return this.getStackValues(base); } getStackValues(start = 0) { const returns = this.getTop() - start; const returnValues = new MultiReturn(returns); for (let i = 0; i < returns; i++) { returnValues[i] = this.getValue(start + i + 1); } return returnValues; } stateToThread(L) { var _a; return L === ((_a = this.parent) === null || _a === void 0 ? void 0 : _a.address) ? this.parent : new Thread(this.lua, this.typeExtensions, L, this.parent || this); } pushValue(rawValue, userdata) { const decoratedValue = this.getValueDecorations(rawValue); const target = decoratedValue.target; if (target instanceof Thread) { const isMain = this.lua.lua_pushthread(target.address) === 1; if (!isMain) { this.lua.lua_xmove(target.address, this.address, 1); } return; } const startTop = this.getTop(); switch (typeof target) { case 'undefined': this.lua.lua_pushnil(this.address); break; case 'number': if (Number.isInteger(target)) { this.lua.lua_pushinteger(this.address, BigInt(target)); } else { this.lua.lua_pushnumber(this.address, target); } break; case 'string': this.lua.lua_pushstring(this.address, target); break; case 'boolean': this.lua.lua_pushboolean(this.address, target ? 1 : 0); break; default: if (!this.typeExtensions.find((wrapper) => wrapper.extension.pushValue(this, decoratedValue, userdata))) { throw new Error(`The type '${typeof target}' is not supported by Lua`); } } if (decoratedValue.options.metatable) { this.setMetatable(-1, decoratedValue.options.metatable); } if (this.getTop() !== startTop + 1) { throw new Error(`pushValue expected stack size ${startTop + 1}, got ${this.getTop()}`); } } setMetatable(index, metatable) { index = this.lua.lua_absindex(this.address, index); if (this.lua.lua_getmetatable(this.address, index)) { this.pop(1); const name = this.getMetatableName(index); throw new Error(`data already has associated metatable: ${name || 'unknown name'}`); } this.pushValue(metatable); this.lua.lua_setmetatable(this.address, index); } getMetatableName(index) { const metatableNameType = this.lua.luaL_getmetafield(this.address, index, '__name'); if (metatableNameType === exports.LuaType.Nil) { return undefined; } if (metatableNameType !== exports.LuaType.String) { this.pop(1); return undefined; } const name = this.lua.lua_tolstring(this.address, -1, null); this.pop(1); return name; } getValue(index, inputType, userdata) { index = this.lua.lua_absindex(this.address, index); const type = inputType !== null && inputType !== void 0 ? inputType : this.lua.lua_type(this.address, index); switch (type) { case exports.LuaType.None: return undefined; case exports.LuaType.Nil: return null; case exports.LuaType.Number: return this.lua.lua_tonumberx(this.address, index, null); case exports.LuaType.String: return this.lua.lua_tolstring(this.address, index, null); case exports.LuaType.Boolean: return Boolean(this.lua.lua_toboolean(this.address, index)); case exports.LuaType.Thread: return this.stateToThread(this.lua.lua_tothread(this.address, index)); default: { let metatableName; if (type === exports.LuaType.Table || type === exports.LuaType.Userdata) { metatableName = this.getMetatableName(index); } const typeExtensionWrapper = this.typeExtensions.find((wrapper) => wrapper.extension.isType(this, index, type, metatableName)); if (typeExtensionWrapper) { return typeExtensionWrapper.extension.getValue(this, index, userdata); } console.warn(`The type '${this.lua.lua_typename(this.address, type)}' returned is not supported on JS`); return new Pointer(this.lua.lua_topointer(this.address, index)); } } } close() { if (this.isClosed()) { return; } if (this.hookFunctionPointer) { this.lua.module.removeFunction(this.hookFunctionPointer); } this.closed = true; } setTimeout(timeout) { if (timeout && timeout > 0) { if (!this.hookFunctionPointer) { this.hookFunctionPointer = this.lua.module.addFunction(() => { if (Date.now() > timeout) { this.pushValue(new LuaTimeoutError(`thread timeout exceeded`)); this.lua.lua_error(this.address); } }, 'vii'); } this.lua.lua_sethook(this.address, this.hookFunctionPointer, exports.LuaEventMasks.Count, INSTRUCTION_HOOK_COUNT); this.timeout = timeout; } else if (this.hookFunctionPointer) { this.hookFunctionPointer = undefined; this.timeout = undefined; this.lua.lua_sethook(this.address, null, 0, 0); } } getTimeout() { return this.timeout; } getPointer(index) { return new Pointer(this.lua.lua_topointer(this.address, index)); } isClosed() { var _a; return !this.address || this.closed || Boolean((_a = this.parent) === null || _a === void 0 ? void 0 : _a.isClosed()); } indexToString(index) { const str = this.lua.luaL_tolstring(this.address, index, null); this.pop(); return str; } dumpStack(log = console.log) { const top = this.getTop(); for (let i = 1; i <= top; i++) { const type = this.lua.lua_type(this.address, i); const typename = this.lua.lua_typename(this.address, type); const pointer = this.getPointer(i); const name = this.indexToString(i); const value = this.getValue(i, type); log(i, typename, pointer, name, value); } } assertOk(result) { if (result !== exports.LuaReturn.Ok && result !== exports.LuaReturn.Yield) { const resultString = exports.LuaReturn[result]; const error = new Error(`Lua Error(${resultString}/${result})`); if (this.getTop() > 0) { if (result === exports.LuaReturn.ErrorMem) { error.message = this.lua.lua_tolstring(this.address, -1, null); } else { const luaError = this.getValue(-1); if (luaError instanceof Error) { error.stack = luaError.stack; } error.message = this.indexToString(-1); } } if (result !== exports.LuaReturn.ErrorMem) { try { this.lua.luaL_traceback(this.address, this.address, null, 1); const traceback = this.lua.lua_tolstring(this.address, -1, null); if (traceback.trim() !== 'stack traceback:') { error.message = `${error.message}\n${traceback}`; } this.pop(1); } catch (err) { console.warn('Failed to generate stack trace', err); } } throw error; } } getValueDecorations(value) { return value instanceof Decoration ? value : new Decoration(value, {}); } } class Global extends Thread { constructor(cmodule, shouldTraceAllocations) { if (shouldTraceAllocations) { const memoryStats = { memoryUsed: 0 }; const allocatorFunctionPointer = cmodule.module.addFunction((_userData, pointer, oldSize, newSize) => { if (newSize === 0) { if (pointer) { memoryStats.memoryUsed -= oldSize; cmodule.module._free(pointer); } return 0; } const endMemoryDelta = pointer ? newSize - oldSize : newSize; const endMemory = memoryStats.memoryUsed + endMemoryDelta; if (newSize > oldSize && memoryStats.memoryMax && endMemory > memoryStats.memoryMax) { return 0; } const reallocated = cmodule.module._realloc(pointer, newSize); if (reallocated) { memoryStats.memoryUsed = endMemory; } return reallocated; }, 'iiiii'); const address = cmodule.lua_newstate(allocatorFunctionPointer, null); if (!address) { cmodule.module.removeFunction(allocatorFunctionPointer); throw new Error('lua_newstate returned a null pointer'); } super(cmodule, [], address); this.memoryStats = memoryStats; this.allocatorFunctionPointer = allocatorFunctionPointer; } else { super(cmodule, [], cmodule.luaL_newstate()); } if (this.isClosed()) { throw new Error('Global state could not be created (probably due to lack of memory)'); } } close() { if (this.isClosed()) { return; } super.close(); this.lua.lua_close(this.address); if (this.allocatorFunctionPointer) { this.lua.module.removeFunction(this.allocatorFunctionPointer); } for (const wrapper of this.typeExtensions) { wrapper.extension.close(); } } registerTypeExtension(priority, extension) { this.typeExtensions.push({ extension, priority }); this.typeExtensions.sort((a, b) => b.priority - a.priority); } loadLibrary(library) { switch (library) { case exports.LuaLibraries.Base: this.lua.luaopen_base(this.address); break; case exports.LuaLibraries.Coroutine: this.lua.luaopen_coroutine(this.address); break; case exports.LuaLibraries.Table: this.lua.luaopen_table(this.address); break; case exports.LuaLibraries.IO: this.lua.luaopen_io(this.address); break; case exports.LuaLibraries.OS: this.lua.luaopen_os(this.address); break; case exports.LuaLibraries.String: this.lua.luaopen_string(this.address); break; case exports.LuaLibraries.UTF8: this.lua.luaopen_string(this.address); break; case exports.LuaLibraries.Math: this.lua.luaopen_math(this.address); break; case exports.LuaLibraries.Debug: this.lua.luaopen_debug(this.address); break; case exports.LuaLibraries.Package: this.lua.luaopen_package(this.address); break; } this.lua.lua_setglobal(this.address, library); } get(name) { const type = this.lua.lua_getglobal(this.address, name); const value = this.getValue(-1, type); this.pop(); return value; } set(name, value) { this.pushValue(value); this.lua.lua_setglobal(this.address, name); } getTable(name, callback) { const startStackTop = this.getTop(); const type = this.lua.lua_getglobal(this.address, name); try { if (type !== exports.LuaType.Table) { throw new TypeError(`Unexpected type in ${name}. Expected ${exports.LuaType[exports.LuaType.Table]}. Got ${exports.LuaType[type]}.`); } callback(startStackTop + 1); } finally { if (this.getTop() !== startStackTop + 1) { console.warn(`getTable: expected stack size ${startStackTop} got ${this.getTop()}`); } this.setTop(startStackTop); } } getMemoryUsed() { return this.getMemoryStatsRef().memoryUsed; } getMemoryMax() { return this.getMemoryStatsRef().memoryMax; } setMemoryMax(max) { this.getMemoryStatsRef().memoryMax = max; } getMemoryStatsRef() { if (!this.memoryStats) { throw new Error('Memory allocations is not being traced, please build engine with { traceAllocations: true }'); } return this.memoryStats; } } class LuaTypeExtension { constructor(thread, name) { this.thread = thread; this.name = name; } isType(_thread, _index, type, name) { return type === exports.LuaType.Userdata && name === this.name; } getValue(thread, index, _userdata) { const refUserdata = thread.lua.luaL_testudata(thread.address, index, this.name); if (!refUserdata) { throw new Error(`data does not have the expected metatable: ${this.name}`); } const referencePointer = thread.lua.module.getValue(refUserdata, '*'); return thread.lua.getRef(referencePointer); } pushValue(thread, decoratedValue, _userdata) { const { target } = decoratedValue; const pointer = thread.lua.ref(target); const userDataPointer = thread.lua.lua_newuserdatauv(thread.address, PointerSize, 0); thread.lua.module.setValue(userDataPointer, pointer, '*'); if (exports.LuaType.Nil === thread.lua.luaL_getmetatable(thread.address, this.name)) { thread.pop(2); throw new Error(`metatable not found: ${this.name}`); } thread.lua.lua_setmetatable(thread.address, -2); return true; } } class ErrorTypeExtension extends LuaTypeExtension { constructor(thread, injectObject) { super(thread, 'js_error'); this.gcPointer = thread.lua.module.addFunction((functionStateAddress) => { const userDataPointer = thread.lua.luaL_checkudata(functionStateAddress, 1, this.name); const referencePointer = thread.lua.module.getValue(userDataPointer, '*'); thread.lua.unref(referencePointer); return exports.LuaReturn.Ok; }, 'ii'); if (thread.lua.luaL_newmetatable(thread.address, this.name)) { const metatableIndex = thread.lua.lua_gettop(thread.address); thread.lua.lua_pushstring(thread.address, 'protected metatable'); thread.lua.lua_setfield(thread.address, metatableIndex, '__metatable'); thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0); thread.lua.lua_setfield(thread.address, metatableIndex, '__gc'); thread.pushValue((jsRefError, key) => { if (key === 'message') { return jsRefError.message; } return null; }); thread.lua.lua_setfield(thread.address, metatableIndex, '__index'); thread.pushValue((jsRefError) => { return jsRefError.message; }); thread.lua.lua_setfield(thread.address, metatableIndex, '__tostring'); } thread.lua.lua_pop(thread.address, 1); if (injectObject) { thread.set('Error', { create: (message) => { if (message && typeof message !== 'string') { throw new Error('message must be a string'); } return new Error(message); }, }); } } pushValue(thread, decoration) { if (!(decoration.target instanceof Error)) { return false; } return super.pushValue(thread, decoration); } close() { this.thread.lua.module.removeFunction(this.gcPointer); } } function createTypeExtension$6(thread, injectObject) { return new ErrorTypeExtension(thread, injectObject); } class RawResult { constructor(count) { this.count = count; } } function decorateFunction(target, options) { return new Decoration(target, options); } class FunctionTypeExtension extends LuaTypeExtension { constructor(thread, options) { super(thread, 'js_function'); this.functionRegistry = typeof FinalizationRegistry !== 'undefined' ? new FinalizationRegistry((func) => { if (!this.thread.isClosed()) { this.thread.lua.luaL_unref(this.thread.address, LUA_REGISTRYINDEX, func); } }) : undefined; this.options = options; this.callbackContext = thread.newThread(); this.callbackContextIndex = this.thread.lua.luaL_ref(thread.address, LUA_REGISTRYINDEX); if (!this.functionRegistry) { console.warn('FunctionTypeExtension: FinalizationRegistry not found. Memory leaks likely.'); } this.gcPointer = thread.lua.module.addFunction((calledL) => { thread.lua.luaL_checkudata(calledL, 1, this.name); const userDataPointer = thread.lua.luaL_checkudata(calledL, 1, this.name); const referencePointer = thread.lua.module.getValue(userDataPointer, '*'); thread.lua.unref(referencePointer); return exports.LuaReturn.Ok; }, 'ii'); if (thread.lua.luaL_newmetatable(thread.address, this.name)) { thread.lua.lua_pushstring(thread.address, '__gc'); thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0); thread.lua.lua_settable(thread.address, -3); thread.lua.lua_pushstring(thread.address, '__metatable'); thread.lua.lua_pushstring(thread.address, 'protected metatable'); thread.lua.lua_settable(thread.address, -3); } thread.lua.lua_pop(thread.address, 1); this.functionWrapper = thread.lua.module.addFunction((calledL) => { const calledThread = thread.stateToThread(calledL); const refUserdata = thread.lua.luaL_checkudata(calledL, thread.lua.lua_upvalueindex(1), this.name); const refPointer = thread.lua.module.getValue(refUserdata, '*'); const { target, options } = thread.lua.getRef(refPointer); const argsQuantity = calledThread.getTop(); const args = []; if (options.receiveThread) { args.push(calledThread); } if (options.receiveArgsQuantity) { args.push(argsQuantity); } else { for (let i = 1; i <= argsQuantity; i++) { const value = calledThread.getValue(i); if (i !== 1 || !(options === null || options === void 0 ? void 0 : options.self) || value !== options.self) { args.push(value); } } } try { const result = target.apply(options === null || options === void 0 ? void 0 : options.self, args); if (result === undefined) { return 0; } else if (result instanceof RawResult) { return result.count; } else if (result instanceof MultiReturn) { for (const item of result) { calledThread.pushValue(item); } return result.length; } else { calledThread.pushValue(result); return 1; } } catch (err) { if (err === Infinity) { throw err; } calledThread.pushValue(err); return calledThread.lua.lua_error(calledThread.address); } }, 'ii'); } close() { this.thread.lua.module.removeFunction(this.gcPointer); this.thread.lua.module.removeFunction(this.functionWrapper); this.callbackContext.close(); this.callbackContext.lua.luaL_unref(this.callbackContext.address, LUA_REGISTRYINDEX, this.callbackContextIndex); } isType(_thread, _index, type) { return type === exports.LuaType.Function; } pushValue(thread, decoration) { if (typeof decoration.target !== 'function') { return false; } const pointer = thread.lua.ref(decoration); const userDataPointer = thread.lua.lua_newuserdatauv(thread.address, PointerSize, 0); thread.lua.module.setValue(userDataPointer, pointer, '*'); if (exports.LuaType.Nil === thread.lua.luaL_getmetatable(thread.address, this.name)) { thread.pop(1); thread.lua.unref(pointer); throw new Error(`metatable not found: ${this.name}`); } thread.lua.lua_setmetatable(thread.address, -2); thread.lua.lua_pushcclosure(thread.address, this.functionWrapper, 1); return true; } getValue(thread, index) { var _a; thread.lua.lua_pushvalue(thread.address, index); const func = thread.lua.luaL_ref(thread.address, LUA_REGISTRYINDEX); const jsFunc = (...args) => { var _a; if (this.callbackContext.isClosed()) { console.warn('Tried to call a function after closing lua state'); return; } const callThread = this.callbackContext.newThread(); try { const internalType = callThread.lua.lua_rawgeti(callThread.address, LUA_REGISTRYINDEX, BigInt(func)); if (internalType !== exports.LuaType.Function) { const callMetafieldType = callThread.lua.luaL_getmetafield(callThread.address, -1, '__call'); callThread.pop(); if (callMetafieldType !== exports.LuaType.Function) { throw new Error(`A value of type '${internalType}' was pushed but it is not callable`); } } for (const arg of args) { callThread.pushValue(arg); } if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.functionTimeout) { callThread.setTimeout(Date.now() + this.options.functionTimeout); } const status = callThread.lua.lua_pcallk(callThread.address, args.length, 1, 0, 0, null); if (status === exports.LuaReturn.Yield) { throw new Error('cannot yield in callbacks from javascript'); } callThread.assertOk(status); if (callThread.getTop() > 0) { return callThread.getValue(-1); } return undefined; } finally { callThread.close(); this.callbackContext.pop(); } }; (_a = this.functionRegistry) === null || _a === void 0 ? void 0 : _a.register(jsFunc, func); return jsFunc; } } function createTypeExtension$5(thread, options) { return new FunctionTypeExtension(thread, options); } class NullTypeExtension extends LuaTypeExtension { constructor(thread) { super(thread, 'js_null'); this.gcPointer = thread.lua.module.addFunction((functionStateAddress) => { const userDataPointer = thread.lua.luaL_checkudata(functionStateAddress, 1, this.name); const referencePointer = thread.lua.module.getValue(userDataPointer, '*'); thread.lua.unref(referencePointer); return exports.LuaReturn.Ok; }, 'ii'); if (thread.lua.luaL_newmetatable(thread.address, this.name)) { const metatableIndex = thread.lua.lua_gettop(thread.address); thread.lua.lua_pushstring(thread.address, 'protected metatable'); thread.lua.lua_setfield(thread.address, metatableIndex, '__metatable'); thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0); thread.lua.lua_setfield(thread.address, metatableIndex, '__gc'); thread.pushValue(() => null); thread.lua.lua_setfield(thread.address, metatableIndex, '__index'); thread.pushValue(() => 'null'); thread.lua.lua_setfield(thread.address, metatableIndex, '__tostring'); thread.pushValue((self, other) => self === other); thread.lua.lua_setfield(thread.address, metatableIndex, '__eq'); } thread.lua.lua_pop(thread.address, 1); super.pushValue(thread, new Decoration({}, {})); thread.lua.lua_setglobal(thread.address, 'null'); } getValue(thread, index) { const refUserData = thread.lua.luaL_testudata(thread.address, index, this.name); if (!refUserData) { throw new Error(`data does not have the expected metatable: ${this.name}`); } return null; } pushValue(thread, decoration) { if ((decoration === null || decoration === void 0 ? void 0 : decoration.target) !== null) { return false; } thread.lua.lua_getglobal(thread.address, 'null'); return true; } close() { this.thread.lua.module.removeFunction(this.gcPointer); } } function createTypeExtension$4(thread) { return new NullTypeExtension(thread); } class PromiseTypeExtension extends LuaTypeExtension { constructor(thread, injectObject) { super(thread, 'js_promise'); this.gcPointer = thread.lua.module.addFunction((functionStateAddress) => { const userDataPointer = thread.lua.luaL_checkudata(functionStateAddress, 1, this.name); const referencePointer = thread.lua.module.getValue(userDataPointer, '*'); thread.lua.unref(referencePointer); return exports.LuaReturn.Ok; }, 'ii'); if (thread.lua.luaL_newmetatable(thread.address, this.name)) { const metatableIndex = thread.lua.lua_gettop(thread.address); thread.lua.lua_pushstring(thread.address, 'protected metatable'); thread.lua.lua_setfield(thread.address, metatableIndex, '__metatable'); thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0); thread.lua.lua_setfield(thread.address, metatableIndex, '__gc'); const checkSelf = (self) => { if (Promise.resolve(self) !== self && typeof self.then !== 'function') { throw new Error('promise method called without self instance'); } return true; }; thread.pushValue({ next: (self, ...args) => checkSelf(self) && self.then(...args), catch: (self, ...args) => checkSelf(self) && self.catch(...args), finally: (self, ...args) => checkSelf(self) && self.finally(...args), await: decorateFunction((functionThread, self) => { checkSelf(self); if (functionThread.address === thread.address) { throw new Error('cannot await in the main thread'); } let promiseResult = undefined; const awaitPromise = self .then((res) => { promiseResult = { status: 'fulfilled', value: res }; }) .catch((err) => { promiseResult = { status: 'rejected', value: err }; }); const continuance = this.thread.lua.module.addFunction((continuanceState) => { if (!promiseResult) { return thread.lua.lua_yieldk(functionThread.address, 0, 0, continuance); } this.thread.lua.module.removeFunction(continuance); const continuanceThread = thread.stateToThread(continuanceState); if (promiseResult.status === 'rejected') { continuanceThread.pushValue(promiseResult.value || new Error('promise rejected with no error')); return this.thread.lua.lua_error(continuanceState); } if (promiseResult.value instanceof RawResult) { return promiseResult.value.count; } else if (promiseResult.value instanceof MultiReturn) { for (const arg of promiseResult.value) { continuanceThread.pushValue(arg); } return promiseResult.value.length; } else { continuanceThread.pushValue(promiseResult.value); return 1; } }, 'iiii'); functionThread.pushValue(awaitPromise); return new RawResult(thread.lua.lua_yieldk(functionThread.address, 1, 0, continuance)); }, { receiveThread: true }), }); thread.lua.lua_setfield(thread.address, metatableIndex, '__index'); thread.pushValue((self, other) => self === other); thread.lua.lua_setfield(thread.address, metatableIndex, '__eq'); } thread.lua.lua_pop(thread.address, 1); if (injectObject) { thread.set('Promise', { create: (callback) => new Promise(callback), all: (promiseArray) => { if (!Array.isArray(promiseArray)) { throw new Error('argument must be an array of promises'); } return Promise.all(promiseArray.map((potentialPromise) => Promise.resolve(potentialPromise))); }, resolve: (value) => Promise.resolve(value), }); } } close() { this.thread.lua.module.removeFunction(this.gcPointer); } pushValue(thread, decoration) { if (Promise.resolve(decoration.target) !== decoration.target && typeof decoration.target.then !== 'function') { return false; } return super.pushValue(thread, decoration); } } function createTypeExtension$3(thread, injectObject) { return new PromiseTypeExtension(thread, injectObject); } function decorateProxy(target, options) { return new Decoration(target, options || {}); } class ProxyTypeExtension extends LuaTypeExtension { constructor(thread) { super(thread, 'js_proxy'); this.gcPointer = thread.lua.module.addFunction((functionStateAddress) => { const userDataPointer = thread.lua.luaL_checkudata(functionStateAddress, 1, this.name); const referencePointer = thread.lua.module.getValue(userDataPointer, '*'); thread.lua.unref(referencePointer); return exports.LuaReturn.Ok; }, 'ii'); if (thread.lua.luaL_newmetatable(thread.address, this.name)) { const metatableIndex = thread.lua.lua_gettop(thread.address); thread.lua.lua_pushstring(thread.address, 'protected metatable'); thread.lua.lua_setfield(thread.address, metatableIndex, '__metatable'); thread.lua.lua_pushcclosure(thread.address, this.gcPointer, 0); thread.lua.lua_setfield(thread.address, metatableIndex, '__gc'); thread.pushValue((self, key) => { switch (typeof key) { case 'number': key = key - 1; case 'string': break; default: throw new Error('Only strings or numbers can index js objects'); } const value = self[key]; if (typeof value === 'function') { return decorateFunction(value, { self }); } return value; }); thread.lua.lua_setfield(thread.address, metatableIndex, '__index'); thread.pushValue((self, key, value) => { switch (typeof key) { case 'number': key = key - 1; case 'string': break; default: throw new Error('Only strings or numbers can index js objects'); } self[key] = value; }); thread.lua.lua_setfield(thread.address, metatableIndex, '__newindex'); thread.pushValue((self) => { var _a, _b; return (_b = (_a = self.toString) === null || _a === void 0 ? void 0 : _a.call(self)) !== null && _b !== void 0 ? _b : typeof self; }); thread.lua.lua_setfield(thread.address, metatableIndex, '__tostring'); thread.pushValue((self) => { return self.length || 0; }); thread.lua.lua_setfield(thread.address, metatableIndex, '__len'); thread.pushValue((self) => { const keys = Object.getOwnPropertyNames(self); let i = 0; return MultiReturn.of(() => { const ret = MultiReturn.of(keys[i], self[keys[i]]); i++; return ret; }, self, null); }); thread.lua.lua_setfield(thread.address, metatableIndex, '__pairs'); thread.pushValue((self, other) => { return self === other; }); thread.lua.lua_setfield(thread.address, metatableIndex, '__eq'); thread.pushValue((self, ...args) => { if (args[0] === self) { args.shift(); } return self(...args); }); thread.lua.lua_setfield(thread.address, metatableIndex, '__call'); } thread.lua.lua_pop(thread.address, 1); } isType(_thread, _index, type, name) { return type === exports.LuaType.Userdata && name === this.name; } getValue(thread, index) { const refUserdata = thread.lua.lua_touserdata(thread.address, index); const referencePointer = thread.lua.module.getValue(refUserdata, '*'); return thread.lua.getRef(referencePointer); } pushValue(thread, decoratedValue) { var _a; const { target, options } = decoratedValue; if (options.proxy === undefined) { if (target === null || target === undefined) { return false; } if (typeof target !== 'object') { const isClass = typeof target === 'function' && ((_a = target.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === target && target.toString().startsWith('class '); if (!isClass) { return false; } } if (Promise.resolve(target) === target || typeof target.then === 'function') { return false; } } else if (options.proxy === false) { return false; } if (options.metatable && !(options.metatable instanceof Decoration)) { decoratedValue.options.metatable = decorateProxy(options.metatable, { proxy: false }); return false; } return super.pushValue(thread, decoratedValue); } close() { this.thread.lua.module.removeFunction(this.gcPointer); } } function createTypeExtension$2(thread) { return new ProxyTypeExtension(thread); } class TableTypeExtension extends LuaTypeExtension { constructor(thread) { super(thread, 'js_table'); } close() { } isType(_thread, _index, type) { return type === exports.LuaType.Table; } getValue(thread, index, userdata) { const seenMap = userdata || new Map(); const pointer = thread.lua.lua_topointer(thread.address, index); let table = seenMap.get(pointer); if (!table) { const keys = this.readTableKeys(thread, index); const isSequential = keys.length > 0 && keys.every((key, index) => key === String(index + 1)); table = isSequential ? [] : {}; seenMap.set(pointer, table); this.readTableValues(thread, index, seenMap, table); } return table; } pushValue(thread, { target }, userdata) { if (typeof target !== 'object' || target === null) { return false; } const seenMap = userdata || new Map(); const existingReference = seenMap.get(target); if (existingReference !== undefined) { thread.lua.lua_rawgeti(thread.address, LUA_REGISTRYINDEX, BigInt(existingReference)); return true; } try { const tableIndex = thread.getTop() + 1; const create