Fix crash on multiline aliases
[lsnes.git] / src / core / command.cpp
blob77f159837cda4cabdc01a56bfadd04e19322bf8a
1 #include "core/command.hpp"
2 #include "core/globalwrap.hpp"
3 #include "core/misc.hpp"
4 #include "core/zip.hpp"
6 #include <set>
7 #include <map>
9 namespace
11 globalwrap<std::map<std::string, command*>> commands;
12 std::set<std::string> command_stack;
13 std::map<std::string, std::list<std::string>> aliases;
15 function_ptr_command<arg_filename> run_script("run-script", "run file as a script",
16 "Syntax: run-script <file>\nRuns file <file> just as it would have been entered in the command line\n",
17 [](arg_filename filename) throw(std::bad_alloc, std::runtime_error) {
18 std::istream* o = NULL;
19 try {
20 o = &open_file_relative(filename, "");
21 messages << "Running '" << std::string(filename) << "'" << std::endl;
22 std::string line;
23 while(std::getline(*o, line))
24 command::invokeC(line);
25 delete o;
26 } catch(std::exception& e) {
27 if(o)
28 delete o;
29 throw;
31 });
33 function_ptr_command<> show_aliases("show-aliases", "show aliases",
34 "Syntax: show-aliases\nShow expansions of all aliases\n",
35 []() throw(std::bad_alloc, std::runtime_error) {
36 for(auto i : aliases)
37 for(auto j = i.second.begin(); j != i.second.end(); j++)
38 messages << "alias " << i.first << " " << *j << std::endl;
39 });
41 function_ptr_command<tokensplitter&> unalias_command("unalias-command", "unalias a command",
42 "Syntax: unalias-command <aliasname>\nClear expansion of alias <aliasname>\n",
43 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
44 std::string aliasname = t;
45 if(t)
46 throw std::runtime_error("This command only takes one argument");
47 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
48 throw std::runtime_error("Illegal alias name");
49 aliases[aliasname].clear();
50 messages << "Command '" << aliasname << "' unaliased" << std::endl;
51 });
53 function_ptr_command<tokensplitter&> alias_command("alias-command", "alias a command",
54 "Syntax: alias-command <aliasname> <command>\nAppend <command> to expansion of alias <aliasname>\n"
55 "Valid alias names can't be empty nor start with '*' or '?'\n",
56 [](tokensplitter& t) throw(std::bad_alloc, std::runtime_error) {
57 std::string aliasname = t;
58 std::string command = t.tail();
59 if(command == "")
60 throw std::runtime_error("Alias name and command needed");
61 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
62 throw std::runtime_error("Illegal alias name");
63 aliases[aliasname].push_back(command);
64 messages << "Command '" << aliasname << "' aliased to '" << command << "'" << std::endl;
65 });
68 command::command(const std::string& cmd) throw(std::bad_alloc)
70 if(commands().count(cmd))
71 std::cerr << "WARNING: Command collision for " << cmd << "!" << std::endl;
72 commands()[commandname = cmd] = this;
75 command::~command() throw()
77 commands().erase(commandname);
80 void command::invokeC(const std::string& cmd) throw()
82 try {
83 std::string cmd2 = cmd;
84 if(cmd2[cmd2.length() - 1] == '\r')
85 cmd2 = cmd2.substr(0, cmd2.length() - 1);
86 if(cmd2 == "?") {
87 //The special ? command.
88 for(auto i : commands())
89 messages << i.first << ": " << i.second->get_short_help() << std::endl;
90 return;
92 if(cmd2.length() > 1 && cmd2[0] == '?') {
93 //?command.
94 size_t split = cmd2.find_first_of(" \t");
95 std::string rcmd;
96 if(split >= cmd2.length())
97 rcmd = cmd2.substr(1);
98 else
99 rcmd = cmd2.substr(1, split - 1);
100 if(rcmd.length() > 0 && rcmd[0] != '*') {
101 //This may be an alias.
102 std::string aname = cmd2.substr(1);
103 if(aliases.count(aname)) {
104 //Yup.
105 messages << aname << " is an alias for: " << std::endl;
106 size_t j = 0;
107 for(auto i : aliases[aname])
108 messages << "#" + (++j) << ": " << i << std::endl;
109 return;
112 if(rcmd.length() > 0 && rcmd[0] == '*')
113 rcmd = rcmd.substr(1);
114 if(!commands().count(rcmd)) {
115 if(rcmd != "")
116 messages << "Unknown command '" << rcmd << "'" << std::endl;
117 return;
119 messages << commands()[rcmd]->get_long_help() << std::endl;
120 return;
122 bool may_be_alias_expanded = true;
123 if(cmd2.length() > 0 && cmd2[0] == '*') {
124 may_be_alias_expanded = false;
125 cmd2 = cmd2.substr(1);
127 if(may_be_alias_expanded && aliases.count(cmd2)) {
128 for(auto i : aliases[cmd2])
129 invokeC(i);
130 return;
132 try {
133 size_t split = cmd2.find_first_of(" \t");
134 std::string rcmd;
135 if(split >= cmd2.length())
136 rcmd = cmd2;
137 else
138 rcmd = cmd2.substr(0, split);
139 split = cmd2.find_first_not_of(" \t", split);
140 std::string args;
141 if(split < cmd2.length())
142 args = cmd2.substr(split);
143 command* cmdh = NULL;
144 if(commands().count(rcmd))
145 cmdh = commands()[rcmd];
146 if(!cmdh) {
147 messages << "Unknown command '" << rcmd << "'" << std::endl;
148 return;
150 if(command_stack.count(cmd2))
151 throw std::runtime_error("Recursive command invocation");
152 command_stack.insert(cmd2);
153 cmdh->invoke(args);
154 command_stack.erase(cmd2);
155 return;
156 } catch(std::bad_alloc& e) {
157 OOM_panic();
158 } catch(std::exception& e) {
159 messages << "Error: " << e.what() << std::endl;
160 command_stack.erase(cmd2);
161 return;
163 } catch(std::bad_alloc& e) {
164 OOM_panic();
168 std::string command::get_short_help() throw(std::bad_alloc)
170 return "No description available";
173 std::string command::get_long_help() throw(std::bad_alloc)
175 return "No help available on command " + commandname;
178 std::set<std::string> command::get_aliases() throw(std::bad_alloc)
180 std::set<std::string> r;
181 for(auto i : aliases)
182 r.insert(i.first);
183 return r;
186 std::string command::get_alias_for(const std::string& aname) throw(std::bad_alloc)
188 if(!valid_alias_name(aname))
189 return "";
190 if(aliases.count(aname)) {
191 std::string x;
192 for(auto i : aliases[aname])
193 x = x + i + "\n";
194 return x;
195 } else
196 return "";
199 void command::set_alias_for(const std::string& aname, const std::string& avalue) throw(std::bad_alloc)
201 if(!valid_alias_name(aname))
202 return;
203 std::list<std::string> newlist;
204 size_t avitr = 0;
205 while(avitr < avalue.length()) {
206 size_t nextsplit = avalue.find_first_of("\n", avitr);
207 if(nextsplit >= avalue.length())
208 nextsplit = avalue.length();
209 std::string x = avalue.substr(avitr, nextsplit - avitr);
210 if(x.length() > 0 && x[x.length() - 1] == '\r')
211 x = x.substr(0, x.length() - 1);
212 if(x.length() > 0)
213 newlist.push_back(x);
214 avitr = nextsplit + 1;
216 if(newlist.empty())
217 aliases.erase(aname);
218 else
219 aliases[aname] = newlist;
222 bool command::valid_alias_name(const std::string& aliasname) throw(std::bad_alloc)
224 if(aliasname.length() == 0 || aliasname[0] == '?' || aliasname[0] == '*')
225 return false;
226 if(aliasname.find_first_of(" \t") < aliasname.length())
227 return false;
228 return true;
232 tokensplitter::tokensplitter(const std::string& _line) throw(std::bad_alloc)
234 line = _line;
235 position = 0;
238 tokensplitter::operator bool() throw()
240 return (position < line.length());
243 tokensplitter::operator std::string() throw(std::bad_alloc)
245 size_t nextp, oldp = position;
246 nextp = line.find_first_of(" \t", position);
247 if(nextp > line.length()) {
248 position = line.length();
249 return line.substr(oldp);
250 } else {
251 position = nextp;
252 while(position < line.length() && (line[position] == ' ' || line[position] == '\t'))
253 position++;
254 return line.substr(oldp, nextp - oldp);
258 std::string tokensplitter::tail() throw(std::bad_alloc)
260 return line.substr(position);
263 template<>
264 void invoke_command_fn(void (*fn)(const std::string& args), const std::string& args)
266 fn(args);
269 template<>
270 void invoke_command_fn(void (*fn)(), const std::string& args)
272 if(args != "")
273 throw std::runtime_error("This command does not take arguments");
274 fn();
277 template<>
278 void invoke_command_fn(void (*fn)(struct arg_filename a), const std::string& args)
280 if(args == "")
281 throw std::runtime_error("Filename required");
282 arg_filename b;
283 b.v = args;
284 fn(b);
287 template<>
288 void invoke_command_fn(void (*fn)(tokensplitter& a), const std::string& args)
290 tokensplitter t(args);
291 fn(t);