Alien-catch
view release on metacpan or search on metacpan
src/catch.hpp view on Meta::CPAN
struct LambdaInvoker {
static_assert( std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult" );
template<typename L, typename ArgType>
static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {
return lambda( arg );
}
};
template<>
struct LambdaInvoker<void> {
template<typename L, typename ArgType>
static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {
lambda( arg );
return ParserResult::ok( ParseResultType::Matched );
}
};
template<typename ArgType, typename L>
inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult {
ArgType temp{};
auto result = convertInto( arg, temp );
return !result
? result
: LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp );
}
template<typename L>
struct BoundLambda : BoundValueRefBase {
L m_lambda;
static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {}
auto setValue( std::string const &arg ) -> ParserResult override {
return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg );
}
};
template<typename L>
struct BoundFlagLambda : BoundFlagRefBase {
L m_lambda;
static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" );
static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" );
explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {}
auto setFlag( bool flag ) -> ParserResult override {
return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag );
}
};
enum class Optionality { Optional, Required };
struct Parser;
class ParserBase {
public:
virtual ~ParserBase() = default;
virtual auto validate() const -> Result { return Result::ok(); }
virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0;
virtual auto cardinality() const -> size_t { return 1; }
auto parse( Args const &args ) const -> InternalParseResult {
return parse( args.exeName(), TokenStream( args ) );
}
};
template<typename DerivedT>
class ComposableParserImpl : public ParserBase {
public:
template<typename T>
auto operator|( T const &other ) const -> Parser;
template<typename T>
auto operator+( T const &other ) const -> Parser;
};
// Common code and state for Args and Opts
template<typename DerivedT>
class ParserRefImpl : public ComposableParserImpl<DerivedT> {
protected:
Optionality m_optionality = Optionality::Optional;
std::shared_ptr<BoundRef> m_ref;
std::string m_hint;
std::string m_description;
explicit ParserRefImpl( std::shared_ptr<BoundRef> const &ref ) : m_ref( ref ) {}
public:
template<typename T>
ParserRefImpl( T &ref, std::string const &hint )
: m_ref( std::make_shared<BoundValueRef<T>>( ref ) ),
m_hint( hint )
{}
template<typename LambdaT>
ParserRefImpl( LambdaT const &ref, std::string const &hint )
: m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ),
m_hint(hint)
{}
auto operator()( std::string const &description ) -> DerivedT & {
m_description = description;
return static_cast<DerivedT &>( *this );
}
auto optional() -> DerivedT & {
m_optionality = Optionality::Optional;
return static_cast<DerivedT &>( *this );
};
auto required() -> DerivedT & {
m_optionality = Optionality::Required;
return static_cast<DerivedT &>( *this );
};
auto isOptional() const -> bool {
return m_optionality == Optionality::Optional;
}
auto cardinality() const -> size_t override {
if( m_ref->isContainer() )
return 0;
else
return 1;
}
auto hint() const -> std::string { return m_hint; }
};
class ExeName : public ComposableParserImpl<ExeName> {
std::shared_ptr<std::string> m_name;
std::shared_ptr<BoundValueRefBase> m_ref;
template<typename LambdaT>
static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundValueRefBase> {
return std::make_shared<BoundLambda<LambdaT>>( lambda) ;
}
public:
ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {}
explicit ExeName( std::string &ref ) : ExeName() {
m_ref = std::make_shared<BoundValueRef<std::string>>( ref );
}
template<typename LambdaT>
explicit ExeName( LambdaT const& lambda ) : ExeName() {
m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda );
}
// The exe name is not parsed out of the normal tokens, but is handled specially
auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
}
auto name() const -> std::string { return *m_name; }
auto set( std::string const& newName ) -> ParserResult {
auto lastSlash = newName.find_last_of( "\\/" );
auto filename = ( lastSlash == std::string::npos )
? newName
: newName.substr( lastSlash+1 );
*m_name = filename;
if( m_ref )
return m_ref->setValue( filename );
else
return ParserResult::ok( ParseResultType::Matched );
}
};
class Arg : public ParserRefImpl<Arg> {
public:
using ParserRefImpl::ParserRefImpl;
auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override {
auto validationResult = validate();
if( !validationResult )
return InternalParseResult( validationResult );
auto remainingTokens = tokens;
auto const &token = *remainingTokens;
if( token.type != TokenType::Argument )
return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );
assert( !m_ref->isFlag() );
auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() );
auto result = valueRef->setValue( remainingTokens->token );
if( !result )
return InternalParseResult( result );
else
return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
}
};
inline auto normaliseOpt( std::string const &optName ) -> std::string {
#ifdef CATCH_PLATFORM_WINDOWS
if( optName[0] == '/' )
return "-" + optName.substr( 1 );
else
#endif
return optName;
}
class Opt : public ParserRefImpl<Opt> {
protected:
std::vector<std::string> m_optNames;
public:
template<typename LambdaT>
explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {}
explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {}
template<typename LambdaT>
Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
template<typename T>
Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
auto operator[]( std::string const &optName ) -> Opt & {
m_optNames.push_back( optName );
return *this;
}
auto getHelpColumns() const -> std::vector<HelpColumns> {
std::ostringstream oss;
bool first = true;
for( auto const &opt : m_optNames ) {
if (first)
first = false;
else
oss << ", ";
oss << opt;
}
if( !m_hint.empty() )
oss << " <" << m_hint << ">";
return { { oss.str(), m_description } };
}
auto isMatch( std::string const &optToken ) const -> bool {
auto normalisedToken = normaliseOpt( optToken );
for( auto const &name : m_optNames ) {
if( normaliseOpt( name ) == normalisedToken )
return true;
}
return false;
}
using ParserBase::parse;
auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
auto validationResult = validate();
if( !validationResult )
return InternalParseResult( validationResult );
auto remainingTokens = tokens;
if( remainingTokens && remainingTokens->type == TokenType::Option ) {
auto const &token = *remainingTokens;
if( isMatch(token.token ) ) {
if( m_ref->isFlag() ) {
auto flagRef = static_cast<detail::BoundFlagRefBase*>( m_ref.get() );
auto result = flagRef->setFlag( true );
if( !result )
return InternalParseResult( result );
if( result.value() == ParseResultType::ShortCircuitAll )
return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );
} else {
auto valueRef = static_cast<detail::BoundValueRefBase*>( m_ref.get() );
++remainingTokens;
if( !remainingTokens )
return InternalParseResult::runtimeError( "Expected argument following " + token.token );
auto const &argToken = *remainingTokens;
if( argToken.type != TokenType::Argument )
return InternalParseResult::runtimeError( "Expected argument following " + token.token );
auto result = valueRef->setValue( argToken.token );
if( !result )
return InternalParseResult( result );
if( result.value() == ParseResultType::ShortCircuitAll )
return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );
}
return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
}
}
return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );
}
auto validate() const -> Result override {
if( m_optNames.empty() )
return Result::logicError( "No options supplied to Opt" );
for( auto const &name : m_optNames ) {
if( name.empty() )
return Result::logicError( "Option name cannot be empty" );
#ifdef CATCH_PLATFORM_WINDOWS
if( name[0] != '-' && name[0] != '/' )
return Result::logicError( "Option name must begin with '-' or '/'" );
#else
if( name[0] != '-' )
return Result::logicError( "Option name must begin with '-'" );
#endif
}
return ParserRefImpl::validate();
}
};
struct Help : Opt {
Help( bool &showHelpFlag )
: Opt([&]( bool flag ) {
showHelpFlag = flag;
return ParserResult::ok( ParseResultType::ShortCircuitAll );
})
{
static_cast<Opt &>( *this )
("display usage information")
["-?"]["-h"]["--help"]
.optional();
}
};
struct Parser : ParserBase {
mutable ExeName m_exeName;
std::vector<Opt> m_options;
std::vector<Arg> m_args;
auto operator|=( ExeName const &exeName ) -> Parser & {
m_exeName = exeName;
return *this;
}
auto operator|=( Arg const &arg ) -> Parser & {
m_args.push_back(arg);
return *this;
}
auto operator|=( Opt const &opt ) -> Parser & {
m_options.push_back(opt);
return *this;
}
auto operator|=( Parser const &other ) -> Parser & {
m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end());
m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end());
return *this;
}
template<typename T>
auto operator|( T const &other ) const -> Parser {
return Parser( *this ) |= other;
}
// Forward deprecated interface with '+' instead of '|'
template<typename T>
auto operator+=( T const &other ) -> Parser & { return operator|=( other ); }
template<typename T>
auto operator+( T const &other ) const -> Parser { return operator|( other ); }
auto getHelpColumns() const -> std::vector<HelpColumns> {
std::vector<HelpColumns> cols;
for (auto const &o : m_options) {
auto childCols = o.getHelpColumns();
cols.insert( cols.end(), childCols.begin(), childCols.end() );
}
return cols;
}
void writeToStream( std::ostream &os ) const {
if (!m_exeName.name().empty()) {
os << "usage:\n" << " " << m_exeName.name() << " ";
bool required = true, first = true;
for( auto const &arg : m_args ) {
if (first)
first = false;
else
os << " ";
if( arg.isOptional() && required ) {
os << "[";
required = false;
}
os << "<" << arg.hint() << ">";
if( arg.cardinality() == 0 )
os << " ... ";
}
if( !required )
os << "]";
if( !m_options.empty() )
os << " options";
os << "\n\nwhere options are:" << std::endl;
}
auto rows = getHelpColumns();
size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH;
size_t optWidth = 0;
for( auto const &cols : rows )
optWidth = (std::max)(optWidth, cols.left.size() + 2);
optWidth = (std::min)(optWidth, consoleWidth/2);
for( auto const &cols : rows ) {
auto row =
TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +
TextFlow::Spacer(4) +
TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth );
os << row << std::endl;
}
}
friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& {
parser.writeToStream( os );
return os;
}
auto validate() const -> Result override {
for( auto const &opt : m_options ) {
auto result = opt.validate();
if( !result )
return result;
}
for( auto const &arg : m_args ) {
auto result = arg.validate();
if( !result )
return result;
}
return Result::ok();
}
using ParserBase::parse;
auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override {
struct ParserInfo {
ParserBase const* parser = nullptr;
size_t count = 0;
};
const size_t totalParsers = m_options.size() + m_args.size();
assert( totalParsers < 512 );
// ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do
ParserInfo parseInfos[512];
{
size_t i = 0;
for (auto const &opt : m_options) parseInfos[i++].parser = &opt;
for (auto const &arg : m_args) parseInfos[i++].parser = &arg;
}
m_exeName.set( exeName );
auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
while( result.value().remainingTokens() ) {
bool tokenParsed = false;
for( size_t i = 0; i < totalParsers; ++i ) {
auto& parseInfo = parseInfos[i];
if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) {
result = parseInfo.parser->parse(exeName, result.value().remainingTokens());
if (!result)
return result;
if (result.value().type() != ParseResultType::NoMatch) {
tokenParsed = true;
++parseInfo.count;
break;
}
}
}
if( result.value().type() == ParseResultType::ShortCircuitAll )
return result;
if( !tokenParsed )
return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token );
}
// !TBD Check missing required options
return result;
}
};
template<typename DerivedT>
template<typename T>
auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser {
return Parser() | static_cast<DerivedT const &>( *this ) | other;
}
( run in 0.607 second using v1.01-cache-2.11-cpan-140bd7fdf52 )