124 lines
3.1 KiB
TypeScript
124 lines
3.1 KiB
TypeScript
import * as readline from "node:readline/promises";
|
|
import { stdin, stdout } from "node:process";
|
|
import type { SpaceConnection } from "./api.ts";
|
|
|
|
// Check if Lua code has unbalanced block structures
|
|
function isIncomplete(code: string): boolean {
|
|
let depth = 0;
|
|
|
|
const openers = code.match(/\b(do|function|if|repeat)\b/g);
|
|
const closers = code.match(/\b(end|until)\b/g);
|
|
depth += (openers?.length || 0) - (closers?.length || 0);
|
|
|
|
for (const ch of code) {
|
|
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
}
|
|
|
|
return depth > 0;
|
|
}
|
|
|
|
function formatResult(result: any): string | null {
|
|
if (result === null || result === undefined) return null;
|
|
if (typeof result === "string") return result;
|
|
return JSON.stringify(result);
|
|
}
|
|
|
|
export async function startRepl(conn: SpaceConnection): Promise<void> {
|
|
const rl = readline.createInterface({
|
|
input: stdin,
|
|
output: stdout,
|
|
prompt: "lua> ",
|
|
});
|
|
|
|
let scriptMode = false;
|
|
let scriptBuffer = "";
|
|
let multiLineBuffer = "";
|
|
|
|
console.log('SilverBullet Lua REPL. Type .exit or Ctrl-D to quit.');
|
|
rl.prompt();
|
|
|
|
rl.on("line", async (line: string) => {
|
|
// Handle dot-commands
|
|
if (line.trim() === ".exit") {
|
|
rl.close();
|
|
return;
|
|
}
|
|
|
|
if (line.trim() === ".script") {
|
|
scriptMode = true;
|
|
scriptBuffer = "";
|
|
console.log("Entering script mode. Type .end to execute.");
|
|
process.stdout.write("...> ");
|
|
return;
|
|
}
|
|
|
|
if (scriptMode) {
|
|
if (line.trim() === ".end") {
|
|
scriptMode = false;
|
|
if (scriptBuffer.trim()) {
|
|
try {
|
|
const result = await conn.evalLuaScript(scriptBuffer);
|
|
const output = formatResult(result);
|
|
if (output) console.log(output);
|
|
} catch (err: any) {
|
|
console.error(`Error: ${err.message}`);
|
|
}
|
|
}
|
|
rl.prompt();
|
|
return;
|
|
}
|
|
scriptBuffer += `${line}\n`;
|
|
process.stdout.write("...> ");
|
|
return;
|
|
}
|
|
|
|
if (line.trim().startsWith(".timeout ")) {
|
|
const val = Number.parseInt(line.trim().split(/\s+/)[1], 10);
|
|
if (Number.isNaN(val) || val <= 0) {
|
|
console.error("Invalid timeout value");
|
|
} else {
|
|
conn.timeout = val;
|
|
console.log(`Timeout set to ${val}s`);
|
|
}
|
|
rl.prompt();
|
|
return;
|
|
}
|
|
|
|
// Multi-line accumulation
|
|
multiLineBuffer += (multiLineBuffer ? "\n" : "") + line;
|
|
|
|
if (isIncomplete(multiLineBuffer)) {
|
|
process.stdout.write("...> ");
|
|
return;
|
|
}
|
|
|
|
const code = multiLineBuffer;
|
|
multiLineBuffer = "";
|
|
|
|
if (!code.trim()) {
|
|
rl.prompt();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Use lua_script for multi-line, lua for single-line
|
|
const isMultiLine = code.includes("\n");
|
|
const result = isMultiLine
|
|
? await conn.evalLuaScript(code)
|
|
: await conn.evalLua(code);
|
|
const output = formatResult(result);
|
|
if (output) console.log(output);
|
|
} catch (err: any) {
|
|
console.error(`Error: ${err.message}`);
|
|
}
|
|
|
|
rl.prompt();
|
|
});
|
|
|
|
rl.on("close", () => {
|
|
console.log("");
|
|
process.exit(0);
|
|
});
|
|
}
|