blob: 1ab01327935f5a1f72f4f2e83da0f0f5b86021a7 [file] [log] [blame]
/*
* 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
namespace wabt {
static int option_match(const char* s,
const char* full,
HasArgument 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 == HasArgument::Yes && 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(OptionParser* parser, const char* format, ...) {
WABT_SNPRINTF_ALLOCA(buffer, length, format);
parser->on_error(parser, buffer);
}
void parse_options(OptionParser* parser, int argc, char** argv) {
parser->argv0 = argv[0];
for (int 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 (int j = 0; j < parser->num_options; ++j) {
Option* 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;
}
Option* best_option = &parser->options[best_index];
const char* option_argument = nullptr;
if (best_option->has_argument == HasArgument::Yes) {
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 (int k = 1; arg[k]; ++k) {
bool matched = false;
for (int j = 0; j < parser->num_options; ++j) {
Option* option = &parser->options[j];
if (option->short_name && arg[k] == option->short_name) {
const char* option_argument = nullptr;
if (option->has_argument == HasArgument::Yes) {
/* 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 print_help(OptionParser* parser, const char* program_name) {
/* 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 (int i = 0; i < parser->num_options; ++i) {
Option* option = &parser->options[i];
int length;
if (option->long_name) {
if (option->metavar) {
length =
snprintf(nullptr, 0, "%s=%s", option->long_name, option->metavar);
} else {
length = snprintf(nullptr, 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 = static_cast<char*>(alloca(buffer_size));
for (int i = 0; i < parser->num_options; ++i) {
Option* 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) {
snprintf(format, sizeof(format), "--%%-%ds",
longest_name_length + extra_space);
if (option->metavar) {
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 */
snprintf(format, sizeof(format), "%%-%ds",
longest_name_length + extra_space + 2);
printf(format, "");
}
if (option->help)
printf("%s", option->help);
printf("\n");
}
}
} // namespace wabt