Implicitly redirect cmdhelp includes to corresponding JSON files
[lsnes.git] / src / library / command.cpp
blob5182c7a725e76c98ea6b88d92f1d0a90c67a4fa6
1 #include "command.hpp"
2 #include "globalwrap.hpp"
3 #include "integer-pool.hpp"
4 #include "minmax.hpp"
5 #include "stateobject.hpp"
6 #include "string.hpp"
7 #include "threads.hpp"
8 #include "zip.hpp"
9 #include <iostream>
10 #include <cstdlib>
12 namespace command
14 namespace
16 struct run_script : public base
18 run_script(group& group, std::ostream*& _output)
19 : base(group, "run-script", true), in_group(group), output(_output)
23 ~run_script() throw()
27 void invoke(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
29 if(filename == "") {
30 (*output) << "Syntax: run-script <scriptfile>" << std::endl;
31 return;
33 std::istream* o = NULL;
34 try {
35 o = &zip::openrel(filename, "");
36 (*output) << "Running '" << std::string(filename) << "'" << std::endl;
37 std::string line;
38 while(std::getline(*o, line))
39 in_group.invoke(line);
40 delete o;
41 } catch(std::exception& e) {
42 delete o;
43 throw;
47 std::string get_short_help() throw(std::bad_alloc)
49 return "Run file as a script";
52 std::string get_long_help() throw(std::bad_alloc)
54 return "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in "
55 "the command line\n";
58 group& in_group;
59 std::ostream*& output;
62 void default_oom_panic()
64 std::cerr << "PANIC: Fatal error, can't continue: Out of memory." << std::endl;
65 exit(1);
68 threads::rlock* global_lock;
69 threads::rlock& get_cmd_lock()
71 if(!global_lock) global_lock = new threads::rlock;
72 return *global_lock;
75 struct set_internal
77 std::set<set::listener*> callbacks;
78 std::map<std::string, factory_base*> commands;
81 struct group_internal
83 std::map<std::string, base*> commands;
84 std::set<set*> set_handles;
87 typedef stateobject::type<set, set_internal> set_internal_t;
88 typedef stateobject::type<group, group_internal> group_internal_t;
91 set::listener::~listener()
95 void factory_base::_factory_base(set& _set, const std::string& cmd) throw(std::bad_alloc)
97 threads::arlock h(get_cmd_lock());
98 in_set = &_set;
99 in_set->do_register(commandname = cmd, *this);
102 factory_base::~factory_base() throw()
104 threads::arlock h(get_cmd_lock());
105 if(in_set)
106 in_set->do_unregister(commandname, *this);
109 void factory_base::set_died() throw()
111 //The lock is held by assumption.
112 in_set = NULL;
115 base::base(group& group, const std::string& cmd, bool dynamic) throw(std::bad_alloc)
117 in_group = &group;
118 is_dynamic = dynamic;
119 threads::arlock h(get_cmd_lock());
120 in_group->do_register(commandname = cmd, *this);
123 base::~base() throw()
125 threads::arlock h(get_cmd_lock());
126 if(in_group)
127 in_group->do_unregister(commandname, *this);
130 void base::group_died() throw()
132 threads::arlock h(get_cmd_lock());
133 in_group = NULL;
134 //If dynamic, we aren't needed anymore.
135 if(is_dynamic) delete this;
138 std::string base::get_short_help() throw(std::bad_alloc)
140 return "No description available";
143 std::string base::get_long_help() throw(std::bad_alloc)
145 return "No help available on command " + commandname;
148 set::set() throw(std::bad_alloc)
152 set::~set() throw()
154 auto state = set_internal_t::get_soft(this);
155 if(!state) return;
156 threads::arlock h(get_cmd_lock());
157 //Call all DCBs on all factories.
158 for(auto i : state->commands)
159 for(auto j : state->callbacks)
160 j->destroy(*this, i.first);
161 //Call all TCBs.
162 for(auto j : state->callbacks)
163 j->kill(*this);
164 //Notify all factories that base set died.
165 for(auto i : state->commands)
166 i.second->set_died();
167 //We assume factories look after themselves, so we don't destroy those.
168 set_internal_t::clear(this);
171 void set::do_register(const std::string& name, factory_base& cmd) throw(std::bad_alloc)
173 threads::arlock h(get_cmd_lock());
174 auto& state = set_internal_t::get(this);
175 if(state.commands.count(name)) {
176 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
177 return;
179 state.commands[name] = &cmd;
180 //Call all CCBs on this.
181 for(auto i : state.callbacks)
182 i->create(*this, name, cmd);
185 void set::do_unregister(const std::string& name, factory_base& cmd) throw(std::bad_alloc)
187 threads::arlock h(get_cmd_lock());
188 auto state = set_internal_t::get_soft(this);
189 if(!state) return;
190 if(!state->commands.count(name) || state->commands[name] != &cmd) return; //Not this.
191 state->commands.erase(name);
192 //Call all DCBs on this.
193 for(auto i : state->callbacks)
194 i->destroy(*this, name);
197 void set::add_callback(set::listener& listener)
198 throw(std::bad_alloc)
200 threads::arlock h(get_cmd_lock());
201 auto& state = set_internal_t::get(this);
202 state.callbacks.insert(&listener);
203 //To avoid races, call CCBs on all factories for this.
204 for(auto j : state.commands)
205 listener.create(*this, j.first, *j.second);
208 void set::drop_callback(set::listener& listener) throw()
210 threads::arlock h(get_cmd_lock());
211 auto state = set_internal_t::get_soft(this);
212 if(!state) return;
213 if(state->callbacks.count(&listener)) {
214 //To avoid races, call DCBs on all factories for this.
215 for(auto j : state->commands)
216 listener.destroy(*this, j.first);
217 state->callbacks.erase(&listener);
221 group::group() throw(std::bad_alloc)
222 : _listener(*this)
224 oom_panic_routine = default_oom_panic;
225 output = &std::cerr;
226 //The builtin commands.
227 builtin[0] = new run_script(*this, output);
230 group::~group() throw()
232 auto state = group_internal_t::get_soft(this);
233 if(!state) return;
234 threads::arlock h(get_cmd_lock());
236 //Notify all bases that base group died.
237 //Builtin commands delete themselves on parent group dying.
238 for(auto i : state->commands)
239 i.second->group_died();
241 //Drop all callbacks.
242 for(auto i : state->set_handles)
243 i->drop_callback(_listener);
244 //We assume all bases that need destroying have already been destroyed.
245 group_internal_t::clear(this);
248 void group::invoke(const std::string& cmd) throw()
250 auto state = group_internal_t::get_soft(this);
251 if(!state) return;
252 try {
253 std::string cmd2 = strip_CR(cmd);
254 if(cmd2 == "?") {
255 //The special ? command.
256 threads::arlock lock(get_cmd_lock());
257 for(auto i : state->commands)
258 (*output) << i.first << ": " << i.second->get_short_help() << std::endl;
259 return;
261 if(firstchar(cmd2) == '?') {
262 //?command.
263 threads::arlock lock(get_cmd_lock());
264 std::string rcmd = cmd2.substr(1, min(cmd2.find_first_of(" \t"), cmd2.length()));
265 if(firstchar(rcmd) != '*') {
266 //This may be an alias.
267 if(aliases.count(rcmd)) {
268 //Yup.
269 (*output) << rcmd << " is an alias for: " << std::endl;
270 size_t j = 0;
271 for(auto i : aliases[rcmd])
272 (*output) << "#" << (++j) << ": " << i << std::endl;
273 return;
275 } else
276 rcmd = rcmd.substr(1);
277 if(!state->commands.count(rcmd))
278 (*output) << "Unknown command '" << rcmd << "'" << std::endl;
279 else
280 (*output) << state->commands[rcmd]->get_long_help() << std::endl;
281 return;
283 bool may_be_alias_expanded = true;
284 if(firstchar(cmd2) == '*') {
285 may_be_alias_expanded = false;
286 cmd2 = cmd2.substr(1);
288 //Now this gets painful as command handlers must not be invoked with lock held.
289 if(may_be_alias_expanded) {
290 std::list<std::string> aexp;
292 threads::arlock lock(get_cmd_lock());
293 if(!aliases.count(cmd))
294 goto not_alias;
295 aexp = aliases[cmd2];
297 for(auto i : aexp)
298 invoke(i);
299 return;
301 not_alias:
302 try {
303 size_t split = cmd2.find_first_of(" \t");
304 std::string rcmd = cmd2.substr(0, min(split, cmd2.length()));
305 std::string args = cmd2.substr(min(cmd2.find_first_not_of(" \t", split), cmd2.length()));
306 invoke(rcmd, args);
307 return;
308 } catch(std::bad_alloc& e) {
309 oom_panic_routine();
310 } catch(std::exception& e) {
311 (*output) << "Error[" << cmd2 << "]: " << e.what() << std::endl;
312 command_stack.erase(cmd2);
313 return;
315 } catch(std::bad_alloc& e) {
316 oom_panic_routine();
320 void group::invoke(const std::string& cmd, const std::string& args) throw()
322 auto state = group_internal_t::get_soft(this);
323 if(!state) return;
324 try {
325 std::string ckey = cmd + " " + args;
326 try {
327 base* cmdh = NULL;
329 threads::arlock lock(get_cmd_lock());
330 if(!state->commands.count(cmd)) {
331 (*output) << "Unknown command '" << cmd << "'" << std::endl;
332 return;
334 cmdh = state->commands[cmd];
336 if(command_stack.count(ckey))
337 throw std::runtime_error("Recursive command invocation");
338 command_stack.insert(ckey);
339 cmdh->invoke(args);
340 command_stack.erase(ckey);
341 return;
342 } catch(std::bad_alloc& e) {
343 oom_panic_routine();
344 } catch(std::exception& e) {
345 (*output) << "Error[" << ckey << "]: " << e.what() << std::endl;
346 command_stack.erase(ckey);
347 return;
349 } catch(std::bad_alloc& e) {
350 oom_panic_routine();
354 std::set<std::string> group::get_aliases() throw(std::bad_alloc)
356 threads::arlock lock(get_cmd_lock());
357 std::set<std::string> r;
358 for(auto i : aliases)
359 r.insert(i.first);
360 return r;
363 std::string group::get_alias_for(const std::string& aname) throw(std::bad_alloc)
365 threads::arlock lock(get_cmd_lock());
366 if(!valid_alias_name(aname))
367 return "";
368 if(aliases.count(aname)) {
369 std::string x;
370 for(auto i : aliases[aname])
371 x = x + i + "\n";
372 return x;
373 } else
374 return "";
377 void group::set_alias_for(const std::string& aname, const std::string& avalue) throw(std::bad_alloc)
379 threads::arlock lock(get_cmd_lock());
380 if(!valid_alias_name(aname))
381 return;
382 std::list<std::string> newlist;
383 size_t avitr = 0;
384 while(avitr < avalue.length()) {
385 size_t nextsplit = min(avalue.find_first_of("\n", avitr), avalue.length());
386 std::string x = strip_CR(avalue.substr(avitr, nextsplit - avitr));
387 if(x.length() > 0)
388 newlist.push_back(x);
389 avitr = nextsplit + 1;
391 if(newlist.empty())
392 aliases.erase(aname);
393 else
394 aliases[aname] = newlist;
397 bool group::valid_alias_name(const std::string& aliasname) throw(std::bad_alloc)
399 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
400 return false;
401 if(aliasname.find_first_of(" \t") < aliasname.length())
402 return false;
403 return true;
406 void group::do_register(const std::string& name, base& cmd) throw(std::bad_alloc)
408 threads::arlock h(get_cmd_lock());
409 auto& state = group_internal_t::get(this);
410 if(state.commands.count(name))
411 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
412 state.commands[name] = &cmd;
415 void group::do_unregister(const std::string& name, base& cmd) throw(std::bad_alloc)
417 threads::arlock h(get_cmd_lock());
418 auto state = group_internal_t::get_soft(this);
419 if(!state) return;
420 if(!state->commands.count(name) || state->commands[name] != &cmd) return;
421 state->commands.erase(name);
424 void group::set_output(std::ostream& s)
426 output = &s;
429 void group::set_oom_panic(void (*fn)())
431 if(fn)
432 oom_panic_routine = fn;
433 else
434 oom_panic_routine = default_oom_panic;
437 void group::add_set(set& s) throw(std::bad_alloc)
439 threads::arlock h(get_cmd_lock());
440 auto& state = group_internal_t::get(this);
441 if(state.set_handles.count(&s)) return;
442 try {
443 state.set_handles.insert(&s);
444 s.add_callback(_listener);
445 } catch(...) {
446 state.set_handles.erase(&s);
450 void group::drop_set(set& s) throw()
452 threads::arlock h(get_cmd_lock());
453 auto state = group_internal_t::get_soft(this);
454 if(!state) return;
455 //Drop the callback. This unregisters all.
456 s.drop_callback(_listener);
457 state->set_handles.erase(&s);
460 group::listener::listener(group& _grp)
461 : grp(_grp)
465 group::listener::~listener()
469 void group::listener::create(set& s, const std::string& name, factory_base& cmd)
471 threads::arlock h(get_cmd_lock());
472 cmd.make(grp);
475 void group::listener::destroy(set& s, const std::string& name)
477 threads::arlock h(get_cmd_lock());
478 auto state = group_internal_t::get_soft(&grp);
479 if(!state) return;
480 state->commands.erase(name);
483 void group::listener::kill(set& s)
485 threads::arlock h(get_cmd_lock());
486 auto state = group_internal_t::get_soft(&grp);
487 if(!state) return;
488 state->set_handles.erase(&s);
491 template<>
492 void invoke_fn(std::function<void(const std::string& args)> fn, const std::string& args)
494 fn(args);
497 template<>
498 void invoke_fn(std::function<void()> fn, const std::string& args)
500 if(args != "")
501 throw std::runtime_error("This command does not take arguments");
502 fn();
505 template<>
506 void invoke_fn(std::function<void(struct arg_filename a)> fn, const std::string& args)
508 if(args == "")
509 throw std::runtime_error("Filename required");
510 arg_filename b;
511 b.v = args;
512 fn(b);