Lua get and set now possibly async

pull/1124/merge
Zef Hemel 2024-10-26 16:02:37 +02:00
parent 77f0c8669c
commit 123309d791
6 changed files with 103 additions and 73 deletions

View File

@ -374,7 +374,17 @@ const operatorsMetaMethods: LuaMetaMethod = {
}, },
"..": { "..": {
metaMethod: "__concat", 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", metaMethod: "__eq",

View File

@ -1,10 +1,6 @@
import { parse } from "$common/space_lua/parse.ts"; import { parse } from "$common/space_lua/parse.ts";
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts"; import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
import { import { LuaEnv, LuaStackFrame } from "$common/space_lua/runtime.ts";
LuaEnv,
type LuaRuntimeError,
LuaStackFrame,
} from "$common/space_lua/runtime.ts";
import { evalStatement } from "$common/space_lua/eval.ts"; import { evalStatement } from "$common/space_lua/eval.ts";
import { assert } from "@std/assert/assert"; import { assert } from "@std/assert/assert";
Deno.test("Lua language tests", async () => { Deno.test("Lua language tests", async () => {
@ -19,38 +15,7 @@ Deno.test("Lua language tests", async () => {
try { try {
await evalStatement(chunk, env, sf); await evalStatement(chunk, env, sf);
} catch (e: any) { } catch (e: any) {
console.error(`Error evaluating script:`, toPrettyString(e, luaFile)); console.error(`Error evaluating script:`, e.toPrettyString(luaFile));
assert(false); 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}`;
}

View File

@ -271,7 +271,6 @@ assert(string.len("Hello") == 5)
assert(string.byte("Hello", 1) == 72) assert(string.byte("Hello", 1) == 72)
assert(string.char(72) == "H") assert(string.char(72) == "H")
assert(string.find("Hello", "l") == 3) assert(string.find("Hello", "l") == 3)
assert(string.format("Hello %s", "world") == "Hello world")
assert(string.rep("Hello", 3) == "HelloHelloHello") assert(string.rep("Hello", 3) == "HelloHelloHello")
assert(string.sub("Hello", 2, 4) == "ell") assert(string.sub("Hello", 2, 4) == "ell")
assert(string.upper("Hello") == "HELLO") assert(string.upper("Hello") == "HELLO")

View File

@ -57,7 +57,10 @@ export class LuaEnv implements ILuaSettable, ILuaGettable {
return false; return false;
} }
get(name: string, sf?: LuaStackFrame): LuaValue | undefined { get(
name: string,
sf?: LuaStackFrame,
): Promise<LuaValue> | LuaValue | undefined {
if (this.variables.has(name)) { if (this.variables.has(name)) {
return this.variables.get(name); return this.variables.get(name);
} }
@ -267,15 +270,25 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
} }
} }
set(key: LuaValue, value: LuaValue, sf?: LuaStackFrame): void { set(
// New index handling for metatables key: LuaValue,
value: LuaValue,
sf?: LuaStackFrame,
): Promise<void> | void {
if (this.metatable && this.metatable.has("__newindex") && !this.has(key)) { if (this.metatable && this.metatable.has("__newindex") && !this.has(key)) {
// Invoke the meta table!
const metaValue = this.metatable.get("__newindex", sf); const metaValue = this.metatable.get("__newindex", sf);
// TODO: This may return a promise, we should handle that if (metaValue.then) {
luaCall(metaValue, [this, key, value], metaValue.ctx, sf); // This is a promise, we need to wait for it
return; 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); this.rawSet(key, value);
} }
@ -289,12 +302,24 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
} }
} }
get(key: LuaValue, sf?: LuaStackFrame): LuaValue | null { get(key: LuaValue, sf?: LuaStackFrame): LuaValue | Promise<LuaValue> | null {
const value = this.rawGet(key); const value = this.rawGet(key);
if (value === undefined || value === null) { if (value === undefined || value === null) {
if (this.metatable && this.metatable.has("__index")) {
// Invoke the meta table // Invoke the meta table
if (this.metatable) {
const metaValue = this.metatable.get("__index", sf); const metaValue = this.metatable.get("__index", 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 {
if (metaValue.call) { if (metaValue.call) {
return metaValue.call(sf, this, key); return metaValue.call(sf, this, key);
} else if (metaValue instanceof LuaTable) { } else if (metaValue instanceof LuaTable) {
@ -303,9 +328,13 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
throw new Error("Meta table __index must be a function or table"); 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) { insert(value: LuaValue, pos: number) {
this.arrayPart.splice(pos - 1, 0, value); this.arrayPart.splice(pos - 1, 0, value);
@ -337,9 +366,9 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
return this.arrayPart.map(luaValueToJS); return this.arrayPart.map(luaValueToJS);
} }
toString(): string { async toStringAsync(): Promise<string> {
if (this.metatable?.has("__tostring")) { if (this.metatable?.has("__tostring")) {
const metaValue = this.metatable.get("__tostring"); const metaValue = await this.metatable.get("__tostring");
if (metaValue.call) { if (metaValue.call) {
return metaValue.call(LuaStackFrame.lostFrame, this); return metaValue.call(LuaStackFrame.lostFrame, this);
} else { } else {
@ -355,7 +384,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
result += ", "; result += ", ";
} }
if (typeof key === "number") { if (typeof key === "number") {
result += luaToString(this.get(key)); result += await luaToString(this.get(key));
continue; continue;
} }
if (typeof key === "string") { if (typeof key === "string") {
@ -363,7 +392,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
} else { } else {
result += "[" + key + "]"; result += "[" + key + "]";
} }
result += " = " + luaToString(this.get(key)); result += " = " + await luaToString(this.get(key));
} }
result += "}"; result += "}";
return 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> | any {
if (!obj) { if (!obj) {
throw new LuaRuntimeError( throw new LuaRuntimeError(
`Attempting to index a nil value`, `Attempting to index a nil value`,
@ -492,6 +525,37 @@ export class LuaRuntimeError extends Error {
super(message, cause); 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() { override toString() {
return `LuaRuntimeError: ${this.message} at ${this.sf.astCtx?.from}, ${this.sf.astCtx?.to}`; 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; return true;
} }
export function luaToString(value: any): string { export function luaToString(value: any): string | Promise<string> {
if (value === null || value === undefined) { if (value === null || value === undefined) {
return "nil"; return "nil";
} }
if (value.toStringAsync) {
return value.toStringAsync();
}
if (value.toString) { if (value.toString) {
return value.toString(); return value.toString();
} }

View File

@ -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) => { gmatch: new LuaBuiltinFunction((_sf, s: string, pattern: string) => {
const regex = new RegExp(pattern, "g"); const regex = new RegExp(pattern, "g");
return () => { return () => {

View File

@ -67,7 +67,8 @@ function exposeDefinitions(
hide: def.get("hide"), hide: def.get("hide"),
} as CommandDef, } as CommandDef,
async (...args: any[]) => { async (...args: any[]) => {
const sf = new LuaStackFrame(new LuaEnv(), null); const tl = new LuaEnv();
const sf = new LuaStackFrame(tl, null);
try { try {
return await def.get(1).call(sf, ...args.map(jsToLuaValue)); return await def.get(1).call(sf, ...args.map(jsToLuaValue));
} catch (e: any) { } catch (e: any) {