Merge branch 'rr1-maint'
[lsnes.git] / src / library / commands.cpp
blob0ff8a9c33c0a02417bbcfbaadf268f3b2b77bd23
1 #include "commands.hpp"
2 #include "globalwrap.hpp"
3 #include "minmax.hpp"
4 #include "register-queue.hpp"
5 #include "string.hpp"
6 #include "zip.hpp"
7 #include <iostream>
8 #include <cstdlib>
10 namespace
12 struct run_script : public command
14 run_script(command_group& group, std::ostream*& _output)
15 : command(group, "run-script"), in_group(group), output(_output)
19 ~run_script() throw()
23 void invoke(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
25 if(filename == "") {
26 (*output) << "Syntax: run-script <scriptfile>" << std::endl;
27 return;
29 std::istream* o = NULL;
30 try {
31 o = &open_file_relative(filename, "");
32 (*output) << "Running '" << std::string(filename) << "'" << std::endl;
33 std::string line;
34 while(std::getline(*o, line))
35 in_group.invoke(line);
36 delete o;
37 } catch(std::exception& e) {
38 delete o;
39 throw;
43 std::string get_short_help() throw(std::bad_alloc)
45 return "Run file as a script";
48 std::string get_long_help() throw(std::bad_alloc)
50 return "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in "
51 "the command line\n";
54 command_group& in_group;
55 std::ostream*& output;
58 void default_oom_panic()
60 std::cerr << "PANIC: Fatal error, can't continue: Out of memory." << std::endl;
61 exit(1);
64 typedef register_queue<command_group, command> regqueue_t;
67 command::command(command_group& group, const std::string& cmd) throw(std::bad_alloc)
68 : in_group(group)
70 regqueue_t::do_register(in_group, commandname = cmd, *this);
73 command::~command() throw()
75 regqueue_t::do_unregister(in_group, commandname);
79 std::string command::get_short_help() throw(std::bad_alloc)
81 return "No description available";
84 std::string command::get_long_help() throw(std::bad_alloc)
86 return "No help available on command " + commandname;
89 command_group::command_group() throw(std::bad_alloc)
91 oom_panic_routine = default_oom_panic;
92 output = &std::cerr;
93 regqueue_t::do_ready(*this, true);
94 //The builtin commands.
95 builtin[0] = new run_script(*this, output);
98 command_group::~command_group() throw()
100 for(size_t i = 0; i < sizeof(builtin)/sizeof(builtin[0]); i++)
101 delete builtin[i];
102 regqueue_t::do_ready(*this, false);
105 void command_group::invoke(const std::string& cmd) throw()
107 try {
108 std::string cmd2 = strip_CR(cmd);
109 if(cmd2 == "?") {
110 //The special ? command.
111 umutex_class lock(int_mutex);
112 for(auto i : commands)
113 (*output) << i.first << ": " << i.second->get_short_help() << std::endl;
114 return;
116 if(firstchar(cmd2) == '?') {
117 //?command.
118 umutex_class lock(int_mutex);
119 std::string rcmd = cmd2.substr(1, min(cmd2.find_first_of(" \t"), cmd2.length()));
120 if(firstchar(rcmd) != '*') {
121 //This may be an alias.
122 if(aliases.count(rcmd)) {
123 //Yup.
124 (*output) << rcmd << " is an alias for: " << std::endl;
125 size_t j = 0;
126 for(auto i : aliases[rcmd])
127 (*output) << "#" << (++j) << ": " << i << std::endl;
128 return;
130 } else
131 rcmd = rcmd.substr(1);
132 if(!commands.count(rcmd))
133 (*output) << "Unknown command '" << rcmd << "'" << std::endl;
134 else
135 (*output) << commands[rcmd]->get_long_help() << std::endl;
136 return;
138 bool may_be_alias_expanded = true;
139 if(firstchar(cmd2) == '*') {
140 may_be_alias_expanded = false;
141 cmd2 = cmd2.substr(1);
143 //Now this gets painful as command handlers must not be invoked with lock held.
144 if(may_be_alias_expanded) {
145 std::list<std::string> aexp;
147 umutex_class lock(int_mutex);
148 if(!aliases.count(cmd))
149 goto not_alias;
150 aexp = aliases[cmd2];
152 for(auto i : aexp)
153 invoke(i);
154 return;
156 not_alias:
157 try {
158 size_t split = cmd2.find_first_of(" \t");
159 std::string rcmd = cmd2.substr(0, min(split, cmd2.length()));
160 std::string args = cmd2.substr(min(cmd2.find_first_not_of(" \t", split), cmd2.length()));
161 command* cmdh = NULL;
163 umutex_class lock(int_mutex);
164 if(!commands.count(rcmd)) {
165 (*output) << "Unknown command '" << rcmd << "'" << std::endl;
166 return;
168 cmdh = commands[rcmd];
170 if(command_stack.count(cmd2))
171 throw std::runtime_error("Recursive command invocation");
172 command_stack.insert(cmd2);
173 cmdh->invoke(args);
174 command_stack.erase(cmd2);
175 return;
176 } catch(std::bad_alloc& e) {
177 oom_panic_routine();
178 } catch(std::exception& e) {
179 (*output) << "Error: " << e.what() << std::endl;
180 command_stack.erase(cmd2);
181 return;
183 } catch(std::bad_alloc& e) {
184 oom_panic_routine();
188 std::set<std::string> command_group::get_aliases() throw(std::bad_alloc)
190 umutex_class lock(int_mutex);
191 std::set<std::string> r;
192 for(auto i : aliases)
193 r.insert(i.first);
194 return r;
197 std::string command_group::get_alias_for(const std::string& aname) throw(std::bad_alloc)
199 umutex_class lock(int_mutex);
200 if(!valid_alias_name(aname))
201 return "";
202 if(aliases.count(aname)) {
203 std::string x;
204 for(auto i : aliases[aname])
205 x = x + i + "\n";
206 return x;
207 } else
208 return "";
211 void command_group::set_alias_for(const std::string& aname, const std::string& avalue) throw(std::bad_alloc)
213 umutex_class lock(int_mutex);
214 if(!valid_alias_name(aname))
215 return;
216 std::list<std::string> newlist;
217 size_t avitr = 0;
218 while(avitr < avalue.length()) {
219 size_t nextsplit = min(avalue.find_first_of("\n", avitr), avalue.length());
220 std::string x = strip_CR(avalue.substr(avitr, nextsplit - avitr));
221 if(x.length() > 0)
222 newlist.push_back(x);
223 avitr = nextsplit + 1;
225 if(newlist.empty())
226 aliases.erase(aname);
227 else
228 aliases[aname] = newlist;
231 bool command_group::valid_alias_name(const std::string& aliasname) throw(std::bad_alloc)
233 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
234 return false;
235 if(aliasname.find_first_of(" \t") < aliasname.length())
236 return false;
237 return true;
240 void command_group::do_register(const std::string& name, command& cmd) throw(std::bad_alloc)
242 umutex_class lock(int_mutex);
243 if(commands.count(name))
244 std::cerr << "WARNING: Command collision for " << name << "!" << std::endl;
245 commands[name] = &cmd;
248 void command_group::do_unregister(const std::string& name) throw(std::bad_alloc)
250 umutex_class lock(int_mutex);
251 commands.erase(name);
254 void command_group::set_output(std::ostream& s)
256 output = &s;
259 void command_group::set_oom_panic(void (*fn)())
261 if(fn)
262 oom_panic_routine = fn;
263 else
264 oom_panic_routine = default_oom_panic;
267 template<>
268 void invoke_command_fn(void (*fn)(const std::string& args), const std::string& args)
270 fn(args);
273 template<>
274 void invoke_command_fn(void (*fn)(), const std::string& args)
276 if(args != "")
277 throw std::runtime_error("This command does not take arguments");
278 fn();
281 template<>
282 void invoke_command_fn(void (*fn)(struct arg_filename a), const std::string& args)
284 if(args == "")
285 throw std::runtime_error("Filename required");
286 arg_filename b;
287 b.v = args;
288 fn(b);