diff options
Diffstat (limited to 'src/option-parser.cc')
-rw-r--r-- | src/option-parser.cc | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/src/option-parser.cc b/src/option-parser.cc new file mode 100644 index 00000000..98ccb59a --- /dev/null +++ b/src/option-parser.cc @@ -0,0 +1,229 @@ +/* + * Copyright 2016 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "option-parser.h" + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include "config.h" + +#if HAVE_ALLOCA +#include <alloca.h> +#endif + +static int option_match(const char* s, + const char* full, + WabtHasArgument has_argument) { + int i; + for (i = 0; ; i++) { + if (full[i] == '\0') { + /* perfect match. return +1, so it will be preferred over a longer option + * with the same prefix */ + if (s[i] == '\0') + return i + 1; + + /* we want to fail if s is longer than full, e.g. --foobar vs. --foo. + * However, if s ends with an '=', it's OK. */ + if (!(has_argument && s[i] == '=')) + return -1; + break; + } + if (s[i] == '\0') + break; + if (s[i] != full[i]) + return -1; + } + return i; +} + +static void WABT_PRINTF_FORMAT(2, 3) + error(WabtOptionParser* parser, const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + parser->on_error(parser, buffer); +} + +void wabt_parse_options(WabtOptionParser* parser, + int argc, + char** argv) { + parser->argv0 = argv[0]; + + int i; + int j; + int k; + for (i = 1; i < argc; ++i) { + char* arg = argv[i]; + if (arg[0] == '-') { + if (arg[1] == '-') { + /* long option */ + int best_index = -1; + int best_length = 0; + int best_count = 0; + for (j = 0; j < parser->num_options; ++j) { + WabtOption* option = &parser->options[j]; + if (option->long_name) { + int match_length = + option_match(&arg[2], option->long_name, option->has_argument); + if (match_length > best_length) { + best_index = j; + best_length = match_length; + best_count = 1; + } else if (match_length == best_length && best_length > 0) { + best_count++; + } + } + } + + if (best_count > 1) { + error(parser, "ambiguous option \"%s\"", arg); + continue; + } else if (best_count == 0) { + error(parser, "unknown option \"%s\"", arg); + continue; + } + + WabtOption* best_option = &parser->options[best_index]; + const char* option_argument = NULL; + if (best_option->has_argument) { + if (arg[best_length] == '=') { + option_argument = &arg[best_length + 1]; + } else { + if (i + 1 == argc || argv[i + 1][0] == '-') { + error(parser, "option \"--%s\" requires argument", + best_option->long_name); + continue; + } + ++i; + option_argument = argv[i]; + } + } + parser->on_option(parser, best_option, option_argument); + } else { + /* short option */ + if (arg[1] == '\0') { + /* just "-" */ + parser->on_argument(parser, arg); + continue; + } + + /* allow short names to be combined, e.g. "-d -v" => "-dv" */ + for (k = 1; arg[k]; ++k) { + bool matched = false; + for (j = 0; j < parser->num_options; ++j) { + WabtOption* option = &parser->options[j]; + if (option->short_name && arg[k] == option->short_name) { + const char* option_argument = NULL; + if (option->has_argument) { + /* a short option with a required argument cannot be followed + * by other short options */ + if (arg[k + 1] != '\0') { + error(parser, "option \"-%c\" requires argument", + option->short_name); + break; + } + + if (i + 1 == argc || argv[i + 1][0] == '-') { + error(parser, "option \"-%c\" requires argument", + option->short_name); + break; + } + ++i; + option_argument = argv[i]; + } + parser->on_option(parser, option, option_argument); + matched = true; + break; + } + } + + if (!matched) { + error(parser, "unknown option \"-%c\"", arg[k]); + continue; + } + } + } + } else { + /* non-option argument */ + parser->on_argument(parser, arg); + } + } +} + +void wabt_print_help(WabtOptionParser* parser, const char* program_name) { + int i; + /* TODO(binji): do something more generic for filename here */ + printf("usage: %s [options] filename\n\n", program_name); + printf("%s\n", parser->description); + printf("options:\n"); + + const int extra_space = 8; + int longest_name_length = 0; + for (i = 0; i < parser->num_options; ++i) { + WabtOption* option = &parser->options[i]; + int length; + if (option->long_name) { + if (option->metavar) { + length = + wabt_snprintf(NULL, 0, "%s=%s", option->long_name, option->metavar); + } else { + length = wabt_snprintf(NULL, 0, "%s", option->long_name); + } + } else { + continue; + } + + if (length > longest_name_length) + longest_name_length = length; + } + + size_t buffer_size = longest_name_length + 1; + char* buffer = (char*)alloca(buffer_size); + + for (i = 0; i < parser->num_options; ++i) { + WabtOption* option = &parser->options[i]; + if (!option->short_name && !option->long_name) + continue; + + if (option->short_name) + printf(" -%c, ", option->short_name); + else + printf(" "); + + char format[20]; + if (option->long_name) { + wabt_snprintf(format, sizeof(format), "--%%-%ds", + longest_name_length + extra_space); + + if (option->metavar) { + wabt_snprintf(buffer, buffer_size, "%s=%s", option->long_name, + option->metavar); + printf(format, buffer); + } else { + printf(format, option->long_name); + } + } else { + /* +2 for the extra "--" above */ + wabt_snprintf(format, sizeof(format), "%%-%ds", + longest_name_length + extra_space + 2); + printf(format, ""); + } + + if (option->help) + printf("%s", option->help); + printf("\n"); + } +} |