#include #include #include using namespace ebcl; class SRDParserTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE( SRDParserTest ); CPPUNIT_TEST( testTokenTypes ); CPPUNIT_TEST( testParse ); CPPUNIT_TEST( testExecStartRuleFinish ); CPPUNIT_TEST( testExecContext ); CPPUNIT_TEST( testExecContextAndRule ); CPPUNIT_TEST( testExecInterruptTop ); CPPUNIT_TEST( testExecInterruptContext ); CPPUNIT_TEST( testExecLoneEnter ); CPPUNIT_TEST( testExecLoneExit ); CPPUNIT_TEST( testExecNoContextHandler ); CPPUNIT_TEST( testParseOLBCList ); CPPUNIT_TEST( testParseOLBCContext ); CPPUNIT_TEST( testParseOLBCListThenContext ); CPPUNIT_TEST( testParseOLBCListOverrides ); CPPUNIT_TEST( testParseOLBCContextOverrides ); CPPUNIT_TEST( testParseOLBCFailure ); CPPUNIT_TEST( testErrorTopLevelJunk ); CPPUNIT_TEST( testErrorMismatchInRule ); CPPUNIT_TEST( testErrorContextJunk ); CPPUNIT_TEST( testErrorUnterminatedLists ); CPPUNIT_TEST( testErrorNoExec ); CPPUNIT_TEST( testErrorInHandlerTop ); CPPUNIT_TEST( testErrorInHandlerContext ); CPPUNIT_TEST_SUITE_END( ); static T_SRDParserDefs makeParseOnlyDefs( ); static T_SRDParserDefs makeExecDefs( ); static T_SRDParserDefs makeOLBCDefs( ); public: void testTokenTypes( ); void testParse( ); void testExecStartRuleFinish( ); void testExecContext( ); void testExecContextAndRule( ); void testExecInterruptTop( ); void testExecInterruptContext( ); void testExecLoneEnter( ); void testExecLoneExit( ); void testExecNoContextHandler( ); // Parse with Optional List Before Context // * Clear choice towards the list void testParseOLBCList( ); // * Clear choice towards the context void testParseOLBCContext( ); // * List then context, both unambiguous void testParseOLBCListThenContext( ); // * Ambiguity between list and context resolved by using the list void testParseOLBCListOverrides( ); // * Initial ambiguity between list and context resolved by using the context void testParseOLBCContextOverrides( ); // * Neither the list nor the context match the input void testParseOLBCFailure( ); void testErrorTopLevelJunk( ); void testErrorMismatchInRule( ); void testErrorContextJunk( ); void testErrorUnterminatedLists( ); void testErrorNoExec( ); void testErrorInHandlerTop( ); void testErrorInHandlerContext( ); }; CPPUNIT_TEST_SUITE_REGISTRATION( SRDParserTest ); /*----------------------------------------------------------------------------*/ namespace { // parse( input , IN/OUT parser , OUT errors ) void parse( char const* input , T_SRDParser & parser , T_SRDErrors & errors ) { T_SRDLexer lexer( T_String( "test" ) , errors , parser ); parser.start( errors ); char const* ptr = input; while ( *ptr != 0 ) { lexer.processCharacter( *ptr ++ ); } lexer.processEnd( ); } // parse( input , defs , OUT errors ) - Helper function for the tests void parse( char const* input , T_SRDParserDefs const& defs , T_SRDErrors & errors ) { T_SRDParserConfig cfg( defs ); T_SRDParser parser( cfg ); parse( input , parser , errors ); } } // namespace // M_PRINTERR_( index ) - Print an error message #define M_PRINTERR_(IDX) \ do { \ auto const& _e( errors[ IDX ] ); \ char err[ _e.error( ).size( ) + 1 ]; \ err[ sizeof( err ) - 1 ] = 0; \ memcpy( err , _e.error( ).data( ) , \ sizeof( err ) - 1 ); \ printf( "ERR %s l. %u c. %lu\n" , err , \ _e.location( ).line( ) , \ _e.location( ).character( ) ); \ } while ( 0 ) // M_CKERR_( index , string , line , character ) - Check an error #define M_CKERR_(IDX,STR,L,C) \ do { \ auto const& _e( errors[ IDX ] ); \ CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \ CPPUNIT_ASSERT_EQUAL( uint32_t( L ) , \ _e.location( ).line( ) ); \ CPPUNIT_ASSERT_EQUAL( size_t( C ) , \ _e.location( ).character( ) ); \ } while ( 0 ) // M_PARSE_( defs , input ) - Run the parser using definitions provided by // SRDParserTest::make{defs}Defs() on the specified input. #define M_PARSE_( DEFS , INPUT ) \ T_SRDErrors errors; \ T_SRDParserConfig cfg( make ##DEFS## Defs( ) ); \ T_SRDParser parser( cfg ); \ parse( INPUT , parser , errors ) /*----------------------------------------------------------------------------*/ T_SRDParserDefs SRDParserTest::makeParseOnlyDefs( ) { using namespace SRD; const T_String name( T_String::Pooled( "base" ) ); const T_String sub( T_String::Pooled( "sub" ) ); T_SRDParserDefs d( name ); d.enumeration( T_String( "enum" ) ) << "this" << "that"; d.context( name ) << ( Rule( ) << "simple" << "test" ) << ( Rule( ) << Enum( "enum" ) << "works" << ( Opt( ) << "too" ) << EnterContext( sub ) ) << ( Rule( ) << Int32( ) << "little" << "piggies" ) << ( Rule( ) << "too" << "sexy" << "for" << "the" << ( AtLeast( 0 ) << "wabbit" << "too" << "sexy" << "for" << "the" ) << "wabbiiiiit" ) ; d.context( sub ) << ( Rule( ) << "sometimes" ); return d; } T_SRDParserDefs SRDParserTest::makeExecDefs( ) { using namespace SRD; const T_String name( T_String::Pooled( "base" ) ); const T_String sub( T_String::Pooled( "sub" ) ); static const F_SRDHandler start( []( T_SRDParserData const& d ) -> bool { *( d.currentData ) = T_StringBuilder( ); d.currentData->value< T_StringBuilder >( ) << "START"; return true; } ); static const F_SRDHandler finish( []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " FINISH"; return true; } ); static const F_SRDHandler op( []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " OP(" << (* d.input)[ 1 ].longValue( ) << ')'; return true; } ); static const F_SRDHandler enter( []( T_SRDParserData const& d ) -> bool { *( d.targetData ) = T_StringBuilder( ); d.targetData->value< T_StringBuilder >( ) << " ENTER(" << d.targetContext << ')'; return true; } ); static const F_SRDHandler exit( []( T_SRDParserData const& d ) -> bool { d.targetData->value< T_StringBuilder >( ) << d.currentData->value< T_StringBuilder >( ) << " EXIT(" << d.currentContext << ')'; return true; } ); static const F_SRDHandler ctxExec( []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " CTXEXEC(" << (* d.input)[ 2 ].longValue( ) << ')'; return true; } ); static const F_SRDHandler failError( []( T_SRDParserData const& d ) -> bool { d.errors.add( "Failure on demand!" , (* d.input)[ 0 ] ); return true; } ); static const F_SRDHandler failHandler( []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " FAIL"; return false; } ); static const F_SRDHandler exitAlone( []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " LONE-EXIT(" << d.currentContext << ')'; return true; } ); static const F_SRDHandler enterAlone( []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " LONE-ENTER(" << d.targetContext << ')'; return true; } ); T_SRDParserDefs d( sub ); d << OnStart( start ) << OnFinish( finish ); d.context( sub ) << ( Rule( ) << "do-op" << Int32( ) << op ) << ( Rule( ) << "fail" << "error" << failError ) << ( Rule( ) << "fail" << "handler" << failHandler ); d.context( name , sub ) << ( Rule( ) << "enter" << EnterContext( sub ) << OnEnter( enter ) << OnExit( exit ) ) << ( Rule( ) << "enter" << "exec" << Int32( ) << ctxExec << EnterContext( sub ) << OnEnter( enter ) << OnExit( exit ) ) << ( Rule( ) << "handle" << "nothing" << EnterContext( sub ) ) << ( Rule( ) << "handle" << "only" << "exit" << EnterContext( sub ) << OnExit( exitAlone ) ) << ( Rule( ) << "handle" << "only" << "entrance" << EnterContext( sub ) << OnEnter( enterAlone ) ); d.defaultContext( name ); return d; } T_SRDParserDefs SRDParserTest::makeOLBCDefs( ) { using namespace SRD; T_SRDParserDefs d( "base" ); d << OnStart( []( T_SRDParserData const& d ) -> bool { *( d.currentData ) = T_StringBuilder( ); d.currentData->value< T_StringBuilder >( ) << "START"; return true; } ) << OnFinish( []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " FINISH"; return true; } ); d.context( "base" ) << ( Rule( ) << "olbc" << ( AtLeast( 0 ) << ( List( ) << ( Alt( ) << "non-context" << "context" ) ) ) << EnterContext( "sub" ) << OnEnter( []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " SUB"; return true; } ) << []( T_SRDParserData const& d ) -> bool { d.currentData->value< T_StringBuilder >( ) << " EXEC(" << d.input->size( ) << ')'; return true; } ) ; d.context( "sub" ) << ( Rule( ) << "context" << Opt( Word( "too" ) ) ) << ( Rule( ) << "in" << "context" ) ; return d; } /*----------------------------------------------------------------------------*/ void SRDParserTest::testTokenTypes( ) { using F_InitRule = std::function< void( T_SRDContext& ) >; using namespace ebcl::SRD; const F_InitRule rules[] = { []( T_SRDContext& c ) { c << ( Rule() << Word( ) ); } , []( T_SRDContext& c ) { c << ( Rule() << String( ) ); } , []( T_SRDContext& c ) { c << ( Rule() << Integer( ) ); } , []( T_SRDContext& c ) { c << ( Rule() << Float( ) ); } , []( T_SRDContext& c ) { c << ( Rule() << Binary( ) ); } , }; char const* const inputs[] = { "(word)" , "(\"string\")" , "(123)" , "(123.0)" , "([])" }; const uint32_t nTests( sizeof( inputs ) / sizeof( inputs[ 0 ] ) ); static_assert( nTests == sizeof( rules ) / sizeof( rules[ 0 ] ) , "bad tests" ); for ( uint32_t i = 0 ; i < nTests ; i ++ ) { T_SRDParserDefs defs( "default" ); rules[ i ]( defs.context( "default" ) ); for ( uint32_t j = 0 ; j < nTests ; j ++ ) { T_SRDErrors errors; parse( inputs[ j ] , defs , errors ); if ( i == j ) { CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); } else { CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); } } } } void SRDParserTest::testParse( ) { static const char input[] = "( simple test )\n" "( this works )\n" "( that works too )\n" "( this works ( sometimes ) )\n" "( 3 little piggies )\n" "( too sexy for the wabbit too sexy for the wabbit too sexy for the wabbiiiiit )" ; T_SRDErrors errors; parse( input , makeParseOnlyDefs( ) , errors ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); } /*----------------------------------------------------------------------------*/ void SRDParserTest::testExecStartRuleFinish( ) { M_PARSE_( Exec , "( do-op 2 )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( sb == "START OP(2) FINISH" ); } void SRDParserTest::testExecContext( ) { M_PARSE_( Exec , "( enter ( do-op 2 ) ( do-op 3 ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( T_String( sb ) == T_String( "START " "ENTER(sub) " "OP(2) " "OP(3) " "EXIT(sub) " "FINISH" ) ); } void SRDParserTest::testExecContextAndRule( ) { M_PARSE_( Exec , "( enter exec 4 ( do-op 2 ) ( do-op 3 ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( T_String( sb ) == T_String( "START " "ENTER(sub) " "OP(2) " "OP(3) " "EXIT(sub) " "CTXEXEC(4) " "FINISH" ) ); } void SRDParserTest::testExecInterruptTop( ) { M_PARSE_( Exec , "( do-op 2 ) ( fail handler )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( T_String( sb ) == T_String( "START " "OP(2) " "FAIL" ) ); } void SRDParserTest::testExecInterruptContext( ) { M_PARSE_( Exec , "( enter ( do-op 2 ) ( fail handler ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) ); } void SRDParserTest::testExecLoneEnter( ) { M_PARSE_( Exec , "( handle only entrance ( do-op 2 ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( T_String( sb ) == T_String( "START " "LONE-ENTER(sub) " "OP(2) " "FINISH" ) ); } void SRDParserTest::testExecLoneExit( ) { M_PARSE_( Exec , "( handle only exit ( do-op 2 ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( T_String( sb ) == T_String( "START " "OP(2) " "LONE-EXIT(sub) " "FINISH" ) ); } void SRDParserTest::testExecNoContextHandler( ) { M_PARSE_( Exec , "( handle nothing ( do-op 2 ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( sb == "START OP(2) FINISH" ); } /*----------------------------------------------------------------------------*/ void SRDParserTest::testParseOLBCList( ) { M_PARSE_( OLBC , "( olbc ( non-context ) ( non-context ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( sb == "START EXEC(3) FINISH" ); } void SRDParserTest::testParseOLBCContext( ) { M_PARSE_( OLBC , "( olbc ( in context ) ( in context ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( sb == "START SUB EXEC(1) FINISH" ); } void SRDParserTest::testParseOLBCListThenContext( ) { M_PARSE_( OLBC , "( olbc ( non-context ) ( in context ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( sb == "START SUB EXEC(2) FINISH" ); } void SRDParserTest::testParseOLBCListOverrides( ) { M_PARSE_( OLBC , "( olbc ( context ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( sb == "START EXEC(2) FINISH" ); } void SRDParserTest::testParseOLBCContextOverrides( ) { M_PARSE_( OLBC , "( olbc ( context too ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( sb == "START SUB EXEC(1) FINISH" ); } void SRDParserTest::testParseOLBCFailure( ) { M_PARSE_( OLBC , "( olbc ( incorrect ) )" ); CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); M_CKERR_( 0 , "'context' or 'in' expected, 'incorrect' found instead." , 1 , 10 ); } /*----------------------------------------------------------------------------*/ void SRDParserTest::testErrorTopLevelJunk( ) { static const char input[] = ")\n" "( simple test )\n" "junk\n" "( this works )"; ; T_SRDErrors errors; parse( input , makeParseOnlyDefs( ) , errors ); CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); M_CKERR_( 0 , "'(' expected, ')' found instead." , 1 , 1 ); M_CKERR_( 1 , "'(' expected, 'junk' found instead." , 3 , 1 ); } void SRDParserTest::testErrorMismatchInRule( ) { static const char input[] = "( simple piggies )\n" "( tests suck )\n" "( 3 little piggies ate a farmer )\n" "( this works when you punch it in the balls )\n" "( this works ( sort of ) )\n" ; T_SRDErrors errors; parse( input , makeParseOnlyDefs( ) , errors ); CPPUNIT_ASSERT_EQUAL( uint32_t( 5 ) , errors.size( ) ); M_CKERR_( 0 , "'test' expected, 'piggies' found instead." , 1 , 10 ); M_CKERR_( 1 , "'simple', 'too', 'enum' enum value or INT token expected, 'tests' found instead." , 2 , 3 ); M_CKERR_( 2 , "')' expected, 'ate' found instead." , 3 , 20 ); M_CKERR_( 3 , "'(', ')' or 'too' expected, 'when' found instead." , 4 , 14 ); M_CKERR_( 4 , "'sometimes' expected, 'sort' found instead." , 5 , 16 ); } void SRDParserTest::testErrorContextJunk( ) { static const char input[] = "( this works (sometimes) when you punch it (sometimes) forcefully )"; ; T_SRDErrors errors; parse( input , makeParseOnlyDefs( ) , errors ); CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); M_CKERR_( 0 , "'(' or ')' expected, 'when' found instead." , 1 , 26 ); // Second error because finding a valid rule resets error recovery M_CKERR_( 1 , "'(' or ')' expected, 'forcefully' found instead." , 1 , 56 ); } void SRDParserTest::testErrorUnterminatedLists( ) { static const char input[] = "( this works\n" "( sometimes" ; T_SRDErrors errors; parse( input , makeParseOnlyDefs( ) , errors ); CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); M_CKERR_( 0 , "Unterminated list." , 1 , 1 ); M_CKERR_( 1 , "Unterminated list." , 2 , 1 ); } /*----------------------------------------------------------------------------*/ void SRDParserTest::testErrorNoExec( ) { static const char input[] = "( enter ( do-op 2 ) ( invalid ) )" ; T_SRDErrors errors; T_SRDParserConfig cfg( makeExecDefs( ) ); T_SRDParser parser( cfg ); parse( input , parser , errors ); CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); CPPUNIT_ASSERT_THROW( parser.getData< T_StringBuilder >( ) , std::bad_cast ); } void SRDParserTest::testErrorInHandlerTop( ) { static const char input[] = "( fail error )" ; T_SRDErrors errors; T_SRDParserConfig cfg( makeExecDefs( ) ); T_SRDParser parser( cfg ); parse( input , parser , errors ); CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); M_CKERR_( 0 , "Failure on demand!" , 1 , 3 ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) ); } void SRDParserTest::testErrorInHandlerContext( ) { static const char input[] = "( enter\n( fail error ) )" ; T_SRDErrors errors; T_SRDParserConfig cfg( makeExecDefs( ) ); T_SRDParser parser( cfg ); parse( input , parser , errors ); CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); M_CKERR_( 0 , "Failure on demand!" , 2 , 3 ); T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) ); }