1 /*
2 * Copyright 2016 WebAssembly Community Group participants
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/option-parser.h"
18
19 #include <cstdarg>
20 #include <cstdio>
21 #include <cstring>
22
23 #include "config.h"
24
25 #if HAVE_ALLOCA
26 #include <alloca.h>
27 #endif
28
29 namespace wabt {
30
Option(char short_name,const std::string & long_name,const std::string & metavar,HasArgument has_argument,const std::string & help,const Callback & callback)31 OptionParser::Option::Option(char short_name,
32 const std::string& long_name,
33 const std::string& metavar,
34 HasArgument has_argument,
35 const std::string& help,
36 const Callback& callback)
37 : short_name(short_name),
38 long_name(long_name),
39 metavar(metavar),
40 has_argument(has_argument == HasArgument::Yes),
41 help(help),
42 callback(callback) {}
43
Argument(const std::string & name,ArgumentCount count,const Callback & callback)44 OptionParser::Argument::Argument(const std::string& name,
45 ArgumentCount count,
46 const Callback& callback)
47 : name(name), count(count), callback(callback) {}
48
OptionParser(const char * program_name,const char * description)49 OptionParser::OptionParser(const char* program_name, const char* description)
50 : program_name_(program_name),
51 description_(description),
52 on_error_([this](const std::string& message) { DefaultError(message); }) {
53
54 // Add common options
__anonbdef44720202() 55 AddOption("help", "Print this help message", [this]() {
56 PrintHelp();
57 exit(0);
58 });
__anonbdef44720302() 59 AddOption("version", "Print version information", []() {
60 printf("%s\n", CMAKE_PROJECT_VERSION);
61 exit(0);
62 });
63 }
64
AddOption(const Option & option)65 void OptionParser::AddOption(const Option& option) {
66 options_.emplace_back(option);
67 }
68
AddArgument(const std::string & name,ArgumentCount count,const Callback & callback)69 void OptionParser::AddArgument(const std::string& name,
70 ArgumentCount count,
71 const Callback& callback) {
72 arguments_.emplace_back(name, count, callback);
73 }
74
AddOption(char short_name,const char * long_name,const char * help,const NullCallback & callback)75 void OptionParser::AddOption(char short_name,
76 const char* long_name,
77 const char* help,
78 const NullCallback& callback) {
79 Option option(short_name, long_name, std::string(), HasArgument::No, help,
80 [callback](const char*) { callback(); });
81 AddOption(option);
82 }
83
AddOption(const char * long_name,const char * help,const NullCallback & callback)84 void OptionParser::AddOption(const char* long_name,
85 const char* help,
86 const NullCallback& callback) {
87 Option option('\0', long_name, std::string(), HasArgument::No, help,
88 [callback](const char*) { callback(); });
89 AddOption(option);
90 }
91
AddOption(char short_name,const char * long_name,const char * metavar,const char * help,const Callback & callback)92 void OptionParser::AddOption(char short_name,
93 const char* long_name,
94 const char* metavar,
95 const char* help,
96 const Callback& callback) {
97 Option option(short_name, long_name, metavar, HasArgument::Yes, help,
98 callback);
99 AddOption(option);
100 }
101
SetErrorCallback(const Callback & callback)102 void OptionParser::SetErrorCallback(const Callback& callback) {
103 on_error_ = callback;
104 }
105
106 // static
Match(const char * s,const std::string & full,bool has_argument)107 int OptionParser::Match(const char* s,
108 const std::string& full,
109 bool has_argument) {
110 int i;
111 for (i = 0;; i++) {
112 if (full[i] == '\0') {
113 // Perfect match. Return +1, so it will be preferred over a longer option
114 // with the same prefix.
115 if (s[i] == '\0') {
116 return i + 1;
117 }
118
119 // We want to fail if s is longer than full, e.g. --foobar vs. --foo.
120 // However, if s ends with an '=', it's OK.
121 if (!(has_argument && s[i] == '=')) {
122 return -1;
123 }
124 break;
125 }
126 if (s[i] == '\0') {
127 break;
128 }
129 if (s[i] != full[i]) {
130 return -1;
131 }
132 }
133 return i;
134 }
135
Errorf(const char * format,...)136 void OptionParser::Errorf(const char* format, ...) {
137 WABT_SNPRINTF_ALLOCA(buffer, length, format);
138 std::string msg(program_name_);
139 msg += ": ";
140 msg += buffer;
141 msg += "\nTry '--help' for more information.";
142 on_error_(msg.c_str());
143 }
144
DefaultError(const std::string & message)145 void OptionParser::DefaultError(const std::string& message) {
146 WABT_FATAL("%s\n", message.c_str());
147 }
148
HandleArgument(size_t * arg_index,const char * arg_value)149 void OptionParser::HandleArgument(size_t* arg_index, const char* arg_value) {
150 if (*arg_index >= arguments_.size()) {
151 Errorf("unexpected argument '%s'", arg_value);
152 return;
153 }
154 Argument& argument = arguments_[*arg_index];
155 argument.callback(arg_value);
156 argument.handled_count++;
157
158 if (argument.count == ArgumentCount::One) {
159 (*arg_index)++;
160 }
161 }
162
Parse(int argc,char * argv[])163 void OptionParser::Parse(int argc, char* argv[]) {
164 size_t arg_index = 0;
165 bool processing_options = true;
166
167 for (int i = 1; i < argc; ++i) {
168 const char* arg = argv[i];
169 if (!processing_options || arg[0] != '-') {
170 // Non-option argument.
171 HandleArgument(&arg_index, arg);
172 continue;
173 }
174
175 if (arg[1] == '-') {
176 if (arg[2] == '\0') {
177 // -- on its own means stop processing args, everything should
178 // be treated as positional.
179 processing_options = false;
180 continue;
181 }
182 // Long option.
183 int best_index = -1;
184 int best_length = 0;
185 int best_count = 0;
186 for (size_t j = 0; j < options_.size(); ++j) {
187 const Option& option = options_[j];
188 if (!option.long_name.empty()) {
189 int match_length =
190 Match(&arg[2], option.long_name, option.has_argument);
191 if (match_length > best_length) {
192 best_index = j;
193 best_length = match_length;
194 best_count = 1;
195 } else if (match_length == best_length && best_length > 0) {
196 best_count++;
197 }
198 }
199 }
200
201 if (best_count > 1) {
202 Errorf("ambiguous option '%s'", arg);
203 continue;
204 } else if (best_count == 0) {
205 Errorf("unknown option '%s'", arg);
206 continue;
207 }
208
209 const Option& best_option = options_[best_index];
210 const char* option_argument = nullptr;
211 if (best_option.has_argument) {
212 if (arg[best_length + 1] != 0 && // This byte is 0 on a full match.
213 arg[best_length + 2] == '=') { // +2 to skip "--".
214 option_argument = &arg[best_length + 3];
215 } else {
216 if (i + 1 == argc || argv[i + 1][0] == '-') {
217 Errorf("option '--%s' requires argument",
218 best_option.long_name.c_str());
219 continue;
220 }
221 ++i;
222 option_argument = argv[i];
223 }
224 }
225 best_option.callback(option_argument);
226 } else {
227 // Short option.
228 if (arg[1] == '\0') {
229 // Just "-".
230 HandleArgument(&arg_index, arg);
231 continue;
232 }
233
234 // Allow short names to be combined, e.g. "-d -v" => "-dv".
235 for (int k = 1; arg[k]; ++k) {
236 bool matched = false;
237 for (const Option& option : options_) {
238 if (option.short_name && arg[k] == option.short_name) {
239 const char* option_argument = nullptr;
240 if (option.has_argument) {
241 // A short option with a required argument cannot be followed
242 // by other short options_.
243 if (arg[k + 1] != '\0') {
244 Errorf("option '-%c' requires argument", option.short_name);
245 break;
246 }
247
248 if (i + 1 == argc || argv[i + 1][0] == '-') {
249 Errorf("option '-%c' requires argument", option.short_name);
250 break;
251 }
252 ++i;
253 option_argument = argv[i];
254 }
255 option.callback(option_argument);
256 matched = true;
257 break;
258 }
259 }
260
261 if (!matched) {
262 Errorf("unknown option '-%c'", arg[k]);
263 continue;
264 }
265 }
266 }
267 }
268
269 // For now, all arguments must be provided. Check that the last Argument was
270 // handled at least once.
271 if (!arguments_.empty() && arguments_.back().handled_count == 0) {
272 for (size_t i = arg_index; i < arguments_.size(); ++i) {
273 if (arguments_[i].count != ArgumentCount::ZeroOrMore) {
274 Errorf("expected %s argument.", arguments_[i].name.c_str());
275 }
276 }
277 }
278 }
279
PrintHelp()280 void OptionParser::PrintHelp() {
281 printf("usage: %s [options]", program_name_.c_str());
282
283 for (size_t i = 0; i < arguments_.size(); ++i) {
284 Argument& argument = arguments_[i];
285 switch (argument.count) {
286 case ArgumentCount::One:
287 printf(" %s", argument.name.c_str());
288 break;
289
290 case ArgumentCount::OneOrMore:
291 printf(" %s+", argument.name.c_str());
292 break;
293
294 case ArgumentCount::ZeroOrMore:
295 printf(" [%s]...", argument.name.c_str());
296 break;
297 }
298 }
299
300 printf("\n\n");
301 printf("%s\n", description_.c_str());
302 printf("options:\n");
303
304 const size_t kExtraSpace = 8;
305 size_t longest_name_length = 0;
306 for (const Option& option : options_) {
307 size_t length;
308 if (!option.long_name.empty()) {
309 length = option.long_name.size();
310 if (!option.metavar.empty()) {
311 // +1 for '='.
312 length += option.metavar.size() + 1;
313 }
314 } else {
315 continue;
316 }
317
318 if (length > longest_name_length) {
319 longest_name_length = length;
320 }
321 }
322
323 for (const Option& option : options_) {
324 if (!option.short_name && option.long_name.empty()) {
325 continue;
326 }
327
328 std::string line;
329 if (option.short_name) {
330 line += std::string(" -") + option.short_name + ", ";
331 } else {
332 line += " ";
333 }
334
335 std::string flag;
336 if (!option.long_name.empty()) {
337 flag = "--";
338 if (!option.metavar.empty()) {
339 flag += option.long_name + '=' + option.metavar;
340 } else {
341 flag += option.long_name;
342 }
343 }
344
345 // +2 for "--" of the long flag name.
346 size_t remaining = longest_name_length + kExtraSpace + 2 - flag.size();
347 line += flag + std::string(remaining, ' ');
348
349 if (!option.help.empty()) {
350 line += option.help;
351 }
352 printf("%s\n", line.c_str());
353 }
354 }
355
356 } // namespace wabt
357