|
1 |
| -import { lauxlib, lua, lualib, to_jsstring, to_luastring, interop } from "fengari-web"; |
2 |
| - |
3 |
| -declare var self: any; |
4 |
| - |
5 |
| -function executeLua(luaStr: string): any { |
6 |
| - // clear print buffer and patch lua print |
7 |
| - self.printStream = ""; |
8 |
| - const printToGlobal = ` |
9 |
| -local js = require "js" |
10 |
| -local oldPrint = print |
11 |
| -_G.print = function(...) |
12 |
| - local elements = table.pack(...) |
13 |
| - for i = 1, elements.n do |
14 |
| - js.global.printStream = js.global.printStream .. tostring(elements[i]) .. "\\t" |
| 1 | +import { interop, lauxlib, lua, lualib, to_luastring } from "fengari-web"; |
| 2 | +import { inspect } from "util"; |
| 3 | + |
| 4 | +// TODO: Use different message types |
| 5 | +export type LuaMessage = { type: "print"; text: string }; |
| 6 | + |
| 7 | +const workerContext = globalThis as typeof globalThis & { printStream: string[] }; |
| 8 | + |
| 9 | +const redirectPrintStream = ` |
| 10 | + local js = require("js") |
| 11 | + _G.print = function(...) |
| 12 | + local elements = {} |
| 13 | + for i = 1, select("#", ...) do |
| 14 | + table.insert(elements, tostring(select(i, ...))) |
| 15 | + end |
| 16 | + js.global.printStream:push(table.concat(elements, "\\t")) |
15 | 17 | end
|
16 |
| - js.global.printStream = js.global.printStream .. "\\n" |
17 |
| -end |
18 | 18 | `;
|
19 | 19 |
|
20 |
| - luaStr = printToGlobal + luaStr; |
| 20 | +function transformLuaValue(rootValue: any) { |
| 21 | + const seenLuaValues = new Set<any>(); |
| 22 | + function transform(luaValue: any) { |
| 23 | + if (typeof luaValue !== "function") return luaValue; |
| 24 | + |
| 25 | + if (luaValue.toString().startsWith("function:")) { |
| 26 | + return { inspect: () => "[Function]" }; |
| 27 | + } |
| 28 | + |
| 29 | + // TODO: Is there some way to get stable reference? |
| 30 | + if (seenLuaValues.has(luaValue.toString())) { |
| 31 | + return { inspect: () => "[Circular]" }; |
| 32 | + } |
| 33 | + |
| 34 | + seenLuaValues.add(luaValue.toString()); |
| 35 | + |
| 36 | + // TODO: Object.fromEntries |
| 37 | + const result: Record<string, any> = {}; |
| 38 | + for (const [key, value] of luaValue) { |
| 39 | + result[key] = transform(value); |
| 40 | + } |
| 41 | + return result; |
| 42 | + } |
| 43 | + |
| 44 | + return transform(rootValue); |
| 45 | +} |
| 46 | + |
| 47 | +function executeLua(code: string): LuaMessage[] { |
| 48 | + workerContext.printStream = []; |
21 | 49 |
|
22 | 50 | const L = lauxlib.luaL_newstate();
|
23 | 51 | lualib.luaL_openlibs(L);
|
24 | 52 | lauxlib.luaL_requiref(L, to_luastring("js"), interop.luaopen_js, 1);
|
25 | 53 | lua.lua_pop(L, 1);
|
26 |
| - const status = lauxlib.luaL_dostring(L, to_luastring(luaStr)); |
27 |
| - |
28 |
| - if (status === lua.LUA_OK) { |
29 |
| - // Read the return value from stack depending on its type. |
30 |
| - if (lua.lua_isboolean(L, -1)) { |
31 |
| - return lua.lua_toboolean(L, -1); |
32 |
| - } else if (lua.lua_isnil(L, -1)) { |
33 |
| - return null; |
34 |
| - } else if (lua.lua_isnumber(L, -1)) { |
35 |
| - return lua.lua_tonumber(L, -1); |
36 |
| - } else if (lua.lua_isstring(L, -1)) { |
37 |
| - return lua.lua_tojsstring(L, -1); |
38 |
| - } else { |
39 |
| - throw new Error("Unsupported lua return type: " + to_jsstring(lua.lua_typename(L, lua.lua_type(L, -1)))); |
40 |
| - } |
41 |
| - } else { |
42 |
| - // If the lua VM did not terminate with status code LUA_OK an error occurred. |
43 |
| - // Throw a JS error with the message, retrieved by reading a string from the stack. |
| 54 | + lauxlib.luaL_dostring(L, to_luastring(redirectPrintStream)); |
44 | 55 |
|
45 |
| - // Filter control characters out of string which are in there because ???? |
46 |
| - throw new Error("LUA ERROR: " + to_jsstring(lua.lua_tostring(L, -1).filter((c: number) => c >= 20))); |
| 56 | + const status = lauxlib.luaL_dostring(L, to_luastring(code)); |
| 57 | + const messageType = status === lua.LUA_OK ? "Module" : "Error"; |
| 58 | + const value = transformLuaValue(interop.tojs(L, -1)); |
| 59 | + |
| 60 | + const messages: LuaMessage[] = workerContext.printStream.map(text => ({ type: "print", text })); |
| 61 | + if (value !== undefined || messageType === "Error") { |
| 62 | + const formattedValue = inspect(value); |
| 63 | + messages.push({ type: "print", text: `${messageType}: ${formattedValue}` }); |
47 | 64 | }
|
| 65 | + |
| 66 | + return messages; |
48 | 67 | }
|
49 | 68 |
|
50 | 69 | onmessage = (event: MessageEvent) => {
|
51 |
| - if (event.data.luaStr) { |
52 |
| - try { |
53 |
| - executeLua(event.data.luaStr); |
54 |
| - postMessage({ luaPrint: self.printStream }); |
55 |
| - } catch (e) { |
56 |
| - postMessage({ luaPrint: e.toString() }); |
57 |
| - } |
58 |
| - } |
| 70 | + postMessage({ messages: executeLua(event.data.code) }); |
59 | 71 | };
|
0 commit comments