[NFC][Py Reformat] Added more commits to .git-blame-ignore-revs
[llvm-project.git] / libc / src / unistd / getopt.cpp
blob4c6aaf629dbe51b72304e9436ec15e061c1899b3
1 //===-- Implementation of getopt ------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
9 #include "src/unistd/getopt.h"
10 #include "src/__support/CPP/optional.h"
11 #include "src/__support/CPP/string_view.h"
12 #include "src/__support/File/file.h"
13 #include "src/__support/common.h"
14 #include "src/stdio/fprintf.h"
16 #include <stdio.h>
18 // This is POSIX compliant and does not support GNU extensions, mainly this is
19 // just the re-ordering of argv elements such that unknown arguments can be
20 // easily iterated over.
22 namespace __llvm_libc {
24 template <typename T> struct RefWrapper {
25 RefWrapper(T *ptr) : ptr(ptr) {}
26 RefWrapper &operator=(const RefWrapper &) = default;
27 operator T &() { return *ptr; }
28 T &get() { return *ptr; }
29 T *ptr;
32 struct GetoptContext {
33 RefWrapper<char *> optarg;
34 RefWrapper<int> optind;
35 RefWrapper<int> optopt;
36 RefWrapper<unsigned> optpos;
38 int opterr;
40 FILE *errstream;
42 GetoptContext &operator=(const GetoptContext &) = default;
44 template <typename... Ts> void report_error(const char *fmt, Ts... ts) {
45 if (opterr)
46 __llvm_libc::fprintf(errstream, fmt, ts...);
50 struct OptstringParser {
51 using value_type = struct {
52 char c;
53 bool arg;
56 cpp::string_view optstring;
58 struct iterator {
59 cpp::string_view curr;
61 iterator operator++() {
62 curr = curr.substr(1);
63 return *this;
66 bool operator!=(iterator other) { return curr.data() != other.curr.data(); }
68 value_type operator*() {
69 value_type r{curr.front(), false};
70 if (!curr.substr(1).empty() && curr.substr(1).front() == ':') {
71 this->operator++();
72 r.arg = true;
74 return r;
78 iterator begin() {
79 bool skip = optstring.front() == '-' || optstring.front() == '+' ||
80 optstring.front() == ':';
81 return {optstring.substr(!!skip)};
84 iterator end() { return {optstring.substr(optstring.size())}; }
87 int getopt_r(int argc, char *const argv[], const char *optstring,
88 GetoptContext &ctx) {
89 auto failure = [&ctx](int ret = -1) {
90 ctx.optpos.get() = 0;
91 return ret;
94 if (ctx.optind >= argc || !argv[ctx.optind])
95 return failure();
97 cpp::string_view current =
98 cpp::string_view{argv[ctx.optind]}.substr(ctx.optpos);
100 auto move_forward = [&current, &ctx] {
101 current = current.substr(1);
102 ctx.optpos.get()++;
105 // If optpos is nonzero, then we are already parsing a valid flag and these
106 // need not be checked.
107 if (ctx.optpos == 0) {
108 if (current[0] != '-')
109 return failure();
111 if (current == "--") {
112 ctx.optind.get()++;
113 return failure();
116 // Eat the '-' char.
117 move_forward();
118 if (current.empty())
119 return failure();
122 auto find_match =
123 [current, optstring]() -> cpp::optional<OptstringParser::value_type> {
124 for (auto i : OptstringParser{optstring})
125 if (i.c == current[0])
126 return i;
127 return {};
130 auto match = find_match();
131 if (!match) {
132 ctx.report_error("%s: illegal option -- %c\n", argv[0], current[0]);
133 ctx.optopt.get() = current[0];
134 return failure('?');
137 // We've matched so eat that character.
138 move_forward();
139 if (match->arg) {
140 // If we found an option that takes an argument and our current is not over,
141 // the rest of current is that argument. Ie, "-cabc" with opstring "c:",
142 // then optarg should point to "abc". Otherwise the argument to c will be in
143 // the next arg like "-c abc".
144 if (!current.empty()) {
145 // This const cast is fine because current was already holding a mutable
146 // string, it just doesn't have the semantics to note that, we could use
147 // span but it doesn't have string_view string niceties.
148 ctx.optarg.get() = const_cast<char *>(current.data());
149 } else {
150 // One char lookahead to see if we ran out of arguments. If so, return ':'
151 // if the first character of optstring is ':'. optind must stay at the
152 // current value so only increase it after we known there is another arg.
153 if (ctx.optind + 1 >= argc || !argv[ctx.optind + 1]) {
154 ctx.report_error("%s: option requires an argument -- %c\n", argv[0],
155 match->c);
156 return failure(optstring[0] == ':' ? ':' : '?');
158 ctx.optarg.get() = argv[++ctx.optind];
160 ctx.optind++;
161 ctx.optpos.get() = 0;
162 } else if (current.empty()) {
163 // If this argument is now empty we are safe to move onto the next one.
164 ctx.optind++;
165 ctx.optpos.get() = 0;
168 return match->c;
171 namespace impl {
173 extern "C" {
175 char *optarg = nullptr;
176 int optind = 1;
177 int optopt = 0;
178 int opterr = 0;
182 static unsigned optpos;
184 static GetoptContext ctx{
185 &impl::optarg, &impl::optind,
186 &impl::optopt, &optpos,
187 impl::opterr, reinterpret_cast<FILE *>(__llvm_libc::stderr)};
189 #ifndef LIBC_COPT_PUBLIC_PACKAGING
190 // This is used exclusively in tests.
191 void set_getopt_state(char **optarg, int *optind, int *optopt, unsigned *optpos,
192 int opterr, FILE *errstream) {
193 ctx = {optarg, optind, optopt, optpos, opterr, errstream};
195 #endif
197 } // namespace impl
199 LLVM_LIBC_FUNCTION(int, getopt,
200 (int argc, char *const argv[], const char *optstring)) {
201 return getopt_r(argc, argv, optstring, impl::ctx);
204 } // namespace __llvm_libc