|
13 | 13 | /// \author David Rohr |
14 | 14 |
|
15 | 15 | #include "GPUDisplayConsole.h" |
16 | | -#include "GPULogging.h" |
17 | 16 | #include <algorithm> |
18 | 17 | #include <charconv> |
19 | 18 | #include <stdexcept> |
20 | 19 | #include <cstdarg> |
21 | 20 | #include <cstdio> |
22 | 21 | #include <cstring> |
| 22 | +#include <fstream> |
| 23 | +#include <sstream> |
23 | 24 |
|
24 | 25 | using namespace o2::gpu; |
25 | 26 |
|
@@ -124,30 +125,19 @@ void GPUDisplayConsole::consoleLimitHistory(uint32_t historySize) |
124 | 125 |
|
125 | 126 | // Tokens separated by spaces: text in single / double quotes and characters after a backslash are plain text (the token is a string), |
126 | 127 | // otherwise a token is int64_t for [+-]digits, double for [+-]digits with one '.', a string otherwise. Throws std::runtime_error for an unterminated quote / trailing backslash. |
127 | | -std::vector<GPUDisplayConsole::consoleArg> GPUDisplayConsole::parseConsoleCommand(const std::string& cmd) |
| 128 | + |
| 129 | +std::tuple<const GPUDisplayConsole::GPUDisplayConsoleCommand&, std::vector<GPUDisplayConsole::consoleArg>> GPUDisplayConsole::parseConsoleCommand(std::string_view cmd) const |
128 | 130 | { |
129 | | - std::vector<consoleArg> retVal; |
| 131 | + std::vector<std::pair<std::string, bool>> tokens; // Text, plain text (quoted / escaped) |
130 | 132 | std::string token; |
131 | 133 | bool inToken = false, plainText = false, escape = false; |
132 | 134 | char quote = 0; |
133 | 135 | auto finishToken = [&]() { |
134 | | - if (!inToken) { |
135 | | - return; |
| 136 | + if (inToken) { |
| 137 | + tokens.emplace_back(std::move(token), plainText); |
| 138 | + token.clear(); |
| 139 | + inToken = plainText = false; |
136 | 140 | } |
137 | | - const char* begin = token.c_str() + (token[0] == '+' || token[0] == '-'); |
138 | | - const size_t nDigits = std::count_if(begin, token.c_str() + token.size(), [](char c) { return c >= '0' && c <= '9'; }); |
139 | | - const size_t nDots = std::count(begin, token.c_str() + token.size(), '.'); |
140 | | - const bool number = !plainText && nDigits && nDigits + nDots == (size_t)(token.c_str() + token.size() - begin); |
141 | | - int64_t intValue = 0; |
142 | | - if (number && nDots == 0 && std::from_chars(begin, token.c_str() + token.size(), intValue).ec == std::errc()) { |
143 | | - retVal.emplace_back(token[0] == '-' ? -intValue : intValue); |
144 | | - } else if (number && nDots <= 1) { |
145 | | - retVal.emplace_back(std::stod(token)); // Also integers out of the int64_t range |
146 | | - } else { |
147 | | - retVal.emplace_back(token); |
148 | | - } |
149 | | - token.clear(); |
150 | | - inToken = plainText = false; |
151 | 141 | }; |
152 | 142 | for (char c : cmd) { |
153 | 143 | if (escape) { |
@@ -175,26 +165,110 @@ std::vector<GPUDisplayConsole::consoleArg> GPUDisplayConsole::parseConsoleComman |
175 | 165 | throw std::runtime_error(quote ? "unterminated quote" : "backslash at the end"); |
176 | 166 | } |
177 | 167 | finishToken(); |
178 | | - return retVal; |
| 168 | + if (tokens.empty()) { |
| 169 | + throw std::runtime_error("empty command"); |
| 170 | + } |
| 171 | + |
| 172 | + const std::string& name = tokens[0].first; |
| 173 | + const auto it = mConsoleCommands.find(name); |
| 174 | + if (it == mConsoleCommands.end()) { |
| 175 | + throw std::runtime_error("unknown command " + name); |
| 176 | + } |
| 177 | + const std::string& types = it->second.args; |
| 178 | + const bool more = types.size() && types.back() == '+'; |
| 179 | + const size_t nFixed = types.size() - more, nArgs = tokens.size() - 1; |
| 180 | + if (nArgs < nFixed || (!more && nArgs > nFixed)) { |
| 181 | + throw std::runtime_error(name + ": " + std::to_string(nFixed) + (more ? " or more" : "") + " arguments expected, got " + std::to_string(nArgs)); |
| 182 | + } |
| 183 | + auto autoType = [](const std::string& text, bool plain) -> consoleArg { // int64_t for [+-]digits, double for [+-]digits with one '.', string otherwise or if plain text |
| 184 | + const char *begin = text.c_str() + (text[0] == '+' || text[0] == '-'), *end = text.c_str() + text.size(); |
| 185 | + const size_t nDigits = std::count_if(begin, end, [](char c) { return c >= '0' && c <= '9'; }); |
| 186 | + const size_t nDots = std::count(begin, end, '.'); |
| 187 | + const bool number = !plain && nDigits && nDigits + nDots == (size_t)(end - begin); |
| 188 | + int64_t intValue = 0; |
| 189 | + if (number && nDots == 0 && std::from_chars(begin, end, intValue).ec == std::errc()) { |
| 190 | + return text[0] == '-' ? -intValue : intValue; |
| 191 | + } else if (number && nDots <= 1) { |
| 192 | + return std::stod(text); // Also integers out of the int64_t range |
| 193 | + } |
| 194 | + return text; |
| 195 | + }; |
| 196 | + std::vector<consoleArg> args; |
| 197 | + for (size_t i = 0; i < nArgs; i++) { |
| 198 | + const auto& [text, plain] = tokens[i + 1]; |
| 199 | + const char type = i < nFixed ? types[i] : '+'; |
| 200 | + consoleArg value = type == 's' ? consoleArg(text) : autoType(text, plain); |
| 201 | + if (type == 'i' && !std::holds_alternative<int64_t>(value)) { |
| 202 | + throw std::runtime_error(name + ": argument " + std::to_string(i + 1) + " (" + text + ") must be an integer"); |
| 203 | + } else if (type == 'd') { |
| 204 | + if (std::holds_alternative<int64_t>(value)) { |
| 205 | + value = (double)std::get<int64_t>(value); |
| 206 | + } else if (!std::holds_alternative<double>(value)) { |
| 207 | + throw std::runtime_error(name + ": argument " + std::to_string(i + 1) + " (" + text + ") must be a number"); |
| 208 | + } |
| 209 | + } else if (type != 's' && type != 'i' && type != '+') { |
| 210 | + throw std::runtime_error(name + ": invalid argument types " + types); |
| 211 | + } |
| 212 | + args.emplace_back(std::move(value)); |
| 213 | + } |
| 214 | + return {it->second, std::move(args)}; |
179 | 215 | } |
180 | 216 |
|
181 | | -void GPUDisplayConsole::handleConsoleCommand(const std::string& cmd) |
| 217 | +void GPUDisplayConsole::handleConsoleCommand(std::string_view cmd) |
182 | 218 | { |
| 219 | + if (cmd.find_first_not_of(' ') == std::string_view::npos) { // Empty line |
| 220 | + return; |
| 221 | + } |
| 222 | + const GPUDisplayConsoleCommand* command = nullptr; |
183 | 223 | std::vector<consoleArg> args; |
184 | 224 | try { |
185 | | - args = parseConsoleCommand(cmd); |
| 225 | + auto parsed = parseConsoleCommand(cmd); |
| 226 | + command = &std::get<0>(parsed); |
| 227 | + args = std::move(std::get<1>(parsed)); |
186 | 228 | } catch (const std::runtime_error& e) { |
187 | 229 | consolePrint("Invalid command: %s", e.what()); |
188 | 230 | return; |
189 | 231 | } |
190 | | - if (args.empty()) { |
| 232 | + command->func(args); |
| 233 | +} |
| 234 | + |
| 235 | +void GPUDisplayConsole::runScript(std::string_view script) |
| 236 | +{ |
| 237 | + if (mScriptDepth >= 16) { |
| 238 | + consolePrint("Scripts nested too deeply"); |
191 | 239 | return; |
192 | 240 | } |
193 | | - const std::string* name = std::get_if<std::string>(&args[0]); |
194 | | - const auto it = name ? mConsoleCommands.find(*name) : mConsoleCommands.end(); |
195 | | - if (it == mConsoleCommands.end()) { |
196 | | - consolePrint("Unknown command: %s", cmd.c_str()); |
| 241 | + mScriptDepth++; |
| 242 | + for (size_t pos = 0; pos < script.size();) { |
| 243 | + const size_t end = std::min(script.find_first_of("\r\n", pos), script.size()); |
| 244 | + handleConsoleCommand(script.substr(pos, end - pos)); // Empty lines (consecutive \r / \n) are ignored |
| 245 | + pos = end + 1; |
| 246 | + } |
| 247 | + mScriptDepth--; |
| 248 | +} |
| 249 | + |
| 250 | +void GPUDisplayConsole::runScriptFile(const std::string& filename) |
| 251 | +{ |
| 252 | + std::ifstream file(filename, std::ios::binary); |
| 253 | + if (!file) { |
| 254 | + consolePrint("Cannot open script file %s", filename.c_str()); |
197 | 255 | return; |
198 | 256 | } |
199 | | - it->second(std::span(args).subspan(1)); |
| 257 | + std::stringstream content; |
| 258 | + content << file.rdbuf(); |
| 259 | + runScript(content.str()); |
| 260 | +} |
| 261 | + |
| 262 | +void GPUDisplayConsole::printCommands(const std::function<void(const std::string&)>& print) |
| 263 | +{ |
| 264 | + std::vector<std::string> names; |
| 265 | + for (const auto& [name, command] : mConsoleCommands) { |
| 266 | + names.emplace_back(name); |
| 267 | + } |
| 268 | + std::sort(names.begin(), names.end()); |
| 269 | + print("Console commands (argument types: s string, i integer, d number, + further arguments):"); |
| 270 | + for (const auto& name : names) { |
| 271 | + const auto& command = mConsoleCommands[name]; |
| 272 | + print(" " + name + (command.args.empty() ? "" : " ") + command.args + ": " + command.description); |
| 273 | + } |
200 | 274 | } |
0 commit comments