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