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 { 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); }, ), });