diff --git a/common/space_lua/runtime.test.ts b/common/space_lua/runtime.test.ts index 015c9e87..6ac42028 100644 --- a/common/space_lua/runtime.test.ts +++ b/common/space_lua/runtime.test.ts @@ -50,4 +50,8 @@ Deno.test("Test Lua Rutime", async () => { assertEquals(await luaToString([{ a: 1 }]), "{{a = 1}}"); // Ensure simple cases are not returning promises assertEquals(luaToString(10), "10"); + // Test circular references + const circular: any = {}; + circular.self = circular; + assertEquals(await luaToString(circular), "{self = }"); }); diff --git a/common/space_lua/runtime.ts b/common/space_lua/runtime.ts index 75ea101f..17288c63 100644 --- a/common/space_lua/runtime.ts +++ b/common/space_lua/runtime.ts @@ -689,18 +689,29 @@ export function luaTruthy(value: any): boolean { return true; } -export function luaToString(value: any): string | Promise { +export function luaToString( + value: any, + visited: Set = new Set(), +): string | Promise { if (value === null || value === undefined) { return "nil"; } if (value instanceof Promise) { - return value.then(luaToString); + return value.then((v) => luaToString(v, visited)); + } + // Check for circular references + if (typeof value === "object" && visited.has(value)) { + return ""; } if (value.toStringAsync) { + // Add to visited before recursing + visited.add(value); return value.toStringAsync(); } // Handle plain JavaScript objects in a Lua-like format if (typeof value === "object") { + // Add to visited before recursing + visited.add(value); return (async () => { let result = "{"; let first = true; @@ -713,8 +724,8 @@ export function luaToString(value: any): string | Promise { } else { result += ", "; } - // Recursively stringify the value - const strVal = await luaToString(val); + // Recursively stringify the value, passing the visited set + const strVal = await luaToString(val, visited); result += strVal; } return result + "}"; @@ -732,8 +743,8 @@ export function luaToString(value: any): string | Promise { } else { result += `["${key}"] = `; } - // Recursively stringify the value - const strVal = await luaToString(val); + // Recursively stringify the value, passing the visited set + const strVal = await luaToString(val, visited); result += strVal; } result += "}";