180529Sdd/*
280529Sdd * *****************************************************************************
380529Sdd *
480529Sdd * SPDX-License-Identifier: BSD-2-Clause
580529Sdd *
680529Sdd * Copyright (c) 2018-2023 Gavin D. Howard and contributors.
780529Sdd *
880529Sdd * Redistribution and use in source and binary forms, with or without
980529Sdd * modification, are permitted provided that the following conditions are met:
1080529Sdd *
1180529Sdd * * Redistributions of source code must retain the above copyright notice, this
1280529Sdd *   list of conditions and the following disclaimer.
1380529Sdd *
1480529Sdd * * Redistributions in binary form must reproduce the above copyright notice,
1580529Sdd *   this list of conditions and the following disclaimer in the documentation
1680529Sdd *   and/or other materials provided with the distribution.
1780529Sdd *
1880529Sdd * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1980529Sdd * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2080529Sdd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2180529Sdd * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
2280529Sdd * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2380529Sdd * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2480529Sdd * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2580529Sdd * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2680529Sdd * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2780529Sdd * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2880529Sdd * POSSIBILITY OF SUCH DAMAGE.
2980529Sdd *
3080529Sdd * *****************************************************************************
3180529Sdd *
3280529Sdd * Adapted from https://github.com/skeeto/optparse
3380529Sdd *
3480529Sdd * *****************************************************************************
3580529Sdd *
3680529Sdd * Code for getopt_long() replacement. It turns out that getopt_long() has
3780529Sdd * different behavior on different platforms.
3880529Sdd *
3980529Sdd */
4080529Sdd
4180529Sdd#include <assert.h>
4280529Sdd#include <stdbool.h>
4380529Sdd#include <stdlib.h>
4480529Sdd#include <string.h>
4580529Sdd
4680529Sdd#include <status.h>
4780529Sdd#include <opt.h>
4880529Sdd#include <vm.h>
4980529Sdd
5080529Sdd/**
5180529Sdd * Returns true if index @a i is the end of the longopts array.
5280529Sdd * @param longopts  The long options array.
5380529Sdd * @param i         The index to test.
5480529Sdd * @return          True if @a i is the last index, false otherwise.
5580529Sdd */
5680529Sddstatic inline bool
5780529Sddbc_opt_longoptsEnd(const BcOptLong* longopts, size_t i)
5880529Sdd{
5980529Sdd	return !longopts[i].name && !longopts[i].val;
6080529Sdd}
6180529Sdd
6280529Sdd/**
6380529Sdd * Returns the name of the long option that matches the character @a c.
6480529Sdd * @param longopts  The long options array.
6580529Sdd * @param c         The character to match against.
6680529Sdd * @return          The name of the long option that matches @a c, or "NULL".
6780529Sdd */
6880529Sddstatic const char*
6980529Sddbc_opt_longopt(const BcOptLong* longopts, int c)
7080529Sdd{
7180529Sdd	size_t i;
7284212Sdillon
7384212Sdillon	for (i = 0; !bc_opt_longoptsEnd(longopts, i); ++i)
7480529Sdd	{
7580529Sdd		if (longopts[i].val == c) return longopts[i].name;
7680529Sdd	}
7780529Sdd
7880529Sdd	BC_UNREACHABLE
7980529Sdd
8080529Sdd#if !BC_CLANG
8180529Sdd	return "NULL";
8280529Sdd#endif // !BC_CLANG
8380529Sdd}
8480529Sdd
8580529Sdd/**
8680529Sdd * Issues a fatal error for an option parsing failure.
8780529Sdd * @param err        The error.
8880529Sdd * @param c          The character for the failing option.
8980529Sdd * @param str        Either the string for the failing option, or the invalid
9080529Sdd *                   option.
9180529Sdd * @param use_short  True if the short option should be used for error printing,
9280529Sdd *                   false otherwise.
9380529Sdd */
9480529Sddstatic void
9580529Sddbc_opt_error(BcErr err, int c, const char* str, bool use_short)
9680529Sdd{
9780529Sdd	if (err == BC_ERR_FATAL_OPTION)
98160840Ssimon	{
99160840Ssimon		if (use_short)
10080529Sdd		{
10180529Sdd			char short_str[2];
10280529Sdd
10380529Sdd			short_str[0] = (char) c;
10480529Sdd			short_str[1] = '\0';
10580529Sdd
106160840Ssimon			bc_error(err, 0, short_str);
107160840Ssimon		}
10880529Sdd		else bc_error(err, 0, str);
10980529Sdd	}
11080529Sdd	else bc_error(err, 0, (int) c, str);
11180529Sdd}
11280529Sdd
11380529Sdd/**
11480529Sdd * Returns the type of the long option that matches @a c.
11580529Sdd * @param longopts  The long options array.
11680529Sdd * @param c         The character to match against.
11780529Sdd * @return          The type of the long option as an integer, or -1 if none.
11880529Sdd */
11980529Sddstatic int
12080529Sddbc_opt_type(const BcOptLong* longopts, char c)
12180529Sdd{
12280529Sdd	size_t i;
12380529Sdd
12480529Sdd	if (c == ':') return -1;
12580529Sdd
12680529Sdd	for (i = 0; !bc_opt_longoptsEnd(longopts, i) && longopts[i].val != c; ++i)
12780529Sdd	{
12880529Sdd		continue;
12980529Sdd	}
13080529Sdd
13180529Sdd	if (bc_opt_longoptsEnd(longopts, i)) return -1;
13280529Sdd
13380529Sdd	return (int) longopts[i].type;
13480529Sdd}
13580529Sdd
13680529Sdd/**
13780529Sdd * Parses a short option.
13880529Sdd * @param o         The option parser.
13980529Sdd * @param longopts  The long options array.
14080529Sdd * @return          The character for the short option, or -1 if none left.
14180529Sdd */
14280529Sddstatic int
14380529Sddbc_opt_parseShort(BcOpt* o, const BcOptLong* longopts)
14480529Sdd{
14580529Sdd	int type;
146160805Ssimon	char* next;
14780529Sdd	char* option = o->argv[o->optind];
148160805Ssimon	int ret = -1;
149160805Ssimon
150160805Ssimon	// Make sure to clear these.
15180529Sdd	o->optopt = 0;
152160805Ssimon	o->optarg = NULL;
15380529Sdd
15480529Sdd	// Get the next option.
155160805Ssimon	option += o->subopt + 1;
15680529Sdd	o->optopt = option[0];
15780529Sdd
15880529Sdd	// Get the type and the next data.
15980529Sdd	type = bc_opt_type(longopts, option[0]);
16080529Sdd	next = o->argv[o->optind + 1];
16180529Sdd
16280529Sdd	switch (type)
16380529Sdd	{
16480529Sdd		case -1:
16580529Sdd		case BC_OPT_BC_ONLY:
16680529Sdd		case BC_OPT_DC_ONLY:
16780529Sdd		{
16880529Sdd			// Check for invalid option and barf if so.
16980529Sdd			if (type == -1 || (type == BC_OPT_BC_ONLY && BC_IS_DC) ||
17080529Sdd			    (type == BC_OPT_DC_ONLY && BC_IS_BC))
17180529Sdd			{
17280529Sdd				char str[2] = { 0, 0 };
17380529Sdd
17480529Sdd				str[0] = option[0];
17580529Sdd				o->optind += 1;
17680529Sdd
17780529Sdd				bc_opt_error(BC_ERR_FATAL_OPTION, option[0], str, true);
17880529Sdd			}
17980529Sdd
18080529Sdd			// Fallthrough.
18180529Sdd			BC_FALLTHROUGH
18280529Sdd		}
18380529Sdd
18480529Sdd		case BC_OPT_NONE:
18580529Sdd		{
18680529Sdd			// If there is something else, update the suboption.
18780529Sdd			if (option[1]) o->subopt += 1;
18880529Sdd			else
18980529Sdd			{
19080529Sdd				// Go to the next argument.
19180529Sdd				o->subopt = 0;
19280529Sdd				o->optind += 1;
19380529Sdd			}
19480529Sdd
19580529Sdd			ret = (int) option[0];
19680529Sdd
19780529Sdd			break;
19880529Sdd		}
19980529Sdd
20080529Sdd		case BC_OPT_REQUIRED_BC_ONLY:
20180529Sdd		{
20280529Sdd#if DC_ENABLED
20380529Sdd			if (BC_IS_DC)
20480529Sdd			{
20580529Sdd				bc_opt_error(BC_ERR_FATAL_OPTION, option[0],
20680529Sdd				             bc_opt_longopt(longopts, option[0]), true);
20780529Sdd			}
20880529Sdd#endif // DC_ENABLED
20980529Sdd
21080529Sdd			// Fallthrough
21180529Sdd			BC_FALLTHROUGH
21280529Sdd		}
21380529Sdd
21480529Sdd		case BC_OPT_REQUIRED:
21580529Sdd		{
21680529Sdd			// Always go to the next argument.
21780529Sdd			o->subopt = 0;
21880529Sdd			o->optind += 1;
21980529Sdd
22080529Sdd			// Use the next characters, if they exist.
22180529Sdd			if (option[1]) o->optarg = option + 1;
22280529Sdd			else if (next != NULL)
22380529Sdd			{
22480529Sdd				// USe the next.
22580529Sdd				o->optarg = next;
22680529Sdd				o->optind += 1;
227160840Ssimon			}
228160840Ssimon			// No argument, barf.
22980529Sdd			else
23080529Sdd			{
23180529Sdd				bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, option[0],
23280529Sdd				             bc_opt_longopt(longopts, option[0]), true);
23380529Sdd			}
234160805Ssimon
23580529Sdd			ret = (int) option[0];
23680529Sdd
23780529Sdd			break;
23880529Sdd		}
23980529Sdd	}
24080529Sdd
24180529Sdd	return ret;
24280529Sdd}
24380529Sdd
244160840Ssimon/**
24580529Sdd * Ensures that a long option argument matches a long option name, regardless of
246160840Ssimon * "=<data>" at the end.
247160840Ssimon * @param name    The name to match.
248160840Ssimon * @param option  The command-line argument.
249160840Ssimon * @return        True if @a option matches @a name, false otherwise.
250160840Ssimon */
25180529Sddstatic bool
25280529Sddbc_opt_longoptsMatch(const char* name, const char* option)
25380529Sdd{
25480529Sdd	const char* a = option;
25580529Sdd	const char* n = name;
25680529Sdd
25780529Sdd	// Can never match a NULL name.
25880529Sdd	if (name == NULL) return false;
25980529Sdd
26080529Sdd	// Loop through.
26180529Sdd	for (; *a && *n && *a != '='; ++a, ++n)
26280529Sdd	{
26380529Sdd		if (*a != *n) return false;
26480529Sdd	}
26580529Sdd
26680529Sdd	// Ensure they both end at the same place.
26780529Sdd	return (*n == '\0' && (*a == '\0' || *a == '='));
26880529Sdd}
26980529Sdd
27080529Sdd/**
27180529Sdd * Returns a pointer to the argument of a long option, or NULL if it not in the
27280529Sdd * same argument.
27380529Sdd * @param option  The option to find the argument of.
27480529Sdd * @return        A pointer to the argument of the option, or NULL if none.
27580529Sdd */
27680529Sddstatic char*
27780529Sddbc_opt_longoptsArg(char* option)
27880529Sdd{
27980529Sdd	// Find the end or equals sign.
28080529Sdd	for (; *option && *option != '='; ++option)
28180529Sdd	{
28280529Sdd		continue;
28380529Sdd	}
28480529Sdd
28580529Sdd	if (*option == '=') return option + 1;
28680529Sdd	else return NULL;
28780529Sdd}
28880529Sdd
28980529Sddint
29080529Sddbc_opt_parse(BcOpt* o, const BcOptLong* longopts)
29180529Sdd{
29280529Sdd	size_t i;
29380529Sdd	char* option;
29480529Sdd	bool empty;
29580529Sdd
29680529Sdd	// This just eats empty options.
29780529Sdd	do
29880529Sdd	{
29980529Sdd		option = o->argv[o->optind];
30080529Sdd		if (option == NULL) return -1;
30180529Sdd
30280529Sdd		empty = !strcmp(option, "");
30380529Sdd		o->optind += empty;
30480529Sdd	}
30580529Sdd	while (empty);
30680529Sdd
30780529Sdd	// If the option is just a "--".
30880529Sdd	if (BC_OPT_ISDASHDASH(option))
30980529Sdd	{
31080529Sdd		// Consume "--".
31180529Sdd		o->optind += 1;
31280529Sdd		return -1;
31380529Sdd	}
31480529Sdd	// Parse a short option.
31580529Sdd	else if (BC_OPT_ISSHORTOPT(option)) return bc_opt_parseShort(o, longopts);
31680529Sdd	// If the option is not long at this point, we are done.
31780529Sdd	else if (!BC_OPT_ISLONGOPT(option)) return -1;
31880529Sdd
31980529Sdd	// Clear these.
32080529Sdd	o->optopt = 0;
32180529Sdd	o->optarg = NULL;
32280529Sdd
32380529Sdd	// Skip "--" at beginning of the option.
32480529Sdd	option += 2;
32580529Sdd	o->optind += 1;
32680529Sdd
32780529Sdd	// Loop through the valid long options.
32880529Sdd	for (i = 0; !bc_opt_longoptsEnd(longopts, i); i++)
32980529Sdd	{
33080529Sdd		const char* name = longopts[i].name;
33180529Sdd
33280529Sdd		// If we have a match...
33380529Sdd		if (bc_opt_longoptsMatch(name, option))
33480529Sdd		{
33580529Sdd			char* arg;
33680529Sdd
33780529Sdd			// Get the option char and the argument.
33880529Sdd			o->optopt = longopts[i].val;
33980529Sdd			arg = bc_opt_longoptsArg(option);
34080529Sdd
34180529Sdd			// Error if the option is invalid..
34280529Sdd			if ((longopts[i].type == BC_OPT_BC_ONLY && BC_IS_DC) ||
34380529Sdd			    (longopts[i].type == BC_OPT_REQUIRED_BC_ONLY && BC_IS_DC) ||
34480529Sdd			    (longopts[i].type == BC_OPT_DC_ONLY && BC_IS_BC))
34580529Sdd			{
34680529Sdd				bc_opt_error(BC_ERR_FATAL_OPTION, o->optopt, name, false);
34780529Sdd			}
34880529Sdd
34980529Sdd			// Error if we have an argument and should not.
35080529Sdd			if (longopts[i].type == BC_OPT_NONE && arg != NULL)
35180529Sdd			{
35280529Sdd				bc_opt_error(BC_ERR_FATAL_OPTION_ARG, o->optopt, name, false);
35380529Sdd			}
35480529Sdd
35580529Sdd			// Set the argument, or check the next argument if we don't have
35680529Sdd			// one.
35780529Sdd			if (arg != NULL) o->optarg = arg;
35880529Sdd			else if (longopts[i].type == BC_OPT_REQUIRED ||
35980529Sdd			         longopts[i].type == BC_OPT_REQUIRED_BC_ONLY)
36080529Sdd			{
36180529Sdd				// Get the next argument.
362160840Ssimon				o->optarg = o->argv[o->optind];
36380529Sdd
36480529Sdd				// All's good if it exists; otherwise, barf.
36580529Sdd				if (o->optarg != NULL) o->optind += 1;
36680529Sdd				else
36780529Sdd				{
368160840Ssimon					bc_opt_error(BC_ERR_FATAL_OPTION_NO_ARG, o->optopt, name,
369160840Ssimon					             false);
370160840Ssimon				}
37180529Sdd			}
37280529Sdd
37380529Sdd			return o->optopt;
37480529Sdd		}
37580529Sdd	}
37680529Sdd
37780529Sdd	// If we reach this point, the option is invalid.
37880529Sdd	bc_opt_error(BC_ERR_FATAL_OPTION, 0, option, false);
379160840Ssimon
38080529Sdd	BC_UNREACHABLE
381160840Ssimon
38280529Sdd#if !BC_CLANG
38380529Sdd	return -1;
38480529Sdd#endif // !BC_CLANG
385160840Ssimon}
38680529Sdd
38780529Sddvoid
38880529Sddbc_opt_init(BcOpt* o, char* argv[])
38980529Sdd{
39080529Sdd	o->argv = argv;
39180529Sdd	o->optind = 1;
39280529Sdd	o->subopt = 0;
39380529Sdd	o->optarg = NULL;
394160840Ssimon}
39580529Sdd