silverbullet/common/space_lua/stdlib.ts

246 lines
6.6 KiB
TypeScript
Raw Normal View History

import {
2024-10-11 21:34:27 +08:00
type ILuaFunction,
2025-01-15 03:26:47 +08:00
jsToLuaValue,
2024-10-11 21:34:27 +08:00
LuaBuiltinFunction,
2024-10-20 21:06:23 +08:00
luaCall,
2024-10-11 21:34:27 +08:00
LuaEnv,
2025-01-09 17:27:41 +08:00
luaGet,
2024-10-11 21:34:27 +08:00
LuaMultiRes,
2024-10-20 21:06:23 +08:00
LuaRuntimeError,
2025-01-15 03:26:47 +08:00
LuaTable,
2024-10-11 21:34:27 +08:00
luaToString,
luaTypeOf,
type LuaValue,
2025-01-16 22:33:18 +08:00
luaValueToJS,
} from "$common/space_lua/runtime.ts";
2024-10-10 02:35:07 +08:00
import { stringApi } from "$common/space_lua/stdlib/string.ts";
import { tableApi } from "$common/space_lua/stdlib/table.ts";
import { osApi } from "$common/space_lua/stdlib/os.ts";
import { jsApi } from "$common/space_lua/stdlib/js.ts";
2025-01-15 03:26:47 +08:00
import {
interpolateLuaString,
spaceLuaApi,
} from "$common/space_lua/stdlib/space_lua.ts";
2025-01-16 22:33:18 +08:00
import {
findAllQueryVariables,
type LuaCollectionQuery,
type LuaQueryCollection,
} from "$common/space_lua/query_collection.ts";
2025-01-15 03:26:47 +08:00
import { templateApi } from "$common/space_lua/stdlib/template.ts";
2025-01-17 17:40:47 +08:00
import { mathApi } from "$common/space_lua/stdlib/math.ts";
2025-01-09 17:27:41 +08:00
const printFunction = new LuaBuiltinFunction(async (_sf, ...args) => {
2025-01-16 16:27:11 +08:00
console.log("[Lua]", ...(await Promise.all(args.map(luaToString))));
});
2024-10-09 01:53:09 +08:00
const assertFunction = new LuaBuiltinFunction(
2024-10-20 21:06:23 +08:00
async (sf, value: any, message?: string) => {
2024-10-11 21:34:27 +08:00
if (!await value) {
2024-10-20 21:06:23 +08:00
throw new LuaRuntimeError(`Assertion failed: ${message}`, sf);
2024-10-11 21:34:27 +08:00
}
},
);
2025-01-09 17:27:41 +08:00
const ipairsFunction = new LuaBuiltinFunction((sf, ar: LuaTable) => {
2024-10-11 21:34:27 +08:00
let i = 1;
2025-01-09 17:27:41 +08:00
return async () => {
2024-10-11 21:34:27 +08:00
if (i > ar.length) {
return;
}
2025-01-09 17:27:41 +08:00
const result = new LuaMultiRes([i, await luaGet(ar, i, sf)]);
2024-10-11 21:34:27 +08:00
i++;
return result;
};
});
2025-01-09 17:27:41 +08:00
const pairsFunction = new LuaBuiltinFunction((sf, t: LuaTable) => {
2024-10-11 21:34:27 +08:00
const keys = t.keys();
let i = 0;
2025-01-09 17:27:41 +08:00
return async () => {
2024-10-11 21:34:27 +08:00
if (i >= keys.length) {
return;
}
const key = keys[i];
i++;
2025-01-09 17:27:41 +08:00
return new LuaMultiRes([key, await luaGet(t, key, sf)]);
2024-10-11 21:34:27 +08:00
};
});
2025-01-15 03:26:47 +08:00
export const eachFunction = new LuaBuiltinFunction((sf, ar: LuaTable) => {
let i = 1;
return async () => {
if (i > ar.length) {
return;
}
const result = await luaGet(ar, i, sf);
i++;
return result;
};
});
2025-01-09 17:27:41 +08:00
const unpackFunction = new LuaBuiltinFunction(async (sf, t: LuaTable) => {
2024-10-11 21:34:27 +08:00
const values: LuaValue[] = [];
for (let i = 1; i <= t.length; i++) {
2025-01-09 17:27:41 +08:00
values.push(await luaGet(t, i, sf));
2024-10-11 21:34:27 +08:00
}
return new LuaMultiRes(values);
});
2024-10-20 21:06:23 +08:00
const typeFunction = new LuaBuiltinFunction((_sf, value: LuaValue): string => {
2024-10-11 21:34:27 +08:00
return luaTypeOf(value);
});
2024-10-20 21:06:23 +08:00
const tostringFunction = new LuaBuiltinFunction((_sf, value: any) => {
2024-10-11 21:34:27 +08:00
return luaToString(value);
});
2024-10-20 21:06:23 +08:00
const tonumberFunction = new LuaBuiltinFunction((_sf, value: LuaValue) => {
2024-10-11 21:34:27 +08:00
return Number(value);
});
2025-01-09 00:09:09 +08:00
const errorFunction = new LuaBuiltinFunction((sf, message: string) => {
throw new LuaRuntimeError(message, sf);
});
2024-10-09 01:53:09 +08:00
const pcallFunction = new LuaBuiltinFunction(
2024-10-20 21:06:23 +08:00
async (sf, fn: ILuaFunction, ...args) => {
2024-10-11 21:34:27 +08:00
try {
2024-10-20 21:06:23 +08:00
return new LuaMultiRes([true, await luaCall(fn, args, sf.astCtx!, sf)]);
2024-10-11 21:34:27 +08:00
} catch (e: any) {
2025-01-09 00:09:09 +08:00
if (e instanceof LuaRuntimeError) {
return new LuaMultiRes([false, e.message]);
}
2024-10-11 21:34:27 +08:00
return new LuaMultiRes([false, e.message]);
}
},
2024-10-09 01:53:09 +08:00
);
const xpcallFunction = new LuaBuiltinFunction(
2024-10-20 21:06:23 +08:00
async (sf, fn: ILuaFunction, errorHandler: ILuaFunction, ...args) => {
2024-10-11 21:34:27 +08:00
try {
2024-10-20 21:06:23 +08:00
return new LuaMultiRes([true, await fn.call(sf, ...args)]);
2024-10-11 21:34:27 +08:00
} catch (e: any) {
2025-01-09 00:09:09 +08:00
const errorMsg = e instanceof LuaRuntimeError ? e.message : e.message;
2024-10-20 21:06:23 +08:00
return new LuaMultiRes([
false,
2025-01-09 00:09:09 +08:00
await luaCall(errorHandler, [errorMsg], sf.astCtx!, sf),
2024-10-20 21:06:23 +08:00
]);
2024-10-11 21:34:27 +08:00
}
},
2024-10-09 01:53:09 +08:00
);
const setmetatableFunction = new LuaBuiltinFunction(
2024-10-20 21:06:23 +08:00
(_sf, table: LuaTable, metatable: LuaTable) => {
2024-10-11 21:34:27 +08:00
table.metatable = metatable;
return table;
},
);
const rawsetFunction = new LuaBuiltinFunction(
2024-10-20 21:06:23 +08:00
(_sf, table: LuaTable, key: LuaValue, value: LuaValue) => {
2025-01-16 22:33:18 +08:00
return table.rawSet(key, value);
2024-10-11 21:34:27 +08:00
},
);
2024-10-20 21:06:23 +08:00
const getmetatableFunction = new LuaBuiltinFunction((_sf, table: LuaTable) => {
2024-10-11 21:34:27 +08:00
return table.metatable;
});
2025-01-15 03:26:47 +08:00
// Non-standard
const tagFunction = new LuaBuiltinFunction(
(sf, tagName: LuaValue): LuaQueryCollection => {
const global = sf.threadLocal.get("_GLOBAL");
if (!global) {
throw new LuaRuntimeError("Global not found", sf);
}
return {
2025-01-16 22:33:18 +08:00
query: async (query: LuaCollectionQuery, env: LuaEnv): Promise<any[]> => {
const localVars = findAllQueryVariables(query).filter((v) =>
!global.has(v) && v !== "_"
);
const scopedVariables: Record<string, any> = {};
for (const v of localVars) {
try {
const jsonValue = await luaValueToJS(env.get(v));
// Ensure this is JSON serializable
JSON.stringify(jsonValue);
scopedVariables[v] = jsonValue;
} catch (e: any) {
console.error(
"Failed to JSON serialize variable",
v,
2025-01-16 22:35:56 +08:00
e,
2025-01-16 22:33:18 +08:00
);
throw new LuaRuntimeError(
`Failed to JSON serialize variable ${v} in query`,
sf,
);
}
}
return (await global.get("datastore").get("query_lua").call(
sf,
[
"idx",
tagName,
],
query,
2025-01-16 22:33:18 +08:00
scopedVariables,
2025-01-15 03:26:47 +08:00
)).toJSArray();
},
};
},
);
2025-01-15 03:26:47 +08:00
const tplFunction = new LuaBuiltinFunction(
(_sf, template: string): ILuaFunction => {
const lines = template.split("\n").map((line) =>
line.replace(/^\s{4}/, "")
);
const processed = lines.join("\n");
return new LuaBuiltinFunction(
async (sf, env: LuaTable | any) => {
if (!(env instanceof LuaTable)) {
env = jsToLuaValue(env);
}
return await interpolateLuaString(sf, processed, env);
},
);
},
);
export function luaBuildStandardEnv() {
2024-10-11 21:34:27 +08:00
const env = new LuaEnv();
// Top-level builtins
env.set("print", printFunction);
env.set("assert", assertFunction);
env.set("type", typeFunction);
env.set("tostring", tostringFunction);
env.set("tonumber", tonumberFunction);
env.set("unpack", unpackFunction);
// Iterators
env.set("pairs", pairsFunction);
env.set("ipairs", ipairsFunction);
// meta table stuff
env.set("setmetatable", setmetatableFunction);
env.set("getmetatable", getmetatableFunction);
env.set("rawset", rawsetFunction);
// Error handling
env.set("error", errorFunction);
env.set("pcall", pcallFunction);
env.set("xpcall", xpcallFunction);
2025-01-15 03:26:47 +08:00
// Non-standard
env.set("tag", tagFunction);
2025-01-15 03:26:47 +08:00
env.set("tpl", tplFunction);
2024-10-11 21:34:27 +08:00
// APIs
env.set("string", stringApi);
env.set("table", tableApi);
env.set("os", osApi);
env.set("js", jsApi);
2025-01-17 17:40:47 +08:00
env.set("math", mathApi);
2025-01-15 03:26:47 +08:00
// Non-standard
env.set("each", eachFunction);
env.set("space_lua", spaceLuaApi);
2025-01-15 03:26:47 +08:00
env.set("template", templateApi);
2024-10-11 21:34:27 +08:00
return env;
}