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