|
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