diff --git a/cmake/unit_testing.cmake b/cmake/unit_testing.cmake index fe697b8..8f0e83a 100644 --- a/cmake/unit_testing.cmake +++ b/cmake/unit_testing.cmake @@ -31,6 +31,7 @@ add_subdirectory( src/tools/Log/test ) add_subdirectory( src/tools/disco/BaseField/test ) add_subdirectory( src/tools/disco/BaseFixedWidthField/test ) add_subdirectory( src/tools/disco/Column/test ) +add_subdirectory( src/tools/disco/Integer/test ) add_subdirectory( src/tools/disco/Real/test ) add_subdirectory( src/tools/disco/Scientific/test ) diff --git a/src/tools/disco/Integer.hpp b/src/tools/disco/Integer.hpp new file mode 100644 index 0000000..9266e21 --- /dev/null +++ b/src/tools/disco/Integer.hpp @@ -0,0 +1,99 @@ +#ifndef NJOY_TOOLS_DISCO_REAL +#define NJOY_TOOLS_DISCO_REAL + +// system includes +#include +#include + +// other includes +#include "fast_float/fast_float.h" +#include "tools/disco/BaseFixedWidthField.hpp" + +namespace njoy { +namespace tools { +namespace disco { + +/** + * @brief A class for reading fixed width data fields containing integer values + */ +template < unsigned int Width > +class Integer : public BaseFixedWidthField< Width > { + + /* fields */ + +protected: + + using BaseFixedWidthField< Width >::isSpace; + using BaseFixedWidthField< Width >::isSpaceOrTabulation; + using BaseFixedWidthField< Width >::isWhiteSpace; + using BaseFixedWidthField< Width >::isNewLine; + using BaseFixedWidthField< Width >::isEndOfFile; + using BaseFixedWidthField< Width >::skipSpaces; + using BaseFixedWidthField< Width >::skipPlusSign; + +public: + + template < typename Representation, typename Iterator > + static Representation read( Iterator& iter, const Iterator& ) { + + unsigned int position = 0; + const auto end = iter + Width; + Representation value = 0; + + skipSpaces( iter, position ); + if ( isNewLine( iter ) || isEndOfFile( iter ) || Width == position ) { + + return value; + } + + skipPlusSign( iter, position ); + if ( Width == position ) { + + throw std::runtime_error( "cannot parse invalid integer number 1" ); + } + + // we are using fast_float::from_chars instead of std::from_chars since + // not all standard c++ libraries implement the floating point version of + // std::from_chars + auto result = fast_float::from_chars( &*iter, &*end, value ); + if ( result.ec == std::errc() ) { + + auto advance = result.ptr - &*iter; + iter += advance; + position += advance; + } + else { + + throw std::runtime_error( "cannot parse invalid integer number 2" ); + } + + skipSpaces( iter, position ); + if ( Width != position ) { + + if ( ! isNewLine( iter ) && ! isEndOfFile( iter ) ) { + + throw std::runtime_error( "cannot parse invalid integer number 3" ); + } + } + + return value; + } + + template< typename Representation, typename Iterator > + static void write( Representation value, Iterator& iter ) { + + std::ostringstream buffer; + buffer << std::right << std::setw( Width ) << value; + + for ( auto b : buffer.str() ) { + + *iter++ = b; + } + } +}; + +} // disco namespace +} // tools namespace +} // njoy namespace + +#endif diff --git a/src/tools/disco/Integer/test/CMakeLists.txt b/src/tools/disco/Integer/test/CMakeLists.txt new file mode 100644 index 0000000..74eda59 --- /dev/null +++ b/src/tools/disco/Integer/test/CMakeLists.txt @@ -0,0 +1 @@ +add_cpp_test( disco.Integer Integer.test.cpp ) diff --git a/src/tools/disco/Integer/test/Integer.test.cpp b/src/tools/disco/Integer/test/Integer.test.cpp new file mode 100644 index 0000000..7646fad --- /dev/null +++ b/src/tools/disco/Integer/test/Integer.test.cpp @@ -0,0 +1,199 @@ +// include Catch2 +#include +#include +using Catch::Matchers::WithinRel; + +// what we are testing +#include "tools/disco/Integer.hpp" + +// other includes + +// convenience typedefs +using namespace njoy::tools::disco; + +SCENARIO( "Integer" ) { + + GIVEN( "strings of length 10" ) { + + THEN( "the strings are entirely consumed for a field width of equal length" ) { + + std::string string; + auto begin = string.begin(); + auto end = string.end(); + + string = " "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 0, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = " +123"; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = " 123"; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = " -123"; + begin = string.begin(); + end = string.end(); + CHECK_THAT( -123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = "+123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = "123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = "-123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( -123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = " +123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = " 123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = " -123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( -123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end ); + + string = " +123\n"; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = " +123"; + string += char{ std::char_traits::eof() }; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + } // THEN + } // GIVEN + + GIVEN( "strings of length 11" ) { + + THEN( "the strings are not entirely consumed for a field width of smaller length" ) { + + std::string string; + auto begin = string.begin(); + auto end = string.end(); + + string = " "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 0, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = " +123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = " 123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = " -123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( -123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = "+123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = "123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = "-123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( -123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = " +123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = " 123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( 123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + + string = " -123 "; + begin = string.begin(); + end = string.end(); + CHECK_THAT( -123, WithinRel( Integer< 10 >::read< double >( begin, end ) ) ); + CHECK( begin == end - 1 ); + } // THEN + } // GIVEN + + GIVEN( "integer values" ) { + + THEN( "they can be written" ) { + + int value; + std::string buffer; + auto iter = std::back_inserter( buffer ); + + value = 0; + buffer = ""; + iter = std::back_inserter( buffer ); + Integer< 12 >::write( value, iter ); + CHECK( " 0" == buffer ); + CHECK( 12 == buffer.size() ); + + value = 2; + buffer = ""; + iter = std::back_inserter( buffer ); + Integer< 12 >::write( value, iter ); + CHECK( " 2" == buffer ); + CHECK( 12 == buffer.size() ); + + value = 10; + buffer = ""; + iter = std::back_inserter( buffer ); + Integer< 12 >::write( value, iter ); + CHECK( " 10" == buffer ); + CHECK( 12 == buffer.size() ); + } // THEN + } // GIVEN +} // SCENARIO diff --git a/src/tools/disco/Real/test/Real.test.cpp b/src/tools/disco/Real/test/Real.test.cpp index 6d3b68c..bac3b1d 100644 --- a/src/tools/disco/Real/test/Real.test.cpp +++ b/src/tools/disco/Real/test/Real.test.cpp @@ -343,19 +343,6 @@ SCENARIO( "Real" ) { end = string.end(); CHECK_THAT( 100, WithinRel( Real< 10 >::read< double >( begin, end ) ) ); CHECK( begin == end - 1 ); - - string = " +123\n"; - begin = string.begin(); - end = string.end(); - CHECK_THAT( 123, WithinRel( Real< 10 >::read< double >( begin, end ) ) ); - CHECK( begin == end - 1 ); - - string = " +123"; - string += char{ std::char_traits::eof() }; - begin = string.begin(); - end = string.end(); - CHECK_THAT( 123, WithinRel( Real< 10 >::read< double >( begin, end ) ) ); - CHECK( begin == end - 1 ); } // THEN } // GIVEN } // SCENARIO