├── examples ├── build.bat ├── .gitignore └── example.cpp ├── README.md └── noclip.h /examples/build.bat: -------------------------------------------------------------------------------- 1 | 2 | cl example.cpp /EHsc 3 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.vs 2 | *.exe 3 | *.o 4 | *.pdb 5 | *.obj -------------------------------------------------------------------------------- /examples/example.cpp: -------------------------------------------------------------------------------- 1 | #include "../noclip.h" 2 | 3 | struct A 4 | { 5 | float x = 3.1415; 6 | 7 | void f(int i) 8 | { 9 | std::cout << "A::f + " << x << std::endl; 10 | } 11 | }; 12 | 13 | void funcwithargs(const std::string& str, const int& i) 14 | { 15 | std::cout << "called it" << str << i << std::endl; 16 | } 17 | 18 | int fib(int n) 19 | { 20 | if (n <= 1) 21 | return n; 22 | return fib(n-1) + fib(n-2); 23 | } 24 | 25 | int main() 26 | { 27 | int i; 28 | float f; 29 | std::string s; 30 | 31 | noclip::console c; 32 | c.bind_cvar("i", &i); 33 | c.bind_cvar("f", &f); 34 | c.bind_cvar("s", &s); 35 | c.bind_cmd("printstuff", printstuff); 36 | 37 | c.bind_cmd("fib", fib); 38 | 39 | A a; 40 | c.bind_cvar("ax", &a.x); 41 | c.bind_cmd("af", &A::f, &a); 42 | 43 | while(true) 44 | { 45 | c.execute(std::cin, std::cout); 46 | } 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # noclip 2 | 3 | Single header C++11 library for parsing and interpreting commands 4 | and arguments from an input stream. Can be used to make REPL-like 5 | environments e.g. drop-down console. 6 | 7 | Type information is baked into the anonymous functions for setting/getting variables 8 | and invoking commands. Type information is used to reify arguments at execution time. 9 | 10 | For input and output, `noclip.h` uses `std::istream` and `std::ostream`. This 11 | makes parsing of custom types possible by overloading the insertion and extraction 12 | operators. 13 | 14 | ![image](https://github.com/user-attachments/assets/21ff12f1-d77e-498c-a0df-af2c8d87c912) 15 | 16 | ### Console Commands 17 | ```c++ 18 | void set_cheats(int mode) { ... } 19 | ... 20 | console.bind_cmd("sv_cheats", set_cheats); 21 | console.execute("sv_cheats 1", std::cout); // calls set_cheats with mode 1 22 | ``` 23 | 24 | ### Console Variables 25 | ```c++ 26 | int hp = 100; 27 | console.bind_cvar("health", &hp); 28 | console.execute("set health 99", std::cout); 29 | console.execute("get health", std::cout); // prints value of hp 30 | ``` 31 | 32 | ### Creating the Console 33 | ```c++ 34 | #include "noclip.h" 35 | noclip::console c; // default constructor 36 | ``` 37 | More documentation included directly in the header file. 38 | -------------------------------------------------------------------------------- /noclip.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | noclip.h 4 | 5 | Single header C++11 library for parsing and interpreting commands 6 | and arguments from an input stream. Can be used to make REPL-like 7 | environments e.g. drop-down console. 8 | 9 | Uses C++11 features (e.g. parameter pack, anonymous functions). 10 | 11 | Type information is baked into the anonymous functions for setting/getting variables 12 | and invoking commands. Type information is used to reify arguments at execution time. 13 | 14 | For input and output, noclip::console uses std::istream and std::ostream. This 15 | makes parsing of custom types possible by overloading the insertion and extraction 16 | operators. 17 | 18 | USAGE: 19 | function | example 20 | ------------|----------------------------------------- 21 | bind_cvar | console.bind_cvar("health", &health); 22 | | 23 | bind_cmd | console.bind_cmd("command", someFunction); 24 | | console.bind_cmd("command", &Object::memberFunc, &objectInstance); 25 | | console.bind_cmd("command", [](std::istream& is, std::ostream& os){ lambda body }); 26 | | 27 | unbind_cvar | console.unbind_cvar("health"); // useful if 'health' goes out of scope (i.e. dealloc'ed) 28 | | 29 | unbind_cmd | console.unbind_cvar("command"); // useful if object owning 'command' goes out of scope 30 | | 31 | execute | console.execute(std::cin, std::cout); 32 | | console.execute("set health 99", std::cout); 33 | 34 | CREATING A CONSOLE: 35 | noclip::console console; 36 | 37 | */ 38 | #ifndef NOCLIP_CONSOLE_H 39 | #define NOCLIP_CONSOLE_H 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #ifndef NOCLIP_MULTIPLE_COMMANDS_DELIMITER 47 | #define NOCLIP_MULTIPLE_COMMANDS_DELIMITER ';' 48 | #endif // NOCLIP_MULTIPLE_COMMANDS_DELIMITER 49 | 50 | namespace noclip 51 | { 52 | typedef std::function console_function_t; 53 | 54 | struct console 55 | { 56 | console() 57 | { 58 | bind_builtin_commands(); 59 | } 60 | 61 | typedef std::map function_table_t; 62 | function_table_t cmd_table; 63 | function_table_t cvar_setter_lambdas; 64 | function_table_t cvar_getter_lambdas; 65 | 66 | template 67 | void bind_cvar(const std::string& vid, T* vmem) 68 | { 69 | cvar_setter_lambdas[vid] = 70 | [this, vid, vmem](std::istream& is, std::ostream& os) 71 | { 72 | T read = this->evaluate_argument(is, os); 73 | 74 | if(is.fail()) 75 | { 76 | const char* vt = typeid(T).name(); 77 | os << "CONSOLE ERROR: Type mismatch. CVar '" << vid 78 | << "' is of type '" << vt << "'." << std::endl; 79 | 80 | is.clear(); 81 | } 82 | else 83 | { 84 | *vmem = read; 85 | } 86 | }; 87 | 88 | cvar_getter_lambdas[vid] = 89 | [vmem](std::istream& is, std::ostream& os) 90 | { 91 | os << *vmem << std::endl; 92 | }; 93 | } 94 | 95 | template 96 | void bind_cmd(const std::string& cid, void(*f_ptr)(Args ...)) 97 | { 98 | cmd_table[cid] = 99 | [this, f_ptr](std::istream& is, std::ostream& os) 100 | { 101 | auto std_fp = std::function(f_ptr); 102 | this->reify_and_execute(is, os, std_fp); 103 | }; 104 | } 105 | 106 | /* e.g. bind_cmd("myObjectMethod", &MyObject::f, &myObjectInstance) */ 107 | template 108 | void bind_cmd(const std::string& cid, void(T::*f_ptr)(Args ...), T* omem) 109 | { 110 | std::function std_fp = 111 | [f_ptr, omem](Args ... args) 112 | { 113 | (omem->*f_ptr)(args...); // could use std::mem_fn instead 114 | }; 115 | 116 | cmd_table[cid] = 117 | [this, std_fp](std::istream &is, std::ostream &os) 118 | { 119 | this->reify_and_execute(is, os, std_fp); 120 | }; 121 | } 122 | 123 | void bind_cmd(const std::string& cid, console_function_t iofunc) 124 | { 125 | cmd_table[cid] = iofunc; 126 | } 127 | 128 | void unbind_cvar(const std::string& vid) 129 | { 130 | auto v_set_iter = cvar_setter_lambdas.find(vid); 131 | if(v_set_iter != cvar_setter_lambdas.end()) 132 | { 133 | cvar_setter_lambdas.erase(v_set_iter); 134 | } 135 | 136 | auto v_get_iter = cvar_getter_lambdas.find(vid); 137 | if(v_get_iter != cvar_getter_lambdas.end()) 138 | { 139 | cvar_getter_lambdas.erase(v_get_iter); 140 | } 141 | } 142 | 143 | void unbind_cmd(const std::string& cid) 144 | { 145 | auto cmd_iter = cmd_table.find(cid); 146 | if(cmd_iter != cmd_table.end()) 147 | { 148 | cmd_table.erase(cmd_iter); 149 | } 150 | } 151 | 152 | void execute(std::istream& input, std::ostream& output) 153 | { 154 | while (!input.eof()) 155 | { 156 | std::string cmd; 157 | 158 | for (char c = 0; !input.eof(); c = 0) 159 | { 160 | input.get(c); 161 | if (c == 0 || c == NOCLIP_MULTIPLE_COMMANDS_DELIMITER) 162 | break; 163 | cmd.push_back(c); 164 | } 165 | 166 | if (!cmd.empty()) 167 | { 168 | std::stringstream cmdstream; 169 | cmdstream.str(cmd); 170 | 171 | std::string cmd_id; 172 | cmdstream >> cmd_id; 173 | 174 | auto cmd_iter = cmd_table.find(cmd_id); 175 | if (cmd_iter == cmd_table.end()) 176 | { 177 | output << "CONSOLE ERROR: Input '" << cmd_id << "' isn't a command." << std::endl; 178 | return; 179 | } 180 | 181 | (cmd_iter->second)(cmdstream, output); 182 | } 183 | } 184 | } 185 | 186 | void execute(const std::string& str, std::ostream& output) 187 | { 188 | std::stringstream cmdstream; 189 | cmdstream.str(str); 190 | execute(cmdstream, output); 191 | } 192 | 193 | private: 194 | void read_arg(std::istream& is) 195 | { 196 | /* base case */ 197 | } 198 | 199 | template 200 | void read_arg(std::istream& is, T& first, Ts &... rest) 201 | { 202 | /* Variadic template that recursively iterates each function 203 | argument type. For each arg type, parse the argument and 204 | set value of first. First is a reference to a parameter 205 | of read_args_and_execute. */ 206 | 207 | std::ostringstream discard; 208 | first = evaluate_argument(is, discard); 209 | read_arg(is, rest ...); 210 | } 211 | 212 | template 213 | void read_args_and_execute(std::istream& is, std::ostream& os, std::function f_ptr, 214 | typename std::remove_const::type>::type ... temps) 215 | { 216 | read_arg(is, temps...); 217 | 218 | if(is.fail()) 219 | { 220 | is.clear(); 221 | os << "CONSOLE ERROR: Incorrect argument types." << std::endl; 222 | return; 223 | } 224 | f_ptr(temps...); 225 | } 226 | 227 | template 228 | void reify_and_execute(std::istream& is, std::ostream& os, std::function f_ptr) 229 | { 230 | read_args_and_execute(is, os, f_ptr, 231 | (reify::type>::type>())...); 232 | } 233 | 234 | template 235 | T reify() 236 | { 237 | /* This function is to materialize the arbitrary number of 238 | types into an arbitrary number of rvalues of those types. */ 239 | return T(); 240 | } 241 | 242 | template 243 | T evaluate_argument(std::istream& is, std::ostream& os) 244 | { 245 | /* Evaluate argument expressions e.g. set x (+ 3 7) */ 246 | 247 | while(isspace(is.peek())) 248 | { 249 | is.ignore(); 250 | } 251 | 252 | if(is.peek() == '(') 253 | { 254 | is.ignore(); // '(' 255 | const int max_argument_size = 256; 256 | char argument_buffer[max_argument_size]; 257 | is.get(argument_buffer, max_argument_size, ')'); 258 | is.ignore(); // ')' 259 | 260 | std::ostringstream result; 261 | execute(std::string(argument_buffer), result); 262 | 263 | T read; 264 | std::istringstream(result.str()) >> read; 265 | return read; 266 | } 267 | else 268 | { 269 | T read; 270 | is >> read; 271 | return read; 272 | } 273 | } 274 | 275 | void bind_builtin_commands() 276 | { 277 | cmd_table["set"] = 278 | [this](std::istream& is, std::ostream& os) 279 | { 280 | std::string vid; 281 | is >> vid; 282 | auto v_iter = cvar_setter_lambdas.find(vid); 283 | if(v_iter == cvar_setter_lambdas.end()) 284 | { 285 | os << "CONSOLE ERROR: There is no bound variable with id '" << vid << "'." << std::endl; 286 | return; 287 | } 288 | else 289 | { 290 | (v_iter->second)(is, os); 291 | } 292 | }; 293 | 294 | cmd_table["get"] = 295 | [this](std::istream& is, std::ostream& os) 296 | { 297 | std::string vid; 298 | is >> vid; 299 | auto v_iter = cvar_getter_lambdas.find(vid); 300 | if(v_iter == cvar_getter_lambdas.end()) 301 | { 302 | os << "CONSOLE ERROR: There is no bound variable with id '" << vid << "'." << std::endl; 303 | return; 304 | } 305 | else 306 | { 307 | (v_iter->second)(is, os); 308 | } 309 | }; 310 | 311 | cmd_table["help"] = 312 | [](std::istream& is, std::ostream& os) 313 | { 314 | os << std::endl; 315 | os << "-- Console Help --" << std::endl; 316 | // os << "Set and get bound variables with" << std::endl; 317 | os << " set " << std::endl; 318 | os << " get " << std::endl; 319 | // os << std::endl; 320 | // os << "Call bound and compiled C++ functions with" << std::endl; 321 | // os << " ... " << std::endl; 322 | // os << std::endl; 323 | // os << "Get help" << std::endl; 324 | os << " help : outputs help message" << std::endl; 325 | os << " cvars : list bound console variables" << std::endl; 326 | os << " procs : list bound console commands" << std::endl; 327 | os << std::endl; 328 | // os << "Perform arithematic and modulo operations" << std::endl; 329 | // os << "(+, -, *, /, %) " << std::endl; 330 | // os << std::endl; 331 | // os << "You can pass expressions as arguments" << std::endl; 332 | // os << "+ (- 3 2) (* 4 5)" << std::endl; 333 | // os << "set x (get y)" << std::endl; 334 | // os << "------------" << std::endl; 335 | }; 336 | 337 | cmd_table["cvars"] = 338 | [this](std::istream& is, std::ostream& os) 339 | { 340 | if(cvar_getter_lambdas.size() == 0) 341 | { 342 | os << "There are no bound console variables..." << std::endl; 343 | return; 344 | } 345 | 346 | os << std::endl; 347 | for (auto& it: cvar_getter_lambdas) 348 | { 349 | os << " " << it.first << std::endl; 350 | } 351 | os << std::endl; 352 | }; 353 | 354 | cmd_table["procs"] = 355 | [this](std::istream& is, std::ostream& os) 356 | { 357 | if(cmd_table.size() == 0) 358 | { 359 | os << "There are no bound console commands..." << std::endl; 360 | return; 361 | } 362 | 363 | os << std::endl; 364 | for (auto& it : cmd_table) 365 | { 366 | if (it.first == "+" || it.first == "-" || it.first == "*" || it.first == "/" 367 | || it.first == "%" || it.first == "set" || it.first == "get" || it.first == "help" 368 | || it.first == "procs" || it.first == "cvars") continue; 369 | 370 | os << " " << it.first << std::endl; 371 | } 372 | os << std::endl; 373 | }; 374 | 375 | cmd_table["+"] = 376 | [this](std::istream& is, std::ostream& os) 377 | { 378 | float a = this->evaluate_argument(is, os); 379 | float b = this->evaluate_argument(is, os); 380 | os << a + b << std::endl; 381 | }; 382 | 383 | cmd_table["-"] = 384 | [this](std::istream& is, std::ostream& os) 385 | { 386 | float a = this->evaluate_argument(is, os); 387 | float b = this->evaluate_argument(is, os); 388 | os << a - b << std::endl; 389 | }; 390 | 391 | cmd_table["*"] = 392 | [this](std::istream& is, std::ostream& os) 393 | { 394 | float a = this->evaluate_argument(is, os); 395 | float b = this->evaluate_argument(is, os); 396 | os << a * b << std::endl; 397 | }; 398 | 399 | cmd_table["/"] = 400 | [this](std::istream& is, std::ostream& os) 401 | { 402 | float a = this->evaluate_argument(is, os); 403 | float b = this->evaluate_argument(is, os); 404 | os << a / b << std::endl; 405 | }; 406 | 407 | cmd_table["%"] = 408 | [this](std::istream& is, std::ostream& os) 409 | { 410 | int a = this->evaluate_argument(is, os); 411 | int b = this->evaluate_argument(is, os); 412 | os << a % b << std::endl; 413 | }; 414 | } 415 | }; 416 | } 417 | 418 | 419 | /* 420 | ------------------------------------------------------------------------------ 421 | This software is dedicated to Public Domain (www.unlicense.org) 422 | 423 | This is free and unencumbered software released into the public domain. 424 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 425 | software, either in source code form or as a compiled binary, for any purpose, 426 | commercial or non-commercial, and by any means. 427 | 428 | In jurisdictions that recognize copyright laws, the author or authors of this 429 | software dedicate any and all copyright interest in the software to the public 430 | domain. We make this dedication for the benefit of the public at large and to 431 | the detriment of our heirs and successors. We intend this dedication to be an 432 | overt act of relinquishment in perpetuity of all present and future rights to 433 | this software under copyright law. 434 | 435 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 436 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 437 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 438 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 439 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 440 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 441 | ------------------------------------------------------------------------------ 442 | */ 443 | 444 | #endif //NOCLIP_CONSOLE_H 445 | --------------------------------------------------------------------------------