corelib/tests/srd-parser.cc

607 lines
18 KiB
C++
Raw Normal View History

#include <lw/lib/SRDParser.hh>
#include <lw/lib/SRDText.hh>
#include <cppunit/extensions/HelperMacros.h>
using namespace lw;
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 lw::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" ) );
}