lsnes rr2-β24
[lsnes.git] / src / library / command.cpp
blob48566c89b71cd160d05951ddc84da91d813c4b68
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 <functional>
10 #include <iostream>
11 #include <cstdlib>
13 namespace command
15 namespace
17 struct run_script : public base
19 run_script(group& group, std::ostream*& _output)
20 : base(group, "run-script", true), in_group(group), output(_output)
24 ~run_script() throw()
28 void invoke(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
30 if(filename == "") {
31 (*output) << "Syntax: run-script <scriptfile>" << std::endl;
32 return;
34 std::istream* o = NULL;
35 try {
36 o = &zip::openrel(filename, "");
37 (*output) << "Running '" << std::string(filename) << "'" << std::endl;
38 std::string line;
39 while(std::getline(*o, line))
40 in_group.invoke(line);
41 delete o;
42 } catch(std::exception& e) {
43 delete o;
44 throw;
48 std::string get_short_help() throw(std::bad_alloc)
50 return "Run file as a script";
53 std::string get_long_help() throw(std::bad_alloc)
55 return "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in "
56 "the command line\n";
59 group& in_group;
60 std::ostream*& output;
63 void default_oom_panic()
65 std::cerr << "PANIC: Fatal error, can't continue: Out of memory." << std::endl;
66 exit(1);
69 threads::rlock* global_lock;
70 threads::rlock& get_cmd_lock()
72 if(!global_lock) global_lock = new threads::rlock;
73 return *global_lock;
76 struct set_internal
78 std::set<set::listener*> callbacks;
79 std::map<std::string, factory_base*> commands;
82 struct group_internal
84 std::map<std::string, base*> commands;
85 std::set<set*> set_handles;
88 typedef stateobject::type<set, set_internal> set_internal_t;
89 typedef stateobject::type<group, group_internal> group_internal_t;
92 set::listener::~listener()
96 void factory_base::_factory_base(set& _set, const std::string& cmd) throw(std::bad_alloc)
98 threads::arlock h(get_cmd_lock());
99 in_set = &_set;
100 in_set->do_register(commandname = cmd, *this);
103 factory_base::~factory_base() throw()
105 threads::arlock h(get_cmd_lock());
106 if(in_set)
107 in_set->do_unregister(commandname, *this);
110 void factory_base::set_died() throw()
112 //The lock is held by assumption.
113 in_set = NULL;
116 base::base(group& group, const std::string& cmd, bool dynamic) throw(std::bad_alloc)
118 in_group = &group;
119 is_dynamic = dynamic;
120 threads::arlock h(get_cmd_lock());
121 in_group->do_register(commandname = cmd, *this);
124 base::~base() throw()
126 threads::arlock h(get_cmd_lock());
127 if(in_group)
128 in_group->do_unregister(commandname, *this);
131 void base::group_died() throw()
133 threads::arlock h(get_cmd_lock());
134 in_group = NULL;
135 //If dynamic, we aren't needed anymore.
136 if(is_dynamic) delete this;
139 std::string base::get_short_help() throw(std::bad_alloc)
141 return "No description available";
144 std::string base::get_long_help() throw(std::bad_alloc)
146 return "No help available on command " + commandname;
149 set::set() throw(std::bad_alloc)
153 set::~set() throw()
155 auto state = set_internal_t::get_soft(this);
156 if(!state) return;
157 threads::arlock h(get_cmd_lock());
158 //Call all DCBs on all factories.
159 for(auto i : state->commands)
160 for(auto j : state->callbacks)
161 j->destroy(*this, i.first);
162 //Call all TCBs.
163 for(auto j : state->callbacks)
164 j->kill(*this);
165 //Notify all factories that base set died.
166 for(auto i : state->commands)
167 i.second->set_died();
168 //We assume factories look after themselves, so we don't destroy those.
169 set_internal_t::clear(this);
172 void set::do_register(const std::string& name, factory_base& cmd) throw(std::bad_alloc)
174 threads::arlock h(get_cmd_lock());
175 auto& state = set_internal_t::get(this);
176 if(state.commands.count(name)) {
177 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
178 return;
180 state.commands[name] = &cmd;
181 //Call all CCBs on this.
182 for(auto i : state.callbacks)
183 i->create(*this, name, cmd);
186 void set::do_unregister(const std::string& name, factory_base& cmd) throw(std::bad_alloc)
188 threads::arlock h(get_cmd_lock());
189 auto state = set_internal_t::get_soft(this);
190 if(!state) return;
191 if(!state->commands.count(name) || state->commands[name] != &cmd) return; //Not this.
192 state->commands.erase(name);
193 //Call all DCBs on this.
194 for(auto i : state->callbacks)
195 i->destroy(*this, name);
198 void set::add_callback(set::listener& listener)
199 throw(std::bad_alloc)
201 threads::arlock h(get_cmd_lock());
202 auto& state = set_internal_t::get(this);
203 state.callbacks.insert(&listener);
204 //To avoid races, call CCBs on all factories for this.
205 for(auto j : state.commands)
206 listener.create(*this, j.first, *j.second);
209 void set::drop_callback(set::listener& listener) throw()
211 threads::arlock h(get_cmd_lock());
212 auto state = set_internal_t::get_soft(this);
213 if(!state) return;
214 if(state->callbacks.count(&listener)) {
215 //To avoid races, call DCBs on all factories for this.
216 for(auto j : state->commands)
217 listener.destroy(*this, j.first);
218 state->callbacks.erase(&listener);
222 group::group() throw(std::bad_alloc)
223 : _listener(*this)
225 oom_panic_routine = default_oom_panic;
226 output = &std::cerr;
227 //The builtin commands.
228 builtin[0] = new run_script(*this, output);
231 group::~group() throw()
233 auto state = group_internal_t::get_soft(this);
234 if(!state) return;
235 threads::arlock h(get_cmd_lock());
237 //Notify all bases that base group died.
238 //Builtin commands delete themselves on parent group dying.
239 for(auto i : state->commands)
240 i.second->group_died();
242 //Drop all callbacks.
243 for(auto i : state->set_handles)
244 i->drop_callback(_listener);
245 //We assume all bases that need destroying have already been destroyed.
246 group_internal_t::clear(this);
249 void group::invoke(const std::string& cmd) throw()
251 auto state = group_internal_t::get_soft(this);
252 if(!state) return;
253 try {
254 std::string cmd2 = strip_CR(cmd);
255 if(cmd2 == "?") {
256 //The special ? command.
257 threads::arlock lock(get_cmd_lock());
258 for(auto i : state->commands)
259 (*output) << i.first << ": " << i.second->get_short_help() << std::endl;
260 return;
262 if(firstchar(cmd2) == '?') {
263 //?command.
264 threads::arlock lock(get_cmd_lock());
265 std::string rcmd = cmd2.substr(1, min(cmd2.find_first_of(" \t"), cmd2.length()));
266 if(firstchar(rcmd) != '*') {
267 //This may be an alias.
268 if(aliases.count(rcmd)) {
269 //Yup.
270 (*output) << rcmd << " is an alias for: " << std::endl;
271 size_t j = 0;
272 for(auto i : aliases[rcmd])
273 (*output) << "#" << (++j) << ": " << i << std::endl;
274 return;
276 } else
277 rcmd = rcmd.substr(1);
278 if(!state->commands.count(rcmd))
279 (*output) << "Unknown command '" << rcmd << "'" << std::endl;
280 else
281 (*output) << state->commands[rcmd]->get_long_help() << std::endl;
282 return;
284 bool may_be_alias_expanded = true;
285 if(firstchar(cmd2) == '*') {
286 may_be_alias_expanded = false;
287 cmd2 = cmd2.substr(1);
289 //Now this gets painful as command handlers must not be invoked with lock held.
290 if(may_be_alias_expanded) {
291 std::list<std::string> aexp;
293 threads::arlock lock(get_cmd_lock());
294 if(!aliases.count(cmd))
295 goto not_alias;
296 aexp = aliases[cmd2];
298 for(auto i : aexp)
299 invoke(i);
300 return;
302 not_alias:
303 try {
304 size_t split = cmd2.find_first_of(" \t");
305 std::string rcmd = cmd2.substr(0, min(split, cmd2.length()));
306 std::string args = cmd2.substr(min(cmd2.find_first_not_of(" \t", split), cmd2.length()));
307 invoke(rcmd, args);
308 return;
309 } catch(std::bad_alloc& e) {
310 oom_panic_routine();
311 } catch(std::exception& e) {
312 (*output) << "Error[" << cmd2 << "]: " << e.what() << std::endl;
313 command_stack.erase(cmd2);
314 return;
316 } catch(std::bad_alloc& e) {
317 oom_panic_routine();
321 void group::invoke(const std::string& cmd, const std::string& args) throw()
323 auto state = group_internal_t::get_soft(this);
324 if(!state) return;
325 try {
326 std::string ckey = cmd + " " + args;
327 try {
328 base* cmdh = NULL;
330 threads::arlock lock(get_cmd_lock());
331 if(!state->commands.count(cmd)) {
332 (*output) << "Unknown command '" << cmd << "'" << std::endl;
333 return;
335 cmdh = state->commands[cmd];
337 if(command_stack.count(ckey))
338 throw std::runtime_error("Recursive command invocation");
339 command_stack.insert(ckey);
340 cmdh->invoke(args);
341 command_stack.erase(ckey);
342 return;
343 } catch(std::bad_alloc& e) {
344 oom_panic_routine();
345 } catch(std::exception& e) {
346 (*output) << "Error[" << ckey << "]: " << e.what() << std::endl;
347 command_stack.erase(ckey);
348 return;
350 } catch(std::bad_alloc& e) {
351 oom_panic_routine();
355 std::set<std::string> group::get_aliases() throw(std::bad_alloc)
357 threads::arlock lock(get_cmd_lock());
358 std::set<std::string> r;
359 for(auto i : aliases)
360 r.insert(i.first);
361 return r;
364 std::string group::get_alias_for(const std::string& aname) throw(std::bad_alloc)
366 threads::arlock lock(get_cmd_lock());
367 if(!valid_alias_name(aname))
368 return "";
369 if(aliases.count(aname)) {
370 std::string x;
371 for(auto i : aliases[aname])
372 x = x + i + "\n";
373 return x;
374 } else
375 return "";
378 void group::set_alias_for(const std::string& aname, const std::string& avalue) throw(std::bad_alloc)
380 threads::arlock lock(get_cmd_lock());
381 if(!valid_alias_name(aname))
382 return;
383 std::list<std::string> newlist;
384 size_t avitr = 0;
385 while(avitr < avalue.length()) {
386 size_t nextsplit = min(avalue.find_first_of("\n", avitr), avalue.length());
387 std::string x = strip_CR(avalue.substr(avitr, nextsplit - avitr));
388 if(x.length() > 0)
389 newlist.push_back(x);
390 avitr = nextsplit + 1;
392 if(newlist.empty())
393 aliases.erase(aname);
394 else
395 aliases[aname] = newlist;
398 bool group::valid_alias_name(const std::string& aliasname) throw(std::bad_alloc)
400 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
401 return false;
402 if(aliasname.find_first_of(" \t") < aliasname.length())
403 return false;
404 return true;
407 void group::do_register(const std::string& name, base& cmd) throw(std::bad_alloc)
409 threads::arlock h(get_cmd_lock());
410 auto& state = group_internal_t::get(this);
411 if(state.commands.count(name))
412 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
413 state.commands[name] = &cmd;
416 void group::do_unregister(const std::string& name, base& cmd) throw(std::bad_alloc)
418 threads::arlock h(get_cmd_lock());
419 auto state = group_internal_t::get_soft(this);
420 if(!state) return;
421 if(!state->commands.count(name) || state->commands[name] != &cmd) return;
422 state->commands.erase(name);
425 void group::set_output(std::ostream& s)
427 output = &s;
430 void group::set_oom_panic(void (*fn)())
432 if(fn)
433 oom_panic_routine = fn;
434 else
435 oom_panic_routine = default_oom_panic;
438 void group::add_set(set& s) throw(std::bad_alloc)
440 threads::arlock h(get_cmd_lock());
441 auto& state = group_internal_t::get(this);
442 if(state.set_handles.count(&s)) return;
443 try {
444 state.set_handles.insert(&s);
445 s.add_callback(_listener);
446 } catch(...) {
447 state.set_handles.erase(&s);
451 void group::drop_set(set& s) throw()
453 threads::arlock h(get_cmd_lock());
454 auto state = group_internal_t::get_soft(this);
455 if(!state) return;
456 //Drop the callback. This unregisters all.
457 s.drop_callback(_listener);
458 state->set_handles.erase(&s);
461 group::listener::listener(group& _grp)
462 : grp(_grp)
466 group::listener::~listener()
470 void group::listener::create(set& s, const std::string& name, factory_base& cmd)
472 threads::arlock h(get_cmd_lock());
473 cmd.make(grp);
476 void group::listener::destroy(set& s, const std::string& name)
478 threads::arlock h(get_cmd_lock());
479 auto state = group_internal_t::get_soft(&grp);
480 if(!state) return;
481 state->commands.erase(name);
484 void group::listener::kill(set& s)
486 threads::arlock h(get_cmd_lock());
487 auto state = group_internal_t::get_soft(&grp);
488 if(!state) return;
489 state->set_handles.erase(&s);
492 template<>
493 void invoke_fn(std::function<void(const std::string& args)> fn, const std::string& args)
495 fn(args);
498 template<>
499 void invoke_fn(std::function<void()> fn, const std::string& args)
501 if(args != "")
502 throw std::runtime_error("This command does not take arguments");
503 fn();
506 template<>
507 void invoke_fn(std::function<void(struct arg_filename a)> fn, const std::string& args)
509 if(args == "")
510 throw std::runtime_error("Filename required");
511 arg_filename b;
512 b.v = args;
513 fn(b);