diff --git a/Exception.test/exception.cc b/Exception.test/exception.cc index e0bcc0b..2edaa86 100644 --- a/Exception.test/exception.cc +++ b/Exception.test/exception.cc @@ -10,8 +10,8 @@ static_assert( __cplusplus > 2020'00 ); namespace { - using Alepha::exports::types::argcnt_t; - using Alepha::exports::types::argvec_t; + using Alepha::Hydrogen::exports::types::argcnt_t; + using Alepha::Hydrogen::exports::types::argvec_t; } int diff --git a/Meta/is_product_type.h b/Meta/is_product_type.h index 20bb9ea..949af2a 100644 --- a/Meta/is_product_type.h +++ b/Meta/is_product_type.h @@ -4,8 +4,8 @@ static_assert( __cplusplus > 2020'00 ); #include -#include -#include +#include +#include namespace Alepha::Hydrogen::Meta { diff --git a/Meta/sequence_kind.h b/Meta/sequence_kind.h new file mode 100644 index 0000000..4d0f5b6 --- /dev/null +++ b/Meta/sequence_kind.h @@ -0,0 +1,47 @@ +static_assert( __cplusplus > 2020'00 ); + +#pragma once + +#include + +#include + +#include + +namespace Alepha::Hydrogen::Meta ::detail:: type_traits::sequence_kind +{ + inline namespace exports {} + + template< typename T > + constexpr bool is_std_array_v= false; + + template< typename T, std::size_t sz > + constexpr bool is_std_array_v< std::array< T, sz > >{ true }; + + template< typename T > + concept SequenceOfKnownKind= is_sequence_v< T > or is_std_array_v< T >; + + template< SequenceOfKnownKind Seq > + constexpr const char * + sequence_kind_f() noexcept + { + if constexpr( is_std_array_v< Seq > ) return "array"; + if constexpr( is_vector_v< Seq > ) return "vector"; + if constexpr( is_deque_v< Seq > ) return "deque"; + if constexpr( is_list_v< Seq > ) return "list"; + if constexpr( is_string_v< Seq > ) return "string"; + if constexpr( is_forward_list_v< Seq > ) return "forward_list"; + } + + namespace exports + { + template< SequenceOfKnownKind Seq > + constexpr const char *const sequence_kind_v= sequence_kind_f< Seq >(); + } +} + +namespace Alepha::Hydrogen::Meta::inline exports::inline type_traits::inline sequence_kind +{ + using namespace detail::type_traits::sequence_kind::exports; +} + diff --git a/Testing/TableTest.h b/Testing/TableTest.h index a11d55a..c9e54dc 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -9,19 +9,47 @@ static_assert( __cplusplus > 2020'00 ); #include #include #include +#include +#include #include +#include #include +#include +#include +#include +#include + #include +#include + +#include +#include + #include + +#include #include namespace Alepha::Hydrogen::Testing ::detail:: table_test { inline namespace exports { - template< auto > struct TableTest; + enum class OutputMode { All, Relaxed }; + } + + template< typename F > + concept FunctionVariable= + requires( const F &f ) + { + { std::function{ f } }; + }; + + + namespace exports + { + template< FunctionVariable auto, OutputMode outputMode= OutputMode::All > struct TableTest; } inline void breakpoint() {} @@ -33,16 +61,184 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test using namespace Alepha::console::C; } + using std::begin, std::end; using namespace Utility::exports::evaluation; + using namespace std::literals::string_literals; - template< typename RetVal, typename ... Args, RetVal (*function)( Args... ) > - struct exports::TableTest< function > + template< template< typename, typename... > class Sequence, typename ... TupleArgs > + auto + withIndex( const Sequence< std::tuple< TupleArgs... > > &original ) { - using args_type= Meta::product_type_decay_t< std::tuple< Args... > >; + auto indices= evaluate <=[&] + { + std::vector< int > v{ std::distance( begin( original ), end( original ) ) }; + std::iota( begin( v ), end( v ), 0 ); + return v; + }; + + auto bindIndex= []( const auto i, const auto e ) { return std::tuple_cat( i, e ); }; + using indexed_table_entry= decltype( bindIndex( indices.front(), original.front() ) ); + std::vector< indexed_table_entry > rv; + std::transform( begin( indices ), end( indices ), begin( original ), std::back_inserter( rv ), bindIndex ); + return rv; + } + + template< OutputMode outputMode, typename T > + std::string + stringifyValue( const T &v ) + { + std::ostringstream oss; + if constexpr( false ) ; // To keep the rest of the clauses regular + else if constexpr( std::is_same_v< std::uint8_t, std::decay_t< T > > ) + { + oss << std::hex << std::setw( 2 ) << std::setfill( '0' ) << int( v ); + } + else if constexpr( std::is_same_v< bool, std::decay_t< T > > ) + { + oss << std::boolalpha << v; + } + else if constexpr( std::is_same_v< std::string, std::decay_t< T > > ) + { + oss << "(String with " << v.size() << " chars)"; + oss << '\n' << R"(""")" << '\n'; + for( const char ch: v ) + { + if( ch == '\n' ) oss << "\n"; + else if( std::isalnum( ch ) or std::ispunct( ch ) or ( ch == ' ' ) ) oss << ch; + else oss << "<\\0x" << std::hex << std::setw( 2 ) << std::setfill( '0' ) << unsigned( ch ) << '>'; + } + oss << '\n' << R"(""")"; + } + else if constexpr( Meta::is_ostreamable_v< T > ) + { + return boost::lexical_cast< std::string >( v ); + } + else if constexpr( Meta::is_optional_v< T > ) + { + return v.has_value() ? stringifyValue< outputMode >( v.value() ) : ""s; + } + else if constexpr( Meta::is_sequence_v< T > ) + { + if constexpr( outputMode == OutputMode::Relaxed and not Meta::is_ostreamable_v< typename T::value_type > ) + { + oss << ""; + } + else + { + oss << Meta::sequence_kind_v< T > << "(" << v.size() << " elements):\n{" << std::endl; + + int index= 0; + for( const auto &elem: v ) oss << "\t" << index++ << ": " << stringifyValue< outputMode >( elem ) << "," << std::endl; + oss << "}" << std::endl; + } + } + else if constexpr( Meta::is_pair_v< T > ) + { + const auto &[ first, second ]= v; + return stringifyValue< outputMode >( std::tie( first, second ) ); + } + else if constexpr( Meta::is_tuple_v< T > ) + { + oss << '['; + tuple_for_each( v ) <=[&oss, first= true]( const auto &elem ) mutable + { + if( not first ) oss << ", "; + first= false; + oss << std::endl << stringifyValue< outputMode >( elem ); + }; + oss << std::endl << ']' << std::endl; + } + else if constexpr( std::is_same_v< T, TotalOrder > ) + { + if( false ) ; // For alignment + else if( v == TotalOrder::less ) oss << "less"; + else if ( v == TotalOrder::equal ) oss << "equal"; + else if( v == TotalOrder::greater ) oss << "greater"; + else throw std::logic_error( "Impossible `TotalOrder` condition." ); + } + else + { + static_assert( dependent_value< false, T >, "One of the types used in the testing table does not support stringification." ); + } + return std::move( oss ).str(); + } + + inline void + printDebuggingForStrings( const std::string &witness, const std::string &expected ) + { + const std::size_t amount= std::min( witness.size(), expected.size() ); + if( witness.size() != expected.size() ) + { + std::cout << "Witness string size did not match the expected string size. Only mismatches found in the first " + << amount << " characters will be printed." << std::endl; + } + + for( int i= 0; i < amount; ++i ) + { + if( witness.at( i ) == expected.at( i ) ) continue; + std::cout << "Mismatch at index: " << i << std::endl; + std::cout << "witness: " << witness.at( i ) << std::endl; + std::cout << "expected: " << expected.at( i ) << std::endl; + } + } + + template< OutputMode outputMode, typename T > + void + printDebugging( const T &witness, const T &expected ) + { + if constexpr( std::is_same_v< std::string, std::decay_t< T > > ) + { + printDebuggingForStrings( witness, expected ); + } + else if constexpr( Meta::is_sequence_v< T > ) + { + if constexpr( std::is_same_v< std::string, typename T::value_type > ) + { + if( witness.size() == expected.size() ) for( std::size_t i= 0; i < witness.size(); ++i ) + { + if( witness.at( i ) != expected.at( i ) ) printDebuggingForStrings( witness.at( i ), expected.at( i ) ); + } + } + else + { + if( witness.size() != expected.size() ) + { + std::cout << "Witness sequence size of " << witness.size() << " did not match the expected sequence size of " + << expected.size() << std::endl; + } + + auto next= std::make_pair( begin( witness ), begin( expected ) ); + bool first= true; + while( next.first != end( witness ) and next.second != end( expected ) ) + { + if( not first ) + { + std::cout << "Mismatch at witness index " << std::distance( begin( witness ), next.first ) << " and " + << "expected index " << std::distance( begin( expected ), next.second ) << std::endl; + ++next.first; ++next.second; + } + first= false; + next= std::mismatch( next.first, end( witness ), next.second, end( expected ) ); + } + } + } + + std::cout << std::endl + << "computed: " << stringifyValue< outputMode >( witness ) << std::endl + << "expected: " << stringifyValue< outputMode >( expected ) << std::endl << std::endl; + } + + template< FunctionVariable auto function, OutputMode outputMode > + struct exports::TableTest + { + using function_traits_type= function_traits< decltype( function ) >; + + using args_type= Meta::product_type_decay_t< typename function_traits_type::args_type >; + using return_type= typename function_traits_type::return_type; struct Cases { - using TestDescription= std::tuple< std::string, args_type, RetVal >; + using TestDescription= std::tuple< std::string, args_type, return_type >; std::vector< TestDescription > tests; @@ -58,10 +254,13 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test { if( C::debugCaseTypes ) std::cerr << boost::core::demangle( typeid( params ).name() ) << std::endl; breakpoint(); - if( std::apply( function, params ) != expected ) + const auto witness= std::apply( function, params ); + const auto result= witness == expected; + if( not result ) { std::cout << C::red << " FAILURE" << C::normal << ": " << comment << std::endl; ++failureCount; + printDebugging< outputMode >( witness, expected ); } else std::cout << C::green << " SUCCESS" << C::normal << ": " << comment << std::endl; } diff --git a/comparisons.test/0.cc b/comparisons.test/0.cc index 032329a..e804ec9 100644 --- a/comparisons.test/0.cc +++ b/comparisons.test/0.cc @@ -8,8 +8,8 @@ static_assert( __cplusplus > 2020'00 ); namespace { - using Alepha::exports::types::argcnt_t; - using Alepha::exports::types::argvec_t; + using Alepha::Hydrogen::exports::types::argcnt_t; + using Alepha::Hydrogen::exports::types::argvec_t; } int @@ -21,8 +21,8 @@ main( const argcnt_t argcnt, const argvec_t argvec ) namespace { using namespace Alepha::Testing::exports; - using namespace Alepha::exports::comparisons; - using namespace Alepha::exports::capabilities; + using namespace Alepha::Hydrogen::exports::comparisons; + using namespace Alepha::Hydrogen::exports::capabilities; template < @@ -41,7 +41,7 @@ namespace }; using Date= Date_core<>; - namespace detail= Alepha::detail::capabilities; + namespace detail= Alepha::Hydrogen::detail::capabilities; namespace Meta= Alepha::Meta; diff --git a/template_for_each.h b/template_for_each.h new file mode 100644 index 0000000..b20528d --- /dev/null +++ b/template_for_each.h @@ -0,0 +1,86 @@ +static_assert( __cplusplus > 2020'00 ); + +#pragma once + +#include + +#include "Concepts.h" + +namespace Alepha::inline Cavorite ::detail:: template_for_each_module +{ + inline namespace exports + { + constexpr void tuple_for_each( const std::tuple<> &, const Functional auto ) noexcept {} + + template< typename ... Args, typename Function > + constexpr void + tuple_for_each( const std::tuple< Args... > &tuple, Function body ) + noexcept + ( + ( ... and noexcept( body( std::declval< const Args & >() ) ) ) + ) + { + const auto callWrapper= [&body]( auto &&element ) { body( element ); return nullptr; }; + + auto loop_body_handler= [&]( auto &&... tuple_elements ) + { + std::nullptr_t expansion[]= { callWrapper( tuple_elements )... }; + + std::ignore= expansion; + }; + + std::apply( loop_body_handler, tuple ); + } + + // Apply type_identity to all tuple elements + template< typename > struct type_identify_tuple; + + template< typename T > + using type_identify_tuple_t= typename type_identify_tuple< T >::type; + + template<> struct type_identify_tuple< std::tuple<> > { using type= std::tuple<>; }; + + template< typename ... Args > + struct type_identify_tuple< std::tuple< Args... > > + { + using type= std::tuple< std::type_identity< Args >... >; + }; + + // Nicer for-each syntax helper: + template< typename Tuple > + struct for_each_syntax_adaptor + { + Tuple &tuple; + + template< typename Function > + constexpr void + operator <= ( Function &&func ) noexcept( noexcept( tuple_for_each( tuple, std::forward< Function >( func ) ) ) ) + { + return tuple_for_each( tuple, std::forward< Function >( func ) ); + } + + constexpr operator decltype( std::ignore ) () const= delete; + }; + + template< typename Tuple > + [[nodiscard]] + constexpr auto + tuple_for_each( Tuple &tuple ) noexcept + { + return for_each_syntax_adaptor< Tuple >{ tuple }; + } + + template< typename Tuple > + [[nodiscard]] + constexpr auto + tuple_for_each( const Tuple &tuple ) noexcept + { + return for_each_syntax_adaptor< const Tuple >{ tuple }; + } + } +} + +namespace Alepha::Cavorite::inline exports::inline template_for_each_module +{ + using namespace detail::template_for_each_module::exports; +} diff --git a/word_wrap.cpp b/word_wrap.cpp index 0a0f76f..654d58e 100644 --- a/word_wrap.cpp +++ b/word_wrap.cpp @@ -23,14 +23,8 @@ namespace Alepha::Cavorite ::detail:: word_wrap { if( currentLineWidth + word.size() > maximumWidth ) { - //std::cerr << "Going to newline on word: " << word << "(currentLineWidth= " << currentLineWidth << ")" << std::endl; result+= '\n'; - //const auto orig= result.size(); std::fill_n( back_inserter( result ), nextLineOffset, ' ' ); - //assert( orig + nextLineOffset == result.size() ); - //std::cerr << "orig: " << orig << " nextLineOffset: " << nextLineOffset << " result: " << result.size() << std::endl; - - //result+= '%'; return nextLineOffset; } else return currentLineWidth; @@ -58,9 +52,17 @@ namespace Alepha::Cavorite ::detail:: word_wrap { if( ch == '\n' ) { - std::ignore= putWord( std::move( word ), result, lineLength ); + const auto prev= lineLength; + const auto size= word.size(); + lineLength= putWord( std::move( word ), result, lineLength ); word.clear(); - lineLength= 0; + result+= '\n'; + if( lineLength == prev + size ) + { + std::fill_n( back_inserter( result ), nextLineOffset, ' ' ); + lineLength= nextLineOffset; + } + else lineLength= 0; } else if( ch == ' ' ) { diff --git a/word_wrap.test/0.cc b/word_wrap.test/0.cc index bc9db65..0a6c03c 100644 --- a/word_wrap.test/0.cc +++ b/word_wrap.test/0.cc @@ -24,21 +24,21 @@ static auto init= Alepha::Utility::enroll <=[] { "Simple", { "Hello", 100, 0 }, "Hello" }, { "space should be kept", { "Hello ", 10, 0 }, "Hello " }, { "space should be dropped", { "Hello ", 5, 0 }, "Hello" }, - { "all spaces should be dropped", { "Hello ", 5, 0 }, "Hello" }, - { "two spaces should be kept", { "Hello ", 7, 0 }, "Hello " }, + { "all spaces should be dropped", { "Hello ", 5, 0 }, "Hello" }, + { "two spaces should be kept", { "Hello ", 7, 0 }, "Hello " }, { "too narrow, forces wrap anyway", { "Hello", 4, 0 }, "\nHello" }, { "too narrow, forces wrap anyway, drops space", { "Hello ", 4, 0 }, "\nHello" }, - { "too narrow, forces wrap anyway, drops spaces", { "Hello ", 4, 0 }, "\nHello" }, + { "too narrow, forces wrap anyway, drops spaces", { "Hello ", 4, 0 }, "\nHello" }, }; const std::string helloWorld= "Hello World"; "word_wrap.noindent.two_words"_test <=TableTest< Alepha::wordWrap >::Cases { { "Simple", { helloWorld, 100, 0 }, helloWorld }, - { "Trailing space should be kept", { helloWorld +" ", 100, 0 }, helloWorld + " " }, - { "Trailing spaces should be kept", { helloWorld + " ", 100, 0 }, helloWorld + " " }, - { "Trailing spaces should be dropped", { helloWorld + " ", helloWorld.size(), 0 }, helloWorld }, - { "All but 2 trailing spaces dropped", { helloWorld + " ", helloWorld.size() + 2, 0 }, + { "Trailing space should be kept", { helloWorld + " ", 100, 0 }, helloWorld + " " }, + { "Trailing spaces should be kept", { helloWorld + " ", 100, 0 }, helloWorld + " " }, + { "Trailing spaces should be dropped", { helloWorld + " ", helloWorld.size(), 0 }, helloWorld }, + { "All but 2 trailing spaces dropped", { helloWorld + " ", helloWorld.size() + 2, 0 }, helloWorld + " "}, { "Split line", { helloWorld, 8, 0 }, "Hello \nWorld" }, // TODO: Should we swallow trailing spaces? @@ -117,8 +117,8 @@ static auto init= Alepha::Utility::enroll <=[] "word_wrap.indent"_test <=TableTest< Alepha::wordWrap >::Cases { { "Two word indent, simple", { "Hello World!", 8, 2 }, "Hello \n World!" }, - { "Three word indent, simple", { "Hello Wonderful World!", 16, 4 }, "Hello Wonderful \n World!" }, - { "Three word indent, corner case", { "Hello Wonderful World!", 15, 4 }, "Hello Wonderful\n World!" }, + { "Three word indent, simple", { "Hello Wonderful World!", 16, 4 }, "Hello Wonderful \n World!" }, + { "Three word indent, corner case", { "Hello Wonderful World!", 15, 4 }, "Hello Wonderful\n World!" }, { "Two word indent, extra newline", { "Hello\n\nWorld!", 8, 2 }, "Hello\n \n World!" }, { "Two word indent, one newline", { "Hello\nWorld!", 8, 2 }, "Hello\n World!" }, };