@@ -208,14 +208,24 @@ void Module::unload() {
208208bool Module::compileScript (const Ltg::ScriptFilePathName& vFilePathName, Ltg::ErrorContainer& vOutErrors) {
209209 try {
210210 m_luaPtr->script_file (vFilePathName);
211+ // validate the required entry points exist — DO NOT call them here (see compileScriptCode
212+ // for the rationale: ScriptingEngine's loop calls them at the right time per source file).
211213 bool res = true ;
212214 sol::function parse = (*m_luaPtr)[" parse" ];
213215 if (!parse.valid ()) {
214216 LogVarLightError (" Lua: %s" , " the lua function parse(buffer) is missing" );
215217 res = false ;
216218 }
217- res &= callScriptStart (vOutErrors);
218- res &= callScriptEnd (vOutErrors);
219+ sol::function startFile = (*m_luaPtr)[" startFile" ];
220+ if (!startFile.valid ()) {
221+ LogVarLightError (" Lua: %s" , " the lua function startFile() is missing" );
222+ res = false ;
223+ }
224+ sol::function endFile = (*m_luaPtr)[" endFile" ];
225+ if (!endFile.valid ()) {
226+ LogVarLightError (" Lua: %s" , " the lua function endFile() is missing" );
227+ res = false ;
228+ }
219229 return res;
220230 } catch (const sol::error& ex) {
221231 LogVarError (" Lua: Error in the Lua script : %s" , ex.what ());
@@ -232,14 +242,25 @@ bool Module::compileScriptCode(const std::string& aCode, Ltg::ErrorContainer& vO
232242 // pass an explicit chunk name so sol2's error messages identify our project script
233243 // (otherwise sol2 uses the first line of code as the chunk name, which breaks routing).
234244 m_luaPtr->script (aCode, Ltg::sc_PROJECT_SCRIPT_CHUNK); // compile + run the chunk (defines parse/startFile/endFile)
245+ // validate the required entry points exist — DO NOT call them here. ScriptingEngine's main
246+ // loop is responsible for calling startFile/parse/endFile at the proper times; calling them
247+ // here would double-execute user code (duplicate logs / state init).
235248 bool res = true ;
236249 sol::function parse = (*m_luaPtr)[" parse" ];
237250 if (!parse.valid ()) {
238251 LogVarLightError (" Lua: %s" , " the lua function parse(buffer) is missing" );
239252 res = false ;
240253 }
241- res &= callScriptStart (vOutErrors);
242- res &= callScriptEnd (vOutErrors);
254+ sol::function startFile = (*m_luaPtr)[" startFile" ];
255+ if (!startFile.valid ()) {
256+ LogVarLightError (" Lua: %s" , " the lua function startFile() is missing" );
257+ res = false ;
258+ }
259+ sol::function endFile = (*m_luaPtr)[" endFile" ];
260+ if (!endFile.valid ()) {
261+ LogVarLightError (" Lua: %s" , " the lua function endFile() is missing" );
262+ res = false ;
263+ }
243264 return res;
244265 } catch (const sol::error& ex) {
245266 Ltg::ScriptingError err;
@@ -261,6 +282,94 @@ bool Module::compileScriptCode(const std::string& aCode, Ltg::ErrorContainer& vO
261282 return false ;
262283}
263284
285+ void Module::m_ensureCompletionState () {
286+ if (m_completionLuaPtr != nullptr ) {
287+ return ;
288+ }
289+ // dedicated state that mirrors the analysis state's bindings but never executes user code.
290+ // we instantiate LuaDatasModel directly (skipping the create() factory which would null it out
291+ // when no IDatasModel is bound) because we only need the usertype methods to be REGISTERED in the
292+ // metatable for introspection — none of them is ever actually called from the completion state.
293+ m_completionLuaPtr = std::unique_ptr<sol::state>(new sol::state ());
294+ m_completionLuaPtr->open_libraries (sol::lib::base);
295+ m_completionLuaPtr->open_libraries (sol::lib::package);
296+ m_completionLuaPtr->open_libraries (sol::lib::coroutine);
297+ m_completionLuaPtr->open_libraries (sol::lib::string);
298+ m_completionLuaPtr->open_libraries (sol::lib::os);
299+ m_completionLuaPtr->open_libraries (sol::lib::math);
300+ m_completionLuaPtr->open_libraries (sol::lib::table);
301+ m_completionLuaPtr->open_libraries (sol::lib::debug);
302+ m_completionLuaPtr->open_libraries (sol::lib::bit32);
303+ m_completionLuaPtr->open_libraries (sol::lib::io);
304+ m_completionLuaPtr->open_libraries (sol::lib::ffi);
305+ m_completionLuaPtr->open_libraries (sol::lib::jit);
306+
307+ // clang-format off
308+ m_completionLuaPtr->new_usertype <LuaDatasModel>(
309+ " LuaDatasModel" , sol::constructors<std::shared_ptr<LuaDatasModel>()>(),
310+ " stringToEpoch" , &LuaDatasModel::luaModuleStringToEpoch,
311+ " epochToString" , &LuaDatasModel::luaModuleEpochToString,
312+ " addSignalTag" , &LuaDatasModel::luaModuleAddSignalTag,
313+ " addSignalStatus" , &LuaDatasModel::luaModuleAddSignalStatus,
314+ " addSignalValue" , sol::overload (
315+ &LuaDatasModel::luaModuleAddSignalValue,
316+ &LuaDatasModel::luaModuleAddSignalValueWithDesc),
317+ " addSignalStartZone" , &LuaDatasModel::luaModuleAddSignalStartZone,
318+ " addSignalEndZone" , &LuaDatasModel::luaModuleAddSignalEndZone,
319+ " logInfo" , &LuaDatasModel::luaModuleLogInfo,
320+ " logWarning" , &LuaDatasModel::luaModuleLogWarning,
321+ " logError" , &LuaDatasModel::luaModuleLogError,
322+ " logDebug" , &LuaDatasModel::luaModuleLogDebug,
323+ " getRowIndex" , &LuaDatasModel::luaModuleGetRowIndex,
324+ " getRowCount" , &LuaDatasModel::luaModuleGetRowCount);
325+ // clang-format on
326+
327+ // empty stub instance: ctor is public, no IDatasModel is required for introspection.
328+ m_completionDatasModelPtr = std::make_shared<LuaDatasModel>();
329+ (*m_completionLuaPtr)[" ltg" ] = m_completionDatasModelPtr;
330+ }
331+
332+ void Module::m_iterateLuaTable (lua_State* apLua, int aTableIndex, std::vector<Ltg::CompletionEntry>& aoEntries) {
333+ // aTableIndex must be an absolute stack index (lua_next manipulates the stack, breaking relative refs).
334+ lua_pushnil (apLua);
335+ while (lua_next (apLua, aTableIndex) != 0 ) {
336+ // -2 is the key, -1 is the value
337+ if (lua_type (apLua, -2 ) == LUA_TSTRING ) {
338+ Ltg::CompletionEntry entry;
339+ entry.name = lua_tostring (apLua, -2 );
340+ entry.type = lua_typename (apLua, lua_type (apLua, -1 ));
341+ aoEntries.push_back (entry);
342+ }
343+ lua_pop (apLua, 1 ); // pop value, keep key for next iter
344+ }
345+ }
346+
347+ void Module::getCompletionEntries (const std::string& aTarget, std::vector<Ltg::CompletionEntry>& aoEntries) {
348+ if (aTarget.empty ()) {
349+ return ;
350+ }
351+ m_ensureCompletionState ();
352+ lua_State* L = m_completionLuaPtr->lua_state ();
353+ const int topBefore = lua_gettop (L);
354+
355+ lua_getglobal (L, aTarget.c_str ());
356+ const int targetType = lua_type (L, -1 );
357+ if (targetType == LUA_TTABLE ) {
358+ // plain table (Lua stdlib namespace, user table) — iterate directly
359+ m_iterateLuaTable (L, lua_gettop (L), aoEntries);
360+ } else if (targetType == LUA_TUSERDATA ) {
361+ // sol2 usertype — methods are stored in the metatable's __index field, hop through it
362+ if (lua_getmetatable (L, -1 ) != 0 ) {
363+ lua_getfield (L, -1 , " __index" );
364+ if (lua_type (L, -1 ) == LUA_TTABLE ) {
365+ m_iterateLuaTable (L, lua_gettop (L), aoEntries);
366+ }
367+ lua_pop (L, 2 ); // __index + metatable
368+ }
369+ }
370+ lua_settop (L, topBefore);
371+ }
372+
264373bool Module::callScriptStart (Ltg::ErrorContainer& vOutErrors) {
265374 sol::protected_function startFile = (*m_luaPtr)[" startFile" ];
266375 if (!startFile.valid ()) {
0 commit comments