Browse Source

Remove getopt_long() and replace it with an adapted optparse

The code I replaced getopt_long() with comes from
https://github.com/skeeto/optparse, and it is in the public domain.

I replaced getopt_long() for several reasons:

1. getopt_long() is not guaranteed to exist.
2. getopt_long() has different behavior on different platforms.
3. glibc's getopt_long() is broken.
4. It allows me to standardize the error messages.
rand
Gavin Howard 2 years ago
parent
commit
586ca590ba
Signed by untrusted user who does not match committer: gavin GPG Key ID: C08038BDF280D33E
  1. 3
      Makefile.in
  2. 69
      configure.sh
  3. 5
      include/args.h
  4. 79
      include/opt.h
  5. 2
      include/status.h
  6. 3
      include/vm.h
  7. 96
      src/args.c
  8. 6
      src/data.c
  9. 261
      src/opt.c
  10. 35
      tests/all.sh

3
Makefile.in

@ -125,7 +125,6 @@ BC_ENABLE_EXTRA_MATH_NAME = BC_ENABLE_EXTRA_MATH
BC_ENABLE_EXTRA_MATH = %%EXTRA_MATH%%
BC_ENABLE_NLS = %%NLS%%
BC_ENABLE_PROMPT = %%PROMPT%%
BC_ENABLE_LONG_OPTIONS = %%LONG_OPTIONS%%
BC_LONG_BIT = %%LONG_BIT%%
RM = rm
@ -151,7 +150,7 @@ CPPFLAGS5 = $(CPPFLAGS4) -DBC_NUM_KARATSUBA_LEN=$(BC_NUM_KARATSUBA_LEN)
CPPFLAGS6 = $(CPPFLAGS5) -DBC_ENABLE_NLS=$(BC_ENABLE_NLS) -DBC_ENABLE_PROMPT=$(BC_ENABLE_PROMPT)
CPPFLAGS7 = $(CPPFLAGS6) -D$(BC_ENABLE_EXTRA_MATH_NAME)=$(BC_ENABLE_EXTRA_MATH)
CPPFLAGS = $(CPPFLAGS7) -DBC_ENABLE_SIGNALS=$(BC_ENABLE_SIGNALS) -DBC_ENABLE_HISTORY=$(BC_ENABLE_HISTORY)
CFLAGS = $(CPPFLAGS) -DBC_ENABLE_LONG_OPTIONS=$(BC_ENABLE_LONG_OPTIONS) %%CPPFLAGS%% %%CFLAGS%%
CFLAGS = $(CPPFLAGS) %%CPPFLAGS%% %%CFLAGS%%
LDFLAGS = %%LDFLAGS%%
HOSTCFLAGS = %%HOSTCFLAGS%%

69
configure.sh

@ -47,15 +47,14 @@ usage() {
printf 'usage: %s -h\n' "$script"
printf ' %s --help\n' "$script"
printf ' %s [-bD|-dB|-c] [-EfgGHLMNPST] [-O OPT_LEVEL] [-k KARATSUBA_LEN]\n' "$script"
printf ' %s [-bD|-dB|-c] [-EfgGHMNPST] [-O OPT_LEVEL] [-k KARATSUBA_LEN]\n' "$script"
printf ' %s \\\n' "$script"
printf ' [--bc-only --disable-dc|--dc-only --disable-bc|--coverage] \\\n'
printf ' [--debug --disable-extra-math --disable-generated-tests] \\\n'
printf ' [--disable-history --disable-long-options --disable-man-pages] \\\n'
printf ' [--disable-nls --disable-prompt --disable-signal-handling] \\\n'
printf ' [--disable-strip] [--opt=OPT_LEVEL] \\\n'
printf ' [--karatsuba-len=KARATSUBA_LEN] [--prefix=PREFIX] \\\n'
printf ' [--bindir=BINDIR] [--datarootdir=DATAROOTDIR] \\\n'
printf ' [--disable-history --disable-man-pages --disable-nls] \\\n'
printf ' [--disable-prompt --disable-signal-handling --disable-strip] \\\n'
printf ' [--opt=OPT_LEVEL] [--karatsuba-len=KARATSUBA_LEN] \\\n'
printf ' [--prefix=PREFIX] [--bindir=BINDIR] [--datarootdir=DATAROOTDIR] \\\n'
printf ' [--datadir=DATADIR] [--mandir=MANDIR] [--man1dir=MAN1DIR] \\\n'
printf ' [--force] \\\n'
printf '\n'
@ -99,10 +98,6 @@ usage() {
printf ' -k KARATSUBA_LEN, --karatsuba-len KARATSUBA_LEN\n'
printf ' Set the karatsuba length to KARATSUBA_LEN (default is 64).\n'
printf ' It is an error if KARATSUBA_LEN is not a number or is less than 16.\n'
printf ' -L, --disable-long-options\n'
printf ' Disable use of getopt_long() and use getopt() instead. This is for\n'
printf ' platforms that do not have getopt_long() since it is not POSIX\n'
printf ' standard. This means that long options will be disabled.\n'
printf ' -M, --disable-man-pages\n'
printf ' Disable installing manpages.\n'
printf ' -N, --disable-nls\n'
@ -327,9 +322,8 @@ nls=1
prompt=1
force=0
strip_bin=1
loptions=1
while getopts "bBcdDEfgGhHk:LMNO:PST-" opt; do
while getopts "bBcdDEfgGhHk:MNO:PST-" opt; do
case "$opt" in
b) bc_only=1 ;;
@ -344,7 +338,6 @@ while getopts "bBcdDEfgGhHk:LMNO:PST-" opt; do
h) usage ;;
H) hist=0 ;;
k) karatsuba_len="$OPTARG" ;;
L) loptions=0 ;;
M) install_manpages=0 ;;
N) nls=0 ;;
O) optimization="$OPTARG" ;;
@ -430,7 +423,6 @@ while getopts "bBcdDEfgGhHk:LMNO:PST-" opt; do
disable-extra-math) extra_math=0 ;;
disable-generated-tests) generate_tests=0 ;;
disable-history) hist=0 ;;
disable-long-options) loptions=0 ;;
disable-man-pages) install_manpages=0 ;;
disable-nls) nls=0 ;;
disable-prompt) prompt=0 ;;
@ -531,57 +523,22 @@ elif [ -z "${HOSTCFLAGS+set}" ]; then
HOSTCFLAGS="$HOST_CFLAGS"
fi
if [ "$loptions" -eq 1 ]; then
set +e
printf 'Testing long options...\n'
flags="-DBC_ENABLE_LONG_OPTIONS=1 -DBC_ENABLED=1 -DDC_ENABLED=1 -DBC_ENABLE_SIGNALS=$signals"
flags="$flags -DBC_ENABLE_NLS=0 -DBC_ENABLE_HISTORY=0"
flags="$flags -DBC_ENABLE_EXTRA_MATH=$extra_math -I./include/"
flags="$flags -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600"
"$HOSTCC" $HOSTCFLAGS $flags -c "src/args.c" -o "$scriptdir/args.o" > /dev/null 2>&1
err="$?"
rm -rf "$scriptdir/args.o"
# If this errors, it's probably because the platform does not have
# getopt_long(), so disable it.
if [ "$err" -ne 0 ]; then
printf 'Long options do not work.\n'
if [ $force -eq 0 ]; then
printf 'Disabling long options...\n\n'
loptions=0
else
printf 'Forcing long options...\n\n'
fi
else
printf 'Long options work.\n\n'
fi
set -e
fi
link="@printf 'No link necessary\\\\n'"
main_exec="BC"
executable="BC_EXEC"
bc_test="@tests/all.sh bc $extra_math 1 $loptions $generate_tests 0 \$(BC_EXEC)"
bc_time_test="@tests/all.sh bc $extra_math 1 $loptions $generate_tests 1 \$(BC_EXEC)"
dc_test="@tests/all.sh dc $extra_math 1 $loptions $generate_tests 0 \$(DC_EXEC)"
dc_time_test="@tests/all.sh dc $extra_math 1 $loptions $generate_tests 1 \$(DC_EXEC)"
bc_test="@tests/all.sh bc $extra_math 1 $generate_tests 0 \$(BC_EXEC)"
bc_time_test="@tests/all.sh bc $extra_math 1 $generate_tests 1 \$(BC_EXEC)"
dc_test="@tests/all.sh dc $extra_math 1 $generate_tests 0 \$(DC_EXEC)"
dc_time_test="@tests/all.sh dc $extra_math 1 $generate_tests 1 \$(DC_EXEC)"
timeconst="@tests/bc/timeconst.sh tests/bc/scripts/timeconst.bc \$(BC_EXEC)"
# In order to have cleanup at exit, we need to be in
# debug mode, so don't run valgrind without that.
if [ "$debug" -ne 0 ]; then
vg_bc_test="@tests/all.sh bc $extra_math 1 $loptions $generate_tests 0 valgrind \$(VALGRIND_ARGS) \$(BC_EXEC)"
vg_dc_test="@tests/all.sh dc $extra_math 1 $loptions $generate_tests 0 valgrind \$(VALGRIND_ARGS) \$(DC_EXEC)"
vg_bc_test="@tests/all.sh bc $extra_math 1 $generate_tests 0 valgrind \$(VALGRIND_ARGS) \$(BC_EXEC)"
vg_dc_test="@tests/all.sh dc $extra_math 1 $generate_tests 0 valgrind \$(VALGRIND_ARGS) \$(DC_EXEC)"
else
vg_bc_test="@printf 'Cannot run valgrind without debug flags\\\\n'"
vg_dc_test="@printf 'Cannot run valgrind without debug flags\\\\n'"
@ -874,7 +831,6 @@ printf 'BC_ENABLE_HISTORY=%s\n' "$hist"
printf 'BC_ENABLE_EXTRA_MATH=%s\n' "$extra_math"
printf 'BC_ENABLE_NLS=%s\n' "$nls"
printf 'BC_ENABLE_PROMPT=%s\n' "$prompt"
printf 'BC_ENABLE_LONG_OPTIONS=%s\n' "$loptions"
printf '\n'
printf 'BC_NUM_KARATSUBA_LEN=%s\n' "$karatsuba_len"
printf '\n'
@ -920,7 +876,6 @@ contents=$(replace "$contents" "HISTORY" "$hist")
contents=$(replace "$contents" "EXTRA_MATH" "$extra_math")
contents=$(replace "$contents" "NLS" "$nls")
contents=$(replace "$contents" "PROMPT" "$prompt")
contents=$(replace "$contents" "LONG_OPTIONS" "$loptions")
contents=$(replace "$contents" "BC_LIB_O" "$bc_lib")
contents=$(replace "$contents" "BC_HELP_O" "$bc_help")
contents=$(replace "$contents" "DC_HELP_O" "$dc_help")

5
include/args.h

@ -41,11 +41,6 @@
BcStatus bc_args(int argc, char *argv[]);
#if !BC_ENABLE_LONG_OPTIONS
#define getopt_long(argc, argv, opts, longopts, longidx) \
getopt(argc, argv, opts)
#endif // BC_ENABLE_LONG_OPTIONS
extern const char* const bc_args_env_name;
#endif // BC_ARGS_H

79
include/opt.h

@ -0,0 +1,79 @@
/*
* *****************************************************************************
*
* Copyright (c) 2018-2020 Gavin D. Howard and contributors.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* *****************************************************************************
*
* Adapted from https://github.com/skeeto/optparse
*
* *****************************************************************************
*
* Definitions for getopt_long() replacement.
*
*/
#ifndef BC_OPT_H
#define BC_OPT_H
#include <stdbool.h>
typedef struct BcOpt {
char **argv;
size_t optind;
int optopt;
int subopt;
char *optarg;
} BcOpt;
typedef enum BcOptType {
BC_OPT_NONE,
BC_OPT_REQUIRED,
BC_OPT_BC_ONLY,
BC_OPT_DC_ONLY,
} BcOptType;
typedef struct BcOptLong {
char *name;
BcOptType type;
int val;
} BcOptLong;
void bc_opt_init(BcOpt *o, char **argv);
int bc_opt_parse(BcOpt *o, const BcOptLong *longopts);
#define BC_OPT_ISDASHDASH(a) \
((a) != NULL && (a)[0] == '-' && (a)[1] == '-' && (a)[2] == '\0')
#define BC_OPT_ISSHORTOPT(a) \
((a) != NULL && (a)[0] == '-' && (a)[1] != '-' && (a)[1] != '\0')
#define BC_OPT_ISLONGOPT(a) \
((a) != NULL && (a)[0] == '-' && (a)[1] == '-' && (a)[2] != '\0')
#endif // BC_OPT_H

2
include/status.h

@ -73,6 +73,8 @@ typedef enum BcError {
BC_ERROR_FATAL_BIN_FILE,
BC_ERROR_FATAL_PATH_DIR,
BC_ERROR_FATAL_OPTION,
BC_ERROR_FATAL_OPTION_NO_ARG,
BC_ERROR_FATAL_OPTION_ARG,
BC_ERROR_EXEC_IBASE,
BC_ERROR_EXEC_OBASE,

3
include/vm.h

@ -210,6 +210,9 @@ typedef struct BcVm {
BcBigDig last_exp;
BcBigDig last_rem;
char *env_args_buffer;
BcVec env_args;
#if BC_ENABLE_NLS
nl_catd catalog;
#endif // BC_ENABLE_NLS

96
src/args.c

@ -33,6 +33,7 @@
*
*/
#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
@ -41,47 +42,35 @@
#include <unistd.h>
#if BC_ENABLE_LONG_OPTIONS
#include <getopt.h>
#endif // BC_ENABLE_LONG_OPTIONS
#include <status.h>
#include <vector.h>
#include <read.h>
#include <vm.h>
#include <args.h>
#include <opt.h>
#if BC_ENABLE_LONG_OPTIONS
static const struct option bc_args_lopt[] = {
static const BcOptLong bc_args_lopt[] = {
{ "expression", required_argument, NULL, 'e' },
{ "file", required_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' },
{ "interactive", no_argument, NULL, 'i' },
{ "no-prompt", no_argument, NULL, 'P' },
{ "expression", BC_OPT_REQUIRED, 'e' },
{ "file", BC_OPT_REQUIRED, 'f' },
{ "help", BC_OPT_NONE, 'h' },
{ "interactive", BC_OPT_NONE, 'i' },
{ "no-prompt", BC_OPT_NONE, 'P' },
#if BC_ENABLED
{ "global-stacks", no_argument, NULL, 'g' },
{ "mathlib", no_argument, NULL, 'l' },
{ "quiet", no_argument, NULL, 'q' },
{ "standard", no_argument, NULL, 's' },
{ "warn", no_argument, NULL, 'w' },
{ "global-stacks", BC_OPT_BC_ONLY, 'g' },
{ "mathlib", BC_OPT_BC_ONLY, 'l' },
{ "quiet", BC_OPT_BC_ONLY, 'q' },
{ "standard", BC_OPT_BC_ONLY, 's' },
{ "warn", BC_OPT_BC_ONLY, 'w' },
#endif // BC_ENABLED
{ "version", no_argument, NULL, 'v' },
{ "version", BC_OPT_NONE, 'v' },
{ "version", BC_OPT_NONE, 'V' },
#if DC_ENABLED
{ "extended-register", no_argument, NULL, 'x' },
{ "extended-register", BC_OPT_DC_ONLY, 'x' },
#endif // DC_ENABLED
{ 0, 0, 0, 0 },
{ NULL, 0, 0 },
};
#endif //BC_ENABLE_LONG_OPTIONS
#if !BC_ENABLED
static const char* const bc_args_opt = "e:f:hiPvVx";
#elif !DC_ENABLED
static const char* const bc_args_opt = "e:f:ghilPqsvVw";
#else // BC_ENABLED && DC_ENABLED
static const char* const bc_args_opt = "e:f:ghilPqsvVwx";
#endif // BC_ENABLED && DC_ENABLED
static void bc_args_exprs(BcVec *exprs, const char *str) {
bc_vec_concat(exprs, str);
@ -107,16 +96,14 @@ static BcStatus bc_args_file(BcVec *exprs, const char *file) {
BcStatus bc_args(int argc, char *argv[]) {
BcStatus s = BC_STATUS_SUCCESS;
int c, i, err = 0;
int c;
size_t i;
bool do_exit = false, version = false;
BcOpt opts;
optind = 1;
#if BC_ENABLE_LONG_OPTIONS
i = 0;
#endif // BC_ENABLE_LONG_OPTIONS
bc_opt_init(&opts, argv);
while ((c = getopt_long(argc, argv, bc_args_opt, bc_args_lopt, &i)) != -1) {
while ((c = bc_opt_parse(&opts, bc_args_lopt)) != -1) {
switch (c) {
@ -128,13 +115,13 @@ BcStatus bc_args(int argc, char *argv[]) {
case 'e':
{
bc_args_exprs(&vm->exprs, optarg);
bc_args_exprs(&vm->exprs, opts.optarg);
break;
}
case 'f':
{
s = bc_args_file(&vm->exprs, optarg);
s = bc_args_file(&vm->exprs, opts.optarg);
if (BC_ERR(s)) return s;
break;
}
@ -161,35 +148,35 @@ BcStatus bc_args(int argc, char *argv[]) {
#if BC_ENABLED
case 'g':
{
if (BC_ERR(!BC_IS_BC)) err = c;
assert(BC_IS_BC);
vm->flags |= BC_FLAG_G;
break;
}
case 'l':
{
if (BC_ERR(!BC_IS_BC)) err = c;
assert(BC_IS_BC);
vm->flags |= BC_FLAG_L;
break;
}
case 'q':
{
if (BC_ERR(!BC_IS_BC)) err = c;
assert(BC_IS_BC);
vm->flags |= BC_FLAG_Q;
break;
}
case 's':
{
if (BC_ERR(!BC_IS_BC)) err = c;
assert(BC_IS_BC);
vm->flags |= BC_FLAG_S;
break;
}
case 'w':
{
if (BC_ERR(!BC_IS_BC)) err = c;
assert(BC_IS_BC);
vm->flags |= BC_FLAG_W;
break;
}
@ -205,13 +192,13 @@ BcStatus bc_args(int argc, char *argv[]) {
#if DC_ENABLED
case 'x':
{
if (BC_ERR(BC_IS_BC)) err = c;
assert(!BC_IS_BC);
vm->flags |= DC_FLAG_X;
break;
}
#endif // DC_ENABLED
// Getopt printed an error message, but we should exit.
// An error message has been printed, but we should exit.
case '?':
case ':':
default:
@ -219,31 +206,14 @@ BcStatus bc_args(int argc, char *argv[]) {
return BC_STATUS_ERROR_FATAL;
}
}
if (BC_ERR(err)) {
const char *name;
#if BC_ENABLE_LONG_OPTIONS
for (i = 0; bc_args_lopt[i].name != NULL; ++i) {
if (bc_args_lopt[i].val == err) break;
}
name = bc_args_lopt[i].name;
#else // BC_ENABLE_LONG_OPTIONS
name = "<long options disabled>";
#endif // BC_ENABLE_LONG_OPTIONS
return bc_vm_verr(BC_ERROR_FATAL_OPTION, err, name);
}
}
if (version) bc_vm_info(NULL);
if (do_exit) exit((int) s);
if (vm->exprs.len > 1 || !BC_IS_BC) vm->flags |= BC_FLAG_Q;
if (argv[optind] != NULL && !strcmp(argv[optind], "--")) ++optind;
for (i = optind; i < argc; ++i) bc_vec_push(&vm->files, argv + i);
for (i = opts.optind; i < (size_t) argc; ++i)
bc_vec_push(&vm->files, argv + i);
return s;
}

6
src/data.c

@ -75,7 +75,7 @@ const uchar bc_err_ids[] = {
BC_ERR_IDX_MATH, BC_ERR_IDX_MATH, BC_ERR_IDX_MATH, BC_ERR_IDX_MATH,
BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL,
BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL,
BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL, BC_ERR_IDX_FATAL,
BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC,
BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC, BC_ERR_IDX_EXEC,
@ -108,7 +108,9 @@ const char* const bc_err_msgs[] = {
"could not open file: %s",
"file is not ASCII: %s",
"path is a directory: %s",
"bad command-line option: '%c' (\"%s\")",
"bad command-line option: \"%s\"",
"option requires an argument: '%c' (\"%s\")",
"option takes no arguments: '%c' (\"%s\")",
"bad ibase: must be [%lu, %lu]",
"bad obase: must be [%lu, %lu]",

261
src/opt.c

@ -0,0 +1,261 @@
/*
* *****************************************************************************
*
* Copyright (c) 2018-2020 Gavin D. Howard and contributors.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* *****************************************************************************
*
* Adapted from https://github.com/skeeto/optparse
*
* *****************************************************************************
*
* Code for getopt_long() replacement. It turns out that getopt_long() has
* different behavior on different platforms.
*
*/
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <status.h>
#include <opt.h>
#include <vm.h>
static bool bc_opt_longoptsEnd(const BcOptLong *longopts, size_t i) {
return !longopts[i].name && !longopts[i].val;
}
static char* bc_opt_longopt(const BcOptLong *longopts, int c) {
size_t i;
for (i = 0; !bc_opt_longoptsEnd(longopts, i); ++i) {
if (longopts[i].val == c) return longopts[i].name;
}
return "NULL";
}
static int bc_opt_error(BcError err, int c, char *str) {
int result;
if (err == BC_ERROR_FATAL_OPTION) {
bc_vm_error(err, 0, str);
result = (int) '?';
}
else {
bc_vm_error(err, 0, (int) c, str);
result = (int) (err == BC_ERROR_FATAL_OPTION_ARG ? '\0' : ':');
}
return result;
}
static int bc_opt_type(const BcOptLong *longopts, char c) {
size_t i;
if (c == ':') return -1;
for (i = 0; !bc_opt_longoptsEnd(longopts, i) && longopts[i].val != c; ++i);
if (bc_opt_longoptsEnd(longopts, i)) return -1;
return (int) longopts[i].type;
}
static int bc_opt_parseShort(BcOpt *o, const BcOptLong *longopts) {
int type;
char *next;
char *option = o->argv[o->optind];
o->optopt = 0;
o->optarg = NULL;
option += o->subopt + 1;
o->optopt = option[0];
type = bc_opt_type(longopts, option[0]);
next = o->argv[o->optind + 1];
switch (type) {
case -1:
case BC_OPT_BC_ONLY:
case BC_OPT_DC_ONLY:
{
if (type == -1 || (type == BC_OPT_BC_ONLY && !BC_IS_BC) ||
(type == BC_OPT_DC_ONLY && BC_IS_BC))
{
char str[2] = {0, 0};
str[0] = option[0];
o->optind += 1;
return bc_opt_error(BC_ERROR_FATAL_OPTION, option[0], str);
}
// Fallthrough.
}
case BC_OPT_NONE:
{
if (option[1]) o->subopt += 1;
else {
o->subopt = 0;
o->optind += 1;
}
return option[0];
}
case BC_OPT_REQUIRED:
{
o->subopt = 0;
o->optind += 1;
if (option[1]) o->optarg = option + 1;
else if (next != NULL) {
o->optarg = next;
o->optind += 1;
}
else {
return bc_opt_error(BC_ERROR_FATAL_OPTION_NO_ARG, option[0],
bc_opt_longopt(longopts, option[0]));
}
return option[0];
}
}
return 0;
}
static bool bc_opt_longoptsMatch(const char *name, const char *option) {
const char *a = option, *n = name;
if (name == NULL) return false;
for (; *a && *n && *a != '='; ++a, ++n) {
if (*a != *n) return false;
}
return (*n == '\0' && (*a == '\0' || *a == '='));
}
static char* bc_opt_longoptsArg(char *option) {
for (; *option && *option != '='; ++option);
if (*option == '=') return option + 1;
else return NULL;
}
int bc_opt_parse(BcOpt *o, const BcOptLong *longopts) {
size_t i;
char *option;
bool empty;
do {
option = o->argv[o->optind];
if (option == NULL) return -1;
empty = !strcmp(option, "");
o->optind += empty;
} while (empty);
if (BC_OPT_ISDASHDASH(option)) {
// Consume "--".
o->optind += 1;
return -1;
}
else if (BC_OPT_ISSHORTOPT(option)) {
return bc_opt_parseShort(o, longopts);
}
else if (!BC_OPT_ISLONGOPT(option)) {
return -1;
}
o->optopt = 0;
o->optarg = NULL;
// Skip "--" at beginning of the option.
option += 2;
o->optind += 1;
for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++) {
char *name = longopts[i].name;
if (bc_opt_longoptsMatch(name, option)) {
char *arg;
o->optopt = longopts[i].val;
arg = bc_opt_longoptsArg(option);
if ((longopts[i].type == BC_OPT_BC_ONLY && !BC_IS_BC) ||
(longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC))
{
return bc_opt_error(BC_ERROR_FATAL_OPTION, o->optopt, name);
}
if (longopts[i].type == BC_OPT_NONE && arg != NULL)
{
return bc_opt_error(BC_ERROR_FATAL_OPTION_ARG, o->optopt, name);
}
if (arg != NULL) o->optarg = arg;
else if (longopts[i].type == BC_OPT_REQUIRED) {
o->optarg = o->argv[o->optind];
if (o->optarg != NULL) o->optind += 1;
else return bc_opt_error(BC_ERROR_FATAL_OPTION_NO_ARG,
o->optopt, name);
}
return o->optopt;
}
}
return bc_opt_error(BC_ERROR_FATAL_OPTION, 0, option);
}
void bc_opt_init(BcOpt *o, char *argv[]) {
o->argv = argv;
o->optind = 1;
o->subopt = 0;
o->optarg = NULL;
}

35
tests/all.sh

@ -38,7 +38,7 @@ if [ "$#" -ge 1 ]; then
d="$1"
shift
else
err_exit "usage: $script dir [run_extra_tests] [run_stack_tests] [use_long_options] [gen_tests] [time_tests] [exec args...]" 1
err_exit "usage: $script dir [run_extra_tests] [run_stack_tests] [gen_tests] [time_tests] [exec args...]" 1
fi
if [ "$#" -lt 1 ]; then
@ -55,13 +55,6 @@ else
shift
fi
if [ "$#" -lt 1 ]; then
use_long_options=1
else
use_long_options="$1"
shift
fi
if [ "$#" -lt 1 ]; then
generate_tests=1
else
@ -194,11 +187,7 @@ results=$(cat "$testdir/$d/add_results.txt")
printf '%s\n%s\n%s\n%s\n' "$results" "$results" "$results" "$results" > "$out1"
if [ "$use_long_options" -ne 0 ]; then
"$exe" "$@" -e "$exprs" -f "$f" --expression "$exprs" --file "$f" -e "$halt" > "$out2"
else
"$exe" "$@" -e "$exprs" -f "$f" -e "$exprs" -f "$f" -e "$halt" > "$out2"
fi
"$exe" "$@" -e "$exprs" -f "$f" --expression "$exprs" --file "$f" -e "$halt" > "$out2"
diff "$out1" "$out2"
@ -223,28 +212,20 @@ err="$?"
checktest "$d" "$err" "invalid option argument" "$out2" "$d"
if [ "$use_long_options" -ne 0 ]; then
"$exe" "$@" "--$lopt" -e "$exprs" > /dev/null 2> "$out2"
err="$?"
checktest "$d" "$err" "invalid long option argument" "$out2" "$d"
"$exe" "$@" "--$lopt" -e "$exprs" > /dev/null 2> "$out2"
err="$?"
fi
checktest "$d" "$err" "invalid long option argument" "$out2" "$d"
"$exe" "$@" "-u" -e "$exprs" > /dev/null 2> "$out2"
err="$?"
checktest "$d" "$err" "unrecognized option argument" "$out2" "$d"
if [ "$use_long_options" -ne 0 ]; then
"$exe" "$@" "--uniform" -e "$exprs" > /dev/null 2> "$out2"
err="$?"
checktest "$d" "$err" "unrecognized long option argument" "$out2" "$d"
"$exe" "$@" "--uniform" -e "$exprs" > /dev/null 2> "$out2"
err="$?"
fi
checktest "$d" "$err" "unrecognized long option argument" "$out2" "$d"
printf 'pass\n'

Loading…
Cancel
Save