diff --git a/CMakeLists.txt b/CMakeLists.txt index d5ff07305cb4f88bf378c626121e1ce6c06dd07d..deade37d1a4999833a15faf8f8f62535d0964cf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,7 +126,7 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") -Weverything # Disable some things because we want C++20 and don't constrol some Qt - # generated files... + # generated files... Also permit VLA -Wno-c++98-compat -Wno-c++98-c++11-c++14-c++17-compat-pedantic -Wno-extra-semi-stmt -Wno-redundant-parens diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a28934fb8dbc059e39cd1acd2f66e68515d05f29..9c614dc0d12264e326bb42721661fa4fa61e8c7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,4 @@ Some of the feature may not be used or not already be in use for multiple reasons: - the new C++20 modules feature is not used because I don't know how it - will play with `moc`. It's not supported by clangd for now anyway - - + will play with `moc`. It's not supported by clangd for now anyway. diff --git a/rsc/VivyRessources.qrc b/rsc/VivyRessources.qrc index 542123ecd6cfc6901242cd5182f3821252f809ce..9078302c70ef82f2cf1a5eda9c0d82fc0b3ec51a 100644 --- a/rsc/VivyRessources.qrc +++ b/rsc/VivyRessources.qrc @@ -24,6 +24,7 @@ <!-- Lua scripts --> <file>lua/lib.lua</file> + <file>lua/module.lua</file> </qresource> <!-- Breeze icons, LGPL-3.0 Licence --> diff --git a/rsc/lua/lib.lua b/rsc/lua/lib.lua index d21e6e14d3c11124043a792c0f74d86b9cb08a86..fb3f7efe7338c8f7be4aa9da7976dccdb82a1aac 100644 --- a/rsc/lua/lib.lua +++ b/rsc/lua/lib.lua @@ -5,86 +5,89 @@ includes some exposed Vivy API things, enums and object to the user. ]] +function getrometatable(tbl, tablename) + local errormsg = tablename ~= nil + and "attempt to update " .. tablename .. " table" + or "attempt to update a read only table" + return { + __index = tbl, + __metatable = false, + __newindex = function (t, k, v) + error(errormsg, 2) + end + } +end + +function setrometatable(tbl, tblname) setmetatable(tbl, getrometatable(tbl, tblname)) end +function generalize(tbl) for k, v in pairs(tbl) do _G[k] = v end end + -- Create an enum function enum(tbl) local length = #tbl for i = 1, length do local v = tbl[i] - tbl[v] = i + tbl[v] = v end - - local mt = { - __index = tbl, - __newindex = function (t, k, v) - error("attempt to update an enum table", 2) - end - } - - setmetatable(tbl, mt) + setrometatable(tbl, "an enum") return tbl end --- Things used to iterate over with a job +-- Enums Iterable = enum { "LINE", - "SYLLABE" + "SYLLABE", } --- Table: ReservedKeywords["keyword"] = "type" -local ReservedKeywords = { - ["Vivy"] = "table" +StdType = enum { + "NUMBER", + "COLOR", + "BOOLEAN", + "STRING", } -- Vivy global object construction -Vivy = { - -- Standard libraries - ["math"] = math, - ["string"] = string, - ["utf8"] = utf8, - ["table"] = table, - - -- Custom things - ["Iterable"] = Iterable, -} - -version = { - ["MAJOR"] = 0, - ["MINOR"] = 1, - ["PATCH"] = 0, - ["LABEL"] = "alpha", -} +os, io, debug = nil, nil, nil +Vivy = { } +Version = { MAJOR = 0, MINOR = 1, PATCH = 0, LABEL = "alpha" } +Vivy["___Fun"] = FreeFunctions() -Vivy["Version"] = setmetatable(version, { +Vivy["Version"] = setmetatable(Version, { __tostring = function () return string.format("%d.%d.%d-%s", - Vivy.Version.MAJOR, - Vivy.Version.MINOR, - Vivy.Version.PATCH, - Vivy.Version.LABEL) + Version.MAJOR, + Version.MINOR, + Version.PATCH, + Version.LABEL) + end, + __index = Version, + __metatable = false, + __newindex = function (t, k, v) + error("attempt to update the version table", 2) end }) --- In case we are able to do the _ENV <- Vivy one day... -Vivy["Vivy"] = Vivy +function Vivy:global () return Vivy.___Fun end -Vivy = setmetatable(Vivy, { - __newindex = function (t, k, v) - error("you can't set new keys in the Vivy table") +function Vivy:module (tbl) + if type(tbl) == "table" and #tbl == 1 then + return Vivy.___Fun.getModule(tbl[1]) + elseif type(tbl) == "string" then + return Vivy.___Fun.getModule(tbl) + else + error("Invalid argument, you should pass a single string", 2) end -}) +end + +function Vivy:newColor (colorString) + -- Not implemented + return false +end --- Hide the io, os and debug libraries -io = nil -os = nil -debug = nil +-- In case we are able to do the _ENV <- Vivy one day... +Vivy["Vivy"] = Vivy -local _ENV = setmetatable(Vivy, { - __newindex = function (t, k, v) - -- Use the ReservedKeywords table here - if (k ~= "Vivy") then - rawset(t, k, v) - else - error("Try to assign something to the protected global symbol 'Vivy'") - end - end -}) +-- Protect the content of Vivy, for now we are unable to protect the +-- Vivy keyword... +setrometatable(Vivy) +generalize(Iterable) +print = Vivy.___Fun.print diff --git a/rsc/lua/module.lua b/rsc/lua/module.lua new file mode 100644 index 0000000000000000000000000000000000000000..3468636a7168c7a2344ea5bb0c52aab401358233 --- /dev/null +++ b/rsc/lua/module.lua @@ -0,0 +1,121 @@ +--[[ + Utilities to handle the module declaration files. +]] + +-- Check if the type of the argument is 'table' +function ___checkTable(tbl) + if type(tbl) ~= "table" then + error("The passed argument is of type " .. type(tbl) .. " and not table", 3) + end +end + +-- Local private function: try to import options from a table into a job or a +-- module declaration. +function ___tryImportOptions(thing, tbl) + ___checkTable(tbl) + if tbl["options"] then + ___checkTable(tbl.options) + thing:setOptions(tbl.options) + end +end + +-- The option declaration block: +-- DeclareOption { opt1 = val1, opt2 = val2 } +function DeclareOption(opt) + ___checkTable(opt) + local option = OptionDeclaration() + for o, val in pairs(opt) do + option:addOptionContent(o, val) + end + return option +end + +-- The function declaration block +-- DeclareFunction { name } +function DeclareFunction(tbl) + ___checkTable(tbl) + if #tbl == 1 and type(tbl[1]) == "string" then + local func = FunctionDeclaration() + func:setName(tbl[1]) + return func + end + error("invalid number of arguments in the DeclareFuncton block", 2) +end + +-- The function import block +-- ImportFunction { script, name } +function ImportFunction(tbl) + ___checkTable(tbl) + if #tbl == 2 and type(tbl[1]) == "string" and type(tbl[2]) == "string" then + local func = FunctionDeclaration() + func:setImportFromScript(tbl[1]) + func:setName(tbl[2]) + return func + end + error("invalid number of arguments in the ImportFunction block", 2) +end + +-- The job declaration block +-- DeclareJob { name } +-- DeclareJob { name, options = { ... } } +function DeclareJob(tbl) + ___checkTable(tbl) + if #tbl >= 1 and #tbl <= 2 and tbl[1] and type(tbl[1]) == "string" then + local job = JobDeclaration() + job:setName(tbl[1]) + ___tryImportOptions(job, tbl) + return job + end + error("invalid DeclareJob block, need at least a name", 2) +end + +-- The job import block +-- ImportJob { script, job } +-- ImportJob { script, job, options = { ... } } +function ImportJob(tbl) + ___checkTable(tbl) + if #tbl >= 2 and #tbl <= 3 and type(tbl[1]) == "string" and type(tbl[2]) == "string" then + local job = JobDeclaration() + job:setName(tbl[2]) + job:setImportFromScript(tbl[1]) + ___tryImportOptions(job, tbl) + return job + end + error("invalid ImportJob block, need at least a script and a name", 2) +end + +-- The module declaration block +-- DeclareModule { +-- name = ..., description = ..., author = ..., +-- revision = ..., imports = { ... }, options = { ... }, +-- fuctions = { ... }, jobs = { ... } +-- } +-- DeclareModule { name = ..., author = ... } +function DeclareModule(tbl) + ___checkTable(tbl) + if not tbl.name then error("Invalid DeclareModule block, Missing name", 2) end + if not tbl.author then error("Invalid DeclareModule block, Missing author", 2) end + local module = ModuleDeclaration() + + -- Standard things + module:setName(tbl.name) + module:setAuthorName(tbl.author) + + -- Optional simple things + if tbl.description then module:setDescription(tbl.description) end + if tbl.revision then module:setRevision(tbl.revision) end + if tbl.imports then module:setImports(tbl.imports) end + + -- A little more complicated thing + if tbl.jobs then module:setJobs(tbl.jobs) end + if tbl.functions then module:setFunctions(tbl.functions) end + + ___tryImportOptions(module, tbl) + -- Register jobs and functions in runtime now that they are added to the module! + for _, job in ipairs(tbl.jobs) do job:pushToRuntime() end + for _, fun in ipairs(tbl.functions) do fun:pushToRuntime() end + + -- Register the module! + module:pushToRuntime() + return module +end diff --git a/src/Lib/Script/CRTPLuaScriptObject.cc b/src/Lib/Script/CRTPLuaScriptObject.cc deleted file mode 100644 index f720dff6032a5d69f86645ecc5340202dd98fae2..0000000000000000000000000000000000000000 --- a/src/Lib/Script/CRTPLuaScriptObject.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "CRTPLuaScriptObject.hh" -#include "lua.hpp" - -// SampleObject implementation -using namespace Vivy::Script; - -SampleObject::SampleObject() noexcept { fprintf(stderr, "CREATE SampleObject\n"); } - -SampleObject::~SampleObject() noexcept { fprintf(stderr, "DESTROY SampleObject\n"); } - -int -SampleObject::foo(lua_State *L) noexcept -{ - SampleObject *obj = CHECK(L, 1); - double bar = luaL_checknumber(L, 2); - fprintf(stderr, "SampleObject(%p)::foo -> %f\n", static_cast<void *>(obj), bar); - return 0; -} diff --git a/src/Lib/Script/CRTPLuaScriptObject.hh b/src/Lib/Script/CRTPLuaScriptObject.hh index b622d71fbb4459b156d3a44676e2c265e94dbd2a..8e9920da1f8c51f0a158168bd9ebcda2c73d5e7c 100644 --- a/src/Lib/Script/CRTPLuaScriptObject.hh +++ b/src/Lib/Script/CRTPLuaScriptObject.hh @@ -1,6 +1,7 @@ #pragma once #include "LuaContext.hh" +#include "ScriptOption.hh" #include "lua.hpp" #include <vector> @@ -9,54 +10,130 @@ namespace Vivy::Script { // clang-format off -#define LUA_DECL_METHOD(class, name) luaL_Reg{ #name, class ::name } -#define LUA_DECL_CREATE(class) luaL_Reg{ "new", class ::CREATE } +#define LUA_DECL_METHOD(class, name) luaL_Reg{ #name, class ::name } +#define LUA_DECL_NAMED_METHOD(class, method, name) luaL_Reg{ method, class ::name } +#define LUA_DECL_META_METHOD(class, meta, name) luaL_Reg{ meta, class ::name } +#define LUA_DECL_CREATE(class) luaL_Reg{ "new", class ::CREATE } +#define LUA_RETURN_NOTHING(L) { lua_settop(L, 0); return 0; } +// clang-format on #define script_class(theClassName) \ class theClassName final : public CRTPLuaScriptObject<theClassName> -#define LUA_SCRIPTABLE_CLASS(theClassName) \ - VIVY_UNMOVABLE_OBJECT(theClassName) \ - static constexpr char className[] = #theClassName; \ - friend CRTPLuaScriptObject<theClassName>; -// clang-format on +#define method_list static constexpr inline auto + +#define LUA_SCRIPTABLE_CLASS(theClassName) \ + VIVY_UNMOVABLE_OBJECT(theClassName) \ + static constexpr char className[] = #theClassName; \ + friend CRTPLuaScriptObject<theClassName>; \ + theClassName(lua_State *L) noexcept \ + { \ + err(LuaContext::getContext(L)) \ + << "Create instance of " << theClassName::className << "\n"; \ + } \ + ~theClassName() noexcept {} + +// The type of the thing that a job iterate over +enum class JobIteratorType { Line = 1, Syllabe = 2 }; +static inline JobIteratorType +getJobIteratorTypeFromString(const std::string_view it) noexcept +{ + if (it == "LINE") + return JobIteratorType::Line; + else if (it == "SYLLABE") + return JobIteratorType::Syllabe; + return static_cast<JobIteratorType>(0); // Invalid! +} // CRTP to expose objects to Lua template <class Object> class CRTPLuaScriptObject { protected: - static int GC(lua_State *L) noexcept + static void __attribute__((__noreturn__)) + luaGlobalError(lua_State *const L, const std::string &str) noexcept + { + const auto *const context = LuaContext::getContext(L); + lua_pushfstring(L, "%s:0:%s", context->getCurrentLuaFile().c_str(), str.c_str()); + lua_error(L); + } + + static bool PushFunctionFromRegistry(lua_State *const L, const int key) noexcept { - const Object *obj = reinterpret_cast<const Object *>(lua_topointer(L, 1)); - puts("CALL DESTRUCTOR"); + if (key >= 0) { + lua_rawgeti(L, LUA_REGISTRYINDEX, key); + return true; + } else { + lua_pushnil(L); + return false; + } + } + + static void IterateOverArray(lua_State *const L, const int tblIndex, + const auto callback) noexcept + { + luaL_checktype(L, tblIndex, LUA_TTABLE); + const std::size_t count = lua_rawlen(L, tblIndex); + if (count >= std::numeric_limits<int>::max()) + luaL_error(L, "To many items (%lu) in table to iterate over!", count); + + const int countInt = static_cast<const int>(count); + for (int i = 1; i <= countInt; ++i) { + luaL_checktype(L, tblIndex, LUA_TTABLE); + lua_pushinteger(L, i); // i -> top stack + lua_gettable(L, tblIndex); // T[i] -> top stack and pop i + + if (lua_type(L, -1) == LUA_TNIL) + break; // Sentinel check + + // If the callback didn't pop the object form the stack, pop + // it ourself. The value will be accessible at the top of the + // stack (index -1). + if (!callback()) + lua_pop(L, -1); + } + } + + static int GC(lua_State *const L) noexcept + { + const Object *const obj = reinterpret_cast<const Object *>(lua_topointer(L, 1)); + auto *context = LuaContext::getContext(L); + err(context) << "Call destructor for " << Object::className << "\n"; obj->~Object(); return 0; } - static int CREATE(lua_State *L) noexcept + static int TOSTRING(lua_State *const L) noexcept { - Object *obj = reinterpret_cast<Object *>(lua_newuserdata(L, sizeof(Object))); - puts("CALL CONSTRUCTOR"); - new (obj) Object; + lua_pushfstring(L, "%s { }", Object::className); + return 1; + } + + static int CREATE(lua_State *const L) noexcept + { + lua_settop(L, 0); + Object *const obj = reinterpret_cast<Object *>(lua_newuserdata(L, sizeof(Object))); + new (obj) Object(L); luaL_getmetatable(L, Object::className); - lua_setmetatable(L, -2); + lua_setmetatable(L, 1); return 1; } - static Object *CHECK(lua_State *L, int index) noexcept + static const std::string_view CHECK_STRING_VIEW(lua_State *const L, int index) noexcept { - luaL_checktype(L, index, LUA_TUSERDATA); - Object *obj = reinterpret_cast<Object *>(luaL_checkudata(L, index, Object::className)); - if (obj == nullptr) { - puts("TYPE ERROR"); - luaL_typeerror(L, index, Object::className); - } - return obj; + std::size_t len = 0; + const char *const str = luaL_checklstring(L, index, &len); + return std::string_view(str, len); } - static inline constexpr luaL_Reg luaRegDefaultGC = { "__gc", GC }; + static inline constexpr luaL_Reg luaRegDefaultGC = { "__gc", GC }; + static inline constexpr luaL_Reg luaRegDefaultTOSTRING = { "__tostring", TOSTRING }; public: - static void Register(lua_State *L) noexcept + static Object *CHECK(lua_State *const L, const int index) noexcept + { + return reinterpret_cast<Object *>(luaL_checkudata(L, index, Object::className)); + } + + static void Register(lua_State *const L) noexcept { // Fill the method table, newclass = {} lua_newtable(L); @@ -76,7 +153,6 @@ public: lua_settable(L, metatable); for (const luaL_Reg &meta : Object::metaMethods) { - fprintf(stderr, "REGISTER %s\n", meta.name); lua_pushstring(L, meta.name); lua_pushcfunction(L, meta.func); lua_settable(L, metatable); @@ -87,7 +163,6 @@ public: // Fill the method table with specified methods for (const luaL_Reg &method : Object::methods) { - fprintf(stderr, "REGISTER %s\n", method.name); lua_pushstring(L, method.name); lua_pushcfunction(L, method.func); lua_settable(L, methodtable); @@ -97,22 +172,172 @@ public: lua_pop(L, 1); // drop method-table lua_register(L, Object::className, CREATE); // Register the create method } + +private: + // The user value count + int userValueCount{ 0 }; + +protected: + // Add a user value to this object. Do not used lua_setuservalue or + // lua_setiuservalue directly, use this proxy method. + void addUserValue(lua_State *const L, const int index) noexcept + { + lua_setiuservalue(L, index, ++userValueCount); + } +}; + +// Option declaration +script_class (OptionDeclaration) { + LUA_SCRIPTABLE_CLASS(OptionDeclaration) + + static int addOptionContent(lua_State *const) noexcept; + + static int TOSTRING(lua_State *const) noexcept; + + method_list metaMethods = { + luaRegDefaultGC, + LUA_DECL_META_METHOD(OptionDeclaration, "__tostring", TOSTRING), + }; + method_list methods = { LUA_DECL_METHOD(OptionDeclaration, addOptionContent), + LUA_DECL_CREATE(OptionDeclaration) }; + +public: + // Different option types + std::unordered_map<std::string, ScriptOption> options; +}; + +// Job declaration +script_class (JobDeclaration) { + LUA_SCRIPTABLE_CLASS(JobDeclaration) + + static int setName(lua_State *) noexcept; + static int setOptions(lua_State *) noexcept; + static int setImportFromScript(lua_State *) noexcept; + + static int pushToRuntime(lua_State *const) noexcept; + + static int TOSTRING(lua_State *const) noexcept; + + method_list metaMethods = { luaRegDefaultGC, + LUA_DECL_META_METHOD(JobDeclaration, "__tostring", TOSTRING) }; + method_list methods = { LUA_DECL_METHOD(JobDeclaration, setName), + LUA_DECL_METHOD(JobDeclaration, setOptions), + LUA_DECL_METHOD(JobDeclaration, setImportFromScript), + LUA_DECL_METHOD(JobDeclaration, pushToRuntime), + LUA_DECL_CREATE(JobDeclaration) }; + + bool isImported{ false }; + int functionRegisterKey{ -1 }; + JobIteratorType iterationType{ static_cast<JobIteratorType>(0) }; // Invalid by default + +public: + std::string name{}; + std::string parentScript{}; + std::vector<OptionDeclaration *> options{}; + + bool isLocalToModule() const noexcept; + void pushLuaFunction(lua_State *const) const noexcept; + void getLuaFunctionFromStack(lua_State *const, const JobIteratorType) noexcept; }; -// Default sample exposer for lua scripts -script_class (SampleObject) { - // Specify that it's a Lua object - LUA_SCRIPTABLE_CLASS(SampleObject) +// Function declaration +script_class (FunctionDeclaration) { + LUA_SCRIPTABLE_CLASS(FunctionDeclaration) + + static int setName(lua_State *const) noexcept; + static int setImportFromScript(lua_State *const) noexcept; + + static int pushToRuntime(lua_State *const) noexcept; + + static int TOSTRING(lua_State *const) noexcept; + + method_list metaMethods = { luaRegDefaultGC, + LUA_DECL_META_METHOD(FunctionDeclaration, "__tostring", TOSTRING) }; + method_list methods = { LUA_DECL_METHOD(FunctionDeclaration, setName), + LUA_DECL_METHOD(FunctionDeclaration, setImportFromScript), + LUA_DECL_METHOD(FunctionDeclaration, pushToRuntime), + LUA_DECL_CREATE(FunctionDeclaration) }; + + bool isImported{ false }; + int functionRegisterKey{ -1 }; + +public: + std::string name{}; + std::string parentScript{}; + + bool isLocalToModule() const noexcept; + void pushLuaFunction(lua_State *const) const noexcept; + void getLuaFunctionFromStack(lua_State *const) noexcept; +}; + +// Module declaration +script_class (ModuleDeclaration) { + LUA_SCRIPTABLE_CLASS(ModuleDeclaration) + + static int setName(lua_State *const) noexcept; + static int setAuthorName(lua_State *const) noexcept; + static int setRevision(lua_State *const) noexcept; + static int setDescription(lua_State *const) noexcept; + static int setImports(lua_State *const) noexcept; + static int setOptions(lua_State *const) noexcept; + static int setFunctions(lua_State *const) noexcept; + static int setJobs(lua_State *const) noexcept; + + static int pushToRuntime(lua_State *const) noexcept; + + static int importMethod(lua_State *const) noexcept; + static int exportMethod(lua_State *const) noexcept; + + method_list metaMethods = { luaRegDefaultGC }; + method_list methods = { + LUA_DECL_METHOD(ModuleDeclaration, setName), + LUA_DECL_METHOD(ModuleDeclaration, setDescription), + LUA_DECL_METHOD(ModuleDeclaration, setRevision), + LUA_DECL_METHOD(ModuleDeclaration, setAuthorName), + LUA_DECL_METHOD(ModuleDeclaration, setImports), + LUA_DECL_METHOD(ModuleDeclaration, setOptions), + LUA_DECL_METHOD(ModuleDeclaration, setFunctions), + LUA_DECL_METHOD(ModuleDeclaration, setJobs), + LUA_DECL_METHOD(ModuleDeclaration, pushToRuntime), + LUA_DECL_NAMED_METHOD(ModuleDeclaration, "import", importMethod), + LUA_DECL_NAMED_METHOD(ModuleDeclaration, "export", exportMethod), + LUA_DECL_CREATE(ModuleDeclaration), + }; + + // Will be resolved later + std::vector<std::string> importNames{}; + std::vector<const ModuleDeclaration *> importedModules{}; + + void validateModule(lua_State *const) const noexcept; + int exportFunction(lua_State *const, const int tableIndex, const std::string_view) noexcept; + int exportJob(lua_State *const, const int tableIndex, const std::string_view, + const std::string_view) noexcept; + +public: + std::string moduleName{}; + std::string authorName{}; + std::string revision{}; + std::string moduleDescription{}; + std::vector<OptionDeclaration *> moduleOptions{}; + std::vector<FunctionDeclaration *> moduleFunctions{}; + std::vector<JobDeclaration *> moduleJobs{}; + + bool resolvModules(LuaContext *const) noexcept; +}; - // CTors and DTors - SampleObject() noexcept; - ~SampleObject() noexcept; +// Holds all the free functions (well, not really free functions here...) +script_class (FreeFunctions) { + LUA_SCRIPTABLE_CLASS(FreeFunctions) - // The methods must be static with Lua interface - static int foo(lua_State * L) noexcept; + static int print(lua_State *const) noexcept; + static int getModule(lua_State *const) noexcept; + static int start(lua_State *const) noexcept; + static int finish(lua_State *const) noexcept; - // Meta and method tables, Lua specific thing - static constexpr inline auto metaMethods = { luaRegDefaultGC, LUA_DECL_CREATE(SampleObject) }; - static constexpr inline auto methods = { LUA_DECL_METHOD(SampleObject, foo) }; + method_list metaMethods = { luaRegDefaultGC }; + method_list methods = { LUA_DECL_METHOD(FreeFunctions, getModule), + LUA_DECL_METHOD(FreeFunctions, start), + LUA_DECL_METHOD(FreeFunctions, finish), + LUA_DECL_METHOD(FreeFunctions, print), LUA_DECL_CREATE(FreeFunctions) }; }; } diff --git a/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc b/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc new file mode 100644 index 0000000000000000000000000000000000000000..b2c3127aa97ce23e229ac651ca7b6c7ae7c6a5fe --- /dev/null +++ b/src/Lib/Script/CRTPLuaScriptObject/FreeFunctions.cc @@ -0,0 +1,39 @@ +#include "../CRTPLuaScriptObject.hh" +#include "lua.hpp" +#include <QFileInfo> +#include <QString> + +using namespace Vivy::Script; + +int +FreeFunctions::print(lua_State *const L) noexcept +{ + const std::string_view arg = CHECK_STRING_VIEW(L, 1); + out(LuaContext::getContext(L)) << "OUT: " << arg << "\n"; + LUA_RETURN_NOTHING(L); +} + +int +FreeFunctions::getModule(lua_State *const L) noexcept +{ + const auto *const context = LuaContext::getContext(L); + const std::string_view modName = CHECK_STRING_VIEW(L, 1); + [[maybe_unused]] const ModuleDeclaration *const mod = context->getModule(modName); + return 1; +} + +int +FreeFunctions::start(lua_State *const L) noexcept +{ + lua_settop(L, 0); + lua_pushinteger(L, 0); + return 1; +} + +int +FreeFunctions::finish(lua_State *const L) noexcept +{ + lua_settop(L, 0); + lua_pushinteger(L, 0); + return 1; +} diff --git a/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc new file mode 100644 index 0000000000000000000000000000000000000000..82afe1daa0b10b2824dbd4b9b1fbda7588f9f371 --- /dev/null +++ b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc @@ -0,0 +1,71 @@ +#include "../CRTPLuaScriptObject.hh" +#include "lua.hpp" +#include <QFileInfo> +#include <QString> + +using namespace Vivy::Script; + +int +FunctionDeclaration::TOSTRING(lua_State *const L) noexcept +{ + FunctionDeclaration *const self = CHECK(L, 1); + std::string str(className); + str += std::string_view(" { "); + str += self->parentScript.empty() ? self->name : self->parentScript + "::" + self->name; + str += std::string_view(" }"); + lua_pushstring(L, str.c_str()); + return 1; +} + +int +FunctionDeclaration::setName(lua_State *const L) noexcept +{ + FunctionDeclaration::CHECK(L, 1)->name = CHECK_STRING_VIEW(L, 2); + LUA_RETURN_NOTHING(L); +} + +int +FunctionDeclaration::setImportFromScript(lua_State *const L) noexcept +{ + auto *const fun = FunctionDeclaration::CHECK(L, 1); + fun->parentScript = CHECK_STRING_VIEW(L, 2); + fun->isImported = true; + LUA_RETURN_NOTHING(L); +} + +int +FunctionDeclaration::pushToRuntime(lua_State *const L) noexcept +{ + FunctionDeclaration *const self = CHECK(L, 1); + auto *const context = LuaContext::getContext(L); + + lua_settop(L, 1); + + if (self->isImported) + err(context) << "The function \"" << self->parentScript << "::" << self->name + << "\" is an imported function\n"; + + else if (context->registerDeclaration(self) == LuaContext::Code::Error) + context->setFailed("Failed to register function " + self->name); + + LUA_RETURN_NOTHING(L); +} + +void +FunctionDeclaration::pushLuaFunction(lua_State *const L) const noexcept +{ + [[maybe_unused]] bool ok = PushFunctionFromRegistry(L, functionRegisterKey); +} + +bool +FunctionDeclaration::isLocalToModule() const noexcept +{ + return parentScript.size() == 0; +} + +void +FunctionDeclaration::getLuaFunctionFromStack(lua_State *const L) noexcept +{ + luaL_checktype(L, -1, LUA_TFUNCTION); + functionRegisterKey = luaL_ref(L, LUA_REGISTRYINDEX); +} diff --git a/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc new file mode 100644 index 0000000000000000000000000000000000000000..39c676d96f83d05072484122761ea0b91dc34d59 --- /dev/null +++ b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc @@ -0,0 +1,103 @@ +#include "../CRTPLuaScriptObject.hh" +#include "lua.hpp" +#include <QFileInfo> +#include <QString> + +using namespace Vivy::Script; + +int +JobDeclaration::TOSTRING(lua_State *const L) noexcept +{ + JobDeclaration *const self = CHECK(L, 1); + std::string str(className); + str += std::string_view(" { "); + + str += self->parentScript.empty() ? self->name : self->parentScript + "::" + self->name; + str += ", options: ["; + + for (const OptionDeclaration *const opt : self->options) { + for (auto const &[name, _] : opt->options) + str += " " + name; + } + + str += std::string_view(" ] }"); + lua_pushstring(L, str.c_str()); + return 1; +} + +int +JobDeclaration::setName(lua_State *L) noexcept +{ + JobDeclaration::CHECK(L, 1)->name = CHECK_STRING_VIEW(L, 2); + LUA_RETURN_NOTHING(L); +} + +int +JobDeclaration::setOptions(lua_State *L) noexcept +{ + auto *const context = LuaContext::getContext(L); + JobDeclaration *const job = JobDeclaration::CHECK(L, 1); + err(context) << "Set options for job '" << job->name << "'\n"; + + IterateOverArray(L, 2, [&context, job, L]() noexcept -> bool { + OptionDeclaration *opt = OptionDeclaration::CHECK(L, -1); + + for (const auto &[name, value] : opt->options) { + err(context) << "\tregister option \"" << name << "\" with type \"" + << value.getSignature() << "\"\n"; + } + + job->options.push_back(opt); + job->addUserValue(L, 1); // Tie T[i] to job and pop stack + return true; // Value was popped from the stack + }); + + LUA_RETURN_NOTHING(L); +} + +int +JobDeclaration::setImportFromScript(lua_State *L) noexcept +{ + auto *const job = JobDeclaration::CHECK(L, 1); + job->parentScript = CHECK_STRING_VIEW(L, 2); + job->isImported = true; + LUA_RETURN_NOTHING(L); +} + +int +JobDeclaration::pushToRuntime(lua_State *const L) noexcept +{ + JobDeclaration *const self = CHECK(L, 1); + auto *const context = LuaContext::getContext(L); + + lua_settop(L, 1); + + if (self->isImported) + err(context) << "The job \"" << self->parentScript << "::" << self->name + << "\" is an imported job\n"; + + else if (context->registerDeclaration(self) == LuaContext::Code::Error) + context->setFailed("Failed to register job " + self->name); + + LUA_RETURN_NOTHING(L); +} + +bool +JobDeclaration::isLocalToModule() const noexcept +{ + return parentScript.size() == 0; +} + +void +JobDeclaration::pushLuaFunction(lua_State *const L) const noexcept +{ + [[maybe_unused]] bool ok = PushFunctionFromRegistry(L, functionRegisterKey); +} + +void +JobDeclaration::getLuaFunctionFromStack(lua_State *const L, const JobIteratorType itType) noexcept +{ + luaL_checktype(L, -1, LUA_TFUNCTION); + functionRegisterKey = luaL_ref(L, LUA_REGISTRYINDEX); + iterationType = itType; +} diff --git a/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc new file mode 100644 index 0000000000000000000000000000000000000000..d30eeee2c284e445eb5332e9db6ec1b4a1ae2656 --- /dev/null +++ b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc @@ -0,0 +1,266 @@ +#include "../CRTPLuaScriptObject.hh" +#include "lua.hpp" +#include <QFileInfo> +#include <QString> +#include <cstring> + +using namespace Vivy::Script; + +bool +ModuleDeclaration::resolvModules(LuaContext *const context) noexcept +{ + for (const auto &str : importNames) { + const ModuleDeclaration *const mod = context->getModule(str); + if (mod) { + importedModules.emplace_back(mod); + } else { + context->setFailed("Failed to find needed module: " + str); + return false; + } + } + return true; +} + +int +ModuleDeclaration::importMethod(lua_State *const L) noexcept +{ + auto *const context = LuaContext::getContext(L); + luaL_checktype(L, 2, LUA_TTABLE); + + // Get the module name + lua_pushinteger(L, 1); // Stack is { top: int, table, self } + lua_gettable(L, 2); // Stack is { top: str, table, self } + const std::string_view mod = CHECK_STRING_VIEW(L, 3); + + // Import a module + context->getModule(mod); // Stack is { top: nil | module, str, table, self } + + // Import a function if needed + if (lua_rawlen(L, 2) == 2) { + lua_pushinteger(L, 2); // Stack is { top: int, module, str, table, self } + lua_gettable(L, 2); // Stack is { top: str, module, str, table, self } + const std::string_view func = CHECK_STRING_VIEW(L, 5); + FunctionDeclaration *const function = context->getFunction(mod, func); + if (function) + function->pushLuaFunction(L); + // Stack is { top: nil | function, FunctionDeclaration, str, module, str, table, self } + } + + // Top of stack is nil | function | module + return 1; +} + +int +ModuleDeclaration::exportMethod(lua_State *const L) noexcept +{ + ModuleDeclaration *const self = CHECK(L, 1); + luaL_checktype(L, 2, LUA_TTABLE); + + // Stack will be { top: string, table, self } + lua_pushinteger(L, 1); + lua_gettable(L, 2); + const std::string_view objectName = CHECK_STRING_VIEW(L, -1); + + const lua_Unsigned len = lua_rawlen(L, 2); + if (len == 2) + return self->exportFunction(L, 2, objectName); + + else if (len == 3) { + lua_pushinteger(L, 2); + lua_gettable(L, 2); + const std::string_view jobItTypeStr = CHECK_STRING_VIEW(L, -1); + return self->exportJob(L, 2, objectName, jobItTypeStr); + } + + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::exportFunction(lua_State *const L, const int tableIndex, + const std::string_view name) noexcept +{ + auto *const context = LuaContext::getContext(L); + FunctionDeclaration *const func = context->getFunction(moduleName, name); + out(context) << "Export function " << moduleName << "::" << name << '\n'; + + lua_pushinteger(L, 2); + lua_gettable(L, tableIndex); + func->getLuaFunctionFromStack(L); + + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::exportJob(lua_State *const L, const int tableIndex, const std::string_view name, + const std::string_view itTypeStr) noexcept +{ + auto *const context = LuaContext::getContext(L); + const JobIteratorType jobItType = getJobIteratorTypeFromString(itTypeStr); + JobDeclaration *const job = context->getJob(moduleName, name); + + if (jobItType != JobIteratorType::Line && jobItType != JobIteratorType::Syllabe) { + const std::string str(itTypeStr); + luaL_error(L, "The iteration type must be LINE or SYLLABE, got %s", str.c_str()); + } + + else { + out(context) << "Export job " << moduleName << "::" << name << "{" << itTypeStr << "}\n"; + + lua_pushinteger(L, 3); + lua_gettable(L, tableIndex); + job->getLuaFunctionFromStack(L, jobItType); + } + + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::setName(lua_State *const L) noexcept +{ + auto *const self = ModuleDeclaration::CHECK(L, 1); + self->moduleName = CHECK_STRING_VIEW(L, 2); + Utils::trim(self->moduleName); + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::setAuthorName(lua_State *const L) noexcept +{ + auto *const self = ModuleDeclaration::CHECK(L, 1); + self->authorName = CHECK_STRING_VIEW(L, 2); + Utils::trim(self->authorName); + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::setRevision(lua_State *const L) noexcept +{ + auto *const self = ModuleDeclaration::CHECK(L, 1); + self->revision = CHECK_STRING_VIEW(L, 2); + Utils::trim(self->revision); + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::setDescription(lua_State *const L) noexcept +{ + auto *const self = ModuleDeclaration::CHECK(L, 1); + self->moduleDescription = CHECK_STRING_VIEW(L, 2); + Utils::trim(self->moduleDescription); + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::setImports(lua_State *const L) noexcept +{ + 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); + return false; // Value was not popped from stack! + }); + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::setOptions(lua_State *const L) noexcept +{ + 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); + self->addUserValue(L, 1); // Tie the opt to the module + return true; // The value was popped from the stack + }); + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::setFunctions(lua_State *const L) noexcept +{ + auto *const self = ModuleDeclaration::CHECK(L, 1); + IterateOverArray(L, 2, [self, L]() noexcept -> bool { + FunctionDeclaration *const fun = FunctionDeclaration::CHECK(L, -1); + if (fun->isLocalToModule()) + fun->parentScript = self->moduleName; + self->moduleFunctions.emplace_back(fun); + self->addUserValue(L, 1); // Tie the function to the module + return true; // The value was popped from the stack + }); + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::setJobs(lua_State *const L) noexcept +{ + auto *const self = ModuleDeclaration::CHECK(L, 1); + IterateOverArray(L, 2, [self, L]() noexcept -> bool { + JobDeclaration *const job = JobDeclaration::CHECK(L, -1); + if (job->isLocalToModule()) + job->parentScript = self->moduleName; + self->moduleJobs.emplace_back(job); + self->addUserValue(L, 1); // Tie the job to the module + return true; // The value was popped from the stack + }); + LUA_RETURN_NOTHING(L); +} + +int +ModuleDeclaration::pushToRuntime(lua_State *const L) noexcept +{ + ModuleDeclaration *const self = CHECK(L, 1); + auto *const context = LuaContext::getContext(L); + + self->importNames.emplace_back(self->moduleName); // You can use localy defined things + Utils::uniqAndSort<std::string>(self->importNames); // Better for all std algorithms + + self->validateModule(L); // May abort the LUA context + err(context) << "Register module \"" << self->moduleName << "\" in the runtime!\n"; + lua_settop(L, 1); + + if (context->registerDeclaration(self) == LuaContext::Code::Error) + context->setFailed("Failed to register module " + self->moduleName); + +#pragma message("Import needed modules here!") + + LUA_RETURN_NOTHING(L); +} + +void +ModuleDeclaration::validateModule(lua_State *const L) const noexcept +{ + // Minimal module file + if (moduleName.empty() || authorName.empty()) + luaGlobalError(L, "The module does not have the minimal required information"); + + // 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); + + if (!unusedImports.empty()) { + std::size_t stringSize = 0; + std::string listStrImports = + "There are imported modules that are used withour begin declared:"; + for (const auto &str : unusedImports) + stringSize += 1 + str.size(); + listStrImports.reserve(stringSize); + for (const auto &str : unusedImports) + listStrImports += " " + str; + luaGlobalError(L, listStrImports); + } + } +} diff --git a/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc new file mode 100644 index 0000000000000000000000000000000000000000..7030865fadffd8f6d7c8a896f6f31d937ce05f47 --- /dev/null +++ b/src/Lib/Script/CRTPLuaScriptObject/OptionDeclaration.cc @@ -0,0 +1,50 @@ +#include "../CRTPLuaScriptObject.hh" +#include "lua.hpp" +#include <QFileInfo> +#include <QString> + +using namespace Vivy::Script; + +int +OptionDeclaration::TOSTRING(lua_State *const L) noexcept +{ + OptionDeclaration *const self = CHECK(L, 1); + std::string str(className); + str += std::string_view(" { "); + + for (auto const &[name, opt] : self->options) { + str += "\"" + name + "\" -> "; + str += opt.getSignature(); + str += ", "; + } + + str.resize(str.size() - 2); // Delete the leading ", " + str += std::string_view(" }"); + lua_pushstring(L, str.c_str()); + return 1; +} + +int +OptionDeclaration::addOptionContent(lua_State *const L) noexcept +{ + OptionDeclaration *const self = CHECK(L, 1); + luaL_checktype(L, 2, LUA_TSTRING); + + const std::string_view optName = CHECK_STRING_VIEW(L, 2); + const int valueType = lua_type(L, 3); + + // XXX: Add support for other types! + if (valueType == LUA_TNUMBER) + self->options.emplace(optName, lua_tonumber(L, 3)); + + else if (valueType == LUA_TSTRING) + self->options.emplace(optName, CHECK_STRING_VIEW(L, 3)); + + else if (valueType == LUA_TBOOLEAN) + self->options.emplace(optName, static_cast<bool>(lua_toboolean(L, 3))); + + else + luaL_error(L, "Unsupported type %s", luaL_typename(L, 3)); + + LUA_RETURN_NOTHING(L); +} diff --git a/src/Lib/Script/LuaContext.cc b/src/Lib/Script/LuaContext.cc index e9e546c6ffe4d964a36a1418c20a278b87c21249..a7c26e659ec30d9e216967d501df263b5c84c285 100644 --- a/src/Lib/Script/LuaContext.cc +++ b/src/Lib/Script/LuaContext.cc @@ -10,28 +10,74 @@ // LuaContext implementation using namespace Vivy::Script; -LuaContext::LuaContext() noexcept +LuaContext::LuaContext(ScriptStore::LoggerType &out, ScriptStore::LoggerType &err) noexcept : L(luaL_newstate()) + , streamOut(out) + , streamErr(err) { luaL_openlibs(L); - QFile libVivy(":lua/lib.lua"); - if (!libVivy.open(QIODevice::ReadOnly | QIODevice::Text)) - qFatal("Failed to find packaged libVivy (:lia/lib.lua)"); - loadString(libVivy.readAll().toStdString().c_str()); // Load the lib - SampleObject::Register(L); // Load the C++ objects + // Register this context + contextList.emplace(L, this); + + // Load all C++ objects + FreeFunctions::Register(L); + OptionDeclaration::Register(L); + JobDeclaration::Register(L); + FunctionDeclaration::Register(L); + ModuleDeclaration::Register(L); + + // Load the lib + loadPackagedFile(":lua/lib.lua"); + loadPackagedFile(":lua/module.lua"); +} + +LuaContext::~LuaContext() noexcept +{ + lua_close(L); // Close lua + contextList.erase(L); // Unregister the context +} + +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::~LuaContext() noexcept { lua_close(L); } +LuaContext * +LuaContext::getContext(const lua_State *const state) noexcept +{ + return contextList.contains(state) ? contextList.at(state) : nullptr; +} LuaContext::Code LuaContext::loadDocument(std::shared_ptr<ScriptDocument> doc) noexcept { - QFileInfo fileCheck(doc->getName()); - if (!fileCheck.exists()) + const QFileInfo fileCheck(doc->getName()); + if (!fileCheck.exists()) { + err(this) << "File " << doc->getName().toStdString() << " for document " + << doc->getUuid().toString().toStdString() << " doesn't exists\n"; return Code::Error; + } - return loadFile(fileCheck.absoluteFilePath().toStdString().c_str()); + lastLoadedModule = ""; + const auto rc = loadFile(fileCheck.absoluteFilePath().toStdString().c_str()); + if (rc != Code::Success) { + setFailed("Failed to load the module file"); + return rc; + } + + if (lastLoadedModule.empty()) { + setFailed("No module was loaded"); + return Code::Error; + } + + return Code::Success; } void @@ -44,7 +90,7 @@ LuaContext::Code LuaContext::exec() noexcept { if (lua_pcall(L, 0, 0, 0) != LUA_OK) { - puts(getLastError().toStdString().c_str()); + err(this) << getLastLuaError().toStdString().c_str() << "\n"; return Code::Error; } return Code::Success; @@ -54,7 +100,7 @@ LuaContext::Code LuaContext::loadString(const char *str) noexcept { if (luaL_loadstring(L, str) != LUA_OK) { - puts("Error loading string"); + err(this) << "Error loading string: " << getLastLuaError().toStdString() << "\n"; return Code::Error; } return exec(); @@ -63,18 +109,173 @@ LuaContext::loadString(const char *str) noexcept LuaContext::Code LuaContext::loadFile(const char *file) noexcept { + currentFile = file; if (luaL_loadfile(L, file) != LUA_OK) { - puts("Error loading file"); + err(this) + << "Error loading file " << file << ": " << getLastLuaError().toStdString() << "\n"; + currentFile = ""; return Code::Error; } - return exec(); + auto rc = exec(); + currentFile = ""; + return rc; } QString -LuaContext::getLastError() noexcept +LuaContext::getLastLuaError() noexcept { + const int type = lua_type(L, -1); + if (type != LUA_TSTRING) { + return QStringLiteral("No lua error detected..."); + } + const char *error = lua_tostring(L, -1); - QString ret = QString("%1").arg(error); + QString ret = QStringLiteral("%1").arg(error); lua_pop(L, 1); return ret; } + +lua_State * +LuaContext::getState() noexcept +{ + return L; +} + +bool +LuaContext::moduleExists(string_view mod) const noexcept +{ + return modules.count(mod) >= 1; +} + +LuaContext::Code +LuaContext::registerDeclaration(const FunctionDeclaration *const fun) noexcept +{ + FunctionDeclaration::CHECK(L, -1); + return registerToMapTuple(fun->parentScript, fun->name, &functions); +} + +LuaContext::Code +LuaContext::registerDeclaration(const JobDeclaration *const job) noexcept +{ + JobDeclaration::CHECK(L, -1); + return registerToMapTuple(job->parentScript, job->name, &jobs); +} + +LuaContext::Code +LuaContext::registerToMapTuple( + string_view module, string_view name, + std::map<std::tuple<string_view, string_view>, const int> *const map) noexcept +{ + if (map->count({ module, name }) >= 1) + return Error; + + streamErr << "Register \"" << module << "::" << name << "\"\n"; + + // WARN: The object needs to be already checked! + const int r = luaL_ref(L, LUA_REGISTRYINDEX); + map->emplace(std::tuple{ module, name }, r); + return Success; +} + +LuaContext::Code +LuaContext::registerDeclaration(const ModuleDeclaration *const mod) noexcept +{ + if (moduleExists(mod->moduleName)) { + streamErr << "The module \"" << mod->moduleName << "\" is already registered!"; + return Code::Error; + } + + streamErr << "Register module \"" << mod->moduleName << "\"\n"; + + ModuleDeclaration::CHECK(L, -1); + const int r = luaL_ref(L, LUA_REGISTRYINDEX); + modules.emplace(mod->moduleName, r); + lastLoadedModule = mod->moduleName; + return Code::Success; +} + +ModuleDeclaration * +LuaContext::getModule(const std::string_view name) const noexcept +{ + if (moduleExists(name)) { + const int r = modules.at(name); + lua_rawgeti(L, LUA_REGISTRYINDEX, r); + return ModuleDeclaration::CHECK(L, -1); + } + + else { + const std::string moduleName(name); + luaL_error(L, "Module %s not found", moduleName.c_str()); + return nullptr; + } +} + +FunctionDeclaration * +LuaContext::getFunction(const std::string_view module, const std::string_view name) const noexcept +{ + if (moduleExists(module)) { + const int r = functions.at({ module, name }); + lua_rawgeti(L, LUA_REGISTRYINDEX, r); + return FunctionDeclaration::CHECK(L, -1); + } + + else { + const std::string moduleName(module); + const std::string functionName(name); + luaL_error(L, "Function %s::%s not found", moduleName.c_str(), functionName.c_str()); + return nullptr; + } +} + +JobDeclaration * +LuaContext::getJob(const std::string_view module, const std::string_view name) const noexcept +{ + if (moduleExists(module) && jobs.count({ module, name }) >= 1) { + const int r = jobs.at({ module, name }); + lua_rawgeti(L, LUA_REGISTRYINDEX, r); + return JobDeclaration::CHECK(L, -1); + } + + else { + const std::string moduleName(module); + const std::string jobName(name); + luaL_error(L, "Job %s::%s not found", moduleName.c_str(), jobName.c_str()); + return nullptr; // Unreachable + } +} + +const std::vector<std::string_view> +LuaContext::getAllModuleName() const noexcept +{ + std::vector<std::string_view> moduleNames{}; + for (auto const &[name, _] : modules) + moduleNames.emplace_back(name); + return moduleNames; +} + +void +LuaContext::setFailed(const std::string &msg) noexcept +{ + failureString = msg; + streamErr << "LuaContext is now in failure state: " << msg << "\n"; +} + +bool +LuaContext::isFailed() const noexcept +{ + return failureString.size() != 0; +} + +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 146c0e5f8d6c71bb17c2678be1aece7b37bd6ee9..a852aac6ab4528084e026251bbc6906ccef2b541 100644 --- a/src/Lib/Script/LuaContext.hh +++ b/src/Lib/Script/LuaContext.hh @@ -1,5 +1,10 @@ #pragma once +#include "ScriptStore.hh" +#include <sstream> +#include <vector> +#include <map> + struct lua_State; class QString; @@ -8,27 +13,75 @@ namespace Vivy class ScriptDocument; } +namespace Vivy::Script +{ +class ModuleDeclaration; +class FunctionDeclaration; +class JobDeclaration; +} + namespace Vivy::Script { // New Lua script instance class LuaContext final { - lua_State *L; + VIVY_UNMOVABLE_OBJECT(LuaContext) -public: - enum class Code { - Success, - Error, - }; + using string_view = const std::string_view; + + lua_State *L{ nullptr }; + + std::string failureString{}; + std::string currentFile{}; + std::string lastLoadedModule{}; + + std::map<string_view, const int> modules = {}; + std::map<std::tuple<string_view, string_view>, const int> functions = {}; + std::map<std::tuple<string_view, string_view>, const int> jobs = {}; + + static inline std::map<const lua_State *const, LuaContext *const> contextList = {}; + + ScriptStore::LoggerType &streamOut; + ScriptStore::LoggerType &streamErr; public: - LuaContext() noexcept; + enum class Code { Success, Error }; + static constexpr inline Code Success = Code::Success; + static constexpr inline Code Error = Code::Error; + + LuaContext(ScriptStore::LoggerType &out, ScriptStore::LoggerType &err) noexcept; ~LuaContext() noexcept; Code loadDocument(std::shared_ptr<ScriptDocument>) noexcept; + QString getLastLuaError() noexcept; + lua_State *getState() noexcept; - QString getLastError() noexcept; + auto &getOutputStream() noexcept { return streamOut; } + auto &getErrorStream() noexcept { return streamErr; } operator lua_State *() noexcept { return L; } + static LuaContext *getContext(const lua_State *const) noexcept; + + // WARN: Need the ModuleDeclaration, the FunctionDeclaration and the + // JobDeclaration to be on top of the stack! + Code registerDeclaration(const ModuleDeclaration *const) noexcept; + Code registerDeclaration(const FunctionDeclaration *const) noexcept; + Code registerDeclaration(const JobDeclaration *const) noexcept; + + // Returns the module or `nullptr` if not found. + ModuleDeclaration *getModule(const std::string_view) const noexcept; + FunctionDeclaration *getFunction(const std::string_view, const std::string_view) const noexcept; + JobDeclaration *getJob(const std::string_view, const std::string_view) const noexcept; + const std::vector<std::string_view> getAllModuleName() const noexcept; + + // Some verifications gone wrong, this is not a Lua error, this is a script + // runtime error. + 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; + void setLastLoadedModule(const std::string &) noexcept; private: // Exec all loaded strings and files @@ -36,9 +89,27 @@ private: Code loadString(const char *) noexcept; Code loadFile(const char *) noexcept; + void loadPackagedFile(const QString &) noexcept; + + Code registerToMapTuple( + const std::string_view module, const std::string_view name, + std::map<std::tuple<string_view, string_view>, const int> *const mapPtr) noexcept; + bool moduleExists(string_view) const noexcept; // Swap the env, needed before executing a user script! void swapEnv() noexcept; }; +static inline auto & +out(LuaContext *const context) noexcept +{ + return context->getOutputStream(); +} + +static inline auto & +err(LuaContext *const context) noexcept +{ + return context->getErrorStream(); +} + } diff --git a/src/Lib/Script/ScriptOption.cc b/src/Lib/Script/ScriptOption.cc new file mode 100644 index 0000000000000000000000000000000000000000..2876d2fc18a14cf3e8560cfada3b0080a76dd2a5 --- /dev/null +++ b/src/Lib/Script/ScriptOption.cc @@ -0,0 +1,62 @@ +#include "ScriptOption.hh" + +// OptionDeclaration +using namespace Vivy::Script; + +ScriptOption::ScriptOption(const std::string &str) noexcept + : string(str) + , type(Type::String) +{ +} + +ScriptOption::ScriptOption(const std::string_view str) noexcept + : string(str) + , type(Type::String) +{ +} + +ScriptOption::ScriptOption(double n) noexcept + : number(n) + , type(Type::Number) +{ +} + +ScriptOption::ScriptOption(bool b) noexcept + : boolean(b) + , type(Type::Boolean) +{ +} + +std::string * +ScriptOption::getString() noexcept +{ + return type == Type::String ? &string : nullptr; +} + +double * +ScriptOption::getNumber() noexcept +{ + return type == Type::Number ? &number : nullptr; +} + +bool * +ScriptOption::getBoolean() noexcept +{ + return type == Type::Boolean ? &boolean : nullptr; +} + +std::string_view +ScriptOption::getSignature() const noexcept +{ + static constexpr std::string_view signatureNumber = "Number"; + static constexpr std::string_view signatureString = "String"; + static constexpr std::string_view signatureBoolean = "Boolean"; + switch (type) { + case Type::String: return signatureString; + case Type::Number: return signatureNumber; + case Type::Boolean: return signatureBoolean; + } + + // Let the program crash + qFatal("UNREACHABLE"); +} diff --git a/src/Lib/Script/ScriptOption.hh b/src/Lib/Script/ScriptOption.hh new file mode 100644 index 0000000000000000000000000000000000000000..db4076b86fab2f02ca62d72bdecd86525acb8815 --- /dev/null +++ b/src/Lib/Script/ScriptOption.hh @@ -0,0 +1,31 @@ +#pragma once + +#include <string> +#include <string_view> + +namespace Vivy::Script +{ +// The options +class ScriptOption { + enum class Type { String, Number, Boolean }; + +private: + std::string string; + double number; + bool boolean; + + Type type; + +public: + ScriptOption(const std::string &) noexcept; + ScriptOption(const std::string_view) noexcept; + ScriptOption(bool) noexcept; + ScriptOption(double) noexcept; + + std::string *getString() noexcept; + double *getNumber() noexcept; + bool *getBoolean() noexcept; + + std::string_view getSignature() const noexcept; +}; +} diff --git a/src/Lib/Script/ScriptStore.cc b/src/Lib/Script/ScriptStore.cc index 12408a70d5ca0cdea7d6e9770d9a0ae8375c273d..61a970d59026592d3b7d10c4e8222f56ba7185f0 100644 --- a/src/Lib/Script/ScriptStore.cc +++ b/src/Lib/Script/ScriptStore.cc @@ -1,8 +1,10 @@ #include "ScriptStore.hh" #include "../Uuid.hh" +#include <iostream> using namespace Vivy; using namespace std::string_literals; +using Code = Script::LuaContext::Code; ScriptStore::ScriptStore() noexcept { resetLoadedScripts(); } @@ -12,13 +14,17 @@ ScriptStore::loadScriptFolder(const QString &folderPath) QDir folder(folderPath); const auto filter = QDir::Files | QDir::NoSymLinks | QDir::Readable; - // TODO: Sort the list with the dependencies between the scripts + // Load all module files, the loadDocument will ask for the implementation + // file. for (const QString &file : folder.entryList(filter)) { + if (!Utils::scriptFileSuffix.contains(QFileInfo(file).suffix())) + continue; + auto doc = loadDocument(file); - if (luaContext->loadDocument(doc) != Script::LuaContext::Code::Success) { + if (luaContext->loadDocument(doc) != Code::Success) { const QString error = QStringLiteral("File: ") + file + QStringLiteral(" [") + doc->getUuid().toString() + QStringLiteral("]") + - luaContext->getLastError(); + luaContext->getLastLuaError(); throw std::runtime_error(error.toStdString()); } } @@ -36,5 +42,45 @@ ScriptStore::getLoadedScripts() const noexcept void ScriptStore::resetLoadedScripts() noexcept { - luaContext.reset(new Script::LuaContext()); + luaContext.reset(new Script::LuaContext(streamOut, streamErr)); +} + +std::optional<std::tuple<int, std::string>> +ScriptStore::executeScript(Uuid id) noexcept +{ + const auto script = documents.at(id); + const bool ok = luaContext->loadDocument(script) == Code::Success; + QString errorString; + std::string buffer; + + std::cout << "STDOUT:\n"; + while (std::getline(streamOut, buffer, '\n')) + std::cout << '\t' << buffer << '\n'; + + if (!ok) { + // /home/.../sample-spec.module:31: bad argument #1 to 'export'... + std::cout << "STDERR:\n"; + QRegExp errorDetect(QStringLiteral("^([^\\:]*)\\:(\\d+)\\:(.*)\\n?$")); + while (std::getline(streamErr, buffer, '\n')) { + std::cout << '\t' << buffer << '\n'; + if (errorDetect.indexIn(buffer.c_str()) != -1) { + std::string errorDesc = errorDetect.cap(3).toStdString(); + return std::tuple{ QString(errorDetect.cap(2)).toInt(), Utils::trim(errorDesc) }; + } + } + } + + return std::nullopt; +} + +const std::vector<std::string_view> +ScriptStore::getLoadedModules() const noexcept +{ + return luaContext ? luaContext->getAllModuleName() : std::vector<std::string_view>{}; +} + +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 88916152d0beb3d42f2f10b8990d5527ede1753b..dd4266a998dc370119156b000c1444d8dbc9ad09 100644 --- a/src/Lib/Script/ScriptStore.hh +++ b/src/Lib/Script/ScriptStore.hh @@ -2,22 +2,41 @@ #include "../CRTPStore.hh" #include "ScriptDocument.hh" -#include "LuaContext.hh" +#include <sstream> +#include <optional> + +namespace Vivy::Script +{ +class LuaContext; +class ModuleDeclaration; +} namespace Vivy { class ScriptStore final : public CRTPStore<ScriptStore, ScriptDocument> { VIVY_STORAGE_CLASS(ScriptStore, ScriptDocument) - std::unique_ptr<Script::LuaContext> luaContext{ nullptr }; + static constexpr inline int outBufferSize = 1024; public: - using Item = std::tuple<Uuid, QString>; + using Item = std::tuple<Uuid, QString>; + using LoggerType = std::stringstream; explicit ScriptStore() noexcept; + // Load scripts void resetLoadedScripts() noexcept; void loadScriptFolder(const QString &folderPath); std::vector<Item> getLoadedScripts() const noexcept; + std::optional<std::tuple<int, std::string>> executeScript(Uuid) noexcept; + + // 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 }; + LoggerType streamOut; + LoggerType streamErr; }; } diff --git a/src/Lib/Utils.hh b/src/Lib/Utils.hh index e8095145f4ed85b989f84e08ac03655115757cbf..bf61eedb9d13e2ee85a8d33e90f766b8c4e9b1bc 100644 --- a/src/Lib/Utils.hh +++ b/src/Lib/Utils.hh @@ -53,7 +53,7 @@ static const QStringList audioFileSuffix = { "wave", "wav", "ogg", "mp3", "m4 static const QStringList videoFileSuffix = { "mkv", "mp4", "mov", "avi", "av1", "m4v", "flv" }; static const QStringList assFileSuffix = { "ass" }; static const QStringList vivyFileSuffix = { "vivy" }; -static const QStringList scriptFileSuffix = { "lua", "vvs" }; +static const QStringList scriptFileSuffix = { "module" }; const QString &getAudioFileSuffixFilter() noexcept; const QString &getVideoFileSuffixFilter() noexcept; @@ -136,7 +136,7 @@ enum class DocumentType : quint64 { /* Meta-types */ Audio = (WAVE | OGG | MP3 | M4A | OPUS | MP2 | AIFF | FLAC | ALAC), - Video = (MKV | MP4 | MOV | AVI | AV1 | M4V | FLV), + Video = (MKV | MP4 | MOV | AVI | AV1 | M4V | FLV) }; template <typename E> constexpr auto @@ -175,7 +175,7 @@ enum class AudioDocumentType : quint64 { MP2 = Utils::toUnderlying(Utils::DocumentType::MP2), AIFF = Utils::toUnderlying(Utils::DocumentType::AIFF), FLAC = Utils::toUnderlying(Utils::DocumentType::FLAC), - ALAC = Utils::toUnderlying(Utils::DocumentType::ALAC), + ALAC = Utils::toUnderlying(Utils::DocumentType::ALAC) }; // Video document types @@ -186,13 +186,11 @@ enum class VideoDocumentType : quint64 { AVI = Utils::toUnderlying(Utils::DocumentType::AVI), AV1 = Utils::toUnderlying(Utils::DocumentType::AV1), M4V = Utils::toUnderlying(Utils::DocumentType::M4V), - FLV = Utils::toUnderlying(Utils::DocumentType::FLV), + FLV = Utils::toUnderlying(Utils::DocumentType::FLV) }; // Ass document types -enum class AssDocumentType : quint64 { - ASS = Utils::toUnderlying(Utils::DocumentType::ASS), -}; +enum class AssDocumentType : quint64 { ASS = Utils::toUnderlying(Utils::DocumentType::ASS) }; } class QMenu; diff --git a/src/Main.cc b/src/Main.cc index d3a75093f4e098ecd178a9ca9e8e9b2830eab53d..7fb3b3ff18a661594cab1def4edf71ebc839162a 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1,8 +1,10 @@ #include "VivyApplication.hh" +#include "VivyCli.hh" #include "Lib/Script/LuaContext.hh" int main(int argc, char **argv) noexcept { - return Vivy::VivyApplication(argc, argv).exec(); + return (argc >= 2) ? (Vivy::VivyCli(argc, argv).exec()) + : (Vivy::VivyApplication(argc, argv).exec()); } diff --git a/src/UI/DocumentViews/MpvContainer.cc b/src/UI/DocumentViews/MpvContainer.cc index 388e57a01a5a343a19bd7991a46c0b6f4c1f7a65..d3fdd9668dda50999c43bf2f32edd6ab6cc2836d 100644 --- a/src/UI/DocumentViews/MpvContainer.cc +++ b/src/UI/DocumentViews/MpvContainer.cc @@ -88,9 +88,9 @@ MpvContainer::handleMpvEvent(mpv_event *event) noexcept mpv_event_property *prop; }; - auto checkProp = [](mpv_event_property *prop, const std::string &str, + auto checkProp = [](mpv_event_property *prop_, const std::string &str, int format) noexcept -> bool { - return (prop->name == str) && (prop->format == format); + return (prop_->name == str) && (prop_->format == format); }; switch (event->event_id) { diff --git a/src/UI/MainWindow.cc b/src/UI/MainWindow.cc index 939d3a3cac5e41babefa2ed52c5019c676a9c3ce..b89262cdca7f46e4b925612725a253e312795f72 100644 --- a/src/UI/MainWindow.cc +++ b/src/UI/MainWindow.cc @@ -265,8 +265,21 @@ MainWindow::openDocument() noexcept (fileType == Utils::DocumentType::Audio) || (fileType == Utils::DocumentType::ASS)) addTab(new VivyDocumentView(vivyApp->documentStore.loadDocument(filename), documents)); - else if (fileType == Utils::DocumentType::VivyScript) - addTab(new ScriptDocumentView(vivyApp->scriptStore.loadDocument(filename), documents)); + else if (fileType == Utils::DocumentType::VivyScript) { + // FIXME: First interpretation of the file to get the error, can do better. + std::shared_ptr<ScriptDocument> scriptDocument = + vivyApp->scriptStore.loadDocument(filename); + std::optional<std::tuple<int, std::string>> errorTuple = + vivyApp->scriptStore.executeScript(scriptDocument->getUuid()); + ScriptDocumentView *newView = new ScriptDocumentView(scriptDocument, documents); + + if (errorTuple.has_value()) { + const auto &[line, desc] = errorTuple.value(); + emit newView->luaErrorFound(line, QString{ desc.c_str() }); + } + + addTab(newView); + } } catch (const std::runtime_error &e) { qCritical() << "Failed to load document" << filename << "with error:" << e.what(); } diff --git a/src/UI/ScriptDocumentView.cc b/src/UI/ScriptDocumentView.cc index 786dd20365e3d3bc16c4bd16bf71829977d20b84..eea7873654e64e945d79c751c4c5c4673942d2ed 100644 --- a/src/UI/ScriptDocumentView.cc +++ b/src/UI/ScriptDocumentView.cc @@ -3,6 +3,7 @@ #include "ScriptViews/ScriptHighlighter.hh" #include "../VivyApplication.hh" +#include <QRegExp> #include <QVBoxLayout> using namespace Vivy; @@ -21,6 +22,8 @@ ScriptDocumentView::ScriptDocumentView(std::shared_ptr<ScriptDocument> ptr, QWid editor->setPlainText(textFile.readAll()); setCentralWidget(editor); editor->setFocus(Qt::OtherFocusReason); + + connect(this, &ScriptDocumentView::luaErrorFound, editor, &ScriptEditor::updateLastLuaError); } void diff --git a/src/UI/ScriptDocumentView.hh b/src/UI/ScriptDocumentView.hh index c78c34833d4a2b5d79d9b3657b1fcca745e92632..8e595086bfeae7deba8cf65de2421da827c56eab 100644 --- a/src/UI/ScriptDocumentView.hh +++ b/src/UI/ScriptDocumentView.hh @@ -32,10 +32,15 @@ public: QIcon getDocumentTabIcon() const noexcept override; AbstractDocument *getDocument() const noexcept override; +signals: + void luaErrorFound(int, QString); + private: ScriptEditor *editor{ nullptr }; ScriptHighlighter *syntax{ nullptr }; std::shared_ptr<ScriptDocument> document{ nullptr }; + QString lastLuaErrorMsg{}; + int lastLuaErrorLine{ -1 }; }; } diff --git a/src/UI/ScriptViews/ScriptEditor.cc b/src/UI/ScriptViews/ScriptEditor.cc index 362598405bba9536432d40c4373edba5e4ba1d35..7b8a73d94e2231a98f6a4542a6b70fac5d55cabc 100644 --- a/src/UI/ScriptViews/ScriptEditor.cc +++ b/src/UI/ScriptViews/ScriptEditor.cc @@ -131,3 +131,9 @@ ScriptEditor::lineNumberAreaPaintEvent(QPaintEvent *event) noexcept ++blockNumber; } } + +void +ScriptEditor::updateLastLuaError(int line, QString desc) +{ + qDebug() << "Update error on line" << line << "with description" << desc; +} diff --git a/src/UI/ScriptViews/ScriptEditor.hh b/src/UI/ScriptViews/ScriptEditor.hh index 601a2ed1305420342f0c81f57b35fe9a28c8dc29..01c01769c60d3d324cacb35bb2a55c0684d957d6 100644 --- a/src/UI/ScriptViews/ScriptEditor.hh +++ b/src/UI/ScriptViews/ScriptEditor.hh @@ -38,6 +38,9 @@ public: void lineNumberAreaPaintEvent(QPaintEvent *event) noexcept; int lineNumberAreaWidth() noexcept; +public slots: + void updateLastLuaError(int, QString); + protected: void resizeEvent(QResizeEvent *event) noexcept override; void keyPressEvent(QKeyEvent *e) noexcept override; diff --git a/src/UI/ScriptViews/ScriptHighlighter.cc b/src/UI/ScriptViews/ScriptHighlighter.cc index 6112d17646926a3d9c811b90a41ae96dc14277c1..85ea10a8ce763a8bc918fb5928cbbf574f77bda3 100644 --- a/src/UI/ScriptViews/ScriptHighlighter.cc +++ b/src/UI/ScriptViews/ScriptHighlighter.cc @@ -21,6 +21,14 @@ ScriptHighlighter::resetHighlightingRule() noexcept highlightingRules.emplace_back(QRegExpLiteral("\\b[A-Za-z0-9_]+[ ]*(?=\\()"), functionFormat); highlightingRules.emplace_back(QRegExpLiteral("\\b[A-Za-z0-9_]+[ ]*(?=\\{)"), functionFormat); + // Member and enum members + enumFormat.setForeground(fromTheme().enumForeground); + enumFormat.setFontWeight(QFont::Bold); + memberFormat.setForeground(fromTheme().memberForeground); + memberFormat.setFontWeight(QFont::StyleItalic); + highlightingRules.emplace_back(QRegExpLiteral("\\.[a-z][\\dA-Za-z]*\\b"), memberFormat); + highlightingRules.emplace_back(QRegExpLiteral("\\b[A-Z][\\dA-Z_]*\\b"), enumFormat); + // keywords const QStringList keywordPatterns = { QStringLiteral("\\bfunction\\b"), QStringLiteral("\\bbreak\\b"), @@ -32,15 +40,16 @@ ScriptHighlighter::resetHighlightingRule() noexcept QStringLiteral("\\bfor\\b"), QStringLiteral("\\bin\\b"), QStringLiteral("\\blocal\\b"), QStringLiteral("\\bor\\b"), QStringLiteral("\\band\\b"), QStringLiteral("\\bnot\\b"), - QStringLiteral("\\breturn\\b"), + QStringLiteral("\\breturn\\b"), QStringLiteral("\\b\\=\\b"), + QStringLiteral("\\b\\+\\=\\b"), QStringLiteral("\\b\\-\\=\\b"), + QStringLiteral("\\b\\*\\=\\b"), QStringLiteral("\\b\\/\\=\\b"), }; keywordFormat.setForeground(fromTheme().keywordForeground); keywordFormat.setFontWeight(QFont::Bold); - for (QString const &pattern : keywordPatterns) { + for (QString const &pattern : keywordPatterns) highlightingRules.emplace_back(QRegExp(pattern), keywordFormat); - } // numbers, boolean, nil const QStringList valuePatterns = { @@ -56,12 +65,11 @@ ScriptHighlighter::resetHighlightingRule() noexcept valueFormat.setForeground(fromTheme().valueForeground); valueFormat.setFontWeight(QFont::Normal); - for (QString const &pattern : valuePatterns) { + for (QString const &pattern : valuePatterns) highlightingRules.emplace_back(QRegExp(pattern), valueFormat); - } // strings - quotationFormat.setForeground(fromTheme().quotationForeground); + quotationFormat.setForeground(fromTheme().valueForeground); highlightingRules.emplace_back(QRegExpLiteral("\"[^\"]*\""), quotationFormat); highlightingRules.emplace_back(QRegExpLiteral("\'[^\']*\'"), quotationFormat); quoteStartExpression = QRegExpLiteral("\\[\\["); diff --git a/src/UI/ScriptViews/ScriptHighlighter.hh b/src/UI/ScriptViews/ScriptHighlighter.hh index 655f8755f96a51b18a9e7e7a9d74f36487eba44b..752c8023455608d13e6177704274d5f9ffd73617 100644 --- a/src/UI/ScriptViews/ScriptHighlighter.hh +++ b/src/UI/ScriptViews/ScriptHighlighter.hh @@ -26,8 +26,9 @@ class ScriptHighlighter final : public QSyntaxHighlighter { const QBrush functionForeground; const QBrush keywordForeground; const QBrush valueForeground; - const QBrush quotationForeground; const QBrush commentForeground; + const QBrush memberForeground; + const QBrush enumForeground; }; enum Theme { DarkTheme = 0, LightTheme = 1, ThemeCount }; @@ -54,19 +55,21 @@ private: void resetHighlightingRule() noexcept; const HighlightingTheme darkHighlightingTheme = { - .functionForeground = Qt::darkCyan, - .keywordForeground = Qt::darkYellow, - .valueForeground = QColor(Qt::cyan).darker(120), - .quotationForeground = Qt::darkGreen, - .commentForeground = QColor(Qt::darkGray).darker(120), + .functionForeground = Qt::darkCyan, + .keywordForeground = Qt::darkYellow, + .valueForeground = QColor(Qt::cyan).darker(120), + .commentForeground = QColor(Qt::darkGray).darker(120), + .memberForeground = QColor(Qt::darkGray).lighter(160), + .enumForeground = QColor(Qt::magenta).darker(170), }; const HighlightingTheme lightHighlightingTheme = { - .functionForeground = Qt::blue, - .keywordForeground = Qt::darkBlue, - .valueForeground = Qt::red, - .quotationForeground = Qt::darkGreen, - .commentForeground = QColor(Qt::darkGray).darker(120), + .functionForeground = Qt::blue, + .keywordForeground = Qt::darkBlue, + .valueForeground = Qt::red, + .commentForeground = QColor(Qt::darkGray).darker(120), + .memberForeground = QColor(Qt::darkGray).lighter(120), + .enumForeground = Qt::darkMagenta, }; const HighlightingTheme highlightingTheme[ThemeCount] = { @@ -88,5 +91,7 @@ private: QTextCharFormat singleLineCommentFormat; QTextCharFormat quotationFormat; QTextCharFormat functionFormat; + QTextCharFormat enumFormat; + QTextCharFormat memberFormat; }; } diff --git a/src/VivyApplication.hh b/src/VivyApplication.hh index 757dcff18f8e029802675da3cff344b4147512a6..1ba91685512f43b533e4e3cd06a511bb871c35ef 100644 --- a/src/VivyApplication.hh +++ b/src/VivyApplication.hh @@ -6,8 +6,8 @@ #define vivyApp static_cast<VivyApplication *>(QApplication::instance()) -#define currentVivyDocument() dynamic_cast<VivyDocument*>(vivyApp->getCurrentDocument()) -#define currentScriptDocument dynamic_cast<ScriptDocument*>(vivyApp->getCurrentDocument()) +#define currentVivyDocument() dynamic_cast<VivyDocument *>(vivyApp->getCurrentDocument()) +#define currentScriptDocument dynamic_cast<ScriptDocument *>(vivyApp->getCurrentDocument()) // Only support dark theme for now #define VIVY_ICON_APP ":icons/vivy.png" diff --git a/src/VivyCli.cc b/src/VivyCli.cc new file mode 100644 index 0000000000000000000000000000000000000000..1305babcbb026ba9c5984a00aae8529bfcdb2202 --- /dev/null +++ b/src/VivyCli.cc @@ -0,0 +1,24 @@ +#include "VivyCli.hh" +#include <iostream> + +using namespace Vivy; + +VivyCli::VivyCli(int &argc, char **argv) noexcept +{ + if (argc >= 2) { + selectedDoc = scriptStore.loadDocument(QString(argv[1])); + } +} + +int +VivyCli::exec() noexcept +{ + if ((selectedDoc == nullptr) || (!scriptStore.executeScript(selectedDoc->getUuid()))) + return 1; + + for (const auto &str : scriptStore.getLoadedModules()) { + std::cout << "Module " << str << " was loaded!\n"; + const auto *mod = scriptStore.getModule(str); + } + return 0; +} diff --git a/src/VivyCli.hh b/src/VivyCli.hh new file mode 100644 index 0000000000000000000000000000000000000000..9ceef273fa57a52fb9d2e0d2bfbf7eb31aefb037 --- /dev/null +++ b/src/VivyCli.hh @@ -0,0 +1,19 @@ +#pragma once + +#ifndef __cplusplus +#error "This is a C++ header" +#endif + +#include "Lib/Script/ScriptStore.hh" + +namespace Vivy +{ +class VivyCli final { + ScriptStore scriptStore{}; + std::shared_ptr<ScriptDocument> selectedDoc{ nullptr }; + +public: + VivyCli(int &argc, char **argv) noexcept; + int exec() noexcept; +}; +} diff --git a/utils/lua/sample-spec.lua b/utils/lua/sample-spec.lua deleted file mode 100644 index 999d19418d588b7127656cb975ce8ac9ea458af1..0000000000000000000000000000000000000000 --- a/utils/lua/sample-spec.lua +++ /dev/null @@ -1,87 +0,0 @@ ---[[ - Sample script to have a Vivy script specification. Note that all things in - a script should be "local" or you won't be saved if you break something - else in Vivy... ---]] - ---[[ - Get the global variables from the Vivy runtime, like the video dimensions - or duration, etc. ---]] - -local global = Vivy:global() - ---[[ - Get other script, here the util script. You can pass an author in case of - ambiguity. The standard scripts are available under the "std" author, thus - you can't register scripts in the name of "std". If there is ambiguity, an - error will be displayed by Vivy. If the tuple (script, author, revision) is - not unique, you will get an error on register time. ---]] - -local utils = Vivy:getScript{script = "utils", author = "std"} - --- Create a new script. -local script = Vivy:newScript{ - name = "sample_script", - author = "Vivy", - revision = "rev-1254", - opt = { - option1 = false, - preTime = -900, - postTime = 300, - color1 = Vivy:newColor("#314159") - }, - description = [[ - Sample script use for the specifications. - ]] -} - --- A local function -local function tripleCopySyl (syl) - return { syl:copy(), syl:copy(), syl:copy() } -end - ---[[ - All functions take as first parameter the opt structure. This is really - usefull when calling multiple scripts on the same Vivy document with a - document defined options. ---]] - -local setRetime = Vivy:newJob { - name = "retime_lines", - iterate = Vivy.Iterable.LINE, - callback = function (opt, line) - line.start += opt.preTime - line.finish += opt.postTime - return line - end -} - -local demultiply_syllabes = Vivy:newJob { - name = "create_syllabes", - iterate = Vivy.Iterable.SYLLABE, - callback = function (opt, syl) - local newSyls = tripleCopySyl(syl) - - newSyls[1].start = syl.line.start - newSyls[1].finish = syl.start - - newSyls[3].finish = syl.line.finish - newSyls[3].start = syl.finish - - -- You can return a table, the runtime will unroll it and add all the - -- elements to the new ASS AST. - return newSyls - end -} - --- Register the callbacks. Will be called in that order -script:addJobs { - set_retime, -- Retime lines - demultiply_syllabes, -- Aegisub's syllabe - utils:getJob("color_after_read_3") -- 3-patern syls, apply color{1,2} -} - --- Register the script. -Vivy:registerScript script diff --git a/utils/lua/sample-spec.module b/utils/lua/sample-spec.module new file mode 100644 index 0000000000000000000000000000000000000000..b007d97a926900929530b3043f19f42f83e31958 --- /dev/null +++ b/utils/lua/sample-spec.module @@ -0,0 +1,54 @@ +--- Declaration + +local custom_opt = DeclareOption { option1 = false } +local time_opt = DeclareOption { preTime = -900, postTime = 300, } +local color_opt = DeclareOption { color1 = Vivy:newColor("#314159") } + +local module = DeclareModule { + name = "sample-spec", + description = "Sample script used for the specification proposition", + author = "Vivy", + revision = "rev-1254", + imports = { "utils" }, + options = { custom_opt, time_opt, color_opt }, + functions = { + DeclareFunction { "tripleCopySyl" }, + DeclareFunction { "printFoo" }, + ImportFunction { "utils", "prettyPrint" }, + }, + jobs = { + DeclareJob { "set_retime", options = { time_opt } }, + DeclareJob { "demultiply_syllabes" }, + ImportJob { "utils", "color_after_read_3", "utils", options = { color_opt } } + } +} + +--- Implementation + +local utils = module:import { "utils" } +local prettyPrint = module:import { "utils", "prettyPrint" } + +local tripleCopySyl = module:export { "tripleCopySyl", function (syl) + return { syl:copy(), syl:copy(), syl:copy() } +end } + +module:export { "printFoo", function () prettyPrint ("Bar") end } + +module:export { "retime_lines", LINE, function (opt, line) + line.start = line.start + opt.preTime + line.finish = line.start + opt.postTime + if line.start <= Vivy:start() then line.start = Vivy:start() end + if line.finish >= Vivy:finish() then line.finish = Vivy:finish() end + return line +end } + +module:export { "create_syllabes", SYLLABE, function (opt, syl) + local newSyls = tripleCopySyl(syl) + newSyls[1].start = syl.line.start + newSyls[1].finish = syl.start + newSyls[3].finish = syl.line.finish + newSyls[3].start = syl.finish + return newSyls +end } + +-- vim: ft=lua diff --git a/utils/lua/simple.module b/utils/lua/simple.module new file mode 100644 index 0000000000000000000000000000000000000000..85439d803a08c8f063282d4428d5aa0a61577e04 --- /dev/null +++ b/utils/lua/simple.module @@ -0,0 +1,48 @@ +--- Declaration + +local custom_opt = DeclareOption { option1 = false } +local time_opt = DeclareOption { preTime = -900, postTime = 300, } +local color_opt = DeclareOption { color1 = Vivy:newColor("#314159") } + +local module = DeclareModule { + name = "sample-spec", + description = "Sample script used for the specification proposition", + author = "Vivy", + revision = "rev-1254", + options = { custom_opt, time_opt, color_opt }, + functions = { + DeclareFunction { "tripleCopySyl" }, + DeclareFunction { "printFoo" }, + }, + jobs = { + DeclareJob { "set_retime", options = { time_opt } }, + DeclareJob { "create_syllabes" }, + } +} + +--- Implementation + +local tripleCopySyl = module:export { "tripleCopySyl", function (syl) + return { syl:copy(), syl:copy(), syl:copy() } +end } + +module:export { "printFoo", function () prettyPrint ("Bar") end } + +module:export { "set_retime", LINE, function (opt, line) + line.start = line.start + opt.preTime + line.finish = line.start + opt.postTime + if line.start <= Vivy:start() then line.start = Vivy:start() end + if line.finish >= Vivy:finish() then line.finish = Vivy:finish() end + return line +end } + +module:export { "create_syllabes", SYLLABE, function (opt, syl) + local newSyls = tripleCopySyl(syl) + newSyls[1].start = syl.line.start + newSyls[1].finish = syl.start + newSyls[3].finish = syl.line.finish + newSyls[3].start = syl.finish + return newSyls +end } + +-- vim: ft=lua