359 lines
13 KiB
C++
359 lines
13 KiB
C++
#include "prx/libc/include/FileStream.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
struct GuestOption {
|
|
const char* name;
|
|
int hasArgument;
|
|
int* flag;
|
|
int value;
|
|
};
|
|
static_assert(sizeof(GuestOption) == 32);
|
|
static_assert(offsetof(GuestOption, hasArgument) == 8);
|
|
static_assert(offsetof(GuestOption, flag) == 16);
|
|
static_assert(offsetof(GuestOption, value) == 24);
|
|
|
|
extern "C" {
|
|
extern const char* __progname_nid_postfix;
|
|
extern FileStream* __stderrp_nid_postfix;
|
|
char* APS5_VABI getenv_nid_postfix(const char* name);
|
|
|
|
int opterr_nid_postfix = 1;
|
|
int optind_nid_postfix = 1;
|
|
int optopt_nid_postfix = 0;
|
|
int optreset_nid_postfix = 0;
|
|
char* optarg_nid_postfix = nullptr;
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr int NoArgument = 0;
|
|
constexpr int RequiredArgument = 1;
|
|
constexpr int OptionalArgument = 2;
|
|
|
|
constexpr int FlagPermute = 1;
|
|
constexpr int FlagAllArguments = 2;
|
|
|
|
constexpr int BadCharacter = '?';
|
|
constexpr int InOrder = 1;
|
|
|
|
char g_emptyMessage[1] {};
|
|
char* g_shortPlace = g_emptyMessage;
|
|
char* g_longPlace = g_emptyMessage;
|
|
int g_nonOptionStart = -1;
|
|
int g_nonOptionEnd = -1;
|
|
int g_posixlyCorrect = -1;
|
|
|
|
void Warn(std::string_view message) {
|
|
const char* const name = __progname_nid_postfix ? __progname_nid_postfix : "(null)";
|
|
std::string line(name);
|
|
line.append(": ").append(message).append("\n");
|
|
FileStream* const stream = __stderrp_nid_postfix;
|
|
std::fwrite(line.data(), 1, line.size(), GetNativeStream(stream));
|
|
stream->SyncStatus();
|
|
}
|
|
|
|
std::string Character(int character) {
|
|
return std::string(1, static_cast<char>(character));
|
|
}
|
|
|
|
char* Argument(char* const* arguments, int index, const char* function) {
|
|
if (!arguments) throw std::invalid_argument(std::string(function) + ": null argument vector");
|
|
if (index < 0) throw std::invalid_argument(std::string(function) + ": negative optind " + std::to_string(index));
|
|
char* const argument = arguments[index];
|
|
if (!argument) throw std::invalid_argument(std::string(function) + ": null argument " + std::to_string(index));
|
|
return argument;
|
|
}
|
|
|
|
int OptionCharacter(const char* place) {
|
|
return static_cast<signed char>(*place);
|
|
}
|
|
|
|
bool PrintError(const char* options) {
|
|
return opterr_nid_postfix && *options != ':';
|
|
}
|
|
|
|
int BadArgument(const char* options) {
|
|
return *options == ':' ? ':' : '?';
|
|
}
|
|
|
|
int Gcd(int a, int b) {
|
|
int c = a % b;
|
|
while (c != 0) {
|
|
a = b;
|
|
b = c;
|
|
c = a % b;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
void PermuteArguments(int nonOptionStart, int nonOptionEnd, int optionEnd, char* const* arguments) {
|
|
char** const vector = const_cast<char**>(arguments);
|
|
const int nonOptions = nonOptionEnd - nonOptionStart;
|
|
const int options = optionEnd - nonOptionEnd;
|
|
if (nonOptions <= 0 || options <= 0) throw std::logic_error("getopt_long: optind moved inside a permutation");
|
|
const int cycles = Gcd(nonOptions, options);
|
|
const int cycleLength = (optionEnd - nonOptionStart) / cycles;
|
|
for (int i = 0; i < cycles; ++i) {
|
|
const int cycleStart = nonOptionEnd + i;
|
|
int position = cycleStart;
|
|
for (int j = 0; j < cycleLength; ++j) {
|
|
if (position >= nonOptionEnd) position -= nonOptions;
|
|
else position += options;
|
|
char* const swap = vector[position];
|
|
vector[position] = vector[cycleStart];
|
|
vector[cycleStart] = swap;
|
|
}
|
|
}
|
|
}
|
|
|
|
int ParseLongOptions(char* const* arguments, const char* options, const GuestOption* longOptions, int* index, const char* dash) {
|
|
char* const current = g_longPlace;
|
|
int match = -1;
|
|
bool exactMatch = false;
|
|
bool secondPartialMatch = false;
|
|
|
|
++optind_nid_postfix;
|
|
|
|
char* hasEqual = std::strchr(current, '=');
|
|
std::size_t length;
|
|
if (hasEqual) {
|
|
length = static_cast<std::size_t>(hasEqual - current);
|
|
++hasEqual;
|
|
} else {
|
|
length = std::strlen(current);
|
|
}
|
|
|
|
for (int i = 0; longOptions[i].name; ++i) {
|
|
if (std::strncmp(current, longOptions[i].name, length) != 0) continue;
|
|
if (std::strlen(longOptions[i].name) == length) {
|
|
match = i;
|
|
exactMatch = true;
|
|
break;
|
|
}
|
|
if (match == -1) match = i;
|
|
else if (longOptions[i].hasArgument != longOptions[match].hasArgument ||
|
|
longOptions[i].flag != longOptions[match].flag ||
|
|
longOptions[i].value != longOptions[match].value)
|
|
secondPartialMatch = true;
|
|
}
|
|
const std::string_view name(current, length);
|
|
if (!exactMatch && secondPartialMatch) {
|
|
if (PrintError(options)) Warn(std::string("option `") + dash + std::string(name) + "' is ambiguous");
|
|
optopt_nid_postfix = 0;
|
|
return BadCharacter;
|
|
}
|
|
if (match == -1) {
|
|
if (PrintError(options)) Warn(std::string("unrecognized option `") + dash + current + "'");
|
|
optopt_nid_postfix = 0;
|
|
return BadCharacter;
|
|
}
|
|
const GuestOption& option = longOptions[match];
|
|
if (option.hasArgument == NoArgument && hasEqual) {
|
|
if (PrintError(options)) Warn(std::string("option `") + dash + std::string(name) + "' doesn't allow an argument");
|
|
optopt_nid_postfix = option.flag ? 0 : option.value;
|
|
return BadCharacter;
|
|
}
|
|
if (option.hasArgument == RequiredArgument || option.hasArgument == OptionalArgument) {
|
|
if (hasEqual) optarg_nid_postfix = hasEqual;
|
|
else if (option.hasArgument == RequiredArgument) optarg_nid_postfix = arguments[optind_nid_postfix++];
|
|
}
|
|
if (option.hasArgument == RequiredArgument && !optarg_nid_postfix) {
|
|
if (PrintError(options)) Warn(std::string("option `") + dash + current + "' requires an argument");
|
|
optopt_nid_postfix = option.flag ? 0 : option.value;
|
|
--optind_nid_postfix;
|
|
return BadArgument(options);
|
|
}
|
|
if (index) *index = match;
|
|
if (option.flag) {
|
|
*option.flag = option.value;
|
|
return 0;
|
|
}
|
|
return option.value;
|
|
}
|
|
|
|
int GetoptInternal(int count, char* const* arguments, const char* options, const GuestOption* longOptions, int* index, int flags) {
|
|
if (!options) return -1;
|
|
|
|
if (optind_nid_postfix == 0) optind_nid_postfix = optreset_nid_postfix = 1;
|
|
|
|
if (g_posixlyCorrect == -1 || optreset_nid_postfix)
|
|
g_posixlyCorrect = getenv_nid_postfix("POSIXLY_CORRECT") != nullptr;
|
|
if (*options == '-') flags |= FlagAllArguments;
|
|
else if (g_posixlyCorrect || *options == '+') flags &= ~FlagPermute;
|
|
if (*options == '+' || *options == '-') ++options;
|
|
|
|
optarg_nid_postfix = nullptr;
|
|
if (optreset_nid_postfix) g_nonOptionStart = g_nonOptionEnd = -1;
|
|
|
|
while (optreset_nid_postfix || !*g_longPlace) {
|
|
optreset_nid_postfix = 0;
|
|
if (optind_nid_postfix >= count) {
|
|
g_longPlace = g_emptyMessage;
|
|
if (g_nonOptionEnd != -1) {
|
|
PermuteArguments(g_nonOptionStart, g_nonOptionEnd, optind_nid_postfix, arguments);
|
|
optind_nid_postfix -= g_nonOptionEnd - g_nonOptionStart;
|
|
} else if (g_nonOptionStart != -1) {
|
|
optind_nid_postfix = g_nonOptionStart;
|
|
}
|
|
g_nonOptionStart = g_nonOptionEnd = -1;
|
|
return -1;
|
|
}
|
|
g_longPlace = Argument(arguments, optind_nid_postfix, "getopt_long");
|
|
if (*g_longPlace != '-' || g_longPlace[1] == '\0') {
|
|
g_longPlace = g_emptyMessage;
|
|
if (flags & FlagAllArguments) {
|
|
optarg_nid_postfix = arguments[optind_nid_postfix++];
|
|
return InOrder;
|
|
}
|
|
if (!(flags & FlagPermute)) return -1;
|
|
if (g_nonOptionStart == -1) {
|
|
g_nonOptionStart = optind_nid_postfix;
|
|
} else if (g_nonOptionEnd != -1) {
|
|
PermuteArguments(g_nonOptionStart, g_nonOptionEnd, optind_nid_postfix, arguments);
|
|
g_nonOptionStart = optind_nid_postfix - (g_nonOptionEnd - g_nonOptionStart);
|
|
g_nonOptionEnd = -1;
|
|
}
|
|
++optind_nid_postfix;
|
|
continue;
|
|
}
|
|
if (g_nonOptionStart != -1 && g_nonOptionEnd == -1) g_nonOptionEnd = optind_nid_postfix;
|
|
|
|
if (g_longPlace[1] != '\0' && *++g_longPlace == '-' && g_longPlace[1] == '\0') {
|
|
++optind_nid_postfix;
|
|
g_longPlace = g_emptyMessage;
|
|
if (g_nonOptionEnd != -1) {
|
|
PermuteArguments(g_nonOptionStart, g_nonOptionEnd, optind_nid_postfix, arguments);
|
|
optind_nid_postfix -= g_nonOptionEnd - g_nonOptionStart;
|
|
}
|
|
g_nonOptionStart = g_nonOptionEnd = -1;
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (longOptions && *g_longPlace == '-' && g_longPlace != arguments[optind_nid_postfix]) {
|
|
++g_longPlace;
|
|
const int result = ParseLongOptions(arguments, options, longOptions, index, "--");
|
|
g_longPlace = g_emptyMessage;
|
|
return result;
|
|
}
|
|
|
|
const int character = OptionCharacter(g_longPlace++);
|
|
const char* found = nullptr;
|
|
if (character == ':' || (character == '-' && *g_longPlace != '\0') ||
|
|
(found = std::strchr(options, character)) == nullptr) {
|
|
if (character == '-' && *g_longPlace == '\0') return -1;
|
|
if (!*g_longPlace) ++optind_nid_postfix;
|
|
if (PrintError(options))
|
|
Warn((g_posixlyCorrect ? "illegal option -- " : "invalid option -- ") + Character(character));
|
|
optopt_nid_postfix = character;
|
|
return BadCharacter;
|
|
}
|
|
if (longOptions && character == 'W' && found[1] == ';') {
|
|
if (!*g_longPlace) {
|
|
if (++optind_nid_postfix >= count) {
|
|
g_longPlace = g_emptyMessage;
|
|
if (PrintError(options)) Warn("option requires an argument -- " + Character(character));
|
|
optopt_nid_postfix = character;
|
|
return BadArgument(options);
|
|
}
|
|
g_longPlace = Argument(arguments, optind_nid_postfix, "getopt_long");
|
|
}
|
|
const int result = ParseLongOptions(arguments, options, longOptions, index, "-W ");
|
|
g_longPlace = g_emptyMessage;
|
|
return result;
|
|
}
|
|
if (*++found != ':') {
|
|
if (!*g_longPlace) ++optind_nid_postfix;
|
|
} else {
|
|
optarg_nid_postfix = nullptr;
|
|
if (*g_longPlace) {
|
|
optarg_nid_postfix = g_longPlace;
|
|
} else if (found[1] != ':') {
|
|
if (++optind_nid_postfix >= count) {
|
|
g_longPlace = g_emptyMessage;
|
|
if (PrintError(options)) Warn("option requires an argument -- " + Character(character));
|
|
optopt_nid_postfix = character;
|
|
return BadArgument(options);
|
|
}
|
|
optarg_nid_postfix = arguments[optind_nid_postfix];
|
|
}
|
|
g_longPlace = g_emptyMessage;
|
|
++optind_nid_postfix;
|
|
}
|
|
return character;
|
|
}
|
|
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
int APS5_VABI getopt_nid_postfix(int count, char* const* arguments, const char* options) {
|
|
if (!options) throw std::invalid_argument("getopt: null option string");
|
|
if (optreset_nid_postfix || *g_shortPlace == '\0') {
|
|
optreset_nid_postfix = 0;
|
|
if (optind_nid_postfix >= count) {
|
|
g_shortPlace = g_emptyMessage;
|
|
return -1;
|
|
}
|
|
g_shortPlace = Argument(arguments, optind_nid_postfix, "getopt");
|
|
if (*g_shortPlace++ != '-') {
|
|
g_shortPlace = g_emptyMessage;
|
|
return -1;
|
|
}
|
|
optopt_nid_postfix = OptionCharacter(g_shortPlace++);
|
|
if (optopt_nid_postfix == '-' && *g_shortPlace == '\0') {
|
|
++optind_nid_postfix;
|
|
g_shortPlace = g_emptyMessage;
|
|
return -1;
|
|
}
|
|
if (optopt_nid_postfix == 0) {
|
|
g_shortPlace = g_emptyMessage;
|
|
if (!std::strchr(options, '-')) return -1;
|
|
optopt_nid_postfix = '-';
|
|
}
|
|
} else {
|
|
optopt_nid_postfix = OptionCharacter(g_shortPlace++);
|
|
}
|
|
|
|
const char* found = nullptr;
|
|
if (optopt_nid_postfix == ':' || (found = std::strchr(options, optopt_nid_postfix)) == nullptr) {
|
|
if (*g_shortPlace == '\0') ++optind_nid_postfix;
|
|
if (opterr_nid_postfix && *options != ':') Warn("illegal option -- " + Character(optopt_nid_postfix));
|
|
return BadCharacter;
|
|
}
|
|
|
|
if (found[1] != ':') {
|
|
optarg_nid_postfix = nullptr;
|
|
if (*g_shortPlace == '\0') ++optind_nid_postfix;
|
|
} else {
|
|
if (*g_shortPlace) {
|
|
optarg_nid_postfix = g_shortPlace;
|
|
} else if (found[2] == ':') {
|
|
optarg_nid_postfix = nullptr;
|
|
} else if (count > ++optind_nid_postfix) {
|
|
optarg_nid_postfix = arguments[optind_nid_postfix];
|
|
} else {
|
|
g_shortPlace = g_emptyMessage;
|
|
if (*options == ':') return ':';
|
|
if (opterr_nid_postfix) Warn("option requires an argument -- " + Character(optopt_nid_postfix));
|
|
return BadCharacter;
|
|
}
|
|
g_shortPlace = g_emptyMessage;
|
|
++optind_nid_postfix;
|
|
}
|
|
return optopt_nid_postfix;
|
|
}
|
|
|
|
int APS5_VABI getopt_long_nid_postfix(int count, char* const* arguments, const char* options, const GuestOption* longOptions, int* index) {
|
|
return GetoptInternal(count, arguments, options, longOptions, index, FlagPermute);
|
|
}
|
|
|
|
}
|