#include "srd-preproc-cmd-common.hh"
#include "srd-preproc-location.hh"


class SRDPreprocCmdCoreTest : public CppUnit::TestFixture
{
	CPPUNIT_TEST_SUITE( SRDPreprocCmdCoreTest );

	CPPUNIT_TEST( testRaw );
	CPPUNIT_TEST( testRawEmpty );
	CPPUNIT_TEST( testRawSkipComments );
	CPPUNIT_TEST( testRawUnterminated );

	CPPUNIT_TEST( testIfMissingArgs );
	CPPUNIT_TEST( testIfExtraArgs );
	CPPUNIT_TEST( testIfBadCondition );
	CPPUNIT_TEST( testIfBadThenBlock );
	CPPUNIT_TEST( testIfBadElseBlock );
	CPPUNIT_TEST( testIfTrueThen );
	CPPUNIT_TEST( testIfFalseThen );
	CPPUNIT_TEST( testIfTrueThenElse );
	CPPUNIT_TEST( testIfFalseThenElse );
	CPPUNIT_TEST( testIfArgSubst );
	CPPUNIT_TEST( testIfThenInnerList );
	CPPUNIT_TEST( testIfElseInnerList );

	CPPUNIT_TEST( testEvalEmpty );
	CPPUNIT_TEST( testEval );
	CPPUNIT_TEST( testEvalErrors );
	CPPUNIT_TEST( testEvalSkipComments );
	CPPUNIT_TEST( testEvalUnterminated );
	CPPUNIT_TEST( testEvalInnerList );
	CPPUNIT_TEST( testEvalCalls );

	CPPUNIT_TEST( testScope );
	CPPUNIT_TEST( testScopeValues );
	CPPUNIT_TEST( testScopeInnerList );

	CPPUNIT_TEST( testClearScopeInstruction );
	CPPUNIT_TEST( testClearScopeBlock );
	CPPUNIT_TEST( testClearScopeInnerList );

	CPPUNIT_TEST( testTryEmpty );
	CPPUNIT_TEST( testTryNoErrors );
	CPPUNIT_TEST( testTryErrors );
	CPPUNIT_TEST( testTryErrorsChained );
	CPPUNIT_TEST( testTryUnterminated );
	CPPUNIT_TEST( testTryTooManyErrors );
	CPPUNIT_TEST( testTryInnerList );

	CPPUNIT_TEST( testOutput );
	CPPUNIT_TEST( testOutputErrors );

	CPPUNIT_TEST( testErrorEmpty );
	CPPUNIT_TEST( testErrorWord );
	CPPUNIT_TEST( testErrorString );
	CPPUNIT_TEST( testErrorVar );
	CPPUNIT_TEST( testErrorInt );
	CPPUNIT_TEST( testErrorLong );
	CPPUNIT_TEST( testErrorReal );
	CPPUNIT_TEST( testErrorList );
	CPPUNIT_TEST( testErrorConcat );

	CPPUNIT_TEST( testBreakMain );
	CPPUNIT_TEST( testBreakMainList );
	CPPUNIT_TEST( testBreakEvalInput );
	CPPUNIT_TEST( testBreakEvalOutput );
	CPPUNIT_TEST( testBreakFunction );
	CPPUNIT_TEST( testBreakRecursiveFunction );
	CPPUNIT_TEST( testBreakMacro );
	CPPUNIT_TEST( testBreakArgs );

	CPPUNIT_TEST( testRethrowNothing );
	CPPUNIT_TEST( testRethrowList );
	CPPUNIT_TEST( testRethrowEmptyList );
	CPPUNIT_TEST( testRethrowUnwrapped );
	CPPUNIT_TEST( testRethrowErrorWord );
	CPPUNIT_TEST( testRethrowSourceWord );
	CPPUNIT_TEST( testRethrowDetails );
	CPPUNIT_TEST( testRethrowChained );
	CPPUNIT_TEST( testRethrowChainedDepth );

	CPPUNIT_TEST( testRethrowNoList );
	CPPUNIT_TEST( testRethrowBadMessage );
	CPPUNIT_TEST( testRethrowNoLocation );
	CPPUNIT_TEST( testRethrowBadLocation );
	CPPUNIT_TEST( testRethrowLocationBadSource );
	CPPUNIT_TEST( testRethrowLocationBadLine );
	CPPUNIT_TEST( testRethrowLocationNegativeLine );
	CPPUNIT_TEST( testRethrowLocationLineTooHigh );
	CPPUNIT_TEST( testRethrowLocationBadChar );
	CPPUNIT_TEST( testRethrowLocationNegativeChar );
	CPPUNIT_TEST( testRethrowChainedBad );
	CPPUNIT_TEST( testRethrowChainedInvalid );
	CPPUNIT_TEST( testRethrowChainedNoLocation );
	CPPUNIT_TEST( testRethrowChainedBadLocation );
	CPPUNIT_TEST( testRethrowChainedNegativeDepth );
	CPPUNIT_TEST( testRethrowChainedDepthTooHigh );
	CPPUNIT_TEST( testRethrowChainedDepthNoLocation );
	CPPUNIT_TEST( testRethrowChainedDepthBadLocation );

	CPPUNIT_TEST_SUITE_END( );

   public:
	void testRaw( );
	void testRawEmpty( );
	void testRawSkipComments( );
	void testRawUnterminated( );

	void testIfMissingArgs( );
	void testIfExtraArgs( );
	void testIfBadCondition( );
	void testIfBadThenBlock( );
	void testIfBadElseBlock( );
	void testIfTrueThen( );
	void testIfFalseThen( );
	void testIfTrueThenElse( );
	void testIfFalseThenElse( );
	void testIfArgSubst( );
	void testIfThenInnerList( );
	void testIfElseInnerList( );

	void testEvalEmpty( );
	void testEval( );
	void testEvalErrors( );
	void testEvalSkipComments( );
	void testEvalUnterminated( );
	void testEvalInnerList( );
	void testEvalCalls( );

	void testScope( );
	void testScopeValues( );
	void testScopeInnerList( );

	void testClearScopeInstruction( );
	void testClearScopeBlock( );
	void testClearScopeInnerList( );

	void testTryEmpty( );
	void testTryNoErrors( );
	void testTryErrors( );
	void testTryErrorsChained( );
	void testTryUnterminated( );
	void testTryTooManyErrors( );
	void testTryInnerList( );

	void testOutput( );
	void testOutputErrors( );

	void testErrorEmpty( );
	void testErrorWord( );
	void testErrorString( );
	void testErrorVar( );
	void testErrorInt( );
	void testErrorLong( );
	void testErrorReal( );
	void testErrorList( );
	void testErrorConcat( );

	void testBreakMain( );
	void testBreakMainList( );
	void testBreakEvalInput( );
	void testBreakEvalOutput( );
	void testBreakFunction( );
	void testBreakRecursiveFunction( );
	void testBreakMacro( );
	void testBreakArgs( );

	void testRethrowNothing( );
	void testRethrowList( );
	void testRethrowEmptyList( );
	void testRethrowUnwrapped( );
	void testRethrowErrorWord( );
	void testRethrowSourceWord( );
	void testRethrowDetails( );
	void testRethrowChained( );
	void testRethrowChainedDepth( );

	void testRethrowNoList( );
	void testRethrowBadMessage( );
	void testRethrowNoLocation( );
	void testRethrowBadLocation( );
	void testRethrowLocationBadSource( );
	void testRethrowLocationBadLine( );
	void testRethrowLocationNegativeLine( );
	void testRethrowLocationLineTooHigh( );
	void testRethrowLocationBadChar( );
	void testRethrowLocationNegativeChar( );
	void testRethrowChainedBad( );
	void testRethrowChainedInvalid( );
	void testRethrowChainedNoLocation( );
	void testRethrowChainedBadLocation( );
	void testRethrowChainedNegativeDepth( );
	void testRethrowChainedDepthTooHigh( );
	void testRethrowChainedDepthNoLocation( );
	void testRethrowChainedDepthBadLocation( );
};
CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdCoreTest );

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testRaw( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -raw ok $x ( -raw ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "ok $x ( -raw )" , output ) );
	M_CKTOKFULL_( 0 , "test" , 1 , 8 , 0 );
	M_CKTOKFULL_( 1 , "test" , 1 , 11 , 0 );
	M_CKTOKFULL_( 2 , "test" , 1 , 14 , 0 );
	M_CKTOKFULL_( 3 , "test" , 1 , 16 , 0 );
	M_CKTOKFULL_( 4 , "test" , 1 , 21 , 0 );
}

void SRDPreprocCmdCoreTest::testRawEmpty( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -raw )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRawSkipComments( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -raw { a comment } x # another comment!\n)" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "x" , output ) );
	M_CKTOKFULL_( 0 , "test" , 1 , 22 , 0 );
}

void SRDPreprocCmdCoreTest::testRawUnterminated( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -raw\n(\n(" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) );
	M_CKERRFULL_( 0 , "unterminated list" , false , "test" , 1 , 1 , 0 );
	M_CKERRFULL_( 1 , "unterminated list" , false , "test" , 2 , 1 , 0 );
	M_CKERRFULL_( 2 , "unterminated list" , false , "test" , 3 , 1 , 0 );
	CPPUNIT_ASSERT( check( "(())" , output ) );
	M_CKTOKFULL_( 0 , "test" , 2 , 1 , 0 );
	M_CKTOKFULL_( 1 , "test" , 3 , 1 , 0 );
	M_CKTOKFULL_( 2 , "*unexpected end of file*" , 0 , 2 , 0 );
	M_CKTOKFULL_( 3 , "*unexpected end of file*" , 0 , 1 , 0 );
}

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testIfMissingArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if )\n( -if 0 )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) );
	M_CKERRFULL_( 0 , "not enough arguments" , false , "test" , 1 , 3 , 0 );
	M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 7 , 0 );
	M_CKERRFULL_( 2 , "not enough arguments" , false , "test" , 2 , 3 , 0 );
	M_CKERRFULL_( 3 , "previous error cause" , true , "test" , 2 , 9 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testIfExtraArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if 0 (a) (b) (blah()) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERRFULL_( 0 , "too many arguments" , false , "test" , 1 , 3 , 0 );
	M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 17 , 0 );
	CPPUNIT_ASSERT( check( "b" , output ) );
	M_CKTOKFULL_( 0 , "test" , 1 , 14 , 0 );
}

void SRDPreprocCmdCoreTest::testIfBadCondition( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if nope () )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERRFULL_( 0 , "numeric value expected" , false , "test" , 1 , 3 , 0 );
	M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 7 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testIfBadThenBlock( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if 1 nope )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERRFULL_( 0 , "list expected" , false , "test" , 1 , 3 , 0 );
	M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 9 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testIfBadElseBlock( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if 1 (a) nope )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERRFULL_( 0 , "list expected" , false , "test" , 1 , 3 , 0 );
	M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 13 , 0 );
	CPPUNIT_ASSERT( check( "a" , output ) );
}

void SRDPreprocCmdCoreTest::testIfTrueThen( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if 1 (x) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "x" , output ) );
	M_CKTOKFULL_( 0 , "test" , 1 , 10 , 0 );
}

void SRDPreprocCmdCoreTest::testIfFalseThen( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if 0 (x) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testIfTrueThenElse( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if 1 (x) (y) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "x" , output ) );
	M_CKTOKFULL_( 0 , "test" , 1 , 10 , 0 );
}

void SRDPreprocCmdCoreTest::testIfFalseThenElse( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -if 0 (x) (y) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "y" , output ) );
	M_CKTOKFULL_( 0 , "test" , 1 , 14 , 0 );
}

void SRDPreprocCmdCoreTest::testIfArgSubst( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x 1 )\n"
				"( -if $x (x) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "x" , output ) );
	M_CKTOKFULL_( 0 , "test" , 2 , 11 , 0 );
}

void SRDPreprocCmdCoreTest::testIfThenInnerList( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x (-raw ( () ) ) )\n"
				"( -bless x )\n"
				"( -if 1 (-not-a-command) )\n"
				"( -if 1 ($x) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) );
	M_CKTOKFULL_( 0 , "test" , 3 , 10 , 0 );
	M_CKTOKFULL_( 1 , "$x" , 0 , 0 , 1 );
		M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "test" , 4 , 10 , 0 );
	M_CKTOKFULL_( 2 , "$x" , 0 , 1 , 1 );
		M_CKTOKCHAIN_( 2 , 0 , SUBSTITUTED , "test" , 4 , 10 , 0 );
	M_CKTOKFULL_( 3 , "$x" , 0 , 2 , 1 );
		M_CKTOKCHAIN_( 3 , 0 , SUBSTITUTED , "test" , 4 , 10 , 0 );
	M_CKTOKFULL_( 4 , "$x" , 0 , 3 , 1 );
		M_CKTOKCHAIN_( 4 , 0 , SUBSTITUTED , "test" , 4 , 10 , 0 );
}

void SRDPreprocCmdCoreTest::testIfElseInnerList( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x (-raw ( () ) ) )\n"
				"( -bless x )\n"
				"( -if 0 () (-not-a-command) )\n"
				"( -if 0 () ($x) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) );
	M_CKTOKFULL_( 0 , "test" , 3 , 13 , 0 );
	M_CKTOKFULL_( 1 , "$x" , 0 , 0 , 1 );
		M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "test" , 4 , 13 , 0 );
	M_CKTOKFULL_( 2 , "$x" , 0 , 1 , 1 );
		M_CKTOKCHAIN_( 2 , 0 , SUBSTITUTED , "test" , 4 , 13 , 0 );
	M_CKTOKFULL_( 3 , "$x" , 0 , 2 , 1 );
		M_CKTOKCHAIN_( 3 , 0 , SUBSTITUTED , "test" , 4 , 13 , 0 );
	M_CKTOKFULL_( 4 , "$x" , 0 , 3 , 1 );
		M_CKTOKCHAIN_( 4 , 0 , SUBSTITUTED , "test" , 4 , 13 , 0 );
}

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testEvalEmpty( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -eval )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testEval( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set (x y)\n"
					"( -raw $y )\n"
					"this-is-y\n"
				")\n"
				"eval ( -eval $x ) actual $x" ,
				errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "eval this-is-y actual $y" , output ) );
	M_CKTOKFULL_( 0 , "test" , 5 , 1 , 0 );
	M_CKTOKFULL_( 1 , "$y" , 0 , 0 , 2 );
		M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "$x" , 0 , 0 , 0 );
		M_CKTOKCHAIN_( 1 , 1 , EVALUATED , "test" , 5 , 8 , 0 );
	M_CKTOKFULL_( 2 , "test" , 5 , 19 , 0 );
	M_CKTOKFULL_( 3 , "$x" , 0 , 0 , 1 );
		M_CKTOKCHAIN_( 3 , 0 , SUBSTITUTED , "test" , 5 , 26 , 0 );
}

void SRDPreprocCmdCoreTest::testEvalErrors( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x ( -raw $does-not-exist ) )\n"
				"( -eval $x )" ,
				errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERRFULL_( 0 , "unknown variable" , false , "$x" , 0 , 0 , 1 );
		M_CKERRCHAIN_( 0 , 0 , EVALUATED , "test" , 2 , 3 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testEvalSkipComments( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -eval (-from-source \"{ a comment } x # another comment!\")\n)" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "x" , output ) );
	M_CKTOKFULL_( 0 , "-from-source" , 1 , 15 , 1 );
		M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 10 , 0 );
}

void SRDPreprocCmdCoreTest::testEvalUnterminated( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -eval\n(\n(" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) );
	M_CKERR_( 0 , "unterminated list" , 1 , 1 );
	M_CKERR_( 1 , "unterminated list" , 2 , 1 );
	M_CKERR_( 2 , "unterminated list" , 3 , 1 );
	CPPUNIT_ASSERT( check( "(())" , output ) );
	M_CKTOKFULL_( 0 , "test" , 2 , 1 , 0 );
	M_CKTOKFULL_( 1 , "test" , 3 , 1 , 0 );
	M_CKTOKFULL_( 2 , "*unexpected end of file*" , 0 , 2 , 0 );
	M_CKTOKFULL_( 3 , "*unexpected end of file*" , 0 , 1 , 0 );
}

void SRDPreprocCmdCoreTest::testEvalInnerList( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x (-raw ( () ) ) )\n"
				"( -bless x )\n"
				"( -eval -not-a-command )\n"
				"( -eval $x )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) );
}

void SRDPreprocCmdCoreTest::testEvalCalls( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set (x y) (-raw (() f1 (-raw $y) f2)) x)\n"
				"(-bless x)\n"
				"( -eval a $y b ($x) c ($x) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "a x b f1 x f2 c f1 x f2" , output ) );
}

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testScope( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set-macro dump ( ( ) ( -raw ( ( -raw\n"
					"( -is-set x )\n"
					"( -is-blessed f )\n"
					"( -is-set y )\n"
					"( -is-macro m )\n"
					"( -is-macro n )\n"
				") ) ) ) )\n"
				"( -set x 1 )\n"
				"( -set f (()) )\n"
				"( -set-macro m ( ( ) ) )\n"
				"( -scope\n"
					"( -unset x )\n"
					"( -bless f )\n"
					"( -set y 0 )\n"
					"( -unset-macro m )\n"
					"( -set-macro n ( ( ) ) )\n"
					"(dump)\n"
				")\n"
				"(dump)\n"
				"" ,
				errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "( 0 1 1 0 1 ) ( 1 0 0 1 0 )" , output ) );
}

void SRDPreprocCmdCoreTest::testScopeValues( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x 1 )\n"
				"$x\n"
				"( -scope\n"
					"( -set x 2 )\n"
					"$x\n"
				")\n"
				"$x\n" ,
				errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "1 2 1" , output ) );
}

void SRDPreprocCmdCoreTest::testScopeInnerList( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x (-raw ( () ) ) )\n"
				"( -bless x )\n"
				"( -scope -not-a-command )\n"
				"( -scope $x )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) );
}

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testClearScopeInstruction( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x )\n"
				"( -set-macro y ( ( ) ) )\n"
				"( -is-set x )\n"
				"( -is-macro y )\n"
				"( -clear-scope )\n"
				"( -is-set x )\n"
				"( -is-macro y )\n"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "1 1 0 0" , output ) );
}

void SRDPreprocCmdCoreTest::testClearScopeBlock( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x )\n"
				"( -set-macro y ( ( ) ) )\n"
				"( -is-set x )\n"
				"( -is-macro y )\n"
				"( -clear-scope\n"
					"( -is-set x )\n"
					"( -is-macro y )\n"
				")\n"
				"( -is-set x )\n"
				"( -is-macro y )\n"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "1 1 0 0 1 1" , output ) );
}

void SRDPreprocCmdCoreTest::testClearScopeInnerList( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -clear-scope -not-a-command )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "-not-a-command" , output ) );
}

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testTryEmpty( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-try )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "() ()" , output ) );
}

void SRDPreprocCmdCoreTest::testTryNoErrors( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-try 1 2 3)" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "(1 2 3) ()" , output ) );
}

void SRDPreprocCmdCoreTest::testTryErrors( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -try $x 2 $y )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "( 2 ) ( "
				"( \"unknown variable\" ( \"test\" 1 8 ) )"
				"( \"unknown variable\" ( \"test\" 1 13 ) )"
				")" , output ) );
}

void SRDPreprocCmdCoreTest::testTryErrorsChained( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set (f1 f2) (-raw\n"
					"(() f1 $x)\n"
					"((a)\n"
						"f2 $a\n"
						"(-if (-gt $a 0) (\n"
							"($f2 (-sub $a 1))\n"
						")(\n"
							"($f1)\n"
						"))\n"
					")\n"
				"))\n"
				"(-bless f1 f2)\n"
				"( -try ($f1) ($f2 5))"
				, errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check(
			"( f1 f2 5 f2 4 f2 3 f2 2 f2 1 f2 0 f1 ) ( "
				"( \"unknown variable\""
					"( \"test\" 2 8 )"
					"called ( \"test\" 13 9 )"
				")"
				"( \"unknown variable\""
					"( \"test\" 2 8 )"
					"called ( \"test\" 8 2 )"
					"called 4 ( \"test\" 6 2 )"
					"called ( \"test\" 13 15 )"
				")"
			")" , output ) );
}

void SRDPreprocCmdCoreTest::testTryUnterminated( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -try\n(\n(" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) );
	M_CKERR_( 0 , "unterminated list" , 1 , 1 );
	M_CKERR_( 1 , "unterminated list" , 2 , 1 );
	M_CKERR_( 2 , "unterminated list" , 3 , 1 );
	CPPUNIT_ASSERT( check( "((()))()" , output ) );
}

void SRDPreprocCmdCoreTest::testTryTooManyErrors( )
{
	T_StringBuilder sb;
	sb << "(-set (output errors) (-try (\n";
	for ( uint32_t i = 0 ; i < T_SRDErrors::MAX_ERRORS + 1 ; i ++ ) {
		sb << "$missing\n";
	}
	sb << ") ) )\n"
		<< "(-set last-error ( ((list)) (-raw\n"
			<< "(-set (a b) $list)\n"
			<< "(-if (-eq ($b) ()) ($a) (($last-error $b)))\n"
		<< ")))\n"
		<< "(-bless last-error)\n"
		<< "$output (-length $errors) ($last-error (-unwrap $errors))\n"
		<< '\0';

	T_SRDErrors errors;
	T_SRDList output( process( sb.data( ) , errors ) );

	sb.clear( );
	sb << "(()) 41 ( \"too many errors\" ( \"test\" " << ( T_SRDErrors::MAX_ERRORS + 1 ) << " 1 ) )" << '\0';
	CPPUNIT_ASSERT( check( sb.data( ) , output ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
}

void SRDPreprocCmdCoreTest::testTryInnerList( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"( -set x (-raw ( () ) ) )\n"
				"( -bless x )\n"
				"( -try -not-a-command )\n"
				"( -try $x )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "(-not-a-command) () ((())) ()" , output ) );
}

/*----------------------------------------------------------------------------*/

namespace {

class T_OutputErrorGen_ : public A_SRDReaderTarget
{
private:
	A_SRDReaderTarget& next_;

public:
	explicit T_OutputErrorGen_( A_SRDReaderTarget& next )
		: A_SRDReaderTarget( ) , next_( next )
	{}

	void start( T_SRDErrors & errors ) override
	{
		next_.start( errors );
	}

	void push( T_SRDErrors & errors , T_SRDToken && token ) override
	{
		if ( token.type( ) == E_SRDTokenType::WORD && token.stringValue( ) == "fail" ) {
			errors.add( "DO NOT WANT" , token );
		}
		next_.push( errors , std::move( token ) );
	}

	void end( T_SRDErrors & errors ) override
	{
		next_.end( errors );
	}
};

}

void SRDPreprocCmdCoreTest::testOutput( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(5 (-output (3 (-output (1 2)) 4)) 6)" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "(1 2) (3 4) (5 6)" , output ) );
}

void SRDPreprocCmdCoreTest::testOutputErrors( )
{
	T_SRDErrors errors;
	T_SRDMemoryTarget mt( false );
	mt.clearFlushToken( true );
	T_OutputErrorGen_ eg( mt );

	T_SRDPreprocessorConfig emptyConfig;
	emptyConfig.addBuiltinCommands( );
	T_SRDPreprocessor pp( emptyConfig , eg );
	T_SRDLexer lexer( T_String( "test" ) , errors , pp );
	pp.start( errors );
	char const* ptr = "(-try (-output fail))";
	while ( *ptr != 0 ) {
		lexer.processCharacter( *ptr ++ );
	}
	lexer.processEnd( );
	mt.end( errors );

	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "fail () ( ( \"DO NOT WANT\" ( \"test\" 1 16 ) ) )" ,
				mt.list( ) ) );
}

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testErrorEmpty( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "user error" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testErrorWord( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error fail )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "fail" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testErrorString( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error \"out of cheese\" )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "out of cheese" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testErrorVar( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error ( -raw $x ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "$x" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testErrorInt( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error 123 )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "123" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testErrorLong( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error 5000000000 )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "5000000000" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testErrorReal( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error 1.5 )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "1.5" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testErrorList( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error ( a list ( 123 \"wut\" ) ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "( a list ( 123 wut ) )" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testErrorConcat( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "( -error And this is the real thing \"(or is it?)\" )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERR_( 0 , "And this is the real thing (or is it?)" , 1 , 3 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testBreakMain( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "a (-break) b" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "a" , output ) );
}

void SRDPreprocCmdCoreTest::testBreakMainList( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(a ((-break)) b)" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "(a())" , output ) );
}

void SRDPreprocCmdCoreTest::testBreakEvalInput( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "a (-eval (b (-break)) c) d" , errors ) );
	for ( uint32_t i = 0 ; i < errors.size( ) ; i ++ ) {
		M_PRINTERR_( i );
	}
	CPPUNIT_ASSERT( check( "a (b) d" , output ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
}

void SRDPreprocCmdCoreTest::testBreakEvalOutput( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "a (-eval b ((-raw ((-break)))) c) d" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "a b (())" , output ) );
}

void SRDPreprocCmdCoreTest::testBreakFunction( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set f (-raw ( (a b) $a (-break) $b ) ))\n"
				"(-bless f)\n"
				"a ($f b c) d" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "a b d" , output ) );
}

void SRDPreprocCmdCoreTest::testBreakRecursiveFunction( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set f (-raw ( (a b)\n"
					"(\n"
						"(-if (-lt $b $a ) (\n"
							"(-break)\n"
						"))\n"
						"$a\n"
						"($f (-add $a 1) $b)\n"
					")\n"
				")))\n"
				"(-bless f)\n"
				"a ($f 1 3) b" , errors ) );
	for ( auto i = 0u ; i < errors.size( ) ; i ++ ) {
		M_PRINTERR_( i );
	}
	CPPUNIT_ASSERT( check( "a (1(2(3()))) b" , output ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
}

void SRDPreprocCmdCoreTest::testBreakMacro( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-set-macro m (-raw ( (a b) ($a (-break) $b) ) ))\n"
				"a (m b c) d" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "a (b) d" , output ) );
}

void SRDPreprocCmdCoreTest::testBreakArgs( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(a (-break even) b)" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "too many arguments" , 1 , 5 );
	M_CKERR_( 1 , "previous error cause" , 1 , 12 );
	CPPUNIT_ASSERT( check( "(a)" , output ) );
}

/*----------------------------------------------------------------------------*/

#define M_CKRT_TEXT_( INDEX , TEXT ) \
	CPPUNIT_ASSERT( errors[ INDEX ].error( ) == (TEXT) )
#define M_CKRT_SOURCE_( INDEX , EID , NAME , LINE , CHAR ) \
	do { \
		RPC_SRDLocation loc( &errors[ INDEX ].location( ) ); \
		int i( EID ); \
		while ( i > 0 ) { \
			assert( loc->isChained( ) ); \
			loc = RPC_SRDLocation( loc->chaining( ).location ); \
			i --; \
		} \
		auto const& l( *loc ); \
		CPPUNIT_ASSERT( l.source( ) == (NAME) ); \
		CPPUNIT_ASSERT( l.line( ) == (LINE) ); \
		CPPUNIT_ASSERT( l.character( ) == (CHAR) ); \
	} while ( 0 )

void SRDPreprocCmdCoreTest::testRethrowNothing( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-rethrow)" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowList( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( ( \"error text\"\n"
					"( \"source\" 2 3 )\n"
				") ( \"second error\"\n"
					"( \"source 2\" 4 5 )\n"
				") ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERRFULL_( 0 , "error text" , false , "source" , 2 , 3 , 0 );
	M_CKERRFULL_( 1 , "second error" , false , "source 2" , 4 , 5 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowEmptyList( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowUnwrapped( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error text\"\n"
					"( \"source\" 2 3 )\n"
				") ( \"second error\"\n"
					"( \"source 2\" 4 5 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERRFULL_( 0 , "error text" , false , "source" , 2 , 3 , 0 );
	M_CKERRFULL_( 1 , "second error" , false , "source 2" , 4 , 5 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowErrorWord( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-rethrow ( error ( \"source\" 2 3 ) ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERRFULL_( 0 , "error" , false , "source" , 2 , 3 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowSourceWord( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( source 2 3 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERRFULL_( 0 , "error" , false , "source" , 2 , 3 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowDetails( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"not an error\" details\n"
					"( source 2 3 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERRFULL_( 0 , "not an error" , true , "source" , 2 , 3 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChained( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 1 1 )\n"
					"called ( \"src\" 2 2 )\n"
					"loaded ( \"src\" 3 3 )\n"
					"included ( \"src\" 4 4 )\n"
					"generated ( \"src\" 5 5 )\n"
					"expanded ( \"src\" 6 6 )\n"
					"evaluated ( \"src\" 7 7 )\n"
					"substituted ( \"src\" 8 8 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERRFULL_( 0 , "error" , false , "src" , 1 , 1 , 7 );
		M_CKERRCHAIN_( 0 , 0 , CALLED , "src" , 2 , 2 , 0 );
		M_CKERRCHAIN_( 0 , 1 , LOADED , "src" , 3 , 3 , 0 );
		M_CKERRCHAIN_( 0 , 2 , INCLUDED , "src" , 4 , 4 , 0 );
		M_CKERRCHAIN_( 0 , 3 , GENERATED , "src" , 5 , 5 , 0 );
		M_CKERRCHAIN_( 0 , 4 , EXPANDED , "src" , 6 , 6 , 0 );
		M_CKERRCHAIN_( 0 , 5 , EVALUATED , "src" , 7 , 7 , 0 );
		M_CKERRCHAIN_( 0 , 6 , SUBSTITUTED , "src" , 8 , 8 , 0 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedDepth( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 1 1 )\n"
					"called 5 ( \"src\" 2 2 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) );
	M_CKERRFULL_( 0 , "error" , false , "src" , 1 , 1 , 1 );
		M_CKERRCHAIN_( 0 , 0 , CALLED , "src" , 2 , 2 , 5 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

/*----------------------------------------------------------------------------*/

void SRDPreprocCmdCoreTest::testRethrowNoList( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-rethrow nope)" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "start of list expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 1 , 11 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowBadMessage( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-rethrow ( 12 ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "error message expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 1 , 13 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowNoLocation( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-rethrow ( error ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "'details' or start of list expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 1 , 19 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowBadLocation( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-rethrow ( \"error\" nope ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "'details' or start of list expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 1 , 21 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowLocationBadSource( )
{
	T_SRDErrors errors;
	T_SRDList output( process( "(-rethrow ( \"error\" ( 12 1 2 ) ) )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "source name expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 1 , 23 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowLocationBadLine( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" nope 3 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "line number expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 2 , 9 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowLocationNegativeLine( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" -1 3 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid line number" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 2 , 9 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowLocationLineTooHigh( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 5000000000 3 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid line number" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 2 , 9 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowLocationBadChar( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 nope )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "character/byte number expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 2 , 11 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowLocationNegativeChar( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 -3 )\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid character/byte number" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 2 , 11 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedBad( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 3 )\n"
					"12\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "chaining type or end of list expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 3 , 1 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedInvalid( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 3 )\n"
					"blah\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid chaining type" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 3 , 1 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedNoLocation( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 3 )\n"
					"loaded\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "depth or start of list expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 4 , 1 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedBadLocation( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 3 )\n"
					"loaded nope\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "depth or start of list expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 3 , 8 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedNegativeDepth( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 3 )\n"
					"loaded -1\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid recursion depth" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 3 , 8 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedDepthTooHigh( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 3 )\n"
					"loaded 5000000000\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "invalid recursion depth" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 3 , 8 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedDepthNoLocation( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 3 )\n"
					"loaded 5\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "start of list expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 4 , 1 );
	CPPUNIT_ASSERT( check( "" , output ) );
}

void SRDPreprocCmdCoreTest::testRethrowChainedDepthBadLocation( )
{
	T_SRDErrors errors;
	T_SRDList output( process(
				"(-rethrow ( \"error\"\n"
					"( \"src\" 2 3 )\n"
					"loaded 5 nope\n"
				") )" , errors ) );
	CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) );
	M_CKERR_( 0 , "start of list expected" , 1 , 2 );
	M_CKERR_( 1 , "previous error cause" , 3 , 10 );
	CPPUNIT_ASSERT( check( "" , output ) );
}