-
Notifications
You must be signed in to change notification settings - Fork 2
/
gcc_cmdline_parser.cc
241 lines (210 loc) · 6.65 KB
/
gcc_cmdline_parser.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/*
* Copyright (C) 2019 SUSE Software Solutions Germany GmbH
*
* This file is part of klp-ccp.
*
* klp-ccp is free software: you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* klp-ccp is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with klp-ccp. If not, see <https://www.gnu.org/licenses/>.
*/
#include <cstring>
#include <cassert>
#include <algorithm>
#include "gcc_cmdline_parser.hh"
#include "cmdline_except.hh"
using namespace klp::ccp;
gcc_cmdline_parser::gcc_cmdline_parser(const gcc_version version) noexcept
: _version(version)
{}
void gcc_cmdline_parser::register_table(const option * const table)
{
std::size_t nentries = 0;
for (const option *entry = table; entry->name; ++entry) {
++nentries;
assert(entry == table || std::strcmp((entry - 1)->name, entry->name) <= 0);
assert(!(entry->arg & option::arg_joined_or_missing) ||
entry->arg == option::arg_joined_or_missing);
}
_tables.push_back(std::make_pair(table, nentries));
}
gcc_cmdline_parser::decoded_opts_type
gcc_cmdline_parser::operator()(const int argc, const char *argv[]) const
{
decoded_opts_type decoded_opts;
auto &&prune_conflicting_opts =
[&](const option &o) {
if (!o.negative)
return;
// GCC options can have a "Negative" property for specifying
// superseded options effectively to be removed from the command
// line. The Negative property is propagated in that it forms
// chains (including circles) of conflicting options. First collect
// a list of options conflicing with the given one.
std::vector<std::string> conflicts;
const char *n = o.negative;
while (n &&
(std::find(conflicts.cbegin(), conflicts.cend(), std::string{n}) ==
conflicts.cend())) {
conflicts.emplace_back(n);
const option * const o = find_option(n).first;
assert(o);
n = o->negative;
}
// And purge all conflicting options from the command line
// assembled so far.
auto it = decoded_opts.begin();
while (it != decoded_opts.end()) {
if (it->o &&
std::find(conflicts.cbegin(), conflicts.cend(),
std::string{it->o->name}) != conflicts.cend()) {
it = decoded_opts.erase(it);
} else {
++it;
}
}
};
int optind = 0;
while (optind < argc) {
if (argv[optind][0] != '-') {
decoded_opts.emplace_back(nullptr, nullptr, argv[optind], false);
++optind;
continue;
}
bool negative = false;
const char *cur_arg = argv[optind];
const option *o = nullptr, *t = nullptr;
std::tie(o, t) = find_option(cur_arg + 1);
if (!o &&
(cur_arg[1] == 'f' || cur_arg[1] == 'W' ||
cur_arg[1] == 'm') &&
cur_arg[2] == 'n' && cur_arg[3] == 'o' && cur_arg[4] == '-') {
negative = true;
const std::size_t cur_arg_len = std::strlen(cur_arg);
std::string name;
name.reserve(cur_arg_len - 1 - 3);
name.push_back(cur_arg[1]);
name.append(&cur_arg[5], cur_arg + cur_arg_len);
std::tie(o, t) = find_option(name.c_str());
}
if (!o || (negative && o->reject_negative)) {
throw cmdline_except{
std::string("unrecognized gcc option '") + argv[optind] + "'"
};
}
cur_arg += 1 + std::strlen(o->name);
if (negative)
cur_arg += 3;
if (*cur_arg == '\0')
cur_arg = nullptr;
if (o->arg != option::argument::arg_joined_or_missing) {
if (cur_arg) {
if (!(o->arg & option::argument::arg_joined)) {
throw cmdline_except{
(std::string("unrecognized gcc option '") +
argv[optind] + "'")
};
}
} else {
if (o->arg & option::argument::arg_separate &&
optind + 1 < argc) {
cur_arg = argv[optind + 1];
++optind;
} else if (o->arg != option::argument::arg_none &&
o->arg != option::argument::arg_joined_or_missing) {
throw cmdline_except{
(std::string("missing argument to gcc option '") +
argv[optind] + "'")
};
}
}
}
while (o->alias.name) {
prune_conflicting_opts(*o);
const option::alias_type a = o->alias;
std::tie(o, t) = find_option(a.name);
assert(o);
if (a.neg_arg) {
assert(!cur_arg);
if (negative) {
negative = false;
cur_arg = a.neg_arg;
} else {
cur_arg = a.pos_arg;
}
} else if (a.pos_arg) {
assert(!negative);
assert(!cur_arg);
cur_arg = a.pos_arg;
}
}
prune_conflicting_opts(*o);
decoded_opts.emplace_back(o, t, cur_arg, negative);
++optind;
}
return decoded_opts;
}
std::pair<const gcc_cmdline_parser::option*, const gcc_cmdline_parser::option*>
gcc_cmdline_parser::find_option(const char *s) const noexcept
{
const option *o = nullptr, *t = nullptr;
std::size_t o_name_len = 0;
for (const auto &_t : _tables) {
const option * const _o = _find_option(s, _t);
if (_o) {
const std::size_t _o_name_len = std::strlen(_o->name);
if (_o_name_len > o_name_len) {
o = _o;
t = _t.first;
o_name_len = _o_name_len;
}
}
}
return std::make_pair(o, t);
}
const gcc_cmdline_parser::option*
gcc_cmdline_parser::_find_option(const char *s, const _table_type &table)
const noexcept
{
// Find the longest match within table, if any.
// table is assumed to be sorted by option name.
const option * const opts_begin = table.first;
const option * const opts_end = opts_begin + table.second;
// Find the last applicable option for which ->name equals the
// beginning of s. If any exists, it will be the last in the table
// applicable to the specified GCC version which strcmp()s <=
// s. Find the first option whose name compares > s. The matching
// one, if any, will be the first option before that one which is
// applicable to the specified GCC version.
const option *o =
std::upper_bound(opts_begin, opts_end, s,
[&](const char *_s, const option &entry) {
return (std::strcmp(_s, entry.name) < 0);
});
if (o == opts_begin)
return nullptr;
// Find the first preceeding option applicable to
// the specified GCC version.
while (o != opts_begin &&
!((o - 1)->min_gcc_version <= _version &&
_version <= (o - 1)->max_gcc_version)) {
--o;
}
if (o == opts_begin)
return nullptr;
--o;
// And see if it is a prefix of s.
const std::size_t o_name_len = std::strlen(o->name);
if (std::strlen(s) < o_name_len)
return nullptr;
if (!std::equal(o->name, o->name + o_name_len, s))
return nullptr;
return o;
}