Allow binding commands to class instance
[lsnes.git] / src / library / command.cpp
blob9cb9328c985e12009179efdb81b200be5a3fc7af
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 base* cmdh = NULL;
308 threads::arlock lock(get_cmd_lock());
309 if(!state->commands.count(rcmd)) {
310 (*output) << "Unknown command '" << rcmd << "'" << std::endl;
311 return;
313 cmdh = state->commands[rcmd];
315 if(command_stack.count(cmd2))
316 throw std::runtime_error("Recursive command invocation");
317 command_stack.insert(cmd2);
318 cmdh->invoke(args);
319 command_stack.erase(cmd2);
320 return;
321 } catch(std::bad_alloc& e) {
322 oom_panic_routine();
323 } catch(std::exception& e) {
324 (*output) << "Error[" << cmd2 << "]: " << e.what() << std::endl;
325 command_stack.erase(cmd2);
326 return;
328 } catch(std::bad_alloc& e) {
329 oom_panic_routine();
333 std::set<std::string> group::get_aliases() throw(std::bad_alloc)
335 threads::arlock lock(get_cmd_lock());
336 std::set<std::string> r;
337 for(auto i : aliases)
338 r.insert(i.first);
339 return r;
342 std::string group::get_alias_for(const std::string& aname) throw(std::bad_alloc)
344 threads::arlock lock(get_cmd_lock());
345 if(!valid_alias_name(aname))
346 return "";
347 if(aliases.count(aname)) {
348 std::string x;
349 for(auto i : aliases[aname])
350 x = x + i + "\n";
351 return x;
352 } else
353 return "";
356 void group::set_alias_for(const std::string& aname, const std::string& avalue) throw(std::bad_alloc)
358 threads::arlock lock(get_cmd_lock());
359 if(!valid_alias_name(aname))
360 return;
361 std::list<std::string> newlist;
362 size_t avitr = 0;
363 while(avitr < avalue.length()) {
364 size_t nextsplit = min(avalue.find_first_of("\n", avitr), avalue.length());
365 std::string x = strip_CR(avalue.substr(avitr, nextsplit - avitr));
366 if(x.length() > 0)
367 newlist.push_back(x);
368 avitr = nextsplit + 1;
370 if(newlist.empty())
371 aliases.erase(aname);
372 else
373 aliases[aname] = newlist;
376 bool group::valid_alias_name(const std::string& aliasname) throw(std::bad_alloc)
378 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
379 return false;
380 if(aliasname.find_first_of(" \t") < aliasname.length())
381 return false;
382 return true;
385 void group::do_register(const std::string& name, base& cmd) throw(std::bad_alloc)
387 threads::arlock h(get_cmd_lock());
388 auto& state = group_internal_t::get(this);
389 if(state.commands.count(name))
390 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
391 state.commands[name] = &cmd;
394 void group::do_unregister(const std::string& name, base& cmd) throw(std::bad_alloc)
396 threads::arlock h(get_cmd_lock());
397 auto state = group_internal_t::get_soft(this);
398 if(!state) return;
399 if(!state->commands.count(name) || state->commands[name] != &cmd) return;
400 state->commands.erase(name);
403 void group::set_output(std::ostream& s)
405 output = &s;
408 void group::set_oom_panic(void (*fn)())
410 if(fn)
411 oom_panic_routine = fn;
412 else
413 oom_panic_routine = default_oom_panic;
416 void group::add_set(set& s) throw(std::bad_alloc)
418 threads::arlock h(get_cmd_lock());
419 auto& state = group_internal_t::get(this);
420 if(state.set_handles.count(&s)) return;
421 try {
422 state.set_handles.insert(&s);
423 s.add_callback(_listener);
424 } catch(...) {
425 state.set_handles.erase(&s);
429 void group::drop_set(set& s) throw()
431 threads::arlock h(get_cmd_lock());
432 auto state = group_internal_t::get_soft(this);
433 if(!state) return;
434 //Drop the callback. This unregisters all.
435 s.drop_callback(_listener);
436 state->set_handles.erase(&s);
439 group::listener::listener(group& _grp)
440 : grp(_grp)
444 group::listener::~listener()
448 void group::listener::create(set& s, const std::string& name, factory_base& cmd)
450 threads::arlock h(get_cmd_lock());
451 cmd.make(grp);
454 void group::listener::destroy(set& s, const std::string& name)
456 threads::arlock h(get_cmd_lock());
457 auto state = group_internal_t::get_soft(&grp);
458 if(!state) return;
459 state->commands.erase(name);
462 void group::listener::kill(set& s)
464 threads::arlock h(get_cmd_lock());
465 auto state = group_internal_t::get_soft(&grp);
466 if(!state) return;
467 state->set_handles.erase(&s);
470 template<>
471 void invoke_fn(std::function<void(const std::string& args)> fn, const std::string& args)
473 fn(args);
476 template<>
477 void invoke_fn(std::function<void()> fn, const std::string& args)
479 if(args != "")
480 throw std::runtime_error("This command does not take arguments");
481 fn();
484 template<>
485 void invoke_fn(std::function<void(struct arg_filename a)> fn, const std::string& args)
487 if(args == "")
488 throw std::runtime_error("Filename required");
489 arg_filename b;
490 b.v = args;
491 fn(b);