1 // Copyright 2018-2022 René Ferdinand Rivera Morell
2 // Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6
7 #ifndef LYRA_ARGUMENTS_HPP
8 #define LYRA_ARGUMENTS_HPP
9
10 #include "lyra/detail/print.hpp"
11 #include "lyra/exe_name.hpp"
12 #include "lyra/parser.hpp"
13
14 #include <functional>
15 #include <sstream>
16
17 namespace lyra {
18
19 /* tag::reference[]
20
21 [#lyra_arguments]
22 = `lyra::arguments`
23
24 A Combined parser made up of any number of parsers. Creating and using
25 one of these as a basis one can incrementally compose other parsers into this
26 one. For example:
27
28 [source]
29 ----
30 auto p = lyra::arguments();
31 std::string what;
32 float when = 0;
33 std::string where;
34 p |= lyra::opt(what, "what")["--make-it-so"]("Make it so.").required();
35 p |= lyra::opt(when. "when")["--time"]("When to do <what>.").optional();
36 p.add_argument(lyra::opt(where, "where").name("--where")
37 .help("There you are.").optional());
38 ----
39
40 */ // end::reference[]
41 class arguments : public parser
42 {
43 public:
44 // How to evaluate the collection of arguments within the limits of the
45 // cardinality.
46 enum evaluation
47 {
48 // Any of the arguments, in any order, are valid. I.e. an inclusive-or.
49 any = 0,
50 // All arguments, in sequence, matched. I.e. conjunctive-and.
51 sequence = 1
52 };
53
54 arguments() = default;
55
arguments(evaluation e)56 arguments(evaluation e)
57 : eval_mode(e)
58 {}
59
60 // Copy construction, needs to copy the the composed parsers.
61 arguments(const arguments & other);
62
63 // Compose a regular parser.
64 arguments & add_argument(parser const & p);
65 arguments & operator|=(parser const & p);
66
67 // Compose the parsers from another `arguments`.
68 arguments & add_argument(arguments const & other);
69 arguments & operator|=(arguments const & other);
70
71 // Concat composition.
72 template <typename T>
operator |(T const & other) const73 arguments operator|(T const & other) const
74 {
75 return arguments(*this) |= other;
76 }
77
78 // Parsing mode.
79 arguments & sequential();
80 arguments & inclusive();
81
82 // Access.
83 template <typename T>
84 T & get(size_t i);
85
86 // Internal..
87
get_usage_text(const option_style & style) const88 virtual std::string get_usage_text(
89 const option_style & style) const override
90 {
91 std::ostringstream os;
92 for (auto const & p : parsers)
93 {
94 std::string usage_text = p->get_usage_text(style);
95 if (usage_text.size() > 0)
96 {
97 if (os.tellp() != std::ostringstream::pos_type(0)) os << " ";
98 if (p->is_group() && p->is_optional())
99 os << "[ " << usage_text << " ]";
100 else if (p->is_group())
101 os << "{ " << usage_text << " }";
102 else if (p->is_optional())
103 os << "[" << usage_text << "]";
104 else
105 os << usage_text;
106 }
107 }
108 return os.str();
109 }
110
get_description_text(const option_style & style) const111 virtual std::string get_description_text(
112 const option_style & style) const override
113 {
114 std::ostringstream os;
115 for (auto const & p : parsers)
116 {
117 if (p->is_group()) continue;
118 auto child_description = p->get_description_text(style);
119 if (!child_description.empty()) os << child_description << "\n";
120 }
121 return os.str();
122 }
123
124 // Return a container of the individual help text for the composed parsers.
get_help_text(const option_style & style) const125 virtual help_text get_help_text(const option_style & style) const override
126 {
127 help_text text;
128 for (auto const & p : parsers)
129 {
130 if (p->is_group()) text.push_back({ "", "" });
131 auto child_help = p->get_help_text(style);
132 text.insert(text.end(), child_help.begin(), child_help.end());
133 }
134 return text;
135 }
136
cardinality() const137 virtual detail::parser_cardinality cardinality() const override
138 {
139 return { 0, 0 };
140 }
141
validate() const142 virtual result validate() const override
143 {
144 for (auto const & p : parsers)
145 {
146 auto result = p->validate();
147 if (!result) return result;
148 }
149 return result::ok();
150 }
151
parse(detail::token_iterator const & tokens,const option_style & style) const152 parse_result parse(detail::token_iterator const & tokens,
153 const option_style & style) const override
154 {
155 switch (eval_mode)
156 {
157 case any: return parse_any(tokens, style);
158 case sequence: return parse_sequence(tokens, style);
159 }
160 return parse_result::error(
161 detail::parse_state(parser_result_type::no_match, tokens),
162 "Unknown evaluation mode; not one of 'any', or 'sequence'.");
163 }
164
165 // Match in any order, any number of times. Returns an error if nothing
166 // matched.
parse_any(detail::token_iterator const & tokens,const option_style & style) const167 parse_result parse_any(
168 detail::token_iterator const & tokens, const option_style & style) const
169 {
170 LYRA_PRINT_SCOPE("arguments::parse_any");
171 LYRA_PRINT_DEBUG("(?)", get_usage_text(style),
172 "?=", tokens ? tokens.argument().name : "", "..");
173
174 struct ParserInfo
175 {
176 parser const * parser_p = nullptr;
177 size_t count = 0;
178 };
179 std::vector<ParserInfo> parser_info(parsers.size());
180 {
181 size_t i = 0;
182 for (auto const & p : parsers) parser_info[i++].parser_p = p.get();
183 }
184
185 auto result = parse_result::ok(
186 detail::parse_state(parser_result_type::matched, tokens));
187 auto error_result = parse_result::ok(
188 detail::parse_state(parser_result_type::no_match, tokens));
189 while (result.value().remainingTokens())
190 {
191 bool token_parsed = false;
192
193 for (auto & parse_info : parser_info)
194 {
195 auto parser_cardinality = parse_info.parser_p->cardinality();
196 if (parser_cardinality.is_unbounded()
197 || parse_info.count < parser_cardinality.maximum)
198 {
199 auto subparse_result = parse_info.parser_p->parse(
200 result.value().remainingTokens(), style);
201 if (!subparse_result)
202 {
203 LYRA_PRINT_DEBUG("(!)", get_usage_text(style), "!=",
204 result.value().remainingTokens().argument().name);
205 // Is the subparse error bad enough to trigger an
206 // immediate return? For example for an option syntax
207 // error.
208 if (subparse_result.has_value()
209 && subparse_result.value().type()
210 == parser_result_type::short_circuit_all)
211 return subparse_result;
212 // For not severe errors, we save the error if it's
213 // the first so that in case no other parsers match
214 // we can report the earliest problem, as that's
215 // the likeliest issue.
216 if (error_result)
217 error_result = parse_result(subparse_result);
218 }
219 else if (subparse_result
220 && subparse_result.value().type()
221 != parser_result_type::no_match)
222 {
223 LYRA_PRINT_DEBUG("(=)", get_usage_text(style), "==",
224 result.value().remainingTokens().argument().name,
225 "==>", subparse_result.value().type());
226 result = parse_result(subparse_result);
227 token_parsed = true;
228 parse_info.count += 1;
229 break;
230 }
231 }
232 }
233
234 if (result.value().type() == parser_result_type::short_circuit_all)
235 return result;
236 // If something signaled and error, and hence we didn't match/parse
237 // anything, we indicate the error.
238 if (!token_parsed && !error_result) return error_result;
239 if (!token_parsed) break;
240 }
241 // Check missing required options. For bounded arguments we check
242 // bound min and max bounds against what we parsed. For the loosest
243 // required arguments we check for only the minimum. As the upper
244 // bound could be infinite.
245 for (auto & parseInfo : parser_info)
246 {
247 auto parser_cardinality = parseInfo.parser_p->cardinality();
248 if ((parser_cardinality.is_bounded()
249 && (parseInfo.count < parser_cardinality.minimum
250 || parser_cardinality.maximum < parseInfo.count))
251 || (parser_cardinality.is_required()
252 && (parseInfo.count < parser_cardinality.minimum)))
253 {
254 return parse_result::error(result.value(),
255 "Expected: " + parseInfo.parser_p->get_usage_text(style));
256 }
257 }
258 return result;
259 }
260
parse_sequence(detail::token_iterator const & tokens,const option_style & style) const261 parse_result parse_sequence(
262 detail::token_iterator const & tokens, const option_style & style) const
263 {
264 LYRA_PRINT_SCOPE("arguments::parse_sequence");
265 LYRA_PRINT_DEBUG("(?)", get_usage_text(style),
266 "?=", tokens ? tokens.argument().name : "", "..");
267
268 struct ParserInfo
269 {
270 parser const * parser_p = nullptr;
271 size_t count = 0;
272 };
273 std::vector<ParserInfo> parser_info(parsers.size());
274 {
275 size_t i = 0;
276 for (auto const & p : parsers) parser_info[i++].parser_p = p.get();
277 }
278
279 auto result = parse_result::ok(
280 detail::parse_state(parser_result_type::matched, tokens));
281
282 // Sequential parsing means we walk through the given parsers in order
283 // and exhaust the tokens as we match persers.
284 for (std::size_t parser_i = 0; parser_i < parsers.size(); ++parser_i)
285 {
286 auto & parse_info = parser_info[parser_i];
287 auto parser_cardinality = parse_info.parser_p->cardinality();
288 // This is a greedy sequential parsing algo. As it parsers the
289 // current argument as much as possible.
290 do
291 {
292 auto subresult = parse_info.parser_p->parse(
293 result.value().remainingTokens(), style);
294 if (!subresult)
295 {
296 break;
297 }
298 if (subresult.value().type()
299 == parser_result_type::short_circuit_all)
300 {
301 return subresult;
302 }
303 if (subresult.value().type() != parser_result_type::no_match)
304 {
305 LYRA_PRINT_DEBUG("(=)", get_usage_text(style), "==",
306 result.value().remainingTokens()
307 ? result.value().remainingTokens().argument().name
308 : "",
309 "==>", subresult.value().type());
310 result = subresult;
311 parse_info.count += 1;
312 }
313 }
314 while (result.value().have_tokens()
315 && (parser_cardinality.is_unbounded()
316 || parse_info.count < parser_cardinality.maximum));
317 // Check missing required options immediately as for sequential the
318 // argument is greedy and will fully match here. For bounded
319 // arguments we check bound min and max bounds against what we
320 // parsed. For the loosest required arguments we check for only the
321 // minimum. As the upper bound could be infinite.
322 if ((parser_cardinality.is_bounded()
323 && (parse_info.count < parser_cardinality.minimum
324 || parser_cardinality.maximum < parse_info.count))
325 || (parser_cardinality.is_required()
326 && (parse_info.count < parser_cardinality.minimum)))
327 {
328 return parse_result::error(result.value(),
329 "Expected: " + parse_info.parser_p->get_usage_text(style));
330 }
331 }
332 // The return is just the last state as it contains any remaining tokens
333 // to parse.
334 return result;
335 }
336
clone() const337 virtual std::unique_ptr<parser> clone() const override
338 {
339 return make_clone<arguments>(this);
340 }
341
operator <<(std::ostream & os,arguments const & parser)342 friend std::ostream & operator<<(
343 std::ostream & os, arguments const & parser)
344 {
345 const option_style & s
346 = parser.opt_style ? *parser.opt_style : option_style::posix();
347 parser.print_help_text(os, s);
348 return os;
349 }
350
get_named(const std::string & n) const351 virtual const parser * get_named(const std::string & n) const override
352 {
353 for (auto & p : parsers)
354 {
355 const parser * result = p->get_named(n);
356 if (result) return result;
357 }
358 return nullptr;
359 }
360
361 protected:
362 std::shared_ptr<option_style> opt_style;
363
364 private:
365 std::vector<std::unique_ptr<parser>> parsers;
366 evaluation eval_mode = any;
367 };
368
369 /* tag::reference[]
370
371 [#lyra_arguments_ctor]
372 == Construction
373
374 end::reference[] */
375
376 /* tag::reference[]
377
378 [#lyra_arguments_ctor_default]
379 === Default
380
381 [source]
382 ----
383 arguments() = default;
384 ----
385
386 Default constructing a `arguments` is the starting point to adding arguments
387 and options for parsing a arguments line.
388
389 end::reference[] */
390
391 /* tag::reference[]
392
393 [#lyra_arguments_ctor_copy]
394 === Copy
395
396 [source]
397 ----
398 arguments::arguments(const arguments& other);
399 ----
400
401 end::reference[] */
arguments(const arguments & other)402 inline arguments::arguments(const arguments & other)
403 : parser(other)
404 , opt_style(other.opt_style)
405 , eval_mode(other.eval_mode)
406 {
407 for (auto & other_parser : other.parsers)
408 {
409 parsers.push_back(other_parser->clone());
410 }
411 }
412
413 /* tag::reference[]
414
415 [#lyra_arguments_specification]
416 == Specification
417
418 end::reference[] */
419
420 // ==
421
422 /* tag::reference[]
423 [#lyra_arguments_add_argument]
424 === `lyra::arguments::add_argument`
425
426 [source]
427 ----
428 arguments& arguments::add_argument(parser const& p);
429 arguments& arguments::operator|=(parser const& p);
430 arguments& arguments::add_argument(arguments const& other);
431 arguments& arguments::operator|=(arguments const& other);
432 ----
433
434 Adds the given argument parser to the considered arguments for this
435 `arguments`. Depending on the parser given it will be: directly added as an
436 argument (for `parser`), or add the parsers from another `arguments` to
437 this one.
438
439 end::reference[] */
add_argument(parser const & p)440 inline arguments & arguments::add_argument(parser const & p)
441 {
442 parsers.push_back(p.clone());
443 return *this;
444 }
operator |=(parser const & p)445 inline arguments & arguments::operator|=(parser const & p)
446 {
447 return this->add_argument(p);
448 }
add_argument(arguments const & other)449 inline arguments & arguments::add_argument(arguments const & other)
450 {
451 if (other.is_group())
452 {
453 parsers.push_back(other.clone());
454 }
455 else
456 {
457 for (auto & p : other.parsers)
458 {
459 parsers.push_back(p->clone());
460 }
461 }
462 return *this;
463 }
operator |=(arguments const & other)464 inline arguments & arguments::operator|=(arguments const & other)
465 {
466 return this->add_argument(other);
467 }
468
469 /* tag::reference[]
470 === `lyra::arguments::sequential`
471
472 [source]
473 ----
474 arguments & arguments::sequential();
475 ----
476
477 Sets the parsing mode for the arguments to "sequential". When parsing the
478 arguments they will be, greedily, consumed in the order they where added.
479 This is useful for sub-commands and structured command lines.
480
481 end::reference[] */
sequential()482 inline arguments & arguments::sequential()
483 {
484 eval_mode = sequence;
485 return *this;
486 }
487
488 /* tag::reference[]
489 === `lyra::arguments::inclusive`
490
491 [source]
492 ----
493 arguments & arguments::inclusive();
494 ----
495
496 Sets the parsing mode for the arguments to "inclusively any". This is the
497 default that attempts to match each parsed argument with all the available
498 parsers. This means that there is no ordering enforced.
499
500 end::reference[] */
inclusive()501 inline arguments & arguments::inclusive()
502 {
503 eval_mode = any;
504 return *this;
505 }
506
507 /* tag::reference[]
508 === `lyra::arguments::get`
509
510 [source]
511 ----
512 template <typename T>
513 T & arguments::get(size_t i);
514 ----
515
516 Get a modifyable reference to one of the parsers specified.
517
518 end::reference[] */
519 template <typename T>
get(size_t i)520 T & arguments::get(size_t i)
521 {
522 return static_cast<T &>(*parsers.at(i));
523 }
524
525 } // namespace lyra
526
527 #endif
528