Lua get and set now possibly async
parent
77f0c8669c
commit
123309d791
|
@ -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",
|
||||||
|
|
|
@ -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}`;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue