Skip to content

Commit 27cc698

Browse files
committed
Improve Lua output handling
1 parent 0d12d1b commit 27cc698

File tree

3 files changed

+62
-47
lines changed

3 files changed

+62
-47
lines changed

assets/styles/play.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,6 @@ $output-height: 120px;
126126
#editor-output-terminal-content {
127127
width: 100%;
128128
font-size: 13px;
129+
white-space: pre;
129130
}
130131
}

src/playground/fengari.worker.ts

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,71 @@
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"))
1517
end
16-
js.global.printStream = js.global.printStream .. "\\n"
17-
end
1818
`;
1919

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 = [];
2149

2250
const L = lauxlib.luaL_newstate();
2351
lualib.luaL_openlibs(L);
2452
lauxlib.luaL_requiref(L, to_luastring("js"), interop.luaopen_js, 1);
2553
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));
4455

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}` });
4764
}
65+
66+
return messages;
4867
}
4968

5069
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) });
5971
};

src/playground/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { getInitialCode, updateCodeHistory } from "./code";
1414
// TODO: Use TypeScript 3.8 type imports
1515
type CustomTypeScriptWorker = import("./ts.worker").CustomTypeScriptWorker;
1616
type LuaBlock = import("typescript-to-lua/dist/LuaAST").Block;
17+
type LuaMessage = import("./fengari.worker").LuaMessage;
1718

1819
(globalThis as any).MonacoEnvironment = {
1920
getWorker(_workerId: any, label: string) {
@@ -70,12 +71,13 @@ async function onCodeChanged() {
7071
const { code, ast } = await client.getTranspileOutput();
7172
luaEditor.setValue(code);
7273
setLuaAST(ast);
73-
fengariWorker.postMessage({ luaStr: code });
74+
fengariWorker.postMessage({ code });
7475
}
7576

7677
const fengariWorker = new FengariWorker();
7778
fengariWorker.onmessage = event => {
78-
outputTerminalContent.innerText = event.data.luaPrint;
79+
const messages: LuaMessage[] = event.data.messages;
80+
outputTerminalContent.innerText = messages.map(m => m.text).join("\n");
7981
};
8082

8183
const tsEditor = monaco.editor.create(tsEditorContainer, {

0 commit comments

Comments
 (0)