//===-- Implementation of getopt ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/unistd/getopt.h"
#include "src/__support/CPP/optional.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/File/file.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/stdio/fprintf.h"
#include "hdr/types/FILE.h"
// This is POSIX compliant and does not support GNU extensions, mainly this is
// just the re-ordering of argv elements such that unknown arguments can be
// easily iterated over.
namespace LIBC_NAMESPACE_DECL {
template <typename T> struct RefWrapper {
RefWrapper() = delete;
constexpr RefWrapper(T *p) : ptr{p} {}
constexpr RefWrapper(const RefWrapper &) = default;
RefWrapper &operator=(const RefWrapper &) = default;
operator T &() { return *ptr; }
T &get() { return *ptr; }
T *ptr;
};
struct GetoptContext {
RefWrapper<char *> optarg;
RefWrapper<int> optind;
RefWrapper<int> optopt;
RefWrapper<unsigned> optpos;
RefWrapper<int> opterr;
FILE *errstream;
GetoptContext &operator=(const GetoptContext &) = default;
template <typename... Ts> void report_error(const char *fmt, Ts... ts) {
if (opterr)
LIBC_NAMESPACE::fprintf(
errstream ? errstream
: reinterpret_cast<FILE *>(LIBC_NAMESPACE::stderr),
fmt, ts...);
}
};
struct OptstringParser {
using value_type = struct {
char c;
bool arg;
};
cpp::string_view optstring;
struct iterator {
cpp::string_view curr;
iterator operator++() {
curr = curr.substr(1);
return *this;
}
bool operator!=(iterator other) { return curr.data() != other.curr.data(); }
value_type operator*() {
value_type r{curr.front(), false};
if (!curr.substr(1).empty() && curr.substr(1).front() == ':') {
this->operator++();
r.arg = true;
}
return r;
}
};
iterator begin() {
bool skip = optstring.front() == '-' || optstring.front() == '+' ||
optstring.front() == ':';
return {optstring.substr(!!skip)};
}
iterator end() { return {optstring.substr(optstring.size())}; }
};
int getopt_r(int argc, char *const argv[], const char *optstring,
GetoptContext &ctx) {
auto failure = [&ctx](int ret = -1) {
ctx.optpos.get() = 0;
return ret;
};
if (ctx.optind >= argc || !argv[ctx.optind])
return failure();
cpp::string_view current =
cpp::string_view{argv[ctx.optind]}.substr(ctx.optpos);
auto move_forward = [¤t, &ctx] {
current = current.substr(1);
ctx.optpos.get()++;
};
// If optpos is nonzero, then we are already parsing a valid flag and these
// need not be checked.
if (ctx.optpos == 0) {
if (current[0] != '-')
return failure();
if (current == "--") {
ctx.optind.get()++;
return failure();
}
// Eat the '-' char.
move_forward();
if (current.empty())
return failure();
}
auto find_match =
[current, optstring]() -> cpp::optional<OptstringParser::value_type> {
for (auto i : OptstringParser{optstring})
if (i.c == current[0])
return i;
return {};
};
auto match = find_match();
if (!match) {
ctx.report_error("%s: illegal option -- %c\n", argv[0], current[0]);
ctx.optopt.get() = current[0];
return failure('?');
}
// We've matched so eat that character.
move_forward();
if (match->arg) {
// If we found an option that takes an argument and our current is not over,
// the rest of current is that argument. Ie, "-cabc" with opstring "c:",
// then optarg should point to "abc". Otherwise the argument to c will be in
// the next arg like "-c abc".
if (!current.empty()) {
// This const cast is fine because current was already holding a mutable
// string, it just doesn't have the semantics to note that, we could use
// span but it doesn't have string_view string niceties.
ctx.optarg.get() = const_cast<char *>(current.data());
} else {
// One char lookahead to see if we ran out of arguments. If so, return ':'
// if the first character of optstring is ':'. optind must stay at the
// current value so only increase it after we known there is another arg.
if (ctx.optind + 1 >= argc || !argv[ctx.optind + 1]) {
ctx.report_error("%s: option requires an argument -- %c\n", argv[0],
match->c);
return failure(optstring[0] == ':' ? ':' : '?');
}
ctx.optarg.get() = argv[++ctx.optind];
}
ctx.optind++;
ctx.optpos.get() = 0;
} else if (current.empty()) {
// If this argument is now empty we are safe to move onto the next one.
ctx.optind++;
ctx.optpos.get() = 0;
}
return match->c;
}
namespace impl {
extern "C" {
char *optarg = nullptr;
int optind = 1;
int optopt = 0;
int opterr = 0;
}
static unsigned optpos;
static GetoptContext ctx{&impl::optarg, &impl::optind, &impl::optopt,
&optpos, &impl::opterr, /*errstream=*/nullptr};
#ifndef LIBC_COPT_PUBLIC_PACKAGING
// This is used exclusively in tests.
void set_getopt_state(char **optarg, int *optind, int *optopt, unsigned *optpos,
int *opterr, FILE *errstream) {
ctx = {optarg, optind, optopt, optpos, opterr, errstream};
}
#endif
} // namespace impl
LLVM_LIBC_FUNCTION(int, getopt,
(int argc, char *const argv[], const char *optstring)) {
return getopt_r(argc, argv, optstring, impl::ctx);
}
} // namespace LIBC_NAMESPACE_DECL