diff options
Diffstat (limited to 'boost/test/utils/runtime/cla/parser.hpp')
-rw-r--r-- | boost/test/utils/runtime/cla/parser.hpp | 515 |
1 files changed, 424 insertions, 91 deletions
diff --git a/boost/test/utils/runtime/cla/parser.hpp b/boost/test/utils/runtime/cla/parser.hpp index ffe09e4adc..effde33a52 100644 --- a/boost/test/utils/runtime/cla/parser.hpp +++ b/boost/test/utils/runtime/cla/parser.hpp @@ -1,158 +1,491 @@ -// (C) Copyright Gennadiy Rozental 2005-2014. +// (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 : $RCSfile$ -// -// Version : $Revision$ -// -// Description : defines parser - public interface for CLA parsing and accessing +//!@file +//!@brief CLA parser // *************************************************************************** #ifndef BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP #define BOOST_TEST_UTILS_RUNTIME_CLA_PARSER_HPP -// Boost.Runtime.Parameter -#include <boost/test/utils/runtime/config.hpp> -#include <boost/test/utils/runtime/fwd.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/fwd.hpp> -#include <boost/test/utils/runtime/cla/modifier.hpp> #include <boost/test/utils/runtime/cla/argv_traverser.hpp> -// Boost -#include <boost/optional.hpp> +// Boost.Test +#include <boost/test/utils/foreach.hpp> +#include <boost/test/utils/algorithm.hpp> +#include <boost/test/detail/throw_exception.hpp> -// STL -#include <list> +#include <boost/algorithm/cxx11/all_of.hpp> // !! ?? unnecessary after cxx11 -namespace boost { +// STL +// !! ?? #include <unordered_set> +#include <set> +#include <iostream> -namespace BOOST_TEST_UTILS_RUNTIME_PARAM_NAMESPACE { +#include <boost/test/detail/suppress_warnings.hpp> +namespace boost { +namespace runtime { namespace cla { // ************************************************************************** // -// ************** runtime::cla::parser ************** // +// ************** runtime::cla::parameter_trie ************** // // ************************************************************************** // -namespace cla_detail { +namespace rt_cla_detail { -template<typename Modifier> -class global_mod_parser { -public: - global_mod_parser( parser& p, Modifier const& m ) - : m_parser( p ) - , m_modifiers( m ) - {} - - template<typename Param> - global_mod_parser const& - operator<<( shared_ptr<Param> param ) const +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 ) { - param->accept_modifier( m_modifiers ); + trie_per_char::const_iterator it = m_subtrie.find( c ); - m_parser << param; + if( it == m_subtrie.end() ) + it = m_subtrie.insert( std::make_pair( c, parameter_trie_ptr( new parameter_trie ) ) ).first; - return *this; + return it->second; } -private: - // Data members; - parser& m_parser; - Modifier const& m_modifiers; + /// 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: - typedef std::list<parameter_ptr>::const_iterator param_iterator; - typedef std::list<parameter_ptr>::size_type param_size_type; - - // Constructor - explicit parser( cstring program_name = cstring() ); + /// 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 ); - // parameter list construction interface - parser& operator<<( parameter_ptr param ); + 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." ); - // parser and global parameters modifiers - template<typename Modifier> - cla_detail::global_mod_parser<Modifier> - operator-( Modifier const& m ) - { - nfp::optionally_assign( m_traverser.p_separator.value, m, input_separator ); - nfp::optionally_assign( m_traverser.p_ignore_mismatch.value, m, ignore_mismatch_m ); + 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." ); - return cla_detail::global_mod_parser<Modifier>( *this, m ); + build_trie( parameters ); } // input processing method - void parse( int& argc, char_type** argv ); + 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() ); + + 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 ) { + // 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() ); + } + + // 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 ); + + tr.next_token(); + } + + // generate the remainder and return it's size + return tr.remainder(); + } - // parameters access - param_iterator first_param() const; - param_iterator last_param() const; - param_size_type num_params() const { return m_parameters.size(); } - void reset(); + // help/usage + void + usage( std::ostream& ostr, cstring param_name = cstring() ) + { + 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 << "Usage: " << m_program_name << " [Boost.Test argument]... "; + if( !m_end_of_param_indicator.empty() ) + ostr << m_end_of_param_indicator << " [custom test module argument]..."; + ostr << "\n"; + } + + ostr << "\nFor detailed help on Boost.Test parameters use:\n" + << " " << m_program_name << " --help\n" + << "or\n" + << " " << m_program_name << " --help=<parameter name>\n"; + } - // arguments access - const_argument_ptr operator[]( cstring string_id ) const; - cstring get( cstring string_id ) const; + void + help( std::ostream& ostr, parameters_store const& parameters, cstring param_name ) + { + 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 ); + return; + } + + ostr << "Usage: " << m_program_name << " [Boost.Test argument]... "; + if( !m_end_of_param_indicator.empty() ) + ostr << m_end_of_param_indicator << " [custom test module argument]..."; + + ostr << "\n\nBoost.Test arguments correspond to parameters listed below. " + "All parameters are optional. You can use specify parameter value either " + "as a command line argument or as a value of corresponding environment " + "variable. In case if argument for the same parameter is specified in both " + "places, command line is taking precedence. Command line argument format " + "supports parameter name guessing, so you can use any unambiguous " + "prefix to identify a parameter."; + if( !m_end_of_param_indicator.empty() ) + ostr << " All the arguments after the " << m_end_of_param_indicator << " are ignored by the Boost.Test."; + + ostr << "\n\nBoost.Test supports following parameters:\n"; + + BOOST_TEST_FOREACH( parameters_store::storage_type::value_type const&, v, parameters.all() ) { + basic_param_ptr param = v.second; + + param->usage( ostr, m_negation_prefix ); + } + + ostr << "\nUse --help=<parameter name> to display detailed help for specific parameter.\n"; + } - template<typename T> - T const& get( cstring string_id ) const +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 ) { - return arg_value<T>( valid_argument( string_id ) ); + // 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) ); + } + } + } } - template<typename T> - void get( cstring string_id, boost::optional<T>& res ) const + bool + validate_token_format( cstring token, cstring& prefix, cstring& name, cstring& separator, bool& negative_form ) { - const_argument_ptr actual_arg = (*this)[string_id]; + // Match prefix + cstring::iterator it = token.begin(); + while( it != token.end() && parameter_cla_id::valid_prefix_char( *it ) ) + ++it; - if( actual_arg ) - res = arg_value<T>( *actual_arg ); - else - res.reset(); - } + prefix.assign( token.begin(), it ); - // help/usage - void usage( out_stream& ostr ); - void help( out_stream& ostr ); + if( prefix.empty() ) + return true; -private: - argument const& valid_argument( cstring string_id ) const; + // Match name + while( it != token.end() && parameter_cla_id::valid_name_char( *it ) ) + ++it; - // Data members - argv_traverser m_traverser; - std::list<parameter_ptr> m_parameters; - dstring m_program_name; -}; + name.assign( prefix.end(), it ); -//____________________________________________________________________________// + if( name.empty() ) { + if( prefix == m_end_of_param_indicator ) + return false; -} // namespace cla + BOOST_TEST_I_THROW( format_error() << "Invalid format for an actual argument " << token ); + } -} // namespace BOOST_TEST_UTILS_RUNTIME_PARAM_NAMESPACE + // Match value separator + while( it != token.end() && parameter_cla_id::valid_separator_char( *it ) ) + ++it; -} // namespace boost + separator.assign( name.end(), it ); -#ifndef BOOST_TEST_UTILS_RUNTIME_PARAM_OFFLINE + // 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() ); -#ifndef BOOST_TEST_UTILS_RUNTIME_PARAM_INLINE -# define BOOST_TEST_UTILS_RUNTIME_PARAM_INLINE inline -#endif -# include <boost/test/utils/runtime/cla/parser.ipp> + 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 |