diff --git a/src/engine.cpp b/src/engine.cpp index 17c5d976..2b64341c 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -23,6 +23,7 @@ #include "graphics/core/Shader.hpp" #include "graphics/ui/GUI.hpp" #include "logic/EngineController.hpp" +#include "logic/CommandsInterpreter.hpp" #include "logic/scripting/scripting.hpp" #include "util/listutil.hpp" #include "util/platform.hpp" @@ -59,7 +60,8 @@ inline void create_channel(Engine* engine, std::string name, NumberSetting& sett } Engine::Engine(EngineSettings& settings, SettingsHandler& settingsHandler, EnginePaths* paths) - : settings(settings), settingsHandler(settingsHandler), paths(paths) + : settings(settings), settingsHandler(settingsHandler), paths(paths), + interpreter(std::make_unique()) { corecontent::setup_bindings(); loadSettings(); @@ -198,6 +200,7 @@ Engine::~Engine() { } content.reset(); assets.reset(); + interpreter.reset(); gui.reset(); logger.info() << "gui finished"; audio::close(); @@ -211,6 +214,10 @@ EngineController* Engine::getController() { return controller.get(); } +cmd::CommandsInterpreter* Engine::getCommandsInterpreter() { + return interpreter.get(); +} + PacksManager Engine::createPacksManager(const fs::path& worldFolder) { PacksManager manager; manager.setSources({ diff --git a/src/engine.hpp b/src/engine.hpp index 331864ed..61066143 100644 --- a/src/engine.hpp +++ b/src/engine.hpp @@ -34,6 +34,10 @@ namespace gui { class GUI; } +namespace cmd { + class CommandsInterpreter; +} + class initialize_error : public std::runtime_error { public: initialize_error(const std::string& message) : std::runtime_error(message) {} @@ -52,6 +56,7 @@ class Engine : public util::ObjectsKeeper { std::queue postRunnables; std::recursive_mutex postRunnablesMutex; std::unique_ptr controller; + std::unique_ptr interpreter; std::vector basePacks {"base"}; uint64_t frame = 0; @@ -136,6 +141,7 @@ public: void saveScreenshot(); EngineController* getController(); + cmd::CommandsInterpreter* getCommandsInterpreter(); PacksManager createPacksManager(const fs::path& worldFolder); diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index c8197aac..d8238bc3 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -8,12 +8,12 @@ using namespace cmd; inline bool is_cmd_identifier_part(char c, bool allowColon) { - return is_identifier_part(c) || c == '.' || c == '$' || c == '@' || + return is_identifier_part(c) || c == '.' || c == '$' || (allowColon && c == ':'); } inline bool is_cmd_identifier_start(char c) { - return is_identifier_start(c) || c == '.' || c == '$' || c == '@'; + return is_identifier_start(c) || c == '.' || c == '$'; } class CommandParser : BasicParser { @@ -37,7 +37,7 @@ class CommandParser : BasicParser { {"num", ArgType::number}, {"int", ArgType::integer}, {"str", ArgType::string}, - {"@", ArgType::selector}, + {"sel", ArgType::selector}, {"enum", ArgType::enumvalue}, }; public: @@ -60,7 +60,7 @@ public: dynamic::Value parseValue() { char c = peek(); - if (is_cmd_identifier_start(c)) { + if (is_cmd_identifier_start(c) || c == '@') { auto str = parseIdentifier(true); if (str == "true") { return true; @@ -171,7 +171,7 @@ public: } template - bool typeCheck(Argument* arg, const dynamic::Value& value, const std::string& tname) { + inline bool typeCheck(Argument* arg, const dynamic::Value& value, const std::string& tname) { if (!std::holds_alternative(value)) { if (arg->optional) { return false; @@ -182,6 +182,22 @@ public: return true; } + inline bool selectorCheck(Argument* arg, const dynamic::Value& value) { + if (auto string = std::get_if(&value)) { + if ((*string)[0] == '@') { + if (!util::is_integer((*string).substr(1))) { + throw argumentError(arg->name, "invalid selector"); + } + return true; + } + } + if (arg->optional) { + return false; + } else { + throw typeError(arg->name, "selector", value); + } + } + bool typeCheck(Argument* arg, const dynamic::Value& value) { switch (arg->type) { case ArgType::enumvalue: { @@ -208,12 +224,12 @@ public: } } break; + case ArgType::selector: + return selectorCheck(arg, value); case ArgType::integer: return typeCheck(arg, value, "integer"); case ArgType::string: return typeCheck(arg, value, "string"); - case ArgType::selector: - return typeCheck(arg, value, "id"); } return true; } @@ -309,8 +325,20 @@ public: } // positional argument - Argument* arg; + Argument* arg = nullptr; do { + if (arg) { + std::cout << "skipped arg " << arg->name << std::endl; + if (auto string = std::get_if(&arg->def)) { + if ((*string)[0] == '$') { + args->put((*interpreter)[*string]); + } else { + args->put(arg->def); + } + } else { + args->put(arg->def); + } + } arg = command->getArgument(arg_index++); if (arg == nullptr) { throw error("extra positional argument"); diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index 1ad6946c..88b62e8f 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -91,6 +91,8 @@ namespace cmd { std::unique_ptr repository; std::unordered_map variables; public: + CommandsInterpreter() : repository(std::make_unique()) {} + CommandsInterpreter(std::unique_ptr repository) : repository(std::move(repository)){} diff --git a/src/logic/scripting/lua/LuaState.cpp b/src/logic/scripting/lua/LuaState.cpp index d35120a5..dfdd7964 100644 --- a/src/logic/scripting/lua/LuaState.cpp +++ b/src/logic/scripting/lua/LuaState.cpp @@ -12,6 +12,10 @@ inline std::string LAMBDAS_TABLE = "$L"; static debug::Logger logger("lua-state"); +namespace scripting { + extern lua::LuaState* state; +} + lua::luaerror::luaerror(const std::string& message) : std::runtime_error(message) { } @@ -123,19 +127,20 @@ void lua::LuaState::remove(const std::string& name) { } void lua::LuaState::createLibs() { - openlib("audio", audiolib, 0); - openlib("block", blocklib, 0); - openlib("core", corelib, 0); - openlib("file", filelib, 0); - openlib("gui", guilib, 0); - openlib("input", inputlib, 0); - openlib("inventory", inventorylib, 0); - openlib("item", itemlib, 0); - openlib("json", jsonlib, 0); - openlib("pack", packlib, 0); - openlib("player", playerlib, 0); - openlib("time", timelib, 0); - openlib("world", worldlib, 0); + openlib("audio", audiolib); + openlib("block", blocklib); + openlib("console", consolelib); + openlib("core", corelib); + openlib("file", filelib); + openlib("gui", guilib); + openlib("input", inputlib); + openlib("inventory", inventorylib); + openlib("item", itemlib); + openlib("json", jsonlib); + openlib("pack", packlib); + openlib("player", playerlib); + openlib("time", timelib); + openlib("world", worldlib); addfunc("print", lua_wrap_errors); } @@ -364,9 +369,9 @@ bool lua::LuaState::isfunction(int idx) { return lua_isfunction(L, idx); } -void lua::LuaState::openlib(const std::string& name, const luaL_Reg* libfuncs, int nup) { +void lua::LuaState::openlib(const std::string& name, const luaL_Reg* libfuncs) { lua_newtable(L); - luaL_setfuncs(L, libfuncs, nup); + luaL_setfuncs(L, libfuncs, 0); lua_setglobal(L, name.c_str()); } @@ -377,7 +382,7 @@ const std::string lua::LuaState::storeAnonymous() { return funcName; } -runnable lua::LuaState::createRunnable() { +std::shared_ptr lua::LuaState::createLambdaHandler() { auto ptr = reinterpret_cast(lua_topointer(L, -1)); auto name = util::mangleid(ptr); lua_getglobal(L, LAMBDAS_TABLE.c_str()); @@ -385,13 +390,17 @@ runnable lua::LuaState::createRunnable() { lua_setfield(L, -2, name.c_str()); lua_pop(L, 2); - std::shared_ptr funcptr(new std::string(name), [=](auto* name) { + return std::shared_ptr(new std::string(name), [=](auto* name) { lua_getglobal(L, LAMBDAS_TABLE.c_str()); lua_pushnil(L); lua_setfield(L, -2, name->c_str()); lua_pop(L, 1); delete name; }); +} + +runnable lua::LuaState::createRunnable() { + auto funcptr = createLambdaHandler(); return [=]() { lua_getglobal(L, LAMBDAS_TABLE.c_str()); lua_getfield(L, -1, funcptr->c_str()); @@ -399,6 +408,23 @@ runnable lua::LuaState::createRunnable() { }; } +scripting::common_func lua::LuaState::createLambda() { + auto funcptr = createLambdaHandler(); + return [=](const std::vector& args) { + lua_getglobal(L, LAMBDAS_TABLE.c_str()); + lua_getfield(L, -1, funcptr->c_str()); + for (const auto& arg : args) { + pushvalue(arg); + } + if (call(args.size())) { + auto result = tovalue(-1); + pop(1); + return result; + } + return dynamic::Value(dynamic::NONE); + }; +} + int lua::LuaState::createEnvironment(int parent) { int id = nextEnvironment++; diff --git a/src/logic/scripting/lua/LuaState.hpp b/src/logic/scripting/lua/LuaState.hpp index 0ab6194c..7fbafed7 100644 --- a/src/logic/scripting/lua/LuaState.hpp +++ b/src/logic/scripting/lua/LuaState.hpp @@ -3,6 +3,7 @@ #include "lua_commons.hpp" +#include "../scripting_functional.hpp" #include "../../../data/dynamic.hpp" #include "../../../delegates.hpp" @@ -26,6 +27,8 @@ namespace lua { void logError(const std::string& text); void removeLibFuncs(const char* libname, const char* funcs[]); void createLibs(); + + std::shared_ptr createLambdaHandler(); public: LuaState(); ~LuaState(); @@ -60,7 +63,7 @@ namespace lua { int callNoThrow(int argc); int execute(int env, const std::string& src, const std::string& file=""); int eval(int env, const std::string& src, const std::string& file=""); - void openlib(const std::string& name, const luaL_Reg* libfuncs, int nup); + void openlib(const std::string& name, const luaL_Reg* libfuncs); void addfunc(const std::string& name, lua_CFunction func); bool getglobal(const std::string& name); void setglobal(const std::string& name); @@ -68,6 +71,8 @@ namespace lua { bool rename(const std::string& from, const std::string& to); void remove(const std::string& name);; runnable createRunnable(); + scripting::common_func createLambda(); + int createEnvironment(int parent); void removeEnvironment(int id); const std::string storeAnonymous(); diff --git a/src/logic/scripting/lua/api_lua.hpp b/src/logic/scripting/lua/api_lua.hpp index 0650fb2d..4d798d82 100644 --- a/src/logic/scripting/lua/api_lua.hpp +++ b/src/logic/scripting/lua/api_lua.hpp @@ -20,6 +20,7 @@ extern const luaL_Reg timelib []; extern const luaL_Reg worldlib []; extern const luaL_Reg jsonlib []; extern const luaL_Reg inputlib []; +extern const luaL_Reg consolelib []; // Lua Overrides extern int l_print(lua_State* L); diff --git a/src/logic/scripting/lua/libconsole.cpp b/src/logic/scripting/lua/libconsole.cpp new file mode 100644 index 00000000..49a0719d --- /dev/null +++ b/src/logic/scripting/lua/libconsole.cpp @@ -0,0 +1,43 @@ +#include "api_lua.hpp" +#include "lua_commons.hpp" +#include "LuaState.hpp" + +#include "../scripting.hpp" +#include "../../CommandsInterpreter.hpp" +#include "../../../engine.hpp" +#include "../../../coders/commons.hpp" + +namespace scripting { + extern lua::LuaState* state; +} + +using namespace scripting; + +static int l_add_command(lua_State* L) { + auto scheme = lua_tostring(L, 1); + lua_pushvalue(L, 2); + auto func = state->createLambda(); + try { + engine->getCommandsInterpreter()->getRepository()->add( + scheme, [func](auto, auto args, auto kwargs) { + return func({args, kwargs}); + } + ); + } catch (const parsing_error& err) { + luaL_error(L, ("command scheme error:\n"+err.errorLog()).c_str()); + } + return 0; +} + +static int l_execute(lua_State* L) { + auto prompt = lua_tostring(L, 1); + auto result = engine->getCommandsInterpreter()->execute(prompt); + state->pushvalue(result); + return 1; +} + +const luaL_Reg consolelib [] = { + {"add_command", lua_wrap_errors}, + {"execute", lua_wrap_errors}, + {NULL, NULL} +}; diff --git a/src/logic/scripting/scripting_functional.hpp b/src/logic/scripting/scripting_functional.hpp index 6ab338ce..e696db37 100644 --- a/src/logic/scripting/scripting_functional.hpp +++ b/src/logic/scripting/scripting_functional.hpp @@ -1,12 +1,16 @@ #ifndef LOGIC_SCRIPTING_SCRIPTING_FUNCTIONAL_HPP_ #define LOGIC_SCRIPTING_SCRIPTING_FUNCTIONAL_HPP_ -#include -#include #include "../../typedefs.hpp" #include "../../delegates.hpp" +#include "../../data/dynamic.hpp" + +#include +#include namespace scripting { + using common_func = std::function&)>; + runnable create_runnable( const scriptenv& env, const std::string& src, diff --git a/src/logic/scripting/scripting_hud.cpp b/src/logic/scripting/scripting_hud.cpp index 53d16521..b0fa40dd 100644 --- a/src/logic/scripting/scripting_hud.cpp +++ b/src/logic/scripting/scripting_hud.cpp @@ -20,7 +20,7 @@ Hud* scripting::hud = nullptr; void scripting::on_frontend_init(Hud* hud) { scripting::hud = hud; - scripting::state->openlib("hud", hudlib, 0); + scripting::state->openlib("hud", hudlib); for (auto& pack : scripting::engine->getContentPacks()) { state->emit_event(pack.id + ".hudopen", [&] (lua::LuaState* state) { diff --git a/src/util/stringutil.cpp b/src/util/stringutil.cpp index 69463ad3..9480af4c 100644 --- a/src/util/stringutil.cpp +++ b/src/util/stringutil.cpp @@ -151,7 +151,7 @@ std::wstring util::str2wstr_utf8(const std::string s) { return std::wstring(chars.data(), chars.size()); } -bool util::is_integer(std::string text) { +bool util::is_integer(const std::string& text) { for (char c : text) { if (c < '0' || c > '9') return false; @@ -159,7 +159,7 @@ bool util::is_integer(std::string text) { return true; } -bool util::is_integer(std::wstring text) { +bool util::is_integer(const std::wstring& text) { for (wchar_t c : text) { if (c < L'0' || c > L'9') return false; diff --git a/src/util/stringutil.hpp b/src/util/stringutil.hpp index 22596572..3270c92e 100644 --- a/src/util/stringutil.hpp +++ b/src/util/stringutil.hpp @@ -20,8 +20,8 @@ namespace util { uint32_t decode_utf8(uint& size, const char* bytes); std::string wstr2str_utf8(const std::wstring ws); std::wstring str2wstr_utf8(const std::string s); - bool is_integer(std::string text); - bool is_integer(std::wstring text); + bool is_integer(const std::string& text); + bool is_integer(const std::wstring& text); bool is_valid_filename(std::wstring name); void ltrim(std::string &s);