Skip to content

Commit 63a7708

Browse files
authored
Merge pull request #12 from TypeScriptToLua/playground-console
Add console to the playground
2 parents 8e5a178 + 582f0cd commit 63a7708

12 files changed

+506
-178
lines changed

package-lock.json

+225-48
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
"@types/react": "^16.9.35",
1818
"@types/react-dom": "^16.9.8",
1919
"@types/react-json-tree": "^0.6.11",
20-
"d3": "^5.16.0",
2120
"@types/webpack-env": "^1.15.2",
2221
"clsx": "^1.1.1",
22+
"console-feed": "^3.0.0",
23+
"d3": "^5.16.0",
2324
"fengari-web": "^0.1.4",
2425
"lua-types": "^2.8.0",
2526
"lz-string": "^1.4.4",

src/pages/play/Playground.tsx

+29-53
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,25 @@
11
import useThemeContext from "@theme/hooks/useThemeContext";
22
import clsx from "clsx";
3+
import { Console as ConsoleFeed } from "console-feed";
34
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
45
import JSONTree from "react-json-tree";
56
import MonacoEditor from "react-monaco-editor";
67
import { version as tstlVersion } from "typescript-to-lua/package.json";
78
import { version as tsVersion } from "typescript/package.json";
8-
import FengariWorker from "worker-loader?name=fengari.worker.js!./fengari.worker";
99
import { debounce } from "../../utils";
1010
import { getInitialCode, updateCodeHistory } from "./code";
11-
import type { LuaMessage } from "./fengari.worker";
11+
import { ConsoleMessage, executeLua } from "./execute";
1212
import { monaco, useMonacoTheme } from "./monaco";
1313
import styles from "./styles.module.scss";
14+
import { consoleFeedTheme, jsonTreeTheme } from "./themes";
1415
import type { CustomTypeScriptWorker } from "./ts.worker";
1516

16-
let fengariWorker = new FengariWorker();
17-
async function executeLua(code: string) {
18-
return new Promise<LuaMessage[]>((resolve) => {
19-
const timeout = setTimeout(() => {
20-
resolve([{ type: "print", text: "Lua code execution timed out" }]);
21-
fengariWorker.terminate();
22-
fengariWorker = new FengariWorker();
23-
}, 2500);
24-
25-
fengariWorker.postMessage({ code });
26-
fengariWorker.addEventListener("message", (event) => {
27-
clearTimeout(timeout);
28-
resolve(event.data.messages);
29-
});
30-
});
31-
}
32-
3317
interface EditorState {
3418
source: string;
3519
lua: string;
3620
sourceMap: string;
3721
ast: object;
38-
results: LuaMessage[];
22+
results: ConsoleMessage[];
3923
}
4024

4125
const EditorContext = React.createContext<EditorContext>(null!);
@@ -67,7 +51,7 @@ const commonMonacoOptions: monaco.editor.IEditorConstructionOptions = {
6751

6852
function InputPane() {
6953
const theme = useMonacoTheme();
70-
const ref = useRef<MonacoEditor>(null!);
54+
const ref = useRef<MonacoEditor>(null);
7155
const { updateModel } = useContext(EditorContext);
7256

7357
useEffect(() => {
@@ -96,35 +80,15 @@ function InputPane() {
9680
);
9781
}
9882

99-
const astTheme = {
100-
scheme: "monokai",
101-
author: "wimer hazenberg (http://www.monokai.nl)",
102-
base00: "#1e1e1e",
103-
base01: "#383830",
104-
base02: "#49483e",
105-
base03: "#75715e",
106-
base04: "#a59f85",
107-
base05: "#f8f8f2",
108-
base06: "#f5f4f1",
109-
base07: "#f9f8f5",
110-
base08: "#f92672",
111-
base09: "#fd971f",
112-
base0A: "#f4bf75",
113-
base0B: "#a6e22e",
114-
base0C: "#a1efe4",
115-
base0D: "#66d9ef",
116-
base0E: "#ae81ff",
117-
base0F: "#cc6633",
118-
};
119-
12083
const LuaSyntaxKind = __LUA_SYNTAX_KIND__;
12184
function LuaAST({ ast }: { ast: object }) {
12285
const { isDarkTheme } = useThemeContext();
86+
12387
return (
12488
<JSONTree
12589
data={ast}
12690
hideRoot={true}
127-
theme={astTheme}
91+
theme={jsonTreeTheme}
12892
invertTheme={!isDarkTheme}
12993
valueRenderer={(raw, value, lastKey) => {
13094
if (lastKey === "kind") {
@@ -137,9 +101,28 @@ function LuaAST({ ast }: { ast: object }) {
137101
);
138102
}
139103

104+
function LuaOutput() {
105+
const { isDarkTheme } = useThemeContext();
106+
const { results } = useContext(EditorContext);
107+
108+
return (
109+
<div className={styles.editorOutput}>
110+
<div className={styles.editorOutputLineNumbers}>{">_"}</div>
111+
<div className={styles.editorOutputTerminal}>
112+
<ConsoleFeed
113+
key={isDarkTheme} // It does not update styles without re-mount
114+
logs={results as any}
115+
variant={isDarkTheme ? "dark" : "light"}
116+
styles={consoleFeedTheme(isDarkTheme)}
117+
/>
118+
</div>
119+
</div>
120+
);
121+
}
122+
140123
function OutputPane() {
141124
const theme = useMonacoTheme();
142-
const { source, lua, sourceMap, ast, results } = useContext(EditorContext);
125+
const { source, lua, sourceMap, ast } = useContext(EditorContext);
143126
const [isAstView, setAstView] = useState(false);
144127
const toggleAstView = useCallback(() => setAstView((x) => !x), []);
145128
const sourceMapUrl = useMemo(() => {
@@ -183,14 +166,7 @@ function OutputPane() {
183166
</div>
184167
</div>
185168

186-
<div className={styles.editorOutput}>
187-
<div className={styles.editorOutputLineNumbers}>>_</div>
188-
<div className={styles.editorOutputTerminal}>
189-
{results.map((message, idx) => (
190-
<div key={idx}>{message.text}</div>
191-
))}
192-
</div>
193-
</div>
169+
<LuaOutput />
194170
</div>
195171
);
196172
}
@@ -204,7 +180,7 @@ export default function Playground() {
204180
<a
205181
href="https://github.com/TypeScriptToLua/TypeScriptToLua/blob/master/CHANGELOG.md"
206182
target="_blank"
207-
rel="noopener noreferrer"
183+
rel="noopener"
208184
>
209185
<b>v{tstlVersion}</b>
210186
</a>

src/pages/play/execute/console.d.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
declare var console: Console;
2+
interface Console {
3+
assert(condition?: boolean, ...data: any[]): void;
4+
clear(): void;
5+
count(label?: string): void;
6+
// countReset(label?: string): void;
7+
debug(...data: any[]): void;
8+
// dir(item?: any, options?: any): void;
9+
// dirxml(...data: any[]): void;
10+
error(...data: any[]): void;
11+
// exception(message?: string, ...optionalParams: any[]): void;
12+
// group(...data: any[]): void;
13+
// groupCollapsed(...data: any[]): void;
14+
// groupEnd(): void;
15+
info(...data: any[]): void;
16+
log(...data: any[]): void;
17+
table(tabularData?: any, properties?: string[]): void;
18+
time(label?: string): void;
19+
timeEnd(label?: string): void;
20+
// timeLog(label?: string, ...data: any[]): void;
21+
// timeStamp(label?: string): void;
22+
// trace(...data: any[]): void;
23+
warn(...data: any[]): void;
24+
}
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import setupCode from "!!raw-loader!./setup.lua";
2+
import type { Message as ConsoleMessage } from "console-feed/lib/definitions/Console";
3+
import { interop, lauxlib, lua, lualib, to_luastring } from "fengari-web";
4+
5+
export type { ConsoleMessage };
6+
7+
const workerContext = globalThis as typeof globalThis & { printStream: any[] };
8+
9+
function transformLuaValue(rootValue: any) {
10+
const seenLuaValues = new Set<any>();
11+
function transform(luaValue: any): any {
12+
if (typeof luaValue !== "function") return luaValue;
13+
14+
if (luaValue.toString().startsWith("function:")) {
15+
return "[Function]";
16+
}
17+
18+
// TODO: Is there some way to get stable reference?
19+
if (seenLuaValues.has(luaValue.toString())) {
20+
return "[Circular]";
21+
}
22+
23+
seenLuaValues.add(luaValue.toString());
24+
25+
const object = Object.fromEntries([...luaValue].map(([key, value]) => [key, transform(value)]));
26+
27+
const arrayLikeEntries = Object.entries(object).map(([key, value]) => [Number(key) - 1, value] as const);
28+
if (
29+
"____tstlArrayLength" in object ||
30+
(arrayLikeEntries.length > 0 &&
31+
arrayLikeEntries.sort(([a], [b]) => a - b).every(([key], index) => key === index))
32+
) {
33+
const array = new Array(object.____tstlArrayLength || arrayLikeEntries.length).fill(undefined);
34+
35+
for (const [key, value] of arrayLikeEntries) {
36+
array[key] = value;
37+
}
38+
39+
return array;
40+
}
41+
42+
return object;
43+
}
44+
45+
return transform(rootValue);
46+
}
47+
48+
function executeLua(code: string) {
49+
workerContext.printStream = [];
50+
51+
const L = lauxlib.luaL_newstate();
52+
lualib.luaL_openlibs(L);
53+
lauxlib.luaL_requiref(L, to_luastring("js"), interop.luaopen_js, 1);
54+
lua.lua_pop(L, 1);
55+
lauxlib.luaL_dostring(L, to_luastring(setupCode));
56+
57+
const status = lauxlib.luaL_dostring(L, to_luastring(code));
58+
const value = transformLuaValue(interop.tojs(L, -1));
59+
const messages: ConsoleMessage[] = workerContext.printStream.map(transformLuaValue);
60+
61+
if (status === lua.LUA_OK) {
62+
if (value !== undefined) {
63+
messages.push({ method: "log", data: ["Module exports:", value] });
64+
}
65+
} else {
66+
messages.push({ method: "error", data: [value] });
67+
}
68+
69+
return messages;
70+
}
71+
72+
onmessage = (event: MessageEvent) => {
73+
postMessage({ messages: executeLua(event.data.code) });
74+
};

src/pages/play/execute/index.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import FengariWorker from "worker-loader?name=fengari.worker.js!./fengari.worker";
2+
import type { ConsoleMessage } from "./fengari.worker";
3+
4+
export type { ConsoleMessage };
5+
6+
let fengariWorker = new FengariWorker();
7+
export async function executeLua(code: string) {
8+
return new Promise<ConsoleMessage[]>((resolve) => {
9+
const timeout = setTimeout(() => {
10+
resolve([{ method: "log", data: ["%cLua code execution timed out", "font-style: italic"] }]);
11+
fengariWorker.terminate();
12+
fengariWorker = new FengariWorker();
13+
}, 2500);
14+
15+
fengariWorker.postMessage({ code });
16+
fengariWorker.addEventListener("message", (event) => {
17+
clearTimeout(timeout);
18+
resolve(event.data.messages);
19+
});
20+
});
21+
}

src/pages/play/execute/setup.lua

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
do
2+
local js = require("js")
3+
4+
local function pushToPrintStream(method, ...)
5+
js.global.printStream:push({
6+
method = method,
7+
data = { ____tstlArrayLength = select("#", ...), ... },
8+
})
9+
end
10+
11+
console = {
12+
assert = function(_, ...) pushToPrintStream("assert", ...) end,
13+
clear = function(_, ...) pushToPrintStream("clear", ...) end,
14+
count = function(_, ...) pushToPrintStream("count", ...) end,
15+
debug = function(_, ...) pushToPrintStream("debug", ...) end,
16+
error = function(_, ...) pushToPrintStream("error", ...) end,
17+
info = function(_, ...) pushToPrintStream("info", ...) end,
18+
log = function(_, ...) pushToPrintStream("log", ...) end,
19+
table = function(_, ...) pushToPrintStream("table", ...) end,
20+
time = function(_, ...) pushToPrintStream("time", ...) end,
21+
timeEnd = function(_, ...) pushToPrintStream("timeEnd", ...) end,
22+
warn = function(_, ...) pushToPrintStream("warn", ...) end,
23+
}
24+
25+
print = function(...)
26+
local elements = {}
27+
for i = 1, select("#", ...) do
28+
table.insert(elements, tostring(select(i, ...)))
29+
end
30+
pushToPrintStream("log", table.concat(elements, "\t"))
31+
end
32+
33+
-- Don't try to resolve required modules
34+
package.path = ""
35+
package.jspath = ""
36+
end

src/pages/play/fengari.worker.ts

-69
This file was deleted.

src/pages/play/monaco.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ function addLibsFromContext(context: __WebpackModuleApi.RequireContext) {
2727
}
2828

2929
addLibsFromContext(require.context("!!raw-loader!typescript/lib/", false, /lib(\.es(.+))?\.d\.ts$/));
30-
addLibsFromContext(require.context("!!raw-loader!lua-types/core", true, /\.d\.ts$/));
30+
monaco.languages.typescript.typescriptDefaults.addExtraLib(require("!!raw-loader!./execute/console.d.ts").default);
31+
addLibsFromContext(require.context("!!raw-loader!lua-types/core/", true, /\.d\.ts$/));
3132
// TODO: Generate it from lua-types/special/5.3.d.ts
3233
for (const module of [
3334
require("!!raw-loader!lua-types/special/5.2-plus.d.ts"),

0 commit comments

Comments
 (0)