From b38128ef392cfd5a177eaa252756eef8fbcb20fe Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 10 May 2024 16:05:46 +0300 Subject: [PATCH] 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); }