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