From 123309d791d84a8bdae4010676e248fa24db4028 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Sat, 26 Oct 2024 16:02:37 +0200 Subject: [PATCH] Lua get and set now possibly async --- common/space_lua/eval.ts | 12 +++- common/space_lua/language.test.ts | 39 +---------- common/space_lua/language_test.lua | 1 - common/space_lua/runtime.ts | 109 +++++++++++++++++++++++------ common/space_lua/stdlib/string.ts | 12 ---- common/space_lua_api.ts | 3 +- 6 files changed, 103 insertions(+), 73 deletions(-) diff --git a/common/space_lua/eval.ts b/common/space_lua/eval.ts index 762579a3..b91fd4cc 100644 --- a/common/space_lua/eval.ts +++ b/common/space_lua/eval.ts @@ -374,7 +374,17 @@ const operatorsMetaMethods: LuaMetaMethod = { }, "..": { metaMethod: "__concat", - nativeImplementation: (a, b) => luaToString(a) + luaToString(b), + nativeImplementation: (a, b) => { + const aString = luaToString(a); + const bString = luaToString(b); + if (aString instanceof Promise || bString instanceof Promise) { + return Promise.all([aString, bString]).then(([aString, bString]) => + aString + bString + ); + } else { + return aString + bString; + } + }, }, "==": { metaMethod: "__eq", diff --git a/common/space_lua/language.test.ts b/common/space_lua/language.test.ts index 372e2afc..f430c5c2 100644 --- a/common/space_lua/language.test.ts +++ b/common/space_lua/language.test.ts @@ -1,10 +1,6 @@ import { parse } from "$common/space_lua/parse.ts"; import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts"; -import { - LuaEnv, - type LuaRuntimeError, - LuaStackFrame, -} from "$common/space_lua/runtime.ts"; +import { LuaEnv, LuaStackFrame } from "$common/space_lua/runtime.ts"; import { evalStatement } from "$common/space_lua/eval.ts"; import { assert } from "@std/assert/assert"; Deno.test("Lua language tests", async () => { @@ -19,38 +15,7 @@ Deno.test("Lua language tests", async () => { try { await evalStatement(chunk, env, sf); } catch (e: any) { - console.error(`Error evaluating script:`, toPrettyString(e, luaFile)); + console.error(`Error evaluating script:`, e.toPrettyString(luaFile)); assert(false); } }); - -function toPrettyString(err: LuaRuntimeError, code: string): string { - if (!err.sf || !err.sf.astCtx?.from || !err.sf.astCtx?.to) { - return err.toString(); - } - let traceStr = ""; - let current: LuaStackFrame | undefined = err.sf; - while (current) { - const ctx = current.astCtx; - if (!ctx || !ctx.from || !ctx.to) { - break; - } - // Find the line and column - let line = 1; - let column = 0; - for (let i = 0; i < ctx.from; i++) { - if (code[i] === "\n") { - line++; - column = 0; - } else { - column++; - } - } - traceStr += `* ${ctx.ref || "(unknown source)"} @ ${line}:${column}:\n ${ - code.substring(ctx.from, ctx.to) - }\n`; - current = current.parent; - } - - return `LuaRuntimeError: ${err.message} ${traceStr}`; -} diff --git a/common/space_lua/language_test.lua b/common/space_lua/language_test.lua index 0da9956c..3ccddb5c 100644 --- a/common/space_lua/language_test.lua +++ b/common/space_lua/language_test.lua @@ -271,7 +271,6 @@ assert(string.len("Hello") == 5) assert(string.byte("Hello", 1) == 72) assert(string.char(72) == "H") assert(string.find("Hello", "l") == 3) -assert(string.format("Hello %s", "world") == "Hello world") assert(string.rep("Hello", 3) == "HelloHelloHello") assert(string.sub("Hello", 2, 4) == "ell") assert(string.upper("Hello") == "HELLO") diff --git a/common/space_lua/runtime.ts b/common/space_lua/runtime.ts index f2677102..1cb806db 100644 --- a/common/space_lua/runtime.ts +++ b/common/space_lua/runtime.ts @@ -57,7 +57,10 @@ export class LuaEnv implements ILuaSettable, ILuaGettable { return false; } - get(name: string, sf?: LuaStackFrame): LuaValue | undefined { + get( + name: string, + sf?: LuaStackFrame, + ): Promise | LuaValue | undefined { if (this.variables.has(name)) { return this.variables.get(name); } @@ -267,15 +270,25 @@ export class LuaTable implements ILuaSettable, ILuaGettable { } } - set(key: LuaValue, value: LuaValue, sf?: LuaStackFrame): void { - // New index handling for metatables + set( + key: LuaValue, + value: LuaValue, + sf?: LuaStackFrame, + ): Promise | void { if (this.metatable && this.metatable.has("__newindex") && !this.has(key)) { + // Invoke the meta table! const metaValue = this.metatable.get("__newindex", sf); - // TODO: This may return a promise, we should handle that - luaCall(metaValue, [this, key, value], metaValue.ctx, sf); - return; + if (metaValue.then) { + // This is a promise, we need to wait for it + return metaValue.then((metaValue: any) => { + return luaCall(metaValue, [this, key, value], metaValue.ctx, sf); + }); + } else { + return luaCall(metaValue, [this, key, value], metaValue.ctx, sf); + } } + // Just set the value this.rawSet(key, value); } @@ -289,22 +302,38 @@ export class LuaTable implements ILuaSettable, ILuaGettable { } } - get(key: LuaValue, sf?: LuaStackFrame): LuaValue | null { + get(key: LuaValue, sf?: LuaStackFrame): LuaValue | Promise | null { const value = this.rawGet(key); if (value === undefined || value === null) { - // Invoke the meta table - if (this.metatable) { + if (this.metatable && this.metatable.has("__index")) { + // Invoke the meta table const metaValue = this.metatable.get("__index", sf); - if (metaValue.call) { - return metaValue.call(sf, this, key); - } else if (metaValue instanceof LuaTable) { - return metaValue.get(key, sf); + if (metaValue.then) { + // Got a promise, we need to wait for it + return metaValue.then((metaValue: any) => { + if (metaValue.call) { + return metaValue.call(sf, this, key); + } else if (metaValue instanceof LuaTable) { + return metaValue.get(key, sf); + } else { + throw new Error("Meta table __index must be a function or table"); + } + }); } else { - throw new Error("Meta table __index must be a function or table"); + if (metaValue.call) { + return metaValue.call(sf, this, key); + } else if (metaValue instanceof LuaTable) { + return metaValue.get(key, sf); + } else { + throw new Error("Meta table __index must be a function or table"); + } } + } else { + return null; } + } else { + return value; } - return value; } insert(value: LuaValue, pos: number) { @@ -337,9 +366,9 @@ export class LuaTable implements ILuaSettable, ILuaGettable { return this.arrayPart.map(luaValueToJS); } - toString(): string { + async toStringAsync(): Promise { if (this.metatable?.has("__tostring")) { - const metaValue = this.metatable.get("__tostring"); + const metaValue = await this.metatable.get("__tostring"); if (metaValue.call) { return metaValue.call(LuaStackFrame.lostFrame, this); } else { @@ -355,7 +384,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable { result += ", "; } if (typeof key === "number") { - result += luaToString(this.get(key)); + result += await luaToString(this.get(key)); continue; } if (typeof key === "string") { @@ -363,7 +392,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable { } else { result += "[" + key + "]"; } - result += " = " + luaToString(this.get(key)); + result += " = " + await luaToString(this.get(key)); } result += "}"; return result; @@ -387,7 +416,11 @@ export function luaSet(obj: any, key: any, value: any, sf: LuaStackFrame) { } } -export function luaGet(obj: any, key: any, sf: LuaStackFrame): any { +export function luaGet( + obj: any, + key: any, + sf: LuaStackFrame, +): Promise | any { if (!obj) { throw new LuaRuntimeError( `Attempting to index a nil value`, @@ -492,6 +525,37 @@ export class LuaRuntimeError extends Error { super(message, cause); } + toPrettyString(code: string): string { + if (!this.sf || !this.sf.astCtx?.from || !this.sf.astCtx?.to) { + return this.toString(); + } + let traceStr = ""; + let current: LuaStackFrame | undefined = this.sf; + while (current) { + const ctx = current.astCtx; + if (!ctx || !ctx.from || !ctx.to) { + break; + } + // Find the line and column + let line = 1; + let column = 0; + for (let i = 0; i < ctx.from; i++) { + if (code[i] === "\n") { + line++; + column = 0; + } else { + column++; + } + } + traceStr += `* ${ + ctx.ref || "(unknown source)" + } @ ${line}:${column}:\n ${code.substring(ctx.from, ctx.to)}\n`; + current = current.parent; + } + + return `LuaRuntimeError: ${this.message} ${traceStr}`; + } + override toString() { return `LuaRuntimeError: ${this.message} at ${this.sf.astCtx?.from}, ${this.sf.astCtx?.to}`; } @@ -507,10 +571,13 @@ export function luaTruthy(value: any): boolean { return true; } -export function luaToString(value: any): string { +export function luaToString(value: any): string | Promise { if (value === null || value === undefined) { return "nil"; } + if (value.toStringAsync) { + return value.toStringAsync(); + } if (value.toString) { return value.toString(); } diff --git a/common/space_lua/stdlib/string.ts b/common/space_lua/stdlib/string.ts index d8aa4c32..85c96fbc 100644 --- a/common/space_lua/stdlib/string.ts +++ b/common/space_lua/stdlib/string.ts @@ -32,18 +32,6 @@ export const stringApi = new LuaTable({ ]); }, ), - format: new LuaBuiltinFunction((_sf, format: string, ...args: any[]) => { - return format.replace(/%./g, (match) => { - switch (match) { - case "%s": - return luaToString(args.shift()); - case "%d": - return String(args.shift()); - default: - return match; - } - }); - }), gmatch: new LuaBuiltinFunction((_sf, s: string, pattern: string) => { const regex = new RegExp(pattern, "g"); return () => { diff --git a/common/space_lua_api.ts b/common/space_lua_api.ts index 2444a774..4c3ee826 100644 --- a/common/space_lua_api.ts +++ b/common/space_lua_api.ts @@ -67,7 +67,8 @@ function exposeDefinitions( hide: def.get("hide"), } as CommandDef, async (...args: any[]) => { - const sf = new LuaStackFrame(new LuaEnv(), null); + const tl = new LuaEnv(); + const sf = new LuaStackFrame(tl, null); try { return await def.get(1).call(sf, ...args.map(jsToLuaValue)); } catch (e: any) {