silverbullet/common/space_lua/stdlib/space_lua.ts

139 lines
3.9 KiB
TypeScript

import { parseExpressionString } from "$common/space_lua/parse.ts";
import type { LuaExpression } from "$common/space_lua/ast.ts";
import { evalExpression } from "$common/space_lua/eval.ts";
import {
LuaBuiltinFunction,
LuaEnv,
LuaRuntimeError,
type LuaStackFrame,
LuaTable,
luaToString,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
/**
* These are Space Lua specific functions that are available to all scripts, but are not part of the standard Lua language.
*/
/**
* Helper function to create an augmented environment
*/
function createAugmentedEnv(
sf: LuaStackFrame,
envAugmentation?: LuaTable,
): LuaEnv {
const globalEnv = sf.threadLocal.get("_GLOBAL");
if (!globalEnv) {
throw new Error("_GLOBAL not defined");
}
const env = new LuaEnv(globalEnv);
if (envAugmentation) {
env.setLocal("_", envAugmentation);
for (const key of envAugmentation.keys()) {
env.setLocal(key, envAugmentation.rawGet(key));
}
}
return env;
}
/**
* Interpolates a string with lua expressions and returns the result.
*
* @param sf - The current space_lua state.
* @param template - The template string to interpolate.
* @param envAugmentation - An optional environment to augment the global environment with.
* @returns The interpolated string.
*/
export async function interpolateLuaString(
sf: LuaStackFrame,
template: string,
envAugmentation?: LuaTable,
): Promise<string> {
let result = "";
let currentIndex = 0;
while (true) {
const startIndex = template.indexOf("${", currentIndex);
if (startIndex === -1) {
result += template.slice(currentIndex);
break;
}
result += template.slice(currentIndex, startIndex);
// Find matching closing brace by counting nesting
let nestLevel = 1;
let endIndex = startIndex + 2;
while (nestLevel > 0 && endIndex < template.length) {
if (template[endIndex] === "{") {
nestLevel++;
} else if (template[endIndex] === "}") {
nestLevel--;
}
if (nestLevel > 0) {
endIndex++;
}
}
if (nestLevel > 0) {
throw new LuaRuntimeError("Unclosed interpolation expression", sf);
}
const expr = template.slice(startIndex + 2, endIndex);
try {
const parsedExpr = parseExpressionString(expr);
const env = createAugmentedEnv(sf, envAugmentation);
const luaResult = luaValueToJS(
await evalExpression(parsedExpr, env, sf),
);
result += luaToString(luaResult);
} catch (e: any) {
throw new LuaRuntimeError(
`Error evaluating "${expr}": ${e.message}`,
sf,
);
}
currentIndex = endIndex + 1;
}
return result;
}
export const spaceLuaApi = new LuaTable({
/**
* Parses a lua expression and returns the parsed expression.
*
* @param sf - The current space_lua state.
* @param luaExpression - The lua expression to parse.
* @returns The parsed expression.
*/
parse_expression: new LuaBuiltinFunction(
(_sf, luaExpression: string) => {
return parseExpressionString(luaExpression);
},
),
/**
* Evaluates a parsed lua expression and returns the result.
*
* @param sf - The current space_lua state.
* @param parsedExpr - The parsed lua expression to evaluate.
* @param envAugmentation - An optional environment to augment the global environment with.
* @returns The result of the evaluated expression.
*/
eval_expression: new LuaBuiltinFunction(
async (sf, parsedExpr: LuaExpression, envAugmentation?: LuaTable) => {
const env = createAugmentedEnv(sf, envAugmentation);
return luaValueToJS(await evalExpression(parsedExpr, env, sf));
},
),
/**
* Interpolates a string with lua expressions and returns the result.
*/
interpolate: new LuaBuiltinFunction(
(sf, template: string, envAugmentation?: LuaTable) => {
return interpolateLuaString(sf, template, envAugmentation);
},
),
});