From 369cb617ce0fa165b44748df2ab5efbac0a41dbe Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Mon, 16 Aug 2021 14:28:24 +0200
Subject: [PATCH] [WIP] SCRIPT: Begin the module validation process

---
 src/Lib/Script/CRTPLuaScriptObject.cc | 90 ++++++++++++++++++++-------
 src/Lib/Script/CRTPLuaScriptObject.hh |  2 +
 src/Lib/Script/LuaContext.cc          | 35 ++++++++++-
 src/Lib/Script/LuaContext.hh          | 10 ++-
 src/Lib/Script/ScriptStore.cc         |  6 ++
 src/Lib/Script/ScriptStore.hh         |  2 +
 src/VivyCli.cc                        |  1 +
 utils/lua/sample-spec.module          | 20 +++---
 8 files changed, 131 insertions(+), 35 deletions(-)

diff --git a/src/Lib/Script/CRTPLuaScriptObject.cc b/src/Lib/Script/CRTPLuaScriptObject.cc
index 21e83d1e..522adad5 100644
--- a/src/Lib/Script/CRTPLuaScriptObject.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject.cc
@@ -1,5 +1,7 @@
 #include "CRTPLuaScriptObject.hh"
 #include "lua.hpp"
+#include <QFileInfo>
+#include <QString>
 
 using namespace Vivy::Script;
 
@@ -63,64 +65,52 @@ ModuleDeclaration::setImplementationFile(lua_State *const L) noexcept
 int
 ModuleDeclaration::setImports(lua_State *const L) noexcept
 {
-    auto *const self    = ModuleDeclaration::CHECK(L, 1);
-    auto *const context = LuaContext::getContext(L);
-    IterateOverArray(L, 2, [&context, self, L]() noexcept -> bool {
+    auto *const self = ModuleDeclaration::CHECK(L, 1);
+    IterateOverArray(L, 2, [self, L]() noexcept -> bool {
         std::string import(CHECK_STRING_VIEW(L, -1));
         Utils::trim(import);
         self->importNames.emplace_back(import);
-        err(context) << "Add import " << import << " for module " << self->moduleName << "\n";
         return false; // Value was not popped from stack!
     });
-    err(context) << "Imports done!\n";
     LUA_RETURN_NOTHING(L);
 }
 
 int
 ModuleDeclaration::setOptions(lua_State *const L) noexcept
 {
-    auto *const self    = ModuleDeclaration::CHECK(L, 1);
-    auto *const context = LuaContext::getContext(L);
-    IterateOverArray(L, 2, [&context, self, L]() noexcept -> bool {
+    auto *const self = ModuleDeclaration::CHECK(L, 1);
+    IterateOverArray(L, 2, [self, L]() noexcept -> bool {
         OptionDeclaration *const opt = OptionDeclaration::CHECK(L, -1);
         self->moduleOptions.emplace_back(opt);
-        err(context) << "Add option for module " << self->moduleName << "\n";
         self->addUserValue(L, 1); // Tie the opt to the module
         return true;              // The value was popped from the stack
     });
-    err(context) << "Options done!\n";
     LUA_RETURN_NOTHING(L);
 }
 
 int
 ModuleDeclaration::setFunctions(lua_State *const L) noexcept
 {
-    auto *const self    = ModuleDeclaration::CHECK(L, 1);
-    auto *const context = LuaContext::getContext(L);
-    IterateOverArray(L, 2, [&context, self, L]() noexcept -> bool {
+    auto *const self = ModuleDeclaration::CHECK(L, 1);
+    IterateOverArray(L, 2, [self, L]() noexcept -> bool {
         FunctionDeclaration *const fun = FunctionDeclaration::CHECK(L, -1);
         self->moduleFunctions.emplace_back(fun);
-        err(context) << "Add function " << fun->name << " to module " << self->moduleName << "\n";
         self->addUserValue(L, 1); // Tie the function to the module
         return true;              // The value was popped from the stack
     });
-    err(context) << "Functions done!\n";
     LUA_RETURN_NOTHING(L);
 }
 
 int
 ModuleDeclaration::setJobs(lua_State *const L) noexcept
 {
-    auto *const self    = ModuleDeclaration::CHECK(L, 1);
-    auto *const context = LuaContext::getContext(L);
-    IterateOverArray(L, 2, [&context, self, L]() noexcept -> bool {
+    auto *const self = ModuleDeclaration::CHECK(L, 1);
+    IterateOverArray(L, 2, [self, L]() noexcept -> bool {
         JobDeclaration *const job = JobDeclaration::CHECK(L, -1);
         self->moduleJobs.emplace_back(job);
-        err(context) << "Add job " << job->name << " to module " << self->moduleName << "\n";
         self->addUserValue(L, 1); // Tie the job to the module
         return true;              // The value was popped from the stack
     });
-    err(context) << "Jobs done!\n";
     LUA_RETURN_NOTHING(L);
 }
 
@@ -129,12 +119,66 @@ ModuleDeclaration::pushToRuntime(lua_State *const L) noexcept
 {
     ModuleDeclaration *const self = CHECK(L, 1);
     auto *const context           = LuaContext::getContext(L);
-    err(context) << "Register module \"" << self->moduleName << "\" in the runtime!\n";
-    lua_settop(L, 1);
-    context->registerModuleDeclaration(self);
+
+    Utils::uniqAndSort<std::string>(self->importNames);
+    if (self->validateModule(L)) {
+        err(context) << "Register module \"" << self->moduleName << "\" in the runtime!\n";
+        lua_settop(L, 1);
+
+        if (context->registerModuleDeclaration(self) == LuaContext::Code::Error)
+            context->setFailed("Failed to register module " + self->moduleName);
+    }
+
+    else
+        context->setFailed("Module " + self->moduleName + " is invalid");
+
     LUA_RETURN_NOTHING(L);
 }
 
+bool
+ModuleDeclaration::validateModule(lua_State *const L) const noexcept
+{
+    auto *const context = LuaContext::getContext(L);
+
+    // Minimal module file
+    if (moduleName.empty() || authorName.empty() || implementationFile.empty()) {
+        context->setFailed("The module does not have the minimal required information");
+        return false;
+    }
+
+    // Implementation file exists, relative to the .module file
+    {
+        QString moduleFile = context->getCurrentLuaFile().c_str();
+        QFileInfo moduleInfo(moduleFile);
+        QFileInfo fileInfo(moduleInfo.absoluteDir(), implementationFile.c_str());
+        if (!fileInfo.exists()) {
+            context->setFailed("The module " + moduleName +
+                               " does not have a valid implementation file");
+            return false;
+        }
+    }
+
+    // Imports
+    {
+        std::vector<std::string> usedImports{};
+        const auto getUsedImports = [&usedImports](auto vectorOfPtr) noexcept -> void {
+            for (const auto *ptr : vectorOfPtr) {
+                if (!ptr->parentScript.empty())
+                    usedImports.emplace_back(ptr->parentScript);
+            }
+        };
+
+        getUsedImports(moduleJobs);
+        getUsedImports(moduleFunctions);
+
+        Utils::uniqAndSort<std::string>(usedImports);
+        std::vector<std::string> unusedImports =
+            Utils::sortedSetDifference(usedImports, importNames);
+    }
+
+    return true;
+}
+
 // OptionDeclaration
 
 int
diff --git a/src/Lib/Script/CRTPLuaScriptObject.hh b/src/Lib/Script/CRTPLuaScriptObject.hh
index f255335c..5f717f7b 100644
--- a/src/Lib/Script/CRTPLuaScriptObject.hh
+++ b/src/Lib/Script/CRTPLuaScriptObject.hh
@@ -251,6 +251,8 @@ script_class (ModuleDeclaration) {
     // Will be resolved later
     std::vector<std::string> importNames{};
 
+    bool validateModule(lua_State *const) const noexcept;
+
 public:
     std::string moduleName{};
     std::string authorName{};
diff --git a/src/Lib/Script/LuaContext.cc b/src/Lib/Script/LuaContext.cc
index 554d104d..bc298d9c 100644
--- a/src/Lib/Script/LuaContext.cc
+++ b/src/Lib/Script/LuaContext.cc
@@ -41,10 +41,12 @@ LuaContext::~LuaContext() noexcept
 void
 LuaContext::loadPackagedFile(const QString &url) noexcept
 {
+    currentFile = url.toStdString();
     QFile libVivy(url);
     if (!libVivy.open(QIODevice::ReadOnly | QIODevice::Text))
         qFatal("FATA -> failed to find packaged Vivy file %s", url.toStdString().c_str());
     loadString(libVivy.readAll().toStdString().c_str());
+    currentFile = "";
 }
 
 LuaContext *
@@ -95,11 +97,15 @@ LuaContext::loadString(const char *str) noexcept
 LuaContext::Code
 LuaContext::loadFile(const char *file) noexcept
 {
+    currentFile = file;
     if (luaL_loadfile(L, file) != LUA_OK) {
         err(this) << "Error loading file " << file << ": " << getLastError().toStdString() << "\n";
+        currentFile = "";
         return Code::Error;
     }
-    return exec();
+    auto rc     = exec();
+    currentFile = "";
+    return rc;
 }
 
 QString
@@ -166,3 +172,30 @@ LuaContext::getAllModuleName() const noexcept
         moduleNames.emplace_back(name);
     return moduleNames;
 }
+
+void
+LuaContext::setFailed(const std::string &msg) noexcept
+{
+    failureString = msg;
+    streamErr << "LuaContext is now in failure state: " << msg;
+}
+
+bool
+LuaContext::isFailed() const noexcept
+{
+    return failureString.empty();
+}
+
+const std::string &
+LuaContext::getFailureDescription() const noexcept
+{
+    return failureString;
+}
+
+const std::string &
+LuaContext::getCurrentLuaFile() const
+{
+    if (currentFile.empty())
+        throw std::logic_error("Not called when a file is being evaluated");
+    return currentFile;
+}
diff --git a/src/Lib/Script/LuaContext.hh b/src/Lib/Script/LuaContext.hh
index 52b8a175..2e165626 100644
--- a/src/Lib/Script/LuaContext.hh
+++ b/src/Lib/Script/LuaContext.hh
@@ -23,6 +23,8 @@ namespace Vivy::Script
 // New Lua script instance
 class LuaContext final {
     lua_State *L{ nullptr };
+    std::string failureString{};
+    std::string currentFile{};
     std::map<const std::string_view, const int> modules                           = {};
     static inline std::map<const lua_State *const, LuaContext *const> contextList = {};
 
@@ -55,9 +57,15 @@ public:
 
     // Returns the module or `nullptr` if not found.
     const ModuleDeclaration *getModule(const std::string_view) const noexcept;
-
     const std::vector<std::string_view> getAllModuleName() const noexcept;
 
+    void setFailed(const std::string &) noexcept;
+    bool isFailed() const noexcept;
+    const std::string &getFailureDescription() const noexcept;
+
+    // Makes only sens when called from within loadFile and loadPackagedFile
+    const std::string &getCurrentLuaFile() const;
+
 private:
     // Exec all loaded strings and files
     Code exec() noexcept;
diff --git a/src/Lib/Script/ScriptStore.cc b/src/Lib/Script/ScriptStore.cc
index 411ebb0b..ad8e16e5 100644
--- a/src/Lib/Script/ScriptStore.cc
+++ b/src/Lib/Script/ScriptStore.cc
@@ -64,3 +64,9 @@ ScriptStore::getLoadedModules() const noexcept
     else
         return {};
 }
+
+const Script::ModuleDeclaration *
+ScriptStore::getModule(const std::string_view str) const noexcept
+{
+    return luaContext->getModule(str);
+}
diff --git a/src/Lib/Script/ScriptStore.hh b/src/Lib/Script/ScriptStore.hh
index c091ad74..9dd345e9 100644
--- a/src/Lib/Script/ScriptStore.hh
+++ b/src/Lib/Script/ScriptStore.hh
@@ -7,6 +7,7 @@
 namespace Vivy::Script
 {
 class LuaContext;
+class ModuleDeclaration;
 }
 
 namespace Vivy
@@ -30,6 +31,7 @@ public:
 
     // Get modules from scripts
     const std::vector<std::string_view> getLoadedModules() const noexcept;
+    const Script::ModuleDeclaration *getModule(const std::string_view) const noexcept;
 
 private:
     std::unique_ptr<Script::LuaContext> luaContext{ nullptr };
diff --git a/src/VivyCli.cc b/src/VivyCli.cc
index 42e1c2f1..0b7017e8 100644
--- a/src/VivyCli.cc
+++ b/src/VivyCli.cc
@@ -18,6 +18,7 @@ VivyCli::exec() noexcept
     scriptStore.executeScript(selectedDoc->getUuid());
     for (const auto &str : scriptStore.getLoadedModules()) {
         std::cout << "Module " << str << " was loaded!\n";
+        const auto *mod = scriptStore.getModule(str);
     }
     return 0;
 }
diff --git a/utils/lua/sample-spec.module b/utils/lua/sample-spec.module
index a6886f8d..c65d82e2 100644
--- a/utils/lua/sample-spec.module
+++ b/utils/lua/sample-spec.module
@@ -3,14 +3,6 @@ custom_opt = DeclareOption { option1 = false }
 time_opt   = DeclareOption { preTime = -900, postTime = 300, }
 color_opt  = DeclareOption { color1 = Vivy:newColor("#314159") }
 
-job1 = DeclareJob { "set_retime", options = { time_opt } }
-job2 = DeclareJob { "demultiply_syllabes" }
-job3 = ImportJob  { "utils", "color_after_read_3", options = { color_opt } }
-
-fun1 = DeclareFunction { "tripleCopySyl" }
-fun2 = DeclareFunction { "printFoo" }
-fun3 = ImportFunction  { "utils", "prettyPrint" }
-
 DeclareModule {
     name        = "sample-spec",
     description = "Sample script used for the specification proposition",
@@ -19,6 +11,14 @@ DeclareModule {
     revision    = "rev-1254",
     imports     = { "utils" },
     options     = { custom_opt, time_opt, color_opt },
-    functions   = { fun1, fun2, fun3 },
-    jobs        = { job1, job2, job3 },
+    functions = {
+        DeclareFunction { "tripleCopySyl" },
+        DeclareFunction { "printFoo" },
+        ImportFunction  { "utils", "prettyPrint" },
+    },
+    jobs = {
+        DeclareJob { "set_retime", options = { time_opt } },
+        DeclareJob { "demultiply_syllabes" },
+        ImportJob  { "utils", "color_after_read_3", options = { color_opt } },
+    },
 }
-- 
GitLab