d782d463cb
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
575 lines
19 KiB
C++
575 lines
19 KiB
C++
#include "lua_engine.h"
|
|
|
|
extern "C" {
|
|
#include "lua.h"
|
|
#include "lualib.h"
|
|
#include "lauxlib.h"
|
|
}
|
|
|
|
#include <cctype>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
namespace lua_engine {
|
|
|
|
struct Engine {
|
|
lua_State* L = nullptr;
|
|
std::vector<RowCtx*> ctx_stack;
|
|
std::vector<int> visiting_derived;
|
|
};
|
|
|
|
namespace {
|
|
|
|
Engine* g_engine = nullptr;
|
|
|
|
Engine* engine_from_state(lua_State* L) {
|
|
return *static_cast<Engine**>(lua_getextraspace(L));
|
|
}
|
|
|
|
RowCtx* current_ctx(lua_State* L) {
|
|
Engine* e = engine_from_state(L);
|
|
if (!e || e->ctx_stack.empty()) return nullptr;
|
|
return e->ctx_stack.back();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Push de cell respetando tipo declarado:
|
|
// Int/Float -> number (integer si exacto)
|
|
// Bool -> boolean (true/false/1/0); en otro caso push string
|
|
// Date/String/Json/Auto -> string
|
|
// Si types_orig == nullptr -> heuristica: parse_number; si parsea -> number.
|
|
// ---------------------------------------------------------------------------
|
|
void push_typed(lua_State* L, const char* v, data_table::ColumnType t) {
|
|
if (!v || !*v) { lua_pushnil(L); return; }
|
|
using data_table::ColumnType;
|
|
using data_table::parse_number;
|
|
if (t == ColumnType::Int) {
|
|
double d;
|
|
if (parse_number(v, d)) {
|
|
long long iv = (long long)d;
|
|
if ((double)iv == d) lua_pushinteger(L, (lua_Integer)iv);
|
|
else lua_pushnumber (L, (lua_Number)d);
|
|
} else lua_pushstring(L, v);
|
|
return;
|
|
}
|
|
if (t == ColumnType::Float) {
|
|
double d;
|
|
if (parse_number(v, d)) {
|
|
long long iv = (long long)d;
|
|
if ((double)iv == d) lua_pushinteger(L, (lua_Integer)iv);
|
|
else lua_pushnumber (L, (lua_Number)d);
|
|
} else lua_pushstring(L, v);
|
|
return;
|
|
}
|
|
if (t == ColumnType::Bool) {
|
|
if (std::strcmp(v, "true") == 0 || std::strcmp(v, "1") == 0) lua_pushboolean(L, 1);
|
|
else if (std::strcmp(v, "false") == 0 || std::strcmp(v, "0") == 0) lua_pushboolean(L, 0);
|
|
else lua_pushstring(L, v);
|
|
return;
|
|
}
|
|
if (t == ColumnType::Auto) {
|
|
// Sin tipo declarado: heuristica. parse_number -> number, else string.
|
|
double d;
|
|
if (parse_number(v, d)) {
|
|
long long iv = (long long)d;
|
|
if ((double)iv == d) lua_pushinteger(L, (lua_Integer)iv);
|
|
else lua_pushnumber (L, (lua_Number)d);
|
|
} else lua_pushstring(L, v);
|
|
return;
|
|
}
|
|
// String / Date / Json
|
|
lua_pushstring(L, v);
|
|
}
|
|
|
|
// Fwd: para recursion en row_index.
|
|
std::string eval_internal(Engine* e, int id, const RowCtx& ctx, std::string* err_out);
|
|
|
|
int row_index(lua_State* L) {
|
|
Engine* eng = engine_from_state(L);
|
|
RowCtx* ctx = current_ctx(L);
|
|
if (!ctx) { lua_pushnil(L); return 1; }
|
|
|
|
using data_table::ColumnType;
|
|
auto get_orig_type = [&](int c) -> ColumnType {
|
|
if (ctx->types_orig && c < ctx->n_types_orig) return ctx->types_orig[c];
|
|
return ColumnType::Auto;
|
|
};
|
|
|
|
if (lua_type(L, 2) == LUA_TSTRING) {
|
|
const char* key = lua_tostring(L, 2);
|
|
if (ctx->name_to_col) {
|
|
auto it = ctx->name_to_col->find(key);
|
|
if (it != ctx->name_to_col->end()) {
|
|
int col = it->second;
|
|
push_typed(L, ctx->cells[ctx->row * ctx->orig_cols + col], get_orig_type(col));
|
|
return 1;
|
|
}
|
|
}
|
|
if (ctx->derived_name_to_idx && ctx->derived) {
|
|
auto it = ctx->derived_name_to_idx->find(key);
|
|
if (it != ctx->derived_name_to_idx->end()) {
|
|
int didx = it->second;
|
|
if (didx < 0 || didx >= (int)ctx->derived->size()) {
|
|
lua_pushnil(L); return 1;
|
|
}
|
|
// cycle check
|
|
for (int v : eng->visiting_derived) {
|
|
if (v == didx) { lua_pushnil(L); return 1; }
|
|
}
|
|
const auto& d = (*ctx->derived)[didx];
|
|
if (d.formula.empty()) {
|
|
// retipo puro
|
|
if (d.source_col < 0 || d.source_col >= ctx->orig_cols) {
|
|
lua_pushnil(L); return 1;
|
|
}
|
|
push_typed(L, ctx->cells[ctx->row * ctx->orig_cols + d.source_col], d.type);
|
|
} else if (d.lua_id < 0) {
|
|
lua_pushnil(L);
|
|
} else {
|
|
eng->visiting_derived.push_back(didx);
|
|
std::string err;
|
|
std::string r = eval_internal(eng, d.lua_id, *ctx, &err);
|
|
eng->visiting_derived.pop_back();
|
|
push_typed(L, r.c_str(), d.type);
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
if (lua_type(L, 2) == LUA_TNUMBER) {
|
|
int idx = (int)lua_tointeger(L, 2);
|
|
if (idx >= 1 && idx <= ctx->orig_cols) {
|
|
int col = idx - 1;
|
|
push_typed(L, ctx->cells[ctx->row * ctx->orig_cols + col], get_orig_type(col));
|
|
return 1;
|
|
}
|
|
}
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
// --- fn.* builtins ---
|
|
int b_upper(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
std::string out(s);
|
|
for (char& c : out) if (c >= 'a' && c <= 'z') c -= 32;
|
|
lua_pushlstring(L, out.data(), out.size());
|
|
return 1;
|
|
}
|
|
int b_lower(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
std::string out(s);
|
|
for (char& c : out) if (c >= 'A' && c <= 'Z') c += 32;
|
|
lua_pushlstring(L, out.data(), out.size());
|
|
return 1;
|
|
}
|
|
int b_length(lua_State* L) {
|
|
if (lua_isnil(L, 1)) { lua_pushinteger(L, 0); return 1; }
|
|
const char* s = luaL_checkstring(L, 1);
|
|
lua_pushinteger(L, (lua_Integer)std::strlen(s));
|
|
return 1;
|
|
}
|
|
int b_substring(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
int start = (int)luaL_checkinteger(L, 2);
|
|
int len = (int)luaL_optinteger(L, 3, -1);
|
|
int slen = (int)std::strlen(s);
|
|
if (start < 1) start = 1;
|
|
if (start > slen) { lua_pushlstring(L, "", 0); return 1; }
|
|
int from = start - 1;
|
|
int take = (len < 0) ? slen - from : len;
|
|
if (from + take > slen) take = slen - from;
|
|
lua_pushlstring(L, s + from, take);
|
|
return 1;
|
|
}
|
|
int b_contains(lua_State* L) {
|
|
const char* h = luaL_checkstring(L, 1);
|
|
const char* n = luaL_checkstring(L, 2);
|
|
lua_pushboolean(L, std::strstr(h, n) != nullptr);
|
|
return 1;
|
|
}
|
|
int b_starts_with(lua_State* L) {
|
|
const char* h = luaL_checkstring(L, 1);
|
|
const char* n = luaL_checkstring(L, 2);
|
|
size_t ln = std::strlen(n);
|
|
lua_pushboolean(L, std::strncmp(h, n, ln) == 0);
|
|
return 1;
|
|
}
|
|
int b_ends_with(lua_State* L) {
|
|
const char* h = luaL_checkstring(L, 1);
|
|
const char* n = luaL_checkstring(L, 2);
|
|
size_t lh = std::strlen(h), ln = std::strlen(n);
|
|
lua_pushboolean(L, ln <= lh && std::strcmp(h + lh - ln, n) == 0);
|
|
return 1;
|
|
}
|
|
int b_replace(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
const char* find = luaL_checkstring(L, 2);
|
|
const char* repl = luaL_checkstring(L, 3);
|
|
std::string out;
|
|
size_t flen = std::strlen(find);
|
|
if (flen == 0) { lua_pushstring(L, s); return 1; }
|
|
for (const char* p = s; *p; ) {
|
|
if (std::strncmp(p, find, flen) == 0) { out += repl; p += flen; }
|
|
else { out += *p++; }
|
|
}
|
|
lua_pushlstring(L, out.data(), out.size());
|
|
return 1;
|
|
}
|
|
int b_trim(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') ++s;
|
|
const char* e = s + std::strlen(s);
|
|
while (e > s && (e[-1] == ' ' || e[-1] == '\t' || e[-1] == '\n' || e[-1] == '\r')) --e;
|
|
lua_pushlstring(L, s, e - s);
|
|
return 1;
|
|
}
|
|
int b_concat(lua_State* L) {
|
|
int n = lua_gettop(L);
|
|
std::string out;
|
|
for (int i = 1; i <= n; ++i) {
|
|
size_t sl = 0;
|
|
const char* s = luaL_tolstring(L, i, &sl);
|
|
out.append(s, sl);
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_pushlstring(L, out.data(), out.size());
|
|
return 1;
|
|
}
|
|
int b_to_number(lua_State* L) {
|
|
if (lua_isnumber(L, 1)) { lua_pushvalue(L, 1); return 1; }
|
|
const char* s = luaL_checkstring(L, 1);
|
|
char* end = nullptr;
|
|
double v = std::strtod(s, &end);
|
|
if (end == s) { lua_pushnil(L); return 1; }
|
|
lua_pushnumber(L, v);
|
|
return 1;
|
|
}
|
|
int b_to_string(lua_State* L) { luaL_tolstring(L, 1, nullptr); return 1; }
|
|
int b_to_bool(lua_State* L) {
|
|
if (lua_isboolean(L, 1)) { lua_pushvalue(L, 1); return 1; }
|
|
const char* s = luaL_optstring(L, 1, "");
|
|
lua_pushboolean(L, std::strcmp(s, "true") == 0 || std::strcmp(s, "1") == 0);
|
|
return 1;
|
|
}
|
|
int b_is_null(lua_State* L) { lua_pushboolean(L, lua_isnil(L, 1)); return 1; }
|
|
int b_is_empty(lua_State* L) {
|
|
if (lua_isnil(L, 1)) { lua_pushboolean(L, 1); return 1; }
|
|
const char* s = luaL_optstring(L, 1, "");
|
|
lua_pushboolean(L, *s == 0);
|
|
return 1;
|
|
}
|
|
int b_coalesce(lua_State* L) {
|
|
int n = lua_gettop(L);
|
|
for (int i = 1; i <= n; ++i) {
|
|
if (!lua_isnil(L, i)) { lua_pushvalue(L, i); return 1; }
|
|
}
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
int b_parse_date(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
if (std::strlen(s) < 10) { lua_pushnil(L); return 1; }
|
|
int y, m, d;
|
|
if (std::sscanf(s, "%d-%d-%d", &y, &m, &d) != 3) { lua_pushnil(L); return 1; }
|
|
lua_createtable(L, 0, 3);
|
|
lua_pushinteger(L, y); lua_setfield(L, -2, "year");
|
|
lua_pushinteger(L, m); lua_setfield(L, -2, "month");
|
|
lua_pushinteger(L, d); lua_setfield(L, -2, "day");
|
|
return 1;
|
|
}
|
|
int b_year(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
int y; if (std::sscanf(s, "%d", &y) != 1) { lua_pushnil(L); return 1; }
|
|
lua_pushinteger(L, y); return 1;
|
|
}
|
|
int b_month(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
int y, m; if (std::sscanf(s, "%d-%d", &y, &m) != 2) { lua_pushnil(L); return 1; }
|
|
lua_pushinteger(L, m); return 1;
|
|
}
|
|
int b_day(lua_State* L) {
|
|
const char* s = luaL_checkstring(L, 1);
|
|
int y, m, d; if (std::sscanf(s, "%d-%d-%d", &y, &m, &d) != 3) { lua_pushnil(L); return 1; }
|
|
lua_pushinteger(L, d); return 1;
|
|
}
|
|
|
|
void apply_sandbox(lua_State* L) {
|
|
const char* nuke[] = { "io", "require", "loadfile", "dofile", "load",
|
|
"package", "debug", nullptr };
|
|
for (int i = 0; nuke[i]; ++i) {
|
|
lua_pushnil(L);
|
|
lua_setglobal(L, nuke[i]);
|
|
}
|
|
lua_getglobal(L, "os");
|
|
if (lua_istable(L, -1)) {
|
|
lua_createtable(L, 0, 4);
|
|
const char* keep[] = {"date", "time", "difftime", "clock", nullptr};
|
|
for (int i = 0; keep[i]; ++i) {
|
|
lua_getfield(L, -2, keep[i]);
|
|
lua_setfield(L, -2, keep[i]);
|
|
}
|
|
lua_setglobal(L, "os");
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
void register_builtins(lua_State* L) {
|
|
lua_createtable(L, 0, 24);
|
|
#define R(name, fn) lua_pushcfunction(L, fn); lua_setfield(L, -2, name);
|
|
R("upper", b_upper);
|
|
R("lower", b_lower);
|
|
R("length", b_length);
|
|
R("substring", b_substring);
|
|
R("contains", b_contains);
|
|
R("starts_with", b_starts_with);
|
|
R("ends_with", b_ends_with);
|
|
R("replace", b_replace);
|
|
R("trim", b_trim);
|
|
R("concat", b_concat);
|
|
R("to_number", b_to_number);
|
|
R("to_string", b_to_string);
|
|
R("to_bool", b_to_bool);
|
|
R("is_null", b_is_null);
|
|
R("is_empty", b_is_empty);
|
|
R("coalesce", b_coalesce);
|
|
R("parse_date", b_parse_date);
|
|
R("year", b_year);
|
|
R("month", b_month);
|
|
R("day", b_day);
|
|
#undef R
|
|
lua_setglobal(L, "fn");
|
|
}
|
|
|
|
void install_row_metatable(lua_State* L) {
|
|
luaL_newmetatable(L, "fn_row_meta");
|
|
lua_pushcfunction(L, row_index);
|
|
lua_setfield(L, -2, "__index");
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Preprocesador: [col] -> row["col"] respetando strings y comentarios.
|
|
// Auto-prepend `return` si la formula es expresion suelta.
|
|
// ---------------------------------------------------------------------------
|
|
bool ident_start(unsigned char c) {
|
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_' || c >= 0x80;
|
|
}
|
|
// Para nombres de cols dentro de [name]: permite espacios para "col with space"
|
|
// y '.' para futuro `alias.col` post-join (fase 9 — issue 0078).
|
|
bool ident_cont(unsigned char c) {
|
|
return ident_start(c) || (c >= '0' && c <= '9') || c == ' ' || c == '.';
|
|
}
|
|
// Para boundary de keywords Lua: NO permite espacio.
|
|
bool word_char(unsigned char c) {
|
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
|
(c >= '0' && c <= '9') || c == '_' || c >= 0x80;
|
|
}
|
|
|
|
bool kw_at(const std::string& s, size_t i, const char* kw) {
|
|
size_t k = std::strlen(kw);
|
|
if (i + k > s.size()) return false;
|
|
if (s.compare(i, k, kw) != 0) return false;
|
|
if (i + k == s.size()) return true;
|
|
unsigned char nc = (unsigned char)s[i + k];
|
|
return !word_char(nc);
|
|
}
|
|
|
|
bool needs_auto_return(const std::string& body) {
|
|
size_t i = 0;
|
|
while (i < body.size()) {
|
|
char c = body[i];
|
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { ++i; continue; }
|
|
// skip short comment
|
|
if (c == '-' && i + 1 < body.size() && body[i+1] == '-') {
|
|
// long comment?
|
|
if (i + 3 < body.size() && body[i+2] == '[' && body[i+3] == '[') {
|
|
size_t j = i + 4;
|
|
while (j + 1 < body.size() && !(body[j] == ']' && body[j+1] == ']')) ++j;
|
|
i = (j + 1 < body.size()) ? j + 2 : body.size();
|
|
continue;
|
|
}
|
|
while (i < body.size() && body[i] != '\n') ++i;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (i >= body.size()) return false;
|
|
const char* kws[] = {"return","if","for","while","do","local","repeat","function", nullptr};
|
|
for (int k = 0; kws[k]; ++k) if (kw_at(body, i, kws[k])) return false;
|
|
return true;
|
|
}
|
|
|
|
std::string brackets_pass(const std::string& src) {
|
|
std::string out;
|
|
out.reserve(src.size() + 16);
|
|
size_t i = 0;
|
|
while (i < src.size()) {
|
|
char c = src[i];
|
|
// strings
|
|
if (c == '"' || c == '\'') {
|
|
char q = c;
|
|
out += c; ++i;
|
|
while (i < src.size()) {
|
|
char d = src[i];
|
|
out += d; ++i;
|
|
if (d == '\\' && i < src.size()) { out += src[i++]; continue; }
|
|
if (d == q) break;
|
|
if (d == '\n') break;
|
|
}
|
|
continue;
|
|
}
|
|
// comentario corto / largo
|
|
if (c == '-' && i + 1 < src.size() && src[i+1] == '-') {
|
|
// long: --[[ ... ]]
|
|
if (i + 3 < src.size() && src[i+2] == '[' && src[i+3] == '[') {
|
|
out.append(src, i, 4); i += 4;
|
|
while (i + 1 < src.size() && !(src[i] == ']' && src[i+1] == ']')) {
|
|
out += src[i++];
|
|
}
|
|
if (i + 1 < src.size()) { out += src[i++]; out += src[i++]; }
|
|
continue;
|
|
}
|
|
// short
|
|
while (i < src.size() && src[i] != '\n') { out += src[i++]; }
|
|
continue;
|
|
}
|
|
// long string [[ ... ]]
|
|
if (c == '[' && i + 1 < src.size() && src[i+1] == '[') {
|
|
out.append(src, i, 2); i += 2;
|
|
while (i + 1 < src.size() && !(src[i] == ']' && src[i+1] == ']')) {
|
|
out += src[i++];
|
|
}
|
|
if (i + 1 < src.size()) { out += src[i++]; out += src[i++]; }
|
|
continue;
|
|
}
|
|
// bracket col-ref [name]
|
|
if (c == '[') {
|
|
// peek if next is valid ident_start
|
|
if (i + 1 < src.size() && ident_start((unsigned char)src[i+1])) {
|
|
size_t j = i + 1;
|
|
while (j < src.size() && src[j] != ']' && src[j] != '\n') {
|
|
if (!ident_cont((unsigned char)src[j])) { j = std::string::npos; break; }
|
|
++j;
|
|
}
|
|
if (j != std::string::npos && j < src.size() && src[j] == ']') {
|
|
std::string name(src, i + 1, j - i - 1);
|
|
// trim trailing space
|
|
while (!name.empty() && name.back() == ' ') name.pop_back();
|
|
out += "row[\"";
|
|
out += name;
|
|
out += "\"]";
|
|
i = j + 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
out += c;
|
|
++i;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
} // anon
|
|
|
|
std::string preprocess(const std::string& body) {
|
|
std::string pre = brackets_pass(body);
|
|
if (needs_auto_return(pre)) return "return " + pre;
|
|
return pre;
|
|
}
|
|
|
|
namespace {
|
|
|
|
std::string eval_internal(Engine* e, int id, const RowCtx& ctx, std::string* err_out) {
|
|
if (!e || !e->L || id < 0) {
|
|
if (err_out) *err_out = "invalid handle";
|
|
return "";
|
|
}
|
|
lua_State* L = e->L;
|
|
e->ctx_stack.push_back(const_cast<RowCtx*>(&ctx));
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, id);
|
|
lua_newuserdata(L, 1);
|
|
luaL_setmetatable(L, "fn_row_meta");
|
|
int rc = lua_pcall(L, 1, 1, 0);
|
|
e->ctx_stack.pop_back();
|
|
if (rc != LUA_OK) {
|
|
if (err_out) *err_out = lua_tostring(L, -1) ? lua_tostring(L, -1) : "runtime error";
|
|
lua_pop(L, 1);
|
|
return "";
|
|
}
|
|
std::string out;
|
|
if (lua_isnil(L, -1)) out = "";
|
|
else {
|
|
size_t n = 0;
|
|
const char* s = luaL_tolstring(L, -1, &n);
|
|
out.assign(s, n);
|
|
lua_pop(L, 1);
|
|
}
|
|
lua_pop(L, 1);
|
|
return out;
|
|
}
|
|
|
|
} // anon
|
|
|
|
Engine* get() {
|
|
if (g_engine) return g_engine;
|
|
g_engine = new Engine();
|
|
g_engine->L = luaL_newstate();
|
|
luaL_openlibs(g_engine->L);
|
|
*static_cast<Engine**>(lua_getextraspace(g_engine->L)) = g_engine;
|
|
apply_sandbox(g_engine->L);
|
|
register_builtins(g_engine->L);
|
|
install_row_metatable(g_engine->L);
|
|
return g_engine;
|
|
}
|
|
|
|
void shutdown() {
|
|
if (!g_engine) return;
|
|
lua_close(g_engine->L);
|
|
delete g_engine;
|
|
g_engine = nullptr;
|
|
}
|
|
|
|
int compile(Engine* e, const std::string& body, std::string* err_out) {
|
|
if (!e || !e->L) { if (err_out) *err_out = "engine null"; return -1; }
|
|
lua_State* L = e->L;
|
|
std::string final_body = preprocess(body);
|
|
std::string wrapped = "return function(row)\n" + final_body + "\nend";
|
|
if (luaL_loadbufferx(L, wrapped.data(), wrapped.size(), "formula", "t") != LUA_OK) {
|
|
if (err_out) *err_out = lua_tostring(L, -1) ? lua_tostring(L, -1) : "parse error";
|
|
lua_pop(L, 1);
|
|
return -1;
|
|
}
|
|
if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
|
|
if (err_out) *err_out = lua_tostring(L, -1) ? lua_tostring(L, -1) : "compile error";
|
|
lua_pop(L, 1);
|
|
return -1;
|
|
}
|
|
if (!lua_isfunction(L, -1)) {
|
|
if (err_out) *err_out = "formula did not produce a function";
|
|
lua_pop(L, 1);
|
|
return -1;
|
|
}
|
|
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
return ref;
|
|
}
|
|
|
|
void release(Engine* e, int id) {
|
|
if (!e || !e->L || id < 0) return;
|
|
luaL_unref(e->L, LUA_REGISTRYINDEX, id);
|
|
}
|
|
|
|
std::string eval(Engine* e, int id, const RowCtx& ctx, std::string* err_out) {
|
|
return eval_internal(e, id, ctx, err_out);
|
|
}
|
|
|
|
lua_State* raw_state() {
|
|
Engine* e = get();
|
|
return e ? e->L : nullptr;
|
|
}
|
|
|
|
} // namespace lua_engine
|