From b1c9e3dae5e7900c24eefa7374bd28a95c96e102 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 8 May 2024 23:14:15 +0300 Subject: [PATCH 01/36] CommandsInterpreter WIP --- src/logic/CommandsInterpreter.cpp | 13 ++++++++ src/logic/CommandsInterpreter.hpp | 54 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/logic/CommandsInterpreter.cpp create mode 100644 src/logic/CommandsInterpreter.hpp diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp new file mode 100644 index 00000000..25c76362 --- /dev/null +++ b/src/logic/CommandsInterpreter.cpp @@ -0,0 +1,13 @@ +#include "CommandsInterpreter.hpp" + +#include "../coders/commons.hpp" + +using namespace cmd; + +class SchemeParser : BasicParser { + +}; + +Command Command::create(std::string_view scheme) { + +} diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp new file mode 100644 index 00000000..8cf93fac --- /dev/null +++ b/src/logic/CommandsInterpreter.hpp @@ -0,0 +1,54 @@ +#ifndef LOGIC_COMMANDS_INTERPRETER_HPP_ +#define LOGIC_COMMANDS_INTERPRETER_HPP_ + +#include "../data/dynamic.hpp" + +#include +#include +#include + +namespace cmd { + enum class ArgType { + number, integer, enumvalue, selector, string + }; + + struct Argument { + std::string name; + ArgType type; + bool optional; + dynamic::Value def; + }; + + struct CommandInput { + dynamic::List_sptr args; // positional arguments list + dynamic::Map_sptr kwargs; // keyword arguments table + }; + + using executor_func = std::function; + + class Command { + std::string name; + std::vector args; + executor_func executor; + public: + Command( + std::string name, + std::vector args, + executor_func executor + ); + + dynamic::Value execute(const CommandInput& input) { + return executor(input.args, input.kwargs); + } + + static Command create(std::string_view scheme); + }; + + class CommandsInterpreter { + }; +} + +#endif // LOGIC_COMMANDS_INTERPRETER_HPP_ From 6ba3334681fb9962ddd66d3aed797592f30c80f0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 9 May 2024 17:03:48 +0300 Subject: [PATCH 02/36] CommandsInterpreter scheme parsing --- src/logic/CommandsInterpreter.cpp | 121 +++++++++++++++++++++++++++++- src/logic/CommandsInterpreter.hpp | 11 ++- 2 files changed, 127 insertions(+), 5 deletions(-) diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index 25c76362..16e979ce 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -1,13 +1,128 @@ #include "CommandsInterpreter.hpp" #include "../coders/commons.hpp" +#include "../util/stringutil.hpp" + +#include using namespace cmd; +inline bool is_cmd_identifier_part(char c) { + return is_identifier_part(c) || c == '.' || c == '$'; +} + class SchemeParser : BasicParser { - + std::string parseIdentifier() { + char c = peek(); + if (!is_identifier_start(c) && c != '$') { + if (c == '"') { + pos++; + return parseString(c); + } + throw error("identifier expected"); + } + int start = pos; + while (hasNext() && is_cmd_identifier_part(source[pos])) { + pos++; + } + return std::string(source.substr(start, pos-start)); + } + + std::unordered_map types { + {"num", ArgType::number}, + {"int", ArgType::integer}, + {"str", ArgType::string}, + {"selector", ArgType::selector}, + }; +public: + SchemeParser(std::string_view filename, std::string_view source) + : BasicParser(filename, source) { + } + + ArgType parseType() { + std::string name = parseIdentifier(); + std::cout << "type.name: " << name << std::endl; + auto found = types.find(name); + if (found != types.end()) { + return found->second; + } else { + throw error("unknown type "+util::quote(name)); + } + } + + dynamic::Value parseValue() { + char c = peek(); + if (is_cmd_identifier_part(c)) { + auto str = parseIdentifier(); + if (str == "true") { + return true; + } else if (str == "false") { + return false; + } else if (str == "none" || str == "nil" || str == "null") { + return std::monostate(); + } + return str; + } + if (c == '"') { + return parseIdentifier(); + } + if (c == '+' || c == '-' || is_digit(c)) { + return parseNumber(c == '-' ? -1 : 1); + } + throw error("invalid character '"+std::string({c})+"'"); + } + + Argument parseArgument() { + std::string name = parseIdentifier(); + expect(':'); + std::cout << "arg.name: " << name << std::endl; + ArgType type = parseType(); + bool optional = false; + dynamic::Value def {}; + dynamic::Value origin {}; + bool loop = true; + while (hasNext() && loop) { + char c = peek(); + switch (c) { + case '=': + nextChar(); + optional = true; + def = parseValue(); + break; + case '~': + nextChar(); + origin = parseValue(); + break; + default: + loop = false; + break; + } + } + return Argument {name, type, optional, def, origin}; + } + + Command parse(executor_func executor) { + std::string name = parseIdentifier(); + std::vector args; + std::unordered_map kwargs; + + std::cout << "command.name: " << name << std::endl; + while (hasNext()) { + if (peek() == '{') { + nextChar(); + while (peek() != '}') { + Argument arg = parseArgument(); + kwargs[arg.name] = arg; + } + nextChar(); + } else { + args.push_back(parseArgument()); + } + } + return Command(name, std::move(args), std::move(kwargs), executor); + } }; -Command Command::create(std::string_view scheme) { - +Command Command::create(std::string_view scheme, executor_func executor) { + return SchemeParser("", scheme).parse(executor); } diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index 8cf93fac..2a1332fc 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace cmd { enum class ArgType { @@ -17,6 +18,7 @@ namespace cmd { ArgType type; bool optional; dynamic::Value def; + dynamic::Value origin; }; struct CommandInput { @@ -32,19 +34,24 @@ namespace cmd { class Command { std::string name; std::vector args; + std::unordered_map kwargs; executor_func executor; public: Command( std::string name, std::vector args, + std::unordered_map kwargs, executor_func executor - ); + ) : name(name), + args(std::move(args)), + kwargs(std::move(kwargs)), + executor(executor) {} dynamic::Value execute(const CommandInput& input) { return executor(input.args, input.kwargs); } - static Command create(std::string_view scheme); + static Command create(std::string_view scheme, executor_func); }; class CommandsInterpreter { From 44d0a462f662b631f41ab4ddf6cd4cad3604a795 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 9 May 2024 17:09:45 +0300 Subject: [PATCH 03/36] fix --- src/logic/CommandsInterpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index 16e979ce..c9c82605 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -59,7 +59,7 @@ public: } else if (str == "false") { return false; } else if (str == "none" || str == "nil" || str == "null") { - return std::monostate(); + return dynamic::NONE; } return str; } From de318bb8ef314fab4b4166a85c218e0a1a85d79b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 9 May 2024 18:48:14 +0300 Subject: [PATCH 04/36] command argument enumeration type --- src/logic/CommandsInterpreter.cpp | 35 ++++++++++++++++++++++++++++--- src/logic/CommandsInterpreter.hpp | 1 + 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index c9c82605..7f5ff3a2 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -8,7 +8,7 @@ using namespace cmd; inline bool is_cmd_identifier_part(char c) { - return is_identifier_part(c) || c == '.' || c == '$'; + return is_identifier_part(c) || c == '.' || c == '$' || c == '@'; } class SchemeParser : BasicParser { @@ -32,7 +32,8 @@ class SchemeParser : BasicParser { {"num", ArgType::number}, {"int", ArgType::integer}, {"str", ArgType::string}, - {"selector", ArgType::selector}, + {"@", ArgType::selector}, + {"enum", ArgType::enumvalue}, }; public: SchemeParser(std::string_view filename, std::string_view source) @@ -40,6 +41,10 @@ public: } ArgType parseType() { + if (peek() == '[') { + std::cout << "type.name: enum" << std::endl; + return ArgType::enumvalue; + } std::string name = parseIdentifier(); std::cout << "type.name: " << name << std::endl; auto found = types.find(name); @@ -72,11 +77,35 @@ public: throw error("invalid character '"+std::string({c})+"'"); } + std::string parseEnum() { + if (peek() == '[') { + nextChar(); + if (peek() == ']') { + throw error("empty enumeration is not allowed"); + } + auto enumvalue = std::string(readUntil(']')); + if (enumvalue.find(' ') != std::string::npos) { + throw error("use '|' as separator, not a space"); + } + nextChar(); + return enumvalue; + } else { + expect('$'); + goBack(); + return parseIdentifier(); + } + } + Argument parseArgument() { std::string name = parseIdentifier(); expect(':'); std::cout << "arg.name: " << name << std::endl; ArgType type = parseType(); + std::string enumname = ""; + if (type == ArgType::enumvalue) { + enumname = parseEnum(); + std::cout << "enum: " << enumname << std::endl; + } bool optional = false; dynamic::Value def {}; dynamic::Value origin {}; @@ -98,7 +127,7 @@ public: break; } } - return Argument {name, type, optional, def, origin}; + return Argument {name, type, optional, def, origin, enumname}; } Command parse(executor_func executor) { diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index 2a1332fc..6f656451 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -19,6 +19,7 @@ namespace cmd { bool optional; dynamic::Value def; dynamic::Value origin; + std::string enumname; }; struct CommandInput { From 022c97b200eb2e77da2b68b17f45b17ff5399a40 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 9 May 2024 20:16:15 +0300 Subject: [PATCH 05/36] CommandsRepository --- src/coders/commons.cpp | 9 ++++++-- src/coders/commons.hpp | 2 +- src/logic/CommandsInterpreter.cpp | 35 ++++++++++++++++++++++++++----- src/logic/CommandsInterpreter.hpp | 20 ++++++++++++++++++ 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index 50538a50..81639499 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -154,8 +154,13 @@ void BasicParser::expectNewLine() { } } -void BasicParser::goBack() { - if (pos) pos--; +void BasicParser::goBack(size_t count) { + if (pos < count) { + throw std::runtime_error("pos < jump"); + } + if (pos) { + pos -= count; + } } char BasicParser::peek() { diff --git a/src/coders/commons.hpp b/src/coders/commons.hpp index c45d4f28..a9609c08 100644 --- a/src/coders/commons.hpp +++ b/src/coders/commons.hpp @@ -86,7 +86,7 @@ protected: void expect(const std::string& substring); bool isNext(const std::string& substring); void expectNewLine(); - void goBack(); + void goBack(size_t count=1); int64_t parseSimpleInt(int base); dynamic::Value parseNumber(int sign); diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index 7f5ff3a2..47a44307 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -11,7 +11,7 @@ inline bool is_cmd_identifier_part(char c) { return is_identifier_part(c) || c == '.' || c == '$' || c == '@'; } -class SchemeParser : BasicParser { +class CommandParser : BasicParser { std::string parseIdentifier() { char c = peek(); if (!is_identifier_start(c) && c != '$') { @@ -36,7 +36,7 @@ class SchemeParser : BasicParser { {"enum", ArgType::enumvalue}, }; public: - SchemeParser(std::string_view filename, std::string_view source) + CommandParser(std::string_view filename, std::string_view source) : BasicParser(filename, source) { } @@ -84,7 +84,9 @@ public: throw error("empty enumeration is not allowed"); } auto enumvalue = std::string(readUntil(']')); - if (enumvalue.find(' ') != std::string::npos) { + size_t offset = enumvalue.find(' '); + if (offset != std::string::npos) { + goBack(enumvalue.length()-offset); throw error("use '|' as separator, not a space"); } nextChar(); @@ -130,7 +132,7 @@ public: return Argument {name, type, optional, def, origin, enumname}; } - Command parse(executor_func executor) { + Command parseScheme(executor_func executor) { std::string name = parseIdentifier(); std::vector args; std::unordered_map kwargs; @@ -150,8 +152,31 @@ public: } return Command(name, std::move(args), std::move(kwargs), executor); } + + CommandInput parsePrompt() { + std::string name = parseIdentifier(); + + return CommandInput {name, nullptr, nullptr}; + } }; Command Command::create(std::string_view scheme, executor_func executor) { - return SchemeParser("", scheme).parse(executor); + return CommandParser("", scheme).parseScheme(executor); +} + +void CommandsRepository::add(std::string_view scheme, executor_func executor) { + Command command = Command::create(scheme, executor); + commands[command.getName()] = command; +} + +Command* CommandsRepository::get(const std::string& name) { + auto found = commands.find(name); + if (found == commands.end()) { + return nullptr; + } + return &found->second; +} + +CommandInput CommandsInterpreter::parse(std::string_view text) { + return CommandParser("", text).parsePrompt(); } diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index 6f656451..7bc80dd6 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -23,6 +23,7 @@ namespace cmd { }; struct CommandInput { + std::string name; // command name dynamic::List_sptr args; // positional arguments list dynamic::Map_sptr kwargs; // keyword arguments table }; @@ -38,6 +39,8 @@ namespace cmd { std::unordered_map kwargs; executor_func executor; public: + Command() {} + Command( std::string name, std::vector args, @@ -52,10 +55,27 @@ namespace cmd { return executor(input.args, input.kwargs); } + const std::string& getName() const { + return name; + } + static Command create(std::string_view scheme, executor_func); }; + class CommandsRepository { + std::unordered_map commands; + public: + void add(std::string_view scheme, executor_func); + Command* get(const std::string& name); + }; + class CommandsInterpreter { + std::unique_ptr repository; + public: + CommandsInterpreter(std::unique_ptr repository) + : repository(std::move(repository)){} + + CommandInput parse(std::string_view text); }; } From 360422cef1c889a9b0c51aab0004b7f0159c12b5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 9 May 2024 21:48:39 +0300 Subject: [PATCH 06/36] CommandsInterpreter::parse --- src/logic/CommandsInterpreter.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index 47a44307..8e157d69 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -11,6 +11,10 @@ inline bool is_cmd_identifier_part(char c) { return is_identifier_part(c) || c == '.' || c == '$' || c == '@'; } +inline bool is_cmd_identifier_start(char c) { + return is_identifier_start(c) || c == '.' || c == '$' || c == '@'; +} + class CommandParser : BasicParser { std::string parseIdentifier() { char c = peek(); @@ -57,7 +61,7 @@ public: dynamic::Value parseValue() { char c = peek(); - if (is_cmd_identifier_part(c)) { + if (is_cmd_identifier_start(c)) { auto str = parseIdentifier(); if (str == "true") { return true; @@ -155,8 +159,20 @@ public: CommandInput parsePrompt() { std::string name = parseIdentifier(); + auto args = dynamic::create_list(); + auto kwargs = dynamic::create_map(); - return CommandInput {name, nullptr, nullptr}; + while (hasNext()) { + auto value = parseValue(); + if (hasNext() && peek() == '=') { + auto key = std::get(value); + nextChar(); + kwargs->put(key, parseValue()); + } + args->put(value); + } + + return CommandInput {name, args, kwargs}; } }; From bdd105507383649049e677d3b332fbb35595b2d0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 01:08:00 +0300 Subject: [PATCH 07/36] some WIP commit --- src/logic/CommandsInterpreter.cpp | 54 +++++++++++++++++++++++++++---- src/logic/CommandsInterpreter.hpp | 22 ++++++++++--- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index 8e157d69..8c773445 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -87,7 +87,7 @@ public: if (peek() == ']') { throw error("empty enumeration is not allowed"); } - auto enumvalue = std::string(readUntil(']')); + auto enumvalue = "|"+std::string(readUntil(']'))+"|"; size_t offset = enumvalue.find(' '); if (offset != std::string::npos) { goBack(enumvalue.length()-offset); @@ -157,22 +157,64 @@ public: return Command(name, std::move(args), std::move(kwargs), executor); } - CommandInput parsePrompt() { + bool typeCheck(Argument* arg, const dynamic::Value& value) { + switch (arg->type) { + case ArgType::enumvalue: { + auto& enumname = arg->enumname; + if (auto* string = std::get_if(&value)) { + if (enumname.find("|"+*string+"|") == std::string::npos) { + throw error("invalid enumeration value"); + } + } else { + if (arg->optional) { + return false; + } + throw error("enumeration value expected"); + } + break; + } + case ArgType::number: { + // FIXME + } + } + return true; + } + + Prompt parsePrompt(CommandsRepository* repo) { std::string name = parseIdentifier(); + auto command = repo->get(name); + if (command == nullptr) { + throw error("unknown command "+util::quote(name)); + } auto args = dynamic::create_list(); auto kwargs = dynamic::create_map(); + int arg_index = 0; + while (hasNext()) { auto value = parseValue(); if (hasNext() && peek() == '=') { auto key = std::get(value); nextChar(); kwargs->put(key, parseValue()); + } else { + Argument* arg; + do { + arg = command->getArgument(arg_index++); + if (arg == nullptr) { + throw error("extra positional argument"); + } + } while (!typeCheck(arg, value)); + args->put(value); } - args->put(value); } - return CommandInput {name, args, kwargs}; + while (auto arg = command->getArgument(arg_index++)) { + if (!arg->optional) { + throw error("missing argument "+util::quote(arg->name)); + } + } + return Prompt {command, args, kwargs}; } }; @@ -193,6 +235,6 @@ Command* CommandsRepository::get(const std::string& name) { return &found->second; } -CommandInput CommandsInterpreter::parse(std::string_view text) { - return CommandParser("", text).parsePrompt(); +Prompt CommandsInterpreter::parse(std::string_view text) { + return CommandParser("", text).parsePrompt(repository.get()); } diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index 7bc80dd6..140c9ac2 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -22,8 +22,10 @@ namespace cmd { std::string enumname; }; - struct CommandInput { - std::string name; // command name + class Command; + + struct Prompt { + Command* command; dynamic::List_sptr args; // positional arguments list dynamic::Map_sptr kwargs; // keyword arguments table }; @@ -50,9 +52,15 @@ namespace cmd { args(std::move(args)), kwargs(std::move(kwargs)), executor(executor) {} + + Argument* getArgument(size_t index) { + if (index >= args.size()) + return nullptr; + return &args[index]; + } - dynamic::Value execute(const CommandInput& input) { - return executor(input.args, input.kwargs); + dynamic::Value execute(const Prompt& prompt) { + return executor(prompt.args, prompt.kwargs); } const std::string& getName() const { @@ -75,7 +83,11 @@ namespace cmd { CommandsInterpreter(std::unique_ptr repository) : repository(std::move(repository)){} - CommandInput parse(std::string_view text); + Prompt parse(std::string_view text); + + CommandsRepository* getRepository() const { + return repository.get(); + } }; } From b38128ef392cfd5a177eaa252756eef8fbcb20fe Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 16:05:46 +0300 Subject: [PATCH 08/36] relative values, types check --- src/coders/commons.cpp | 7 ++ src/coders/commons.hpp | 1 + src/data/dynamic.cpp | 30 ++++++ src/data/dynamic.hpp | 8 ++ src/logic/CommandsInterpreter.cpp | 150 ++++++++++++++++++++++++++---- src/logic/CommandsInterpreter.hpp | 8 ++ 6 files changed, 185 insertions(+), 19 deletions(-) diff --git a/src/coders/commons.cpp b/src/coders/commons.cpp index 81639499..df59fcb3 100644 --- a/src/coders/commons.cpp +++ b/src/coders/commons.cpp @@ -171,6 +171,13 @@ char BasicParser::peek() { return source[pos]; } +char BasicParser::peekNoJump() { + if (pos >= source.length()) { + throw error("unexpected end"); + } + return source[pos]; +} + std::string_view BasicParser::readUntil(char c) { int start = pos; while (hasNext() && source[pos] != c) { diff --git a/src/coders/commons.hpp b/src/coders/commons.hpp index a9609c08..29a6d24f 100644 --- a/src/coders/commons.hpp +++ b/src/coders/commons.hpp @@ -99,6 +99,7 @@ public: std::string parseName(); bool hasNext(); char peek(); + char peekNoJump(); char nextChar(); BasicParser(std::string_view file, std::string_view source); diff --git a/src/data/dynamic.cpp b/src/data/dynamic.cpp index cbfe6a7b..4d60d2a5 100644 --- a/src/data/dynamic.cpp +++ b/src/data/dynamic.cpp @@ -242,6 +242,20 @@ size_t Map::size() const { return values.size(); } +static const std::string TYPE_NAMES[] { + "none", + "map", + "list", + "string", + "number", + "bool", + "integer", +}; + +const std::string& dynamic::type_name(const Value& value) { + return TYPE_NAMES[value.index()]; +} + List_sptr dynamic::create_list(std::initializer_list values) { return std::make_shared(values); } @@ -249,3 +263,19 @@ List_sptr dynamic::create_list(std::initializer_list values) { Map_sptr dynamic::create_map(std::initializer_list> entries) { return std::make_shared(entries); } + +number_t dynamic::get_number(const Value& value) { + if (auto num = std::get_if(&value)) { + return *num; + } else if (auto num = std::get_if(&value)) { + return *num; + } + throw std::runtime_error("cannot cast "+type_name(value)+" to number"); +} + +integer_t dynamic::get_integer(const Value& value) { + if (auto num = std::get_if(&value)) { + return *num; + } + throw std::runtime_error("cannot cast "+type_name(value)+" to integer"); +} diff --git a/src/data/dynamic.hpp b/src/data/dynamic.hpp index 39bb8fec..0dd84ed1 100644 --- a/src/data/dynamic.hpp +++ b/src/data/dynamic.hpp @@ -35,8 +35,16 @@ namespace dynamic { integer_t >; + const std::string& type_name(const Value& value); List_sptr create_list(std::initializer_list values={}); Map_sptr create_map(std::initializer_list> entries={}); + number_t get_number(const Value& value); + integer_t get_integer(const Value& value); + + inline bool is_numeric(const Value& value) { + return std::holds_alternative(value) || + std::holds_alternative(value); + } class List { public: diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index 8c773445..a484dbb4 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -157,29 +157,126 @@ public: return Command(name, std::move(args), std::move(kwargs), executor); } + inline parsing_error argumentError( + const std::string& argname, + const std::string& message + ) { + return error("argument "+util::quote(argname)+": "+message); + } + + inline parsing_error typeError( + const std::string& argname, + const std::string& expected, + const dynamic::Value& value + ) { + return argumentError( + argname, expected+" expected, got "+dynamic::type_name(value) + ); + } + + template + bool typeCheck(Argument* arg, const dynamic::Value& value, const std::string& tname) { + if (!std::holds_alternative(value)) { + if (arg->optional) { + return false; + } else { + throw typeError(arg->name, tname, value); + } + } + return true; + } + bool typeCheck(Argument* arg, const dynamic::Value& value) { switch (arg->type) { case ArgType::enumvalue: { - auto& enumname = arg->enumname; if (auto* string = std::get_if(&value)) { + auto& enumname = arg->enumname; if (enumname.find("|"+*string+"|") == std::string::npos) { - throw error("invalid enumeration value"); + throw error("argument "+util::quote(arg->name)+ + ": invalid enumeration value"); } } else { if (arg->optional) { return false; } - throw error("enumeration value expected"); + throw typeError(arg->name, "enumeration value", value); } break; } - case ArgType::number: { - // FIXME - } + case ArgType::number: + if (!dynamic::is_numeric(value)) { + if (arg->optional) { + return false; + } else { + throw typeError(arg->name, "number", value); + } + } + break; + 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; } + dynamic::Value fetchOrigin(Argument* arg) { + if (dynamic::is_numeric(arg->origin)) { + return arg->origin; + } + return dynamic::NONE; + } + + dynamic::Value applyRelative( + Argument* arg, + dynamic::Value value, + dynamic::Value origin + ) { + if (origin.index() == 0) { + return value; + } + try { + if (arg->type == ArgType::number) { + return dynamic::get_number(origin) + dynamic::get_number(value); + } else { + return dynamic::get_integer(origin) + dynamic::get_integer(value); + } + } catch (std::runtime_error& err) { + throw argumentError(arg->name, err.what()); + } + } + + dynamic::Value parseRelativeValue(Argument* arg) { + if (arg->type != ArgType::number && arg->type != ArgType::integer) { + throw error("'~' operator is only allowed for numeric arguments"); + } + nextChar(); + auto origin = fetchOrigin(arg); + if (peekNoJump() == ' ' || !hasNext()) { + return origin; + } + auto value = parseValue(); + if (origin.index() == 0) { + return value; + } + return applyRelative(arg, value, origin); + } + + inline dynamic::Value performKeywordArg( + Command* command, const std::string& key + ) { + if (auto arg = command->getArgument(key)) { + nextChar(); + auto value = peek() == '~' ? parseRelativeValue(arg) : parseValue(); + typeCheck(arg, value); + return value; + } else { + throw error("unknown keyword "+util::quote(key)); + } + } + Prompt parsePrompt(CommandsRepository* repo) { std::string name = parseIdentifier(); auto command = repo->get(name); @@ -192,21 +289,36 @@ public: int arg_index = 0; while (hasNext()) { - auto value = parseValue(); - if (hasNext() && peek() == '=') { - auto key = std::get(value); + bool relative = false; + if (peek() == '~') { + relative = true; nextChar(); - kwargs->put(key, parseValue()); - } else { - Argument* arg; - do { - arg = command->getArgument(arg_index++); - if (arg == nullptr) { - throw error("extra positional argument"); - } - } while (!typeCheck(arg, value)); - args->put(value); } + dynamic::Value value = dynamic::NONE; + if (hasNext() && peekNoJump() != ' ') { + value = parseValue(); + + // keyword argument + if (!relative && hasNext() && peek() == '=') { + auto key = std::get(value); + kwargs->put(key, performKeywordArg(command, key)); + } + } + + // positional argument + Argument* arg; + do { + arg = command->getArgument(arg_index++); + if (arg == nullptr) { + throw error("extra positional argument"); + } + } while (!typeCheck(arg, value)); + + if (relative) { + value = applyRelative(arg, value, fetchOrigin(arg)); + } + std::cout << "argument value: " << value << std::endl; + args->put(value); } while (auto arg = command->getArgument(arg_index++)) { diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index 140c9ac2..42db5de1 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -59,6 +59,14 @@ namespace cmd { return &args[index]; } + Argument* getArgument(const std::string& keyword) { + auto found = kwargs.find(keyword); + if (found == kwargs.end()) { + return nullptr; + } + return &found->second; + } + dynamic::Value execute(const Prompt& prompt) { return executor(prompt.args, prompt.kwargs); } From 88d0553eda91cf24ca2772650981277905b51cd9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 16:37:10 +0300 Subject: [PATCH 09/36] CommandsInterpreter variables --- src/logic/CommandsInterpreter.cpp | 23 ++++++++++++++--------- src/logic/CommandsInterpreter.hpp | 5 +++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index a484dbb4..8aa28cdf 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -222,9 +222,11 @@ public: return true; } - dynamic::Value fetchOrigin(Argument* arg) { + dynamic::Value fetchOrigin(CommandsInterpreter* interpreter, Argument* arg) { if (dynamic::is_numeric(arg->origin)) { return arg->origin; + } else if (auto string = std::get_if(&arg->origin)) { + return (*interpreter)[*string]; } return dynamic::NONE; } @@ -248,12 +250,12 @@ public: } } - dynamic::Value parseRelativeValue(Argument* arg) { + dynamic::Value parseRelativeValue(CommandsInterpreter* interpreter, Argument* arg) { if (arg->type != ArgType::number && arg->type != ArgType::integer) { throw error("'~' operator is only allowed for numeric arguments"); } nextChar(); - auto origin = fetchOrigin(arg); + auto origin = fetchOrigin(interpreter, arg); if (peekNoJump() == ' ' || !hasNext()) { return origin; } @@ -265,11 +267,13 @@ public: } inline dynamic::Value performKeywordArg( - Command* command, const std::string& key + CommandsInterpreter* interpreter, Command* command, const std::string& key ) { if (auto arg = command->getArgument(key)) { nextChar(); - auto value = peek() == '~' ? parseRelativeValue(arg) : parseValue(); + auto value = peek() == '~' + ? parseRelativeValue(interpreter, arg) + : parseValue(); typeCheck(arg, value); return value; } else { @@ -277,7 +281,8 @@ public: } } - Prompt parsePrompt(CommandsRepository* repo) { + Prompt parsePrompt(CommandsInterpreter* interpreter) { + auto repo = interpreter->getRepository(); std::string name = parseIdentifier(); auto command = repo->get(name); if (command == nullptr) { @@ -301,7 +306,7 @@ public: // keyword argument if (!relative && hasNext() && peek() == '=') { auto key = std::get(value); - kwargs->put(key, performKeywordArg(command, key)); + kwargs->put(key, performKeywordArg(interpreter, command, key)); } } @@ -315,7 +320,7 @@ public: } while (!typeCheck(arg, value)); if (relative) { - value = applyRelative(arg, value, fetchOrigin(arg)); + value = applyRelative(arg, value, fetchOrigin(interpreter, arg)); } std::cout << "argument value: " << value << std::endl; args->put(value); @@ -348,5 +353,5 @@ Command* CommandsRepository::get(const std::string& name) { } Prompt CommandsInterpreter::parse(std::string_view text) { - return CommandParser("", text).parsePrompt(repository.get()); + return CommandParser("", text).parsePrompt(this); } diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index 42db5de1..3b040684 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -87,12 +87,17 @@ namespace cmd { class CommandsInterpreter { std::unique_ptr repository; + std::unordered_map variables; public: CommandsInterpreter(std::unique_ptr repository) : repository(std::move(repository)){} Prompt parse(std::string_view text); + dynamic::Value& operator[](const std::string& name) { + return variables[name]; + } + CommandsRepository* getRepository() const { return repository.get(); } From 45b0f4be2d098d8790574fe83ed2e6ca3090ddf2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 16:55:44 +0300 Subject: [PATCH 10/36] origin value --- src/logic/CommandsInterpreter.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index 8aa28cdf..da8e6957 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -295,11 +295,13 @@ public: while (hasNext()) { bool relative = false; + dynamic::Value value = dynamic::NONE; if (peek() == '~') { relative = true; + value = 0; nextChar(); } - dynamic::Value value = dynamic::NONE; + if (hasNext() && peekNoJump() != ' ') { value = parseValue(); @@ -317,6 +319,9 @@ public: if (arg == nullptr) { throw error("extra positional argument"); } + if (arg->origin.index() && relative) { + break; + } } while (!typeCheck(arg, value)); if (relative) { From 5a2bbb54c21f559dcd72cdeebd3a182f52eab556 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 17:05:18 +0300 Subject: [PATCH 11/36] string literal --- src/logic/CommandsInterpreter.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index da8e6957..7728642c 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -7,8 +7,9 @@ using namespace cmd; -inline bool is_cmd_identifier_part(char c) { - return is_identifier_part(c) || c == '.' || c == '$' || c == '@'; +inline bool is_cmd_identifier_part(char c, bool allowColon) { + return is_identifier_part(c) || c == '.' || c == '$' || c == '@' || + (allowColon && c == ':'); } inline bool is_cmd_identifier_start(char c) { @@ -16,7 +17,7 @@ inline bool is_cmd_identifier_start(char c) { } class CommandParser : BasicParser { - std::string parseIdentifier() { + std::string parseIdentifier(bool allowColon) { char c = peek(); if (!is_identifier_start(c) && c != '$') { if (c == '"') { @@ -26,7 +27,7 @@ class CommandParser : BasicParser { throw error("identifier expected"); } int start = pos; - while (hasNext() && is_cmd_identifier_part(source[pos])) { + while (hasNext() && is_cmd_identifier_part(source[pos], allowColon)) { pos++; } return std::string(source.substr(start, pos-start)); @@ -49,7 +50,7 @@ public: std::cout << "type.name: enum" << std::endl; return ArgType::enumvalue; } - std::string name = parseIdentifier(); + std::string name = parseIdentifier(false); std::cout << "type.name: " << name << std::endl; auto found = types.find(name); if (found != types.end()) { @@ -62,7 +63,7 @@ public: dynamic::Value parseValue() { char c = peek(); if (is_cmd_identifier_start(c)) { - auto str = parseIdentifier(); + auto str = parseIdentifier(true); if (str == "true") { return true; } else if (str == "false") { @@ -72,8 +73,9 @@ public: } return str; } - if (c == '"') { - return parseIdentifier(); + if (c == '"' || c == '\'') { + nextChar(); + return parseString(c); } if (c == '+' || c == '-' || is_digit(c)) { return parseNumber(c == '-' ? -1 : 1); @@ -98,12 +100,12 @@ public: } else { expect('$'); goBack(); - return parseIdentifier(); + return parseIdentifier(false); } } Argument parseArgument() { - std::string name = parseIdentifier(); + std::string name = parseIdentifier(false); expect(':'); std::cout << "arg.name: " << name << std::endl; ArgType type = parseType(); @@ -137,7 +139,7 @@ public: } Command parseScheme(executor_func executor) { - std::string name = parseIdentifier(); + std::string name = parseIdentifier(true); std::vector args; std::unordered_map kwargs; @@ -283,7 +285,7 @@ public: Prompt parsePrompt(CommandsInterpreter* interpreter) { auto repo = interpreter->getRepository(); - std::string name = parseIdentifier(); + std::string name = parseIdentifier(true); auto command = repo->get(name); if (command == nullptr) { throw error("unknown command "+util::quote(name)); From 3f8f34cf5d87187fbc7149a87d581e8d66e7f2cc Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 17:49:42 +0300 Subject: [PATCH 12/36] cleanup --- src/coders/json.cpp | 10 ++-------- src/logic/CommandsInterpreter.cpp | 7 ------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/coders/json.cpp b/src/coders/json.cpp index f6a7a19e..89f06354 100644 --- a/src/coders/json.cpp +++ b/src/coders/json.cpp @@ -36,14 +36,6 @@ inline void newline( } } -void stringify( - const Value& value, - std::stringstream& ss, - int indent, - const std::string& indentstr, - bool nice -); - void stringifyObj( const Map* obj, std::stringstream& ss, @@ -91,6 +83,8 @@ void stringifyValue( ss << *num; } else if (auto str = std::get_if(&value)) { ss << util::escape(*str); + } else { + ss << "null"; } } diff --git a/src/logic/CommandsInterpreter.cpp b/src/logic/CommandsInterpreter.cpp index 7728642c..c8197aac 100644 --- a/src/logic/CommandsInterpreter.cpp +++ b/src/logic/CommandsInterpreter.cpp @@ -47,11 +47,9 @@ public: ArgType parseType() { if (peek() == '[') { - std::cout << "type.name: enum" << std::endl; return ArgType::enumvalue; } std::string name = parseIdentifier(false); - std::cout << "type.name: " << name << std::endl; auto found = types.find(name); if (found != types.end()) { return found->second; @@ -107,12 +105,10 @@ public: Argument parseArgument() { std::string name = parseIdentifier(false); expect(':'); - std::cout << "arg.name: " << name << std::endl; ArgType type = parseType(); std::string enumname = ""; if (type == ArgType::enumvalue) { enumname = parseEnum(); - std::cout << "enum: " << enumname << std::endl; } bool optional = false; dynamic::Value def {}; @@ -142,8 +138,6 @@ public: std::string name = parseIdentifier(true); std::vector args; std::unordered_map kwargs; - - std::cout << "command.name: " << name << std::endl; while (hasNext()) { if (peek() == '{') { nextChar(); @@ -329,7 +323,6 @@ public: if (relative) { value = applyRelative(arg, value, fetchOrigin(interpreter, arg)); } - std::cout << "argument value: " << value << std::endl; args->put(value); } From 172cfe4ec984618990b00c5ff7c3a2fbbc7c76a9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 17:55:30 +0300 Subject: [PATCH 13/36] CommandsInterpreter.execute --- src/logic/CommandsInterpreter.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index 3b040684..e4d9b13b 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -94,6 +94,14 @@ namespace cmd { Prompt parse(std::string_view text); + dynamic::Value execute(std::string_view input) { + return execute(parse(input)); + } + + dynamic::Value execute(const Prompt& prompt) { + return prompt.command->execute(prompt); + } + dynamic::Value& operator[](const std::string& name) { return variables[name]; } From 9dc3c40b692e517191a4352622525e5e7e30af19 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 19:36:25 +0300 Subject: [PATCH 14/36] executor_func update --- src/logic/CommandsInterpreter.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/logic/CommandsInterpreter.hpp b/src/logic/CommandsInterpreter.hpp index e4d9b13b..1ad6946c 100644 --- a/src/logic/CommandsInterpreter.hpp +++ b/src/logic/CommandsInterpreter.hpp @@ -23,6 +23,7 @@ namespace cmd { }; class Command; + class CommandsInterpreter; struct Prompt { Command* command; @@ -31,6 +32,7 @@ namespace cmd { }; using executor_func = std::function; @@ -67,8 +69,8 @@ namespace cmd { return &found->second; } - dynamic::Value execute(const Prompt& prompt) { - return executor(prompt.args, prompt.kwargs); + dynamic::Value execute(CommandsInterpreter* interpreter, const Prompt& prompt) { + return executor(interpreter, prompt.args, prompt.kwargs); } const std::string& getName() const { @@ -99,7 +101,7 @@ namespace cmd { } dynamic::Value execute(const Prompt& prompt) { - return prompt.command->execute(prompt); + return prompt.command->execute(this, prompt); } dynamic::Value& operator[](const std::string& name) { From c2ea0d53264682c8b89d816fbbdab22d7b177c25 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 23:35:46 +0300 Subject: [PATCH 15/36] lua console library --- src/engine.cpp | 9 ++- src/engine.hpp | 6 ++ src/logic/CommandsInterpreter.cpp | 44 +++++++++++--- src/logic/CommandsInterpreter.hpp | 2 + src/logic/scripting/lua/LuaState.cpp | 60 ++++++++++++++------ src/logic/scripting/lua/LuaState.hpp | 7 ++- src/logic/scripting/lua/api_lua.hpp | 1 + src/logic/scripting/lua/libconsole.cpp | 43 ++++++++++++++ src/logic/scripting/scripting_functional.hpp | 8 ++- src/logic/scripting/scripting_hud.cpp | 2 +- src/util/stringutil.cpp | 4 +- src/util/stringutil.hpp | 4 +- 12 files changed, 156 insertions(+), 34 deletions(-) create mode 100644 src/logic/scripting/lua/libconsole.cpp 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); From d59061e5b15e7a5dcd959939e5218ff61246f7d7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 23:43:20 +0300 Subject: [PATCH 16/36] test command --- res/scripts/stdlib.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index dcc7f382..6e075768 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -276,6 +276,13 @@ function table.tostring(t) return s..']' end +console.add_command( + "tp obj:sel=$obj.id x:num~pos.x y:num~pos.y z:num~pos.z", + function (args, kwargs) + player.set_pos(unpack(args)) + end +) + -- Deprecated functions block_index = block.index block_name = block.name From f4e98b209139463033681e03e25eb81e6cb6717e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 13 May 2024 17:08:58 +0300 Subject: [PATCH 17/36] console layout WIP --- res/layouts/console.xml | 3 +++ res/layouts/console.xml.lua | 3 +++ src/frontend/UiDocument.cpp | 2 +- src/frontend/hud.cpp | 2 +- src/frontend/menu.cpp | 2 +- src/logic/scripting/lua/LuaState.cpp | 2 +- 6 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 res/layouts/console.xml create mode 100644 res/layouts/console.xml.lua diff --git a/res/layouts/console.xml b/res/layouts/console.xml new file mode 100644 index 00000000..6991d3f8 --- /dev/null +++ b/res/layouts/console.xml @@ -0,0 +1,3 @@ + + + diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua new file mode 100644 index 00000000..ebef4b35 --- /dev/null +++ b/res/layouts/console.xml.lua @@ -0,0 +1,3 @@ +function on_open() + document.prompt.focused = true +end diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 5cf09589..b593f9e0 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -56,7 +56,7 @@ std::unique_ptr UiDocument::read(scriptenv penv, std::string name, f auto xmldoc = xml::parse(file.u8string(), text); auto env = penv == nullptr - ? scripting::get_root_environment() + ? scripting::create_doc_environment(scripting::get_root_environment(), name) : scripting::create_doc_environment(penv, name); gui::UiXmlReader reader(env); diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index c22d8fe5..0f58a7c7 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -501,7 +501,7 @@ void Hud::updateElementsPosition(const Viewport& viewport) { )); } secondUI->setPos(glm::vec2( - glm::min(width/2-invwidth/2, width-caWidth-10-invwidth), + glm::min(width/2-invwidth/2, width-caWidth-(inventoryView ? 10 : 0)-invwidth), height/2-totalHeight/2 )); } diff --git a/src/frontend/menu.cpp b/src/frontend/menu.cpp index 06b805c2..4c89aeaa 100644 --- a/src/frontend/menu.cpp +++ b/src/frontend/menu.cpp @@ -27,7 +27,7 @@ void menus::create_version_label(Engine* engine) { auto gui = engine->getGUI(); auto text = ENGINE_VERSION_STRING+" debug build"; gui->add(guiutil::create( - "