Lua get and set now possibly async
parent
77f0c8669c
commit
123309d791
|
@ -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",
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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> | 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> | 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<LuaValue> | 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<string> {
|
||||
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> | 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<string> {
|
||||
if (value === null || value === undefined) {
|
||||
return "nil";
|
||||
}
|
||||
if (value.toStringAsync) {
|
||||
return value.toStringAsync();
|
||||
}
|
||||
if (value.toString) {
|
||||
return value.toString();
|
||||
}
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue