|
4 | 4 |
|
5 | 5 | #include "AbstractParser-Commands.h" |
6 | 6 |
|
7 | | -#include "../global/AsyncTasks.h" |
8 | 7 | #include "../global/CaseUtils.h" |
9 | 8 | #include "../global/Consts.h" |
10 | 9 | #include "../global/LineUtils.h" |
|
18 | 17 | #include "../map/enums.h" |
19 | 18 | #include "../map/infomark.h" |
20 | 19 | #include "../mapdata/mapdata.h" |
| 20 | +#include "../observer/gameobserver.h" |
21 | 21 | #include "../syntax/SyntaxArgs.h" |
22 | 22 | #include "../syntax/TreeParser.h" |
23 | 23 | #include "../viewers/AnsiViewWindow.h" |
24 | 24 | #include "../viewers/LaunchAsyncViewer.h" |
25 | | -#include "../viewers/TopLevelWindows.h" |
26 | 25 | #include "Abbrev.h" |
27 | 26 | #include "AbstractParser-Utils.h" |
28 | 27 | #include "DoorAction.h" |
|
38 | 37 | #include <utility> |
39 | 38 | #include <vector> |
40 | 39 |
|
41 | | -#include <QMessageLogContext> |
42 | 40 | #include <QtCore> |
43 | 41 |
|
| 42 | +namespace { // anonymous |
| 43 | + |
| 44 | +struct NODISCARD SendToUserHelper final |
| 45 | +{ |
| 46 | +private: |
| 47 | + std::ostringstream m_oss; |
| 48 | + AnsiOstream m_aos{m_oss}; |
| 49 | + AbstractParser &m_self; |
| 50 | + SendToUserSourceEnum m_source = SendToUserSourceEnum::FromMMapper; |
| 51 | + |
| 52 | +public: |
| 53 | + explicit SendToUserHelper(AbstractParser &self, |
| 54 | + SendToUserSourceEnum src = SendToUserSourceEnum::FromMMapper) |
| 55 | + : m_self{self} |
| 56 | + , m_source{src} |
| 57 | + {} |
| 58 | + DELETE_CTORS_AND_ASSIGN_OPS(SendToUserHelper); |
| 59 | + ~SendToUserHelper() |
| 60 | + { |
| 61 | + if (!m_aos.hasNewline()) { |
| 62 | + m_aos.writeNewline(); |
| 63 | + } |
| 64 | + m_self.sendToUser(m_source, m_oss.str()); |
| 65 | + } |
| 66 | + NODISCARD AnsiOstream &getAnsiOstream() { return m_aos; } |
| 67 | +}; |
| 68 | + |
| 69 | +template<typename Container> |
| 70 | +NODISCARD auto find_abbrev(const Container &container, |
| 71 | + const StringView input, |
| 72 | + const size_t required_len) |
| 73 | + -> std::optional<typename Container::index_type> |
| 74 | +{ |
| 75 | + using E = typename Container::index_type; |
| 76 | + for (const std::string_view w : container) { |
| 77 | + if (required_len <= w.size() && isAbbrev(input, w, static_cast<int>(required_len))) { |
| 78 | + if (std::optional<E> opt = container.findIndexOf(w)) { |
| 79 | + return opt; |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + return std::nullopt; |
| 84 | +}; |
| 85 | + |
| 86 | +template<typename Container> |
| 87 | +void concat_comma_and_or(AnsiOstream &aos, |
| 88 | + const Container &container, |
| 89 | + // color each word int he container |
| 90 | + const RawAnsi &ansi, |
| 91 | + // e.g. "and" or "or" |
| 92 | + const std::string_view and_or) |
| 93 | +{ |
| 94 | + using E = typename Container::index_type; |
| 95 | + if (container.empty()) { |
| 96 | + return; // This is probably an error, so maybe it should throw or print "(none)" ? |
| 97 | + } |
| 98 | + const size_t size = container.size(); |
| 99 | + for (size_t i = 0; i < size; ++i) { |
| 100 | + if (i != 0) { |
| 101 | + aos << ", "; |
| 102 | + if (i + 1 == size && !and_or.empty()) { |
| 103 | + aos << and_or << " "; |
| 104 | + } |
| 105 | + } |
| 106 | + const auto e = static_cast<E>(i); |
| 107 | + aos << ColoredValue{ansi, container[e]}; |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +// one or more question marks |
| 112 | +template<typename T> |
| 113 | +NODISCARD bool is_question_marks(const T word) |
| 114 | +{ |
| 115 | + if constexpr (std::is_same_v<T, std::string_view>) { |
| 116 | + return !word.empty() && std::all_of(word.begin(), word.end(), [](char c) -> bool { |
| 117 | + return c == char_consts::C_QUESTION_MARK; |
| 118 | + }); |
| 119 | + } else if constexpr (std::is_same_v<T, StringView>) { |
| 120 | + return is_question_marks<std::string_view>(word.getStdStringView()); |
| 121 | + } else { |
| 122 | + static_assert(std::is_same_v<T, void>, "unsupported word type"); |
| 123 | + std::abort(); |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +constexpr auto green = getRawAnsi(AnsiColor16Enum::green); |
| 128 | +constexpr auto red = getRawAnsi(AnsiColor16Enum::red); |
| 129 | +constexpr auto yellow = getRawAnsi(AnsiColor16Enum::yellow); |
| 130 | + |
| 131 | +constexpr EnumIndexedArray<std::string_view, PromptFogEnum, NUM_PROMPT_FOG_TYPES> all_fog_names{ |
| 132 | +#define X_CASE(_x) std::string_view{#_x}, |
| 133 | + XFOREACH_PROMPT_FOG_ENUM(X_CASE) |
| 134 | +#undef X_CASE |
| 135 | +}; |
| 136 | +constexpr EnumIndexedArray<std::string_view, PromptWeatherEnum, NUM_PROMPT_WEATHER_TYPES> |
| 137 | + all_weather_names{ |
| 138 | +#define X_CASE(_x) std::string_view{#_x}, |
| 139 | + XFOREACH_PROMPT_WEATHER_ENUM(X_CASE) |
| 140 | +#undef X_CASE |
| 141 | + }; |
| 142 | + |
| 143 | +template<typename Container, typename Getter, typename Setter> |
| 144 | +void trySetEnumValue(AbstractParser &parser, |
| 145 | + StringView view, |
| 146 | + const std::string_view what, |
| 147 | + const Container &enum_names, |
| 148 | + const size_t required_len, |
| 149 | + Getter &&get, |
| 150 | + Setter &&set) |
| 151 | +{ |
| 152 | + using E = typename Container::index_type; |
| 153 | + |
| 154 | + if (view.isEmpty()) { |
| 155 | + SendToUserHelper helper{parser}; |
| 156 | + AnsiOstream &aos = helper.getAnsiOstream(); |
| 157 | + aos << "The current " << what << " is: " << ColoredValue{green, get()} << "."; |
| 158 | + return; |
| 159 | + } |
| 160 | + |
| 161 | + const auto next = view.takeFirstWord(); |
| 162 | + const auto input_sv = next.getStdStringView(); |
| 163 | + |
| 164 | + if (const std::optional<E> opt = find_abbrev(enum_names, next, required_len)) { |
| 165 | + // success |
| 166 | + const E e = *opt; |
| 167 | + { |
| 168 | + SendToUserHelper helper{parser}; |
| 169 | + AnsiOstream &aos = helper.getAnsiOstream(); |
| 170 | + aos << "Setting " << what << " to " << ColoredValue{green, enum_names[e]} << "..."; |
| 171 | + } |
| 172 | + set(e); |
| 173 | + } else { |
| 174 | + // failure |
| 175 | + SendToUserHelper helper{parser}; |
| 176 | + AnsiOstream &aos = helper.getAnsiOstream(); |
| 177 | + |
| 178 | + if (!is_question_marks(input_sv)) { |
| 179 | + aos << "Error: Unrecognized " << what |
| 180 | + << " option: " << ColoredQuotedStringView{red, yellow, input_sv} << "." |
| 181 | + << AnsiOstream::endl; |
| 182 | + } |
| 183 | + |
| 184 | + aos << "Valid " << what << " options: "; |
| 185 | + concat_comma_and_or(aos, enum_names, green, "or"); |
| 186 | + aos << "." << AnsiOstream::endl; |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +} // namespace |
| 191 | + |
44 | 192 | const Abbrev cmdBack{"back"}; |
45 | 193 | const Abbrev cmdConfig{"config", 4}; |
46 | 194 | const Abbrev cmdConnect{"connect", 4}; |
@@ -515,13 +663,36 @@ bool AbstractParser::parseDoorAction(const DoorActionEnum dat, StringView words) |
515 | 663 |
|
516 | 664 | void AbstractParser::parseSetCommand(StringView view) |
517 | 665 | { |
| 666 | + auto show_set_syntax = [this] { |
| 667 | + SendToUserHelper helper{*this}; |
| 668 | + AnsiOstream &aos = helper.getAnsiOstream(); |
| 669 | + |
| 670 | + aos << "Syntax:" << AnsiOstream::endl; |
| 671 | + aos << " " << getPrefixChar() << "set ..." << AnsiOstream::endl; |
| 672 | + { |
| 673 | + aos << " ... fog ["; |
| 674 | + concat_comma_and_or(aos, all_fog_names, green, "or"); |
| 675 | + aos << "]" << AnsiOstream::endl; |
| 676 | + } |
| 677 | + aos << " ... prefix [punct-char]" << AnsiOstream::endl; |
| 678 | + { |
| 679 | + aos << " ... weather ["; |
| 680 | + concat_comma_and_or(aos, all_weather_names, green, "or"); |
| 681 | + aos << "]" << AnsiOstream::endl; |
| 682 | + } |
| 683 | + }; |
| 684 | + |
518 | 685 | if (view.isEmpty()) { |
519 | | - sendToUser(SendToUserSourceEnum::FromMMapper, |
520 | | - QString("Syntax: %1set prefix [punct-char]\n").arg(getPrefixChar())); |
| 686 | + show_set_syntax(); |
521 | 687 | return; |
522 | 688 | } |
523 | 689 |
|
524 | 690 | auto first = view.takeFirstWord(); |
| 691 | + if (is_question_marks(first)) { |
| 692 | + show_set_syntax(); |
| 693 | + return; |
| 694 | + } |
| 695 | + |
525 | 696 | if (Abbrev{"prefix", 3}.matches(first)) { |
526 | 697 | if (view.isEmpty()) { |
527 | 698 | showCommandPrefix(); |
@@ -550,7 +721,37 @@ void AbstractParser::parseSetCommand(StringView view) |
550 | 721 | return; |
551 | 722 | } |
552 | 723 |
|
553 | | - sendToUser(SendToUserSourceEnum::FromMMapper, "That variable is not supported."); |
| 724 | + if (Abbrev{"fog", 3}.matches(first)) { |
| 725 | + trySetEnumValue( |
| 726 | + *this, |
| 727 | + view, |
| 728 | + "fog", |
| 729 | + all_fog_names, |
| 730 | + 2, // "no" should match "no_fog" |
| 731 | + [this]() -> std::string_view { return to_string_view(m_gameObserver.getFog()); }, |
| 732 | + [this](const PromptFogEnum e) { m_gameObserver.observeFog(e); }); |
| 733 | + return; |
| 734 | + } |
| 735 | + |
| 736 | + if (Abbrev{"weather", 3}.matches(first)) { |
| 737 | + trySetEnumValue( |
| 738 | + *this, |
| 739 | + view, |
| 740 | + "weather", |
| 741 | + all_weather_names, |
| 742 | + 3, |
| 743 | + [this]() -> std::string_view { return to_string_view(m_gameObserver.getWeather()); }, |
| 744 | + [this](const PromptWeatherEnum e) { m_gameObserver.observeWeather(e); }); |
| 745 | + return; |
| 746 | + } |
| 747 | + |
| 748 | + { |
| 749 | + SendToUserHelper helper{*this}; |
| 750 | + AnsiOstream &aos = helper.getAnsiOstream(); |
| 751 | + aos << "Error: " << ColoredQuotedStringView{red, yellow, first.getStdStringView()} |
| 752 | + << " is not a valid variable name." << AnsiOstream::endl; |
| 753 | + } |
| 754 | + show_set_syntax(); |
554 | 755 | } |
555 | 756 |
|
556 | 757 | void AbstractParser::parseSpecialCommand(StringView wholeCommand) |
|
0 commit comments