diff options
Diffstat (limited to 'src/third_party/boost-1.69.0/boost/test/utils/runtime/cla/parser.hpp')
-rw-r--r-- | src/third_party/boost-1.69.0/boost/test/utils/runtime/cla/parser.hpp | 625 |
1 files changed, 625 insertions, 0 deletions
diff --git a/src/third_party/boost-1.69.0/boost/test/utils/runtime/cla/parser.hpp b/src/third_party/boost-1.69.0/boost/test/utils/runtime/cla/parser.hpp new file mode 100644 index 00000000000..bd3df7090c5 --- /dev/null +++ b/src/third_party/boost-1.69.0/boost/test/utils/runtime/cla/parser.hpp @@ -0,0 +1,625 @@ +// (C) Copyright Gennadiy Rozental 2001. +// Use, modification, and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/test for the library home page. +// +//!@file +//!@brief CLA parser +// *************************************************************************** + +#ifndef BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP +#define BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP + +// Boost.Test Runtime parameters +#include <boost/test/utils/runtime/argument.hpp> +#include <boost/test/utils/runtime/modifier.hpp> +#include <boost/test/utils/runtime/parameter.hpp> + +#include <boost/test/utils/runtime/cla/argv_traverser.hpp> + +// Boost.Test +#include <boost/test/utils/foreach.hpp> +#include <boost/test/utils/algorithm.hpp> +#include <boost/test/detail/throw_exception.hpp> +#include <boost/test/detail/global_typedef.hpp> + +#include <boost/algorithm/cxx11/all_of.hpp> // !! ?? unnecessary after cxx11 + +// STL +// !! ?? #include <unordered_set> +#include <set> +#include <iostream> + +#include <boost/test/detail/suppress_warnings.hpp> + +namespace boost { +namespace runtime { +namespace cla { + +// ************************************************************************** // +// ************** runtime::cla::parameter_trie ************** // +// ************************************************************************** // + +namespace rt_cla_detail { + +struct parameter_trie; +typedef shared_ptr<parameter_trie> parameter_trie_ptr; +typedef std::map<char,parameter_trie_ptr> trie_per_char; +typedef std::vector<boost::reference_wrapper<parameter_cla_id const> > param_cla_id_list; + +struct parameter_trie { + parameter_trie() : m_has_final_candidate( false ) {} + + /// If subtrie corresponding to the char c exists returns it otherwise creates new + parameter_trie_ptr make_subtrie( char c ) + { + trie_per_char::const_iterator it = m_subtrie.find( c ); + + if( it == m_subtrie.end() ) + it = m_subtrie.insert( std::make_pair( c, parameter_trie_ptr( new parameter_trie ) ) ).first; + + return it->second; + } + + /// Creates series of sub-tries per characters in a string + parameter_trie_ptr make_subtrie( cstring s ) + { + parameter_trie_ptr res; + + BOOST_TEST_FOREACH( char, c, s ) + res = (res ? res->make_subtrie( c ) : make_subtrie( c )); + + return res; + } + + /// Registers candidate parameter for this subtrie. If final, it needs to be unique + void add_candidate_id( parameter_cla_id const& param_id, basic_param_ptr param_candidate, bool final ) + { + BOOST_TEST_I_ASSRT( !m_has_final_candidate && (!final || m_id_candidates.empty()), + conflicting_param() << "Parameter cla id " << param_id.m_tag << " conflicts with the " + << "parameter cla id " << m_id_candidates.back().get().m_tag ); + + m_has_final_candidate = final; + m_id_candidates.push_back( ref(param_id) ); + + if( m_id_candidates.size() == 1 ) + m_param_candidate = param_candidate; + else + m_param_candidate.reset(); + } + + /// Gets subtrie for specified char if present or nullptr otherwise + parameter_trie_ptr get_subtrie( char c ) const + { + trie_per_char::const_iterator it = m_subtrie.find( c ); + + return it != m_subtrie.end() ? it->second : parameter_trie_ptr(); + } + + // Data members + trie_per_char m_subtrie; + param_cla_id_list m_id_candidates; + basic_param_ptr m_param_candidate; + bool m_has_final_candidate; +}; + +// ************************************************************************** // +// ************** runtime::cla::report_foreing_token ************** // +// ************************************************************************** // + +static void +report_foreing_token( cstring program_name, cstring token ) +{ + std::cerr << "Boost.Test WARNING: token \"" << token << "\" does not correspond to the Boost.Test argument \n" + << " and should be placed after all Boost.Test arguments and the -- separator.\n" + << " For example: " << program_name << " --random -- " << token << "\n"; +} + +} // namespace rt_cla_detail + +// ************************************************************************** // +// ************** runtime::cla::parser ************** // +// ************************************************************************** // + +class parser { +public: + /// Initializes a parser and builds internal trie representation used for + /// parsing based on the supplied parameters +#ifndef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS + template<typename Modifiers=nfp::no_params_type> + parser( parameters_store const& parameters, Modifiers const& m = nfp::no_params ) +#else + template<typename Modifiers> + parser( parameters_store const& parameters, Modifiers const& m ) +#endif + { + nfp::opt_assign( m_end_of_param_indicator, m, end_of_params ); + nfp::opt_assign( m_negation_prefix, m, negation_prefix ); + + BOOST_TEST_I_ASSRT( algorithm::all_of( m_end_of_param_indicator.begin(), + m_end_of_param_indicator.end(), + parameter_cla_id::valid_prefix_char ), + invalid_cla_id() << "End of parameters indicator can only consist of prefix characters." ); + + BOOST_TEST_I_ASSRT( algorithm::all_of( m_negation_prefix.begin(), + m_negation_prefix.end(), + parameter_cla_id::valid_name_char ), + invalid_cla_id() << "Negation prefix can only consist of prefix characters." ); + + build_trie( parameters ); + } + + // input processing method + int + parse( int argc, char** argv, runtime::arguments_store& res ) + { + // save program name for help message + m_program_name = argv[0]; + cstring path_sep( "\\/" ); + + cstring::iterator it = unit_test::utils::find_last_of( m_program_name.begin(), m_program_name.end(), + path_sep.begin(), path_sep.end() ); + if( it != m_program_name.end() ) + m_program_name.trim_left( it + 1 ); + + // Set up the traverser + argv_traverser tr( argc, (char const**)argv ); + + // Loop till we reach end of input + while( !tr.eoi() ) { + cstring curr_token = tr.current_token(); + + cstring prefix; + cstring name; + cstring value_separator; + bool negative_form = false; + + // Perform format validations and split the argument into prefix, name and separator + // False return value indicates end of params indicator is met + if( !validate_token_format( curr_token, prefix, name, value_separator, negative_form ) ) { + // get rid of "end of params" token + tr.next_token(); + break; + } + + // Locate trie corresponding to found prefix and skip it in the input + trie_ptr curr_trie = m_param_trie[prefix]; + + if( !curr_trie ) { + // format_error() << "Unrecognized parameter prefix in the argument " << tr.current_token() + rt_cla_detail::report_foreing_token( m_program_name, curr_token ); + tr.save_token(); + continue; + } + + curr_token.trim_left( prefix.size() ); + + // Locate parameter based on a name and skip it in the input + locate_result locate_res = locate_parameter( curr_trie, name, curr_token ); + parameter_cla_id const& found_id = locate_res.first; + basic_param_ptr found_param = locate_res.second; + + if( negative_form ) { + BOOST_TEST_I_ASSRT( found_id.m_negatable, + format_error( found_param->p_name ) + << "Parameter tag " << found_id.m_tag << " is not negatable." ); + + curr_token.trim_left( m_negation_prefix.size() ); + } + + curr_token.trim_left( name.size() ); + + bool should_go_to_next = true; + cstring value; + + + // Skip validations if parameter has optional value and we are at the end of token + if( !value_separator.is_empty() || !found_param->p_has_optional_value ) { + + // we are given a separator or there is no optional value + + // Validate and skip value separator in the input + BOOST_TEST_I_ASSRT( found_id.m_value_separator == value_separator, + format_error( found_param->p_name ) + << "Invalid separator for the parameter " + << found_param->p_name + << " in the argument " << tr.current_token() ); + + curr_token.trim_left( value_separator.size() ); + + // Deduce value source + value = curr_token; + if( value.is_empty() ) { + tr.next_token(); + value = tr.current_token(); + } + + BOOST_TEST_I_ASSRT( !value.is_empty(), + format_error( found_param->p_name ) + << "Missing an argument value for the parameter " + << found_param->p_name + << " in the argument " << tr.current_token() ); + } + else if( (value_separator.is_empty() && found_id.m_value_separator.empty()) ) { + // Deduce value source + value = curr_token; + if( value.is_empty() ) { + tr.next_token(); // tokenization broke the value, we check the next one + + if(!found_param->p_has_optional_value) { + // there is no separator and there is no optional value + // we look for the value on the next token + // example "-t XXXX" (no default) + // and we commit this value as being the passed value + value = tr.current_token(); + } + else { + // there is no separator and the value is optional + // we check the next token + // example "-c" (defaults to true) + // and commit this as the value if this is not a token + cstring value_check = tr.current_token(); + + cstring prefix_test, name_test, value_separator_test; + bool negative_form_test; + if( validate_token_format( value_check, prefix_test, name_test, value_separator_test, negative_form_test ) + && m_param_trie[prefix_test]) { + // this is a token, we consume what we have + should_go_to_next = false; + } + else { + // this is a value, we commit it + value = value_check; + } + } + } + } + + // Validate against argument duplication + BOOST_TEST_I_ASSRT( !res.has( found_param->p_name ) || found_param->p_repeatable, + duplicate_arg( found_param->p_name ) + << "Duplicate argument value for the parameter " + << found_param->p_name + << " in the argument " << tr.current_token() ); + + // Produce argument value + found_param->produce_argument( value, negative_form, res ); + + if(should_go_to_next) { + tr.next_token(); + } + } + + // generate the remainder and return it's size + return tr.remainder(); + } + + // help/usage/version + void + version( std::ostream& ostr ) + { + ostr << "Boost.Test module "; + +#if defined(BOOST_TEST_MODULE) + // we do not want to refer to the master test suite there + ostr << '\'' << BOOST_TEST_STRINGIZE( BOOST_TEST_MODULE ).trim( "\"" ) << "' "; +#endif + + ostr << "in executable '" << m_program_name << "'\n"; + ostr << "Compiled from Boost version " + << BOOST_VERSION/100000 << "." + << BOOST_VERSION/100 % 1000 << "." + << BOOST_VERSION % 100 ; + ostr << " with "; +#if defined(BOOST_TEST_INCLUDED) + ostr << "header-only inclusion of"; +#elif defined(BOOST_TEST_DYN_LINK) + ostr << "dynamic linking to"; +#else + ostr << "static linking to"; +#endif + ostr << " Boost.Test\n"; + ostr << "- Compiler: " << BOOST_COMPILER << '\n' + << "- Platform: " << BOOST_PLATFORM << '\n' + << "- STL : " << BOOST_STDLIB; + ostr << std::endl; + } + + void + usage(std::ostream& ostr, + cstring param_name = cstring(), + bool use_color = true) + { + namespace utils = unit_test::utils; + namespace ut_detail = unit_test::ut_detail; + + if( !param_name.is_empty() ) { + basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second; + param->usage( ostr, m_negation_prefix ); + } + else { + ostr << "\n The program '" << m_program_name << "' is a Boost.test module containing unit tests."; + + { + BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL ); + ostr << "\n\n Usage\n "; + } + + { + BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN ); + ostr << m_program_name << " [Boost.Test argument]... "; + } + if( !m_end_of_param_indicator.empty() ) { + BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW ); + ostr << '[' << m_end_of_param_indicator << " [custom test module argument]...]"; + } + } + + ostr << "\n\n Use\n "; + { + + BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN ); + ostr << m_program_name << " --help"; + } + ostr << "\n or "; + { + BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::GREEN ); + ostr << m_program_name << " --help=<parameter name>"; + } + ostr << "\n for detailed help on Boost.Test parameters.\n"; + } + + void + help(std::ostream& ostr, + parameters_store const& parameters, + cstring param_name, + bool use_color = true) + { + namespace utils = unit_test::utils; + namespace ut_detail = unit_test::ut_detail; + + if( !param_name.is_empty() ) { + basic_param_ptr param = locate_parameter( m_param_trie[help_prefix], param_name, "" ).second; + param->help( ostr, m_negation_prefix, use_color); + return; + } + + usage(ostr, cstring(), use_color); + + ostr << "\n\n"; + { + BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL ); + ostr << " Command line flags:\n"; + } + runtime::commandline_pretty_print( + ostr, + " ", + "The command line flags of Boost.Test are listed below. " + "All parameters are optional. You can specify parameter value either " + "as a command line argument or as a value of its corresponding environment " + "variable. If a flag is specified as a command line argument and an environment variable " + "at the same time, the command line takes precedence. " + "The command line argument " + "support name guessing, and works with shorter names as long as those are not ambiguous." + ); + + if( !m_end_of_param_indicator.empty() ) { + ostr << "\n\n All the arguments after the '"; + { + BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::YELLOW ); + ostr << m_end_of_param_indicator; + } + ostr << "' are ignored by Boost.Test."; + } + + + { + BOOST_TEST_SCOPE_SETCOLOR( use_color, ostr, term_attr::BRIGHT, term_color::ORIGINAL ); + ostr << "\n\n Environment variables:\n"; + } + runtime::commandline_pretty_print( + ostr, + " ", + "Every argument listed below may also be set by a corresponding environment" + "variable. For an argument '--argument_x=<value>', the corresponding " + "environment variable is 'BOOST_TEST_ARGUMENT_X=value" + ); + + + + ostr << "\n\n The following parameters are supported:\n"; + + BOOST_TEST_FOREACH( + parameters_store::storage_type::value_type const&, + v, + parameters.all() ) + { + basic_param_ptr param = v.second; + ostr << "\n"; + param->usage( ostr, m_negation_prefix, use_color); + } + + } + +private: + typedef rt_cla_detail::parameter_trie_ptr trie_ptr; + typedef rt_cla_detail::trie_per_char trie_per_char; + typedef std::map<cstring,trie_ptr> str_to_trie; + + void + build_trie( parameters_store const& parameters ) + { + // Iterate over all parameters + BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) { + basic_param_ptr param = v.second; + + // Register all parameter's ids in trie. + BOOST_TEST_FOREACH( parameter_cla_id const&, id, param->cla_ids() ) { + // This is the trie corresponding to the prefix. + trie_ptr next_trie = m_param_trie[id.m_prefix]; + if( !next_trie ) + next_trie = m_param_trie[id.m_prefix] = trie_ptr( new rt_cla_detail::parameter_trie ); + + // Build the trie, by following name's characters + // and register this parameter as candidate on each level + for( size_t index = 0; index < id.m_tag.size(); ++index ) { + next_trie = next_trie->make_subtrie( id.m_tag[index] ); + + next_trie->add_candidate_id( id, param, index == (id.m_tag.size() - 1) ); + } + } + } + } + + bool + validate_token_format( cstring token, cstring& prefix, cstring& name, cstring& separator, bool& negative_form ) + { + // Match prefix + cstring::iterator it = token.begin(); + while( it != token.end() && parameter_cla_id::valid_prefix_char( *it ) ) + ++it; + + prefix.assign( token.begin(), it ); + + if( prefix.empty() ) + return true; + + // Match name + while( it != token.end() && parameter_cla_id::valid_name_char( *it ) ) + ++it; + + name.assign( prefix.end(), it ); + + if( name.empty() ) { + if( prefix == m_end_of_param_indicator ) + return false; + + BOOST_TEST_I_THROW( format_error() << "Invalid format for an actual argument " << token ); + } + + // Match value separator + while( it != token.end() && parameter_cla_id::valid_separator_char( *it ) ) + ++it; + + separator.assign( name.end(), it ); + + // Match negation prefix + negative_form = !m_negation_prefix.empty() && ( name.substr( 0, m_negation_prefix.size() ) == m_negation_prefix ); + if( negative_form ) + name.trim_left( m_negation_prefix.size() ); + + return true; + } + + // C++03: cannot have references as types + typedef std::pair<parameter_cla_id, basic_param_ptr> locate_result; + + locate_result + locate_parameter( trie_ptr curr_trie, cstring name, cstring token ) + { + std::vector<trie_ptr> typo_candidates; + std::vector<trie_ptr> next_typo_candidates; + trie_ptr next_trie; + + BOOST_TEST_FOREACH( char, c, name ) { + if( curr_trie ) { + // locate next subtrie corresponding to the char + next_trie = curr_trie->get_subtrie( c ); + + if( next_trie ) + curr_trie = next_trie; + else { + // Initiate search for typo candicates. We will account for 'wrong char' typo + // 'missing char' typo and 'extra char' typo + BOOST_TEST_FOREACH( trie_per_char::value_type const&, typo_cand, curr_trie->m_subtrie ) { + // 'wrong char' typo + typo_candidates.push_back( typo_cand.second ); + + // 'missing char' typo + if( (next_trie = typo_cand.second->get_subtrie( c )) ) + typo_candidates.push_back( next_trie ); + } + + // 'extra char' typo + typo_candidates.push_back( curr_trie ); + + curr_trie.reset(); + } + } + else { + // go over existing typo candidates and see if they are still viable + BOOST_TEST_FOREACH( trie_ptr, typo_cand, typo_candidates ) { + trie_ptr next_typo_cand = typo_cand->get_subtrie( c ); + + if( next_typo_cand ) + next_typo_candidates.push_back( next_typo_cand ); + } + + next_typo_candidates.swap( typo_candidates ); + next_typo_candidates.clear(); + } + } + + if( !curr_trie ) { + std::vector<cstring> typo_candidate_names; + std::set<parameter_cla_id const*> unique_typo_candidate; // !! ?? unordered_set + typo_candidate_names.reserve( typo_candidates.size() ); +// !! ?? unique_typo_candidate.reserve( typo_candidates.size() ); + + BOOST_TEST_FOREACH( trie_ptr, trie_cand, typo_candidates ) { + // avoid ambiguos candidate trie + if( trie_cand->m_id_candidates.size() > 1 ) + continue; + + BOOST_TEST_FOREACH( parameter_cla_id const&, param_cand, trie_cand->m_id_candidates ) { + if( !unique_typo_candidate.insert( ¶m_cand ).second ) + continue; + + typo_candidate_names.push_back( param_cand.m_tag ); + } + } + +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES + BOOST_TEST_I_THROW( unrecognized_param( std::move(typo_candidate_names) ) + << "An unrecognized parameter in the argument " + << token ); +#else + BOOST_TEST_I_THROW( unrecognized_param( typo_candidate_names ) + << "An unrecognized parameter in the argument " + << token ); +#endif + } + + if( curr_trie->m_id_candidates.size() > 1 ) { + std::vector<cstring> amb_names; + BOOST_TEST_FOREACH( parameter_cla_id const&, param_id, curr_trie->m_id_candidates ) + amb_names.push_back( param_id.m_tag ); + +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES + BOOST_TEST_I_THROW( ambiguous_param( std::move( amb_names ) ) + << "An ambiguous parameter name in the argument " << token ); +#else + BOOST_TEST_I_THROW( ambiguous_param( amb_names ) + << "An ambiguous parameter name in the argument " << token ); +#endif + } + + return locate_result( curr_trie->m_id_candidates.back().get(), curr_trie->m_param_candidate ); + } + + // Data members + cstring m_program_name; + std::string m_end_of_param_indicator; + std::string m_negation_prefix; + str_to_trie m_param_trie; +}; + +} // namespace cla +} // namespace runtime +} // namespace boost + +#include <boost/test/detail/enable_warnings.hpp> + +#endif // BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP |