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
AddOption(const Option & option)55 void OptionParser::AddOption(const Option& option) {
56 options_.emplace_back(option);
57 }
58
AddArgument(const std::string & name,ArgumentCount count,const Callback & callback)59 void OptionParser::AddArgument(const std::string& name,
60 ArgumentCount count,
61 const Callback& callback) {
62 arguments_.emplace_back(name, count, callback);
63 }
64
AddOption(char short_name,const char * long_name,const char * help,const NullCallback & callback)65 void OptionParser::AddOption(char short_name,
66 const char* long_name,
67 const char* help,
68 const NullCallback& callback) {
69 Option option(short_name, long_name, std::string(), HasArgument::No, help,
70 [callback](const char*) { callback(); });
71 AddOption(option);
72 }
73
AddOption(const char * long_name,const char * help,const NullCallback & callback)74 void OptionParser::AddOption(const char* long_name,
75 const char* help,
76 const NullCallback& callback) {
77 Option option('\0', long_name, std::string(), HasArgument::No, help,
78 [callback](const char*) { callback(); });
79 AddOption(option);
80 }
81
AddOption(char short_name,const char * long_name,const char * metavar,const char * help,const Callback & callback)82 void OptionParser::AddOption(char short_name,
83 const char* long_name,
84 const char* metavar,
85 const char* help,
86 const Callback& callback) {
87 Option option(short_name, long_name, metavar, HasArgument::Yes, help,
88 callback);
89 AddOption(option);
90 }
91
AddHelpOption()92 void OptionParser::AddHelpOption() {
93 AddOption("help", "Print this help message", [this]() {
94 PrintHelp();
95 exit(0);
96 });
97 }
98
SetErrorCallback(const Callback & callback)99 void OptionParser::SetErrorCallback(const Callback& callback) {
100 on_error_ = callback;
101 }
102
103 // static
Match(const char * s,const std::string & full,bool has_argument)104 int OptionParser::Match(const char* s,
105 const std::string& full,
106 bool has_argument) {
107 int i;
108 for (i = 0;; i++) {
109 if (full[i] == '\0') {
110 // Perfect match. Return +1, so it will be preferred over a longer option
111 // with the same prefix.
112 if (s[i] == '\0') {
113 return i + 1;
114 }
115
116 // We want to fail if s is longer than full, e.g. --foobar vs. --foo.
117 // However, if s ends with an '=', it's OK.
118 if (!(has_argument && s[i] == '=')) {
119 return -1;
120 }
121 break;
122 }
123 if (s[i] == '\0') {
124 break;
125 }
126 if (s[i] != full[i]) {
127 return -1;
128 }
129 }
130 return i;
131 }
132
Errorf(const char * format,...)133 void OptionParser::Errorf(const char* format, ...) {
134 WABT_SNPRINTF_ALLOCA(buffer, length, format);
135 on_error_(buffer);
136 }
137
DefaultError(const std::string & message)138 void OptionParser::DefaultError(const std::string& message) {
139 WABT_FATAL("%s\n", message.c_str());
140 }
141
HandleArgument(size_t * arg_index,const char * arg_value)142 void OptionParser::HandleArgument(size_t* arg_index, const char* arg_value) {
143 if (*arg_index >= arguments_.size()) {
144 Errorf("unexpected argument '%s'", arg_value);
145 return;
146 }
147 Argument& argument = arguments_[*arg_index];
148 argument.callback(arg_value);
149 argument.handled_count++;
150
151 if (argument.count == ArgumentCount::One) {
152 (*arg_index)++;
153 }
154 }
155
Parse(int argc,char * argv[])156 void OptionParser::Parse(int argc, char* argv[]) {
157 size_t arg_index = 0;
158
159 for (int i = 1; i < argc; ++i) {
160 const char* arg = argv[i];
161 if (arg[0] == '-') {
162 if (arg[1] == '-') {
163 // Long option.
164 int best_index = -1;
165 int best_length = 0;
166 int best_count = 0;
167 for (size_t j = 0; j < options_.size(); ++j) {
168 const Option& option = options_[j];
169 if (!option.long_name.empty()) {
170 int match_length =
171 Match(&arg[2], option.long_name, option.has_argument);
172 if (match_length > best_length) {
173 best_index = j;
174 best_length = match_length;
175 best_count = 1;
176 } else if (match_length == best_length && best_length > 0) {
177 best_count++;
178 }
179 }
180 }
181
182 if (best_count > 1) {
183 Errorf("ambiguous option '%s'", arg);
184 continue;
185 } else if (best_count == 0) {
186 Errorf("unknown option '%s'", arg);
187 continue;
188 }
189
190 const Option& best_option = options_[best_index];
191 const char* option_argument = nullptr;
192 if (best_option.has_argument) {
193 if (arg[best_length + 1] != 0 && // This byte is 0 on a full match.
194 arg[best_length + 2] == '=') { // +2 to skip "--".
195 option_argument = &arg[best_length + 3];
196 } else {
197 if (i + 1 == argc || argv[i + 1][0] == '-') {
198 Errorf("option '--%s' requires argument",
199 best_option.long_name.c_str());
200 continue;
201 }
202 ++i;
203 option_argument = argv[i];
204 }
205 }
206 best_option.callback(option_argument);
207 } else {
208 // Short option.
209 if (arg[1] == '\0') {
210 // Just "-".
211 HandleArgument(&arg_index, arg);
212 continue;
213 }
214
215 // Allow short names to be combined, e.g. "-d -v" => "-dv".
216 for (int k = 1; arg[k]; ++k) {
217 bool matched = false;
218 for (const Option& option : options_) {
219 if (option.short_name && arg[k] == option.short_name) {
220 const char* option_argument = nullptr;
221 if (option.has_argument) {
222 // A short option with a required argument cannot be followed
223 // by other short options_.
224 if (arg[k + 1] != '\0') {
225 Errorf("option '-%c' requires argument", option.short_name);
226 break;
227 }
228
229 if (i + 1 == argc || argv[i + 1][0] == '-') {
230 Errorf("option '-%c' requires argument", option.short_name);
231 break;
232 }
233 ++i;
234 option_argument = argv[i];
235 }
236 option.callback(option_argument);
237 matched = true;
238 break;
239 }
240 }
241
242 if (!matched) {
243 Errorf("unknown option '-%c'", arg[k]);
244 continue;
245 }
246 }
247 }
248 } else {
249 // Non-option argument.
250 HandleArgument(&arg_index, arg);
251 }
252 }
253
254 // For now, all arguments must be provided. Check that the last Argument was
255 // handled at least once.
256 if (!arguments_.empty() && arguments_.back().handled_count == 0) {
257 PrintHelp();
258 for (size_t i = arg_index; i < arguments_.size(); ++i) {
259 Errorf("expected %s argument.\n", arguments_[i].name.c_str());
260 }
261 }
262 }
263
PrintHelp()264 void OptionParser::PrintHelp() {
265 printf("usage: %s [options]", program_name_.c_str());
266
267 for (size_t i = 0; i < arguments_.size(); ++i) {
268 Argument& argument = arguments_[i];
269 switch (argument.count) {
270 case ArgumentCount::One:
271 printf(" %s", argument.name.c_str());
272 break;
273
274 case ArgumentCount::OneOrMore:
275 printf(" %s+", argument.name.c_str());
276 break;
277 }
278 }
279
280 printf("\n\n");
281 printf("%s\n", description_.c_str());
282 printf("options:\n");
283
284 const size_t kExtraSpace = 8;
285 size_t longest_name_length = 0;
286 for (const Option& option : options_) {
287 size_t length;
288 if (!option.long_name.empty()) {
289 length = option.long_name.size();
290 if (!option.metavar.empty()) {
291 // +1 for '='.
292 length += option.metavar.size() + 1;
293 }
294 } else {
295 continue;
296 }
297
298 if (length > longest_name_length) {
299 longest_name_length = length;
300 }
301 }
302
303 for (const Option& option : options_) {
304 if (!option.short_name && option.long_name.empty()) {
305 continue;
306 }
307
308 std::string line;
309 if (option.short_name) {
310 line += std::string(" -") + option.short_name + ", ";
311 } else {
312 line += " ";
313 }
314
315 std::string flag;
316 if (!option.long_name.empty()) {
317 flag = "--";
318 if (!option.metavar.empty()) {
319 flag += option.long_name + '=' + option.metavar;
320 } else {
321 flag += option.long_name;
322 }
323 }
324
325 // +2 for "--" of the long flag name.
326 size_t remaining = longest_name_length + kExtraSpace + 2 - flag.size();
327 line += flag + std::string(remaining, ' ');
328
329 if (!option.help.empty()) {
330 line += option.help;
331 }
332 printf("%s\n", line.c_str());
333 }
334 }
335
336 } // namespace wabt
337