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