]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/boost/test/utils/runtime/cla/parser.hpp
update sources to v12.2.3
[ceph.git] / ceph / src / boost / boost / test / utils / runtime / cla / parser.hpp
1 // (C) Copyright Gennadiy Rozental 2001.
2 // Use, modification, and distribution are subject to the
3 // Boost Software License, Version 1.0. (See accompanying file
4 // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5
6 // See http://www.boost.org/libs/test for the library home page.
7 //
8 //!@file
9 //!@brief CLA parser
10 // ***************************************************************************
11
12 #ifndef BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
13 #define BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP
14
15 // Boost.Test Runtime parameters
16 #include <boost/test/utils/runtime/argument.hpp>
17 #include <boost/test/utils/runtime/modifier.hpp>
18 #include <boost/test/utils/runtime/parameter.hpp>
19
20 #include <boost/test/utils/runtime/cla/argv_traverser.hpp>
21
22 // Boost.Test
23 #include <boost/test/utils/foreach.hpp>
24 #include <boost/test/utils/algorithm.hpp>
25 #include <boost/test/detail/throw_exception.hpp>
26 #include <boost/test/detail/global_typedef.hpp>
27
28 #include <boost/algorithm/cxx11/all_of.hpp> // !! ?? unnecessary after cxx11
29
30 // STL
31 // !! ?? #include <unordered_set>
32 #include <set>
33 #include <iostream>
34
35 #include <boost/test/detail/suppress_warnings.hpp>
36
37 namespace boost {
38 namespace runtime {
39 namespace cla {
40
41 // ************************************************************************** //
42 // ************** runtime::cla::parameter_trie ************** //
43 // ************************************************************************** //
44
45 namespace rt_cla_detail {
46
47 struct parameter_trie;
48 typedef shared_ptr<parameter_trie> parameter_trie_ptr;
49 typedef std::map<char,parameter_trie_ptr> trie_per_char;
50 typedef std::vector<boost::reference_wrapper<parameter_cla_id const> > param_cla_id_list;
51
52 struct parameter_trie {
53 parameter_trie() : m_has_final_candidate( false ) {}
54
55 /// If subtrie corresponding to the char c exists returns it otherwise creates new
56 parameter_trie_ptr make_subtrie( char c )
57 {
58 trie_per_char::const_iterator it = m_subtrie.find( c );
59
60 if( it == m_subtrie.end() )
61 it = m_subtrie.insert( std::make_pair( c, parameter_trie_ptr( new parameter_trie ) ) ).first;
62
63 return it->second;
64 }
65
66 /// Creates series of sub-tries per characters in a string
67 parameter_trie_ptr make_subtrie( cstring s )
68 {
69 parameter_trie_ptr res;
70
71 BOOST_TEST_FOREACH( char, c, s )
72 res = (res ? res->make_subtrie( c ) : make_subtrie( c ));
73
74 return res;
75 }
76
77 /// Registers candidate parameter for this subtrie. If final, it needs to be unique
78 void add_candidate_id( parameter_cla_id const& param_id, basic_param_ptr param_candidate, bool final )
79 {
80 BOOST_TEST_I_ASSRT( !m_has_final_candidate && (!final || m_id_candidates.empty()),
81 conflicting_param() << "Parameter cla id " << param_id.m_tag << " conflicts with the "
82 << "parameter cla id " << m_id_candidates.back().get().m_tag );
83
84 m_has_final_candidate = final;
85 m_id_candidates.push_back( ref(param_id) );
86
87 if( m_id_candidates.size() == 1 )
88 m_param_candidate = param_candidate;
89 else
90 m_param_candidate.reset();
91 }
92
93 /// Gets subtrie for specified char if present or nullptr otherwise
94 parameter_trie_ptr get_subtrie( char c ) const
95 {
96 trie_per_char::const_iterator it = m_subtrie.find( c );
97
98 return it != m_subtrie.end() ? it->second : parameter_trie_ptr();
99 }
100
101 // Data members
102 trie_per_char m_subtrie;
103 param_cla_id_list m_id_candidates;
104 basic_param_ptr m_param_candidate;
105 bool m_has_final_candidate;
106 };
107
108 // ************************************************************************** //
109 // ************** runtime::cla::report_foreing_token ************** //
110 // ************************************************************************** //
111
112 static void
113 report_foreing_token( cstring program_name, cstring token )
114 {
115 std::cerr << "Boost.Test WARNING: token \"" << token << "\" does not correspond to the Boost.Test argument \n"
116 << " and should be placed after all Boost.Test arguments and the -- separator.\n"
117 << " For example: " << program_name << " --random -- " << token << "\n";
118 }
119
120 } // namespace rt_cla_detail
121
122 // ************************************************************************** //
123 // ************** runtime::cla::parser ************** //
124 // ************************************************************************** //
125
126 class parser {
127 public:
128 /// Initializes a parser and builds internal trie representation used for
129 /// parsing based on the supplied parameters
130 #ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS
131 template<typename Modifiers=nfp::no_params_type>
132 parser( parameters_store const& parameters, Modifiers const& m = nfp::no_params )
133 #else
134 template<typename Modifiers>
135 parser( parameters_store const& parameters, Modifiers const& m )
136 #endif
137 {
138 nfp::opt_assign( m_end_of_param_indicator, m, end_of_params );
139 nfp::opt_assign( m_negation_prefix, m, negation_prefix );
140
141 BOOST_TEST_I_ASSRT( algorithm::all_of( m_end_of_param_indicator.begin(),
142 m_end_of_param_indicator.end(),
143 parameter_cla_id::valid_prefix_char ),
144 invalid_cla_id() << "End of parameters indicator can only consist of prefix characters." );
145
146 BOOST_TEST_I_ASSRT( algorithm::all_of( m_negation_prefix.begin(),
147 m_negation_prefix.end(),
148 parameter_cla_id::valid_name_char ),
149 invalid_cla_id() << "Negation prefix can only consist of prefix characters." );
150
151 build_trie( parameters );
152 }
153
154 // input processing method
155 int
156 parse( int argc, char** argv, runtime::arguments_store& res )
157 {
158 // save program name for help message
159 m_program_name = argv[0];
160 cstring path_sep( "\\/" );
161
162 cstring::iterator it = unit_test::utils::find_last_of( m_program_name.begin(), m_program_name.end(),
163 path_sep.begin(), path_sep.end() );
164 if( it != m_program_name.end() )
165 m_program_name.trim_left( it + 1 );
166
167 // Set up the traverser
168 argv_traverser tr( argc, (char const**)argv );
169
170 // Loop till we reach end of input
171 while( !tr.eoi() ) {
172 cstring curr_token = tr.current_token();
173
174 cstring prefix;
175 cstring name;
176 cstring value_separator;
177 bool negative_form = false;
178
179 // Perform format validations and split the argument into prefix, name and separator
180 // False return value indicates end of params indicator is met
181 if( !validate_token_format( curr_token, prefix, name, value_separator, negative_form ) ) {
182 // get rid of "end of params" token
183 tr.next_token();
184 break;
185 }
186
187 // Locate trie corresponding to found prefix and skip it in the input
188 trie_ptr curr_trie = m_param_trie[prefix];
189
190 if( !curr_trie ) {
191 // format_error() << "Unrecognized parameter prefix in the argument " << tr.current_token()
192 rt_cla_detail::report_foreing_token( m_program_name, curr_token );
193 tr.save_token();
194 continue;
195 }
196
197 curr_token.trim_left( prefix.size() );
198
199 // Locate parameter based on a name and skip it in the input
200 locate_result locate_res = locate_parameter( curr_trie, name, curr_token );
201 parameter_cla_id const& found_id = locate_res.first;
202 basic_param_ptr found_param = locate_res.second;
203
204 if( negative_form ) {
205 BOOST_TEST_I_ASSRT( found_id.m_negatable,
206 format_error( found_param->p_name )
207 << "Parameter tag " << found_id.m_tag << " is not negatable." );
208
209 curr_token.trim_left( m_negation_prefix.size() );
210 }
211
212 curr_token.trim_left( name.size() );
213
214 cstring value;
215
216 // Skip validations if parameter has optional value and we are at the end of token
217 if( !value_separator.is_empty() || !found_param->p_has_optional_value ) {
218 // Validate and skip value separator in the input
219 BOOST_TEST_I_ASSRT( found_id.m_value_separator == value_separator,
220 format_error( found_param->p_name )
221 << "Invalid separator for the parameter "
222 << found_param->p_name
223 << " in the argument " << tr.current_token() );
224
225 curr_token.trim_left( value_separator.size() );
226
227 // Deduce value source
228 value = curr_token;
229 if( value.is_empty() ) {
230 tr.next_token();
231 value = tr.current_token();
232 }
233
234 BOOST_TEST_I_ASSRT( !value.is_empty(),
235 format_error( found_param->p_name )
236 << "Missing an argument value for the parameter "
237 << found_param->p_name
238 << " in the argument " << tr.current_token() );
239 }
240
241 // Validate against argument duplication
242 BOOST_TEST_I_ASSRT( !res.has( found_param->p_name ) || found_param->p_repeatable,
243 duplicate_arg( found_param->p_name )
244 << "Duplicate argument value for the parameter "
245 << found_param->p_name
246 << " in the argument " << tr.current_token() );
247
248 // Produce argument value
249 found_param->produce_argument( value, negative_form, res );
250
251 tr.next_token();
252 }
253
254 // generate the remainder and return it's size
255 return tr.remainder();
256 }
257
258 // help/usage/version
259 void
260 version( std::ostream& ostr )
261 {
262 ostr << "Boost.Test module ";
263
264 #if defined(BOOST_TEST_MODULE)
265 // we do not want to refer to the master test suite there
266 ostr << '\'' << BOOST_TEST_STRINGIZE( BOOST_TEST_MODULE ).trim( "\"" ) << "' ";
267 #endif
268
269 ostr << "in executable '" << m_program_name << "'\n";
270 ostr << "Compiled from Boost version "
271 << BOOST_VERSION/100000 << "."
272 << BOOST_VERSION/100 % 1000 << "."
273 << BOOST_VERSION % 100 ;
274 ostr << " with ";
275 #if defined(BOOST_TEST_INCLUDED)
276 ostr << "single header inclusion of";
277 #elif defined(BOOST_TEST_DYN_LINK)
278 ostr << "dynamic linking to";
279 #else
280 ostr << "static linking to";
281 #endif
282 ostr << " Boost.Test\n";
283 ostr << "- Compiler: " << BOOST_COMPILER << '\n'
284 << "- Platform: " << BOOST_PLATFORM << '\n'
285 << "- STL : " << BOOST_STDLIB;
286 ostr << std::endl;
287 }
288
289 void
290 usage( std::ostream& ostr, cstring param_name = cstring() )
291 {
292 if( !param_name.is_empty() ) {
293 basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second;
294 param->usage( ostr, m_negation_prefix );
295 }
296 else {
297 ostr << "Usage: " << m_program_name << " [Boost.Test argument]... ";
298 if( !m_end_of_param_indicator.empty() )
299 ostr << m_end_of_param_indicator << " [custom test module argument]...";
300 ostr << "\n";
301 }
302
303 ostr << "\nFor detailed help on Boost.Test parameters use:\n"
304 << " " << m_program_name << " --help\n"
305 << "or\n"
306 << " " << m_program_name << " --help=<parameter name>\n";
307 }
308
309 void
310 help( std::ostream& ostr, parameters_store const& parameters, cstring param_name )
311 {
312 if( !param_name.is_empty() ) {
313 basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second;
314 param->help( ostr, m_negation_prefix );
315 return;
316 }
317
318 ostr << "Usage: " << m_program_name << " [Boost.Test argument]... ";
319 if( !m_end_of_param_indicator.empty() )
320 ostr << m_end_of_param_indicator << " [custom test module argument]...";
321
322 ostr << "\n\nBoost.Test arguments correspond to parameters listed below. "
323 "All parameters are optional. You can use specify parameter value either "
324 "as a command line argument or as a value of corresponding environment "
325 "variable. In case if argument for the same parameter is specified in both "
326 "places, command line is taking precedence. Command line argument format "
327 "supports parameter name guessing, so you can use any unambiguous "
328 "prefix to identify a parameter.";
329 if( !m_end_of_param_indicator.empty() )
330 ostr << " All the arguments after the " << m_end_of_param_indicator << " are ignored by the Boost.Test.";
331
332 ostr << "\n\nBoost.Test supports following parameters:\n";
333
334 BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) {
335 basic_param_ptr param = v.second;
336
337 param->usage( ostr, m_negation_prefix );
338 }
339
340 ostr << "\nUse --help=<parameter name> to display detailed help for specific parameter.\n";
341 }
342
343 private:
344 typedef rt_cla_detail::parameter_trie_ptr trie_ptr;
345 typedef rt_cla_detail::trie_per_char trie_per_char;
346 typedef std::map<cstring,trie_ptr> str_to_trie;
347
348 void
349 build_trie( parameters_store const& parameters )
350 {
351 // Iterate over all parameters
352 BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) {
353 basic_param_ptr param = v.second;
354
355 // Register all parameter's ids in trie.
356 BOOST_TEST_FOREACH( parameter_cla_id const&, id, param->cla_ids() ) {
357 // This is the trie corresponding to the prefix.
358 trie_ptr next_trie = m_param_trie[id.m_prefix];
359 if( !next_trie )
360 next_trie = m_param_trie[id.m_prefix] = trie_ptr( new rt_cla_detail::parameter_trie );
361
362 // Build the trie, by following name's characters
363 // and register this parameter as candidate on each level
364 for( size_t index = 0; index < id.m_tag.size(); ++index ) {
365 next_trie = next_trie->make_subtrie( id.m_tag[index] );
366
367 next_trie->add_candidate_id( id, param, index == (id.m_tag.size() - 1) );
368 }
369 }
370 }
371 }
372
373 bool
374 validate_token_format( cstring token, cstring& prefix, cstring& name, cstring& separator, bool& negative_form )
375 {
376 // Match prefix
377 cstring::iterator it = token.begin();
378 while( it != token.end() && parameter_cla_id::valid_prefix_char( *it ) )
379 ++it;
380
381 prefix.assign( token.begin(), it );
382
383 if( prefix.empty() )
384 return true;
385
386 // Match name
387 while( it != token.end() && parameter_cla_id::valid_name_char( *it ) )
388 ++it;
389
390 name.assign( prefix.end(), it );
391
392 if( name.empty() ) {
393 if( prefix == m_end_of_param_indicator )
394 return false;
395
396 BOOST_TEST_I_THROW( format_error() << "Invalid format for an actual argument " << token );
397 }
398
399 // Match value separator
400 while( it != token.end() && parameter_cla_id::valid_separator_char( *it ) )
401 ++it;
402
403 separator.assign( name.end(), it );
404
405 // Match negation prefix
406 negative_form = !m_negation_prefix.empty() && ( name.substr( 0, m_negation_prefix.size() ) == m_negation_prefix );
407 if( negative_form )
408 name.trim_left( m_negation_prefix.size() );
409
410 return true;
411 }
412
413 // C++03: cannot have references as types
414 typedef std::pair<parameter_cla_id, basic_param_ptr> locate_result;
415
416 locate_result
417 locate_parameter( trie_ptr curr_trie, cstring name, cstring token )
418 {
419 std::vector<trie_ptr> typo_candidates;
420 std::vector<trie_ptr> next_typo_candidates;
421 trie_ptr next_trie;
422
423 BOOST_TEST_FOREACH( char, c, name ) {
424 if( curr_trie ) {
425 // locate next subtrie corresponding to the char
426 next_trie = curr_trie->get_subtrie( c );
427
428 if( next_trie )
429 curr_trie = next_trie;
430 else {
431 // Initiate search for typo candicates. We will account for 'wrong char' typo
432 // 'missing char' typo and 'extra char' typo
433 BOOST_TEST_FOREACH( trie_per_char::value_type const&, typo_cand, curr_trie->m_subtrie ) {
434 // 'wrong char' typo
435 typo_candidates.push_back( typo_cand.second );
436
437 // 'missing char' typo
438 if( (next_trie = typo_cand.second->get_subtrie( c )) )
439 typo_candidates.push_back( next_trie );
440 }
441
442 // 'extra char' typo
443 typo_candidates.push_back( curr_trie );
444
445 curr_trie.reset();
446 }
447 }
448 else {
449 // go over existing typo candidates and see if they are still viable
450 BOOST_TEST_FOREACH( trie_ptr, typo_cand, typo_candidates ) {
451 trie_ptr next_typo_cand = typo_cand->get_subtrie( c );
452
453 if( next_typo_cand )
454 next_typo_candidates.push_back( next_typo_cand );
455 }
456
457 next_typo_candidates.swap( typo_candidates );
458 next_typo_candidates.clear();
459 }
460 }
461
462 if( !curr_trie ) {
463 std::vector<cstring> typo_candidate_names;
464 std::set<parameter_cla_id const*> unique_typo_candidate; // !! ?? unordered_set
465 typo_candidate_names.reserve( typo_candidates.size() );
466 // !! ?? unique_typo_candidate.reserve( typo_candidates.size() );
467
468 BOOST_TEST_FOREACH( trie_ptr, trie_cand, typo_candidates ) {
469 // avoid ambiguos candidate trie
470 if( trie_cand->m_id_candidates.size() > 1 )
471 continue;
472
473 BOOST_TEST_FOREACH( parameter_cla_id const&, param_cand, trie_cand->m_id_candidates ) {
474 if( !unique_typo_candidate.insert( &param_cand ).second )
475 continue;
476
477 typo_candidate_names.push_back( param_cand.m_tag );
478 }
479 }
480
481 #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
482 BOOST_TEST_I_THROW( unrecognized_param( std::move(typo_candidate_names) )
483 << "An unrecognized parameter in the argument "
484 << token );
485 #else
486 BOOST_TEST_I_THROW( unrecognized_param( typo_candidate_names )
487 << "An unrecognized parameter in the argument "
488 << token );
489 #endif
490 }
491
492 if( curr_trie->m_id_candidates.size() > 1 ) {
493 std::vector<cstring> amb_names;
494 BOOST_TEST_FOREACH( parameter_cla_id const&, param_id, curr_trie->m_id_candidates )
495 amb_names.push_back( param_id.m_tag );
496
497 #ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
498 BOOST_TEST_I_THROW( ambiguous_param( std::move( amb_names ) )
499 << "An ambiguous parameter name in the argument " << token );
500 #else
501 BOOST_TEST_I_THROW( ambiguous_param( amb_names )
502 << "An ambiguous parameter name in the argument " << token );
503 #endif
504 }
505
506 return locate_result( curr_trie->m_id_candidates.back().get(), curr_trie->m_param_candidate );
507 }
508
509 // Data members
510 cstring m_program_name;
511 std::string m_end_of_param_indicator;
512 std::string m_negation_prefix;
513 str_to_trie m_param_trie;
514 };
515
516 } // namespace cla
517 } // namespace runtime
518 } // namespace boost
519
520 #include <boost/test/detail/enable_warnings.hpp>
521
522 #endif // BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP