/******************************************************************************/
/* SRD - PREPROCESSOR COMMANDS ************************************************/
/******************************************************************************/

#include <lw/lib/MemoryStreams.hh>
#include <lw/lib/SRDBinary.hh>
#include <lw/lib/SRDPPCommands.hh>
#include <lw/lib/SRDText.hh>
#include <lw/lib/VFS.hh>
#include <cmath>
using namespace lw;


/*= BUILTIN COMMANDS INITIALIZER =============================================*/

#define M_ADDCMD_( NAME , ARGS... ) \
	add< T_SRDPPCMD_ ## NAME >( ARGS )

void T_SRDPreprocessorConfig::addBuiltinCommands( ) noexcept
{
	M_ADDCMD_( Add );
	M_ADDCMD_( And );
	M_ADDCMD_( Bless );
	M_ADDCMD_( Break );
	M_ADDCMD_( BwAnd );
	M_ADDCMD_( BwNot );
	M_ADDCMD_( BwOr );
	M_ADDCMD_( BwXor );
	M_ADDCMD_( Call );
	M_ADDCMD_( CastString );
	M_ADDCMD_( CastWord );
	M_ADDCMD_( CastInt );
	M_ADDCMD_( CastLong );
	M_ADDCMD_( CastBestInt );
	M_ADDCMD_( CastReal );
	M_ADDCMD_( CastVar );
	M_ADDCMD_( CastList );
	M_ADDCMD_( ClearScope );
	M_ADDCMD_( Cmp );
	M_ADDCMD_( CmpEq );
	M_ADDCMD_( CmpNe );
	M_ADDCMD_( CmpLt );
	M_ADDCMD_( CmpGt );
	M_ADDCMD_( CmpLe );
	M_ADDCMD_( CmpGe );
	M_ADDCMD_( Concat );
	M_ADDCMD_( Div );
	M_ADDCMD_( EndsWith );
	M_ADDCMD_( Eval );
	M_ADDCMD_( Error );
	M_ADDCMD_( FromSource );
	M_ADDCMD_( FromSRB );
	M_ADDCMD_( Get );
	M_ADDCMD_( If );
	M_ADDCMD_( Ignore );
	M_ADDCMD_( IsBlessed );
	M_ADDCMD_( IsMacro );
	M_ADDCMD_( IsSet );
	M_ADDCMD_( Length );
	M_ADDCMD_( ListMacros );
	M_ADDCMD_( ListVariables );
	M_ADDCMD_( Mod );
	M_ADDCMD_( Mul );
	M_ADDCMD_( Neg );
	M_ADDCMD_( Not );
	M_ADDCMD_( Or );
	M_ADDCMD_( Output );
	M_ADDCMD_( Raw );
	M_ADDCMD_( Rethrow );
	M_ADDCMD_( Scope );
	M_ADDCMD_( Set );
	M_ADDCMD_( SetMacro );
	M_ADDCMD_( StartsWith );
	M_ADDCMD_( StrFind );
	M_ADDCMD_( StrSplit );
	M_ADDCMD_( Sub );
	M_ADDCMD_( Substr );
	M_ADDCMD_( ToSource );
	M_ADDCMD_( Try );
	M_ADDCMD_( TypeOf );
	M_ADDCMD_( Unset );
	M_ADDCMD_( UnsetMacro );
	M_ADDCMD_( Unwrap );
	M_ADDCMD_( Xor );
}

void T_SRDPreprocessorConfig::addVFSCommands(
		T_VFS& vfs ) noexcept
{
	M_ADDCMD_( VFSList , vfs );
	M_ADDCMD_( VFSLoad , vfs );
	M_ADDCMD_( VFSType , vfs );
}


/*= HELPER FOR MATH COMMANDS =================================================*/

typedef std::function< int64_t( int64_t , int64_t ) > F_LongOp_;
typedef std::function< double ( double , double ) > F_RealOp_;

namespace {

void DoMathOp_( T_SRDPreprocessorState& state , F_LongOp_ longOp , F_RealOp_ realOp , bool nonZero = false )
{
	auto& input( state.output( ) );
	auto& output( state.data( ).top( ).output( ) );
	auto const& location( *state.initialLocation( ) );

	const uint32_t nInputs( input.size( ) );
	if ( nInputs < 2 ) {
		state.data( ).addError( location , "not enough arguments" );
		if ( nInputs == 0 ) {
			T_SRDToken r( T_SRDToken::Integer( 0 ) );
			r.location( location );
			output.add( std::move( r ) );
			return;
		}
	}

	const E_SRDTokenType type( input[ 0 ].type( ) );
	if ( type != E_SRDTokenType::INT && type != E_SRDTokenType::LONG && type != E_SRDTokenType::FLOAT ) {
		state.data( ).addError( input[ 0 ] , "numeric argument expected" );
	}

	bool hadReal( type == E_SRDTokenType::FLOAT );
	int64_t rl( input[ 0 ].longValue( ) );
	double rr( input[ 0 ].floatValue( ) );
	for ( uint32_t i = 1 ; i < nInputs ; i ++ ) {
		auto& tok( input[ i ] );
		const auto tt( tok.type( ) );
		if ( tt != E_SRDTokenType::INT && tt != E_SRDTokenType::LONG &&
		    tt != E_SRDTokenType::FLOAT ) {
			state.data( ).addError( tok , "numeric argument expected" );
			continue;
		}
		if ( tok.floatValue( ) == 0 && nonZero ) {
			state.data( ).addError( tok , "non-zero argument expected" );
			continue;
		}
		hadReal = hadReal || tt == E_SRDTokenType::FLOAT;
		if ( !hadReal ) {
			rl = longOp( rl , tok.longValue( ) );
		}
		rr = realOp( rr , tok.floatValue( ) );
	}

	T_SRDToken result( hadReal ? T_SRDToken::Float( rr ) : T_SRDToken::AutoInteger( rl ) );
	result.location( location );
	output.add( std::move( result ) );
}

void DoLogicOp_( T_SRDPreprocessorState& state , F_LongOp_ op )
{
	auto& input( state.output( ) );
	auto& output( state.data( ).top( ).output( ) );
	auto const& location( *state.initialLocation( ) );

	const uint32_t nInputs( input.size( ) );
	if ( nInputs < 2 ) {
		state.data( ).addError( location , "not enough arguments" );
		if ( nInputs == 0 ) {
			T_SRDToken r( T_SRDToken::Integer( 0 ) );
			r.location( location );
			output.add( std::move( r ) );
			return;
		}
	}
	if ( !input[ 0 ].isInteger( ) ) {
		state.data( ).addError( input[ 0 ] , "integer argument expected" );
	}

	int64_t rl( input[ 0 ].longValue( ) );
	for ( uint32_t i = 1 ; i < nInputs ; i ++ ) {
		auto& tok( input[ i ] );
		if ( !tok.isInteger( ) ) {
			state.data( ).addError( tok , "integer argument expected" );
			continue;
		}
		rl = op( rl , tok.longValue( ) );
	}

	T_SRDToken result( T_SRDToken::AutoInteger( rl ) );
	result.location( location );
	output.add( std::move( result ) );
}

}


/*= HELPER FOR COMPARISONS ===================================================*/

namespace {

bool CompareInput_( T_SRDPreprocessorState & state , int & result )
{
	auto& data( state.data( ) );
	auto& input( state.output( ) );
	auto& loc( *state.initialLocation( ) );

	if ( input.size( ) < 2 ) {
		data.addError( loc , "not enough arguments" );
		return false;
	}
	if ( input.size( ) > 2 ) {
		data.addError( loc , "too many arguments" );
		return false;
	}

	result = input[ 0 ].compare( input[ 1 ] );
	return true;
}

}


/*= COMMANDS =================================================================*/

M_SRDPP_COMMAND_EXEC( Add )
{
	DoMathOp_( state ,
		  []( auto a , auto b ) {
		return a + b;
	} ,
		  []( auto a , auto b ) {
		return a + b;
	} );
}

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

M_SRDPP_COMMAND_EXEC( And )
{
	DoLogicOp_( state ,
		  []( auto a , auto b ) {
		return ( a && b ) ? 1 : 0;
	} );
}

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

M_SRDPP_COMMAND_EXEC( Bless )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	const auto n( input.size( ) );
	if ( n == 0 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}

	for ( uint32_t i = 0 ; i < n ; i ++ ) {
		auto const& ntok( input[ i ] );
		if ( ntok.type( ) != E_SRDTokenType::WORD ) {
			data.addError( ntok , "word expected" );
			continue;
		}

		auto const& vn( ntok.stringValue( ) );
		auto& s( data.scopes( ) );
		if ( s.isBlessed( vn ) ) {
			continue;
		}

		RPC_SRDList var( s.get( false , vn ) );
		if ( var == nullptr ) {
			data.addError( ntok , "unknown variable" );
			continue;
		}

		if ( T_SRDPreprocessor::isValidFunction( *var ) ) {
			s.bless( vn );
		} else {
			data.addError( ntok , "variable does not contain a valid function" );
		}
	}
}

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

M_SRDPP_COMMAND_EXEC( Break )
{
	if ( state.output( ).size( ) ) {
		state.data().addError( *state.initialLocation( ) , "too many arguments" );
	}
	state.data( ).interrupt( T_SRDPreprocessorState::C_CALL );
}

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

M_SRDPP_COMMAND_EXEC( BwAnd )
{
	DoLogicOp_( state ,
		  []( auto a , auto b ) {
		return a & b;
	} );
}

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

M_SRDPP_COMMAND_EXEC( BwNot )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	bool useLong( false );
	int64_t iv( 0 );
	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
	} else if ( input.size( ) != 1 ) {
		data.addError( loc , "too many arguments" );
	} else if ( !input[ 0 ].isInteger( ) ) {
		data.addError( input[ 0 ] , "integer argument expected" );
	} else {
		iv = ~input[ 0 ].longValue( );
		useLong = ( input[ 0 ].type( ) == E_SRDTokenType::LONG );
		if ( !useLong ) {
			iv = iv & 0xffffffff;
		}
	}

	T_SRDToken output( useLong ? T_SRDToken::Long( iv ) : T_SRDToken::Integer( iv ) );
	output.location( loc );
	state.data( ).top( ).output( ).add( std::move( output ) );
}

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

M_SRDPP_COMMAND_EXEC( BwOr )
{
	DoLogicOp_( state ,
		  []( auto a , auto b ) {
		return a | b;
	} );
}

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

M_SRDPP_COMMAND_EXEC( BwXor )
{
	DoLogicOp_( state ,
		  []( auto a , auto b ) {
		return a ^ b;
	} );
}

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

M_SRDPP_COMMAND_EXEC( Call )
{
	auto& data( state.data( ) );
	auto& input( state.output( ) );
	auto const& loc( *state.initialLocation( ) );

	if ( input.size( ) < 1 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}

	auto& func( input[ 0 ] );
	if ( !T_SRDPreprocessor::isValidFunction( func ) ) {
		data.addError( func , "invalid function" );
		return;
	}

	auto const& argsTok( func.list( )[ 0 ] );
	auto const& args( argsTok.list( ) );
	const bool hasOptArgs( args.size( ) > 0
			      && args[ args.size( ) - 1 ].type( ) == E_SRDTokenType::LIST );
	const uint32_t reqArgs( args.size( ) - ( hasOptArgs ? 1 : 0 ) );
	const auto nValues( input.size( ) - 1 );
	if ( nValues < reqArgs ) {
		data.addError( loc , "not enough function arguments" );
		return;
	}
	if ( nValues > reqArgs && !hasOptArgs ) {
		data.addError( loc , "too many function arguments" );
		return;
	}

	data.scopes( ).setupCallScope( args , input , 1 );
	data.push( T_SRDPreprocessorState::C_CALL ,
			NewShared< T_SRDList >( func.list( ).moveRange( 1 ) ) ,
			data.top( ).outputPointer( ) ,
			[]( T_SRDPreprocessorState& ns ) {
				ns.data( ).scopes( ).exit( );
			} );
}

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

M_SRDPP_COMMAND_EXEC( CastString )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	const auto size( input.size( ) );
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto& token( input[ i ] );

		T_String outString;
		switch ( token.type( ) ) {

			case E_SRDTokenType::STRING:
				output.add( std::move( token ) );
				continue;

			case E_SRDTokenType::WORD:
				outString = token.stringValue( );
				break;

			case E_SRDTokenType::INT:
			case E_SRDTokenType::LONG:
			{
				T_StringBuilder sb;
				sb << token.longValue( );
				outString = std::move( sb );
				break;
			}

			case E_SRDTokenType::FLOAT:
			{
				T_StringBuilder sb;
				sb << token.floatValue( );
				outString = std::move( sb );
				break;
			}

			case E_SRDTokenType::VAR:
			{
				T_StringBuilder sb;
				sb << '$' << token.stringValue( );
				outString = std::move( sb );
				break;
			}

			case E_SRDTokenType::BINARY:
			{
				auto const& buffer( token.binary( ) );
				outString = T_String( (char const*) buffer.data( ) ,
						buffer.bytes( ) );
				if ( !outString.valid( ) ) {
					outString = T_String( );
					data.addError( token , "invalid UTF-8 data" );
				}
				break;
			}

			default:
				data.addError( token , "unsupported type conversion" );
				continue;
				
		}

		T_SRDToken otok( T_SRDToken::String( std::move( outString ) ) );
		otok.copyLocationOf( token );
		output.add( std::move( otok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( CastWord )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	const auto size( input.size( ) );
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto& token( input[ i ] );

		T_String outString;
		switch ( token.type( ) ) {

			case E_SRDTokenType::STRING:
				outString = token.stringValue( );
				if ( !T_SRDToken::IsWord( outString ) ) {
					data.addError( token , "invalid word" );
					continue;
				}
				break;

			case E_SRDTokenType::WORD:
				output.add( std::move( token ) );
				continue;

			case E_SRDTokenType::VAR:
				outString = token.stringValue( );
				break;

			default:
				data.addError( token , "unsupported type conversion" );
				continue;
				
		}

		T_SRDToken otok( T_SRDToken::Word( std::move( outString ) ) );
		otok.copyLocationOf( token );
		output.add( std::move( otok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( CastInt )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	const auto size( input.size( ) );
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto& token( input[ i ] );

		int64_t value;
		switch ( token.type( ) ) {

			case E_SRDTokenType::INT:
				output.add( std::move( token ) );
				continue;

			case E_SRDTokenType::STRING:
			{
				bool ok;
				value = token.stringValue( ).toInteger( &ok );
				if ( !ok ) {
					data.addError( token , "invalid integer value" );
					continue;
				}
				break;
			}

			case E_SRDTokenType::LONG:
				value = token.longValue( );
				break;

			case E_SRDTokenType::FLOAT:
				value = token.floatValue( );
				break;

			default:
				data.addError( token , "unsupported type conversion" );
				continue;
				
		}
		if ( value < INT32_MIN || value > INT32_MAX ) {
			data.addError( token , "value out of range" );
			continue;
		}

		T_SRDToken otok( T_SRDToken::Integer( value ) );
		otok.copyLocationOf( token );
		output.add( std::move( otok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( CastLong )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	const auto size( input.size( ) );
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto& token( input[ i ] );

		int64_t value;
		switch ( token.type( ) ) {

			case E_SRDTokenType::LONG:
				output.add( std::move( token ) );
				continue;

			case E_SRDTokenType::STRING:
			{
				bool ok;
				value = token.stringValue( ).toInteger( &ok );
				if ( !ok ) {
					data.addError( token , "invalid integer value" );
					continue;
				}
				break;
			}

			case E_SRDTokenType::INT:
				value = token.longValue( );
				break;

			case E_SRDTokenType::FLOAT:
				value = token.floatValue( );
				break;

			default:
				data.addError( token , "unsupported type conversion" );
				continue;
				
		}

		T_SRDToken otok( T_SRDToken::Long( value ) );
		otok.copyLocationOf( token );
		output.add( std::move( otok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( CastBestInt )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	const auto size( input.size( ) );
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto& token( input[ i ] );

		int64_t value;
		switch ( token.type( ) ) {

			case E_SRDTokenType::INT:
				output.add( std::move( token ) );
				continue;

			case E_SRDTokenType::STRING:
			{
				bool ok;
				value = token.stringValue( ).toInteger( &ok );
				if ( !ok ) {
					data.addError( token , "invalid integer value" );
					continue;
				}
				break;
			}

			case E_SRDTokenType::LONG:
				value = token.longValue( );
				break;

			case E_SRDTokenType::FLOAT:
				value = token.floatValue( );
				break;

			default:
				data.addError( token , "unsupported type conversion" );
				continue;
				
		}

		T_SRDToken otok( T_SRDToken::AutoInteger( value ) );
		otok.copyLocationOf( token );
		output.add( std::move( otok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( CastReal )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	const auto size( input.size( ) );
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto& token( input[ i ] );

		double value;
		switch ( token.type( ) ) {

			case E_SRDTokenType::FLOAT:
				output.add( std::move( token ) );
				continue;

			case E_SRDTokenType::STRING:
			{
				bool ok;
				value = token.stringValue( ).toDouble( &ok );
				if ( !ok ) {
					data.addError( token , "invalid real value" );
					continue;
				}
				break;
			}

			case E_SRDTokenType::INT:
			case E_SRDTokenType::LONG:
				value = token.longValue( );
				break;

			default:
				data.addError( token , "unsupported type conversion" );
				continue;
				
		}

		T_SRDToken otok( T_SRDToken::Float( value ) );
		otok.copyLocationOf( token );
		output.add( std::move( otok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( CastVar )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	const auto size( input.size( ) );
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto& token( input[ i ] );

		T_String outString;
		switch ( token.type( ) ) {

			case E_SRDTokenType::STRING:
				outString = token.stringValue( );
				if ( outString[ 0 ] == '$' ) {
					outString = outString.substr( 1 );
				}
				if ( !T_SRDToken::IsWord( outString ) || outString[ 0 ] == '-' ) {
					data.addError( token , "invalid variable name" );
					continue;
				}
				break;

			case E_SRDTokenType::WORD:
				outString = token.stringValue( );
				if ( outString[ 0 ] == '-' ) {
					data.addError( token , "invalid variable name" );
					continue;
				}
				break;

			case E_SRDTokenType::VAR:
				output.add( std::move( token ) );
				continue;

			default:
				data.addError( token , "unsupported type conversion" );
				continue;
				
		}

		T_SRDToken otok( T_SRDToken::Variable( std::move( outString ) ) );
		otok.copyLocationOf( token );
		output.add( std::move( otok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( CastList )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	const auto size( input.size( ) );
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto& token( input[ i ] );
		switch ( token.type( ) ) {

			case E_SRDTokenType::STRING:
			case E_SRDTokenType::WORD:
			{
				T_StringIterator it( token.stringValue( ) );
				T_StringBuilder sb;
				while ( !it.atEnd( ) ) {
					T_Character c( it );
					it.next( );
					sb << c;
					T_SRDToken ctok( T_SRDToken::String( sb ) );
					ctok.copyLocationOf( token );
					output.add( std::move( ctok ) );
					sb.clear( );
				}
				break;
			}

			case E_SRDTokenType::BINARY:
			{
				auto const& buffer( token.binary( ) );
				const auto bs( buffer.size( ) );
				for ( auto i = 0u ; i < bs ; i ++ ) {
					T_SRDToken vtok( T_SRDToken::Integer( buffer[ i ] ) );
					vtok.copyLocationOf( token );
					output.add( std::move( vtok ) );
				}
				break;
			}

			case E_SRDTokenType::LIST:
			{
				auto const& l( token.list( ) );
				const auto ls( l.size( ) );
				for ( auto i = 0u ; i < ls ; i ++ ) {
					output.add( std::move( l[ i ] ) );
				}
				break;
			}

			default:
				output.add( std::move( token ) );
				continue;
				
		}
	}
}

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

M_SRDPP_COMMAND_EXEC( ClearScope )
{
	if ( state.output( ).size( ) != 0 ) {
		auto& data( state.data( ) );
		data.scopes( ).enter( );
		data.scopes( ).clear( );
		data.push( T_SRDPreprocessorState::C_INNER ,
				state.outputPointer( ) , data.top( ).outputPointer( ) ,
				[]( T_SRDPreprocessorState & ns ) {
					ns.data( ).scopes( ).exit( );
				} );
	} else {
		state.data( ).scopes( ).clear( );
	}
}

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

M_SRDPP_COMMAND_EXEC( Cmp )
{
	int result;
	bool success( CompareInput_( state , result ) );
	T_SRDToken token( T_SRDToken::Integer( success ? result : 0 ) );
	token.location( *state.initialLocation( ) );
	state.data( ).top( ).output( ).add( std::move( token ) );
}

M_SRDPP_COMMAND_EXEC( CmpEq )
{
	int result;
	bool success( CompareInput_( state , result ) );
	T_SRDToken token( T_SRDToken::Integer( ( success && result == 0 ) ? 1 : 0 ) );
	token.location( *state.initialLocation( ) );
	state.data( ).top( ).output( ).add( std::move( token ) );
}

M_SRDPP_COMMAND_EXEC( CmpNe )
{
	int result;
	bool success( CompareInput_( state , result ) );
	T_SRDToken token( T_SRDToken::Integer( ( success && result != 0 ) ? 1 : 0 ) );
	token.location( *state.initialLocation( ) );
	state.data( ).top( ).output( ).add( std::move( token ) );
}

M_SRDPP_COMMAND_EXEC( CmpLt )
{
	int result;
	bool success( CompareInput_( state , result ) );
	T_SRDToken token( T_SRDToken::Integer( ( success && result < 0 ) ? 1 : 0 ) );
	token.location( *state.initialLocation( ) );
	state.data( ).top( ).output( ).add( std::move( token ) );
}

M_SRDPP_COMMAND_EXEC( CmpGt )
{
	int result;
	bool success( CompareInput_( state , result ) );
	T_SRDToken token( T_SRDToken::Integer( ( success && result > 0 ) ? 1 : 0 ) );
	token.location( *state.initialLocation( ) );
	state.data( ).top( ).output( ).add( std::move( token ) );
}

M_SRDPP_COMMAND_EXEC( CmpLe )
{
	int result;
	bool success( CompareInput_( state , result ) );
	T_SRDToken token( T_SRDToken::Integer( ( success && result <= 0 ) ? 1 : 0 ) );
	token.location( *state.initialLocation( ) );
	state.data( ).top( ).output( ).add( std::move( token ) );
}

M_SRDPP_COMMAND_EXEC( CmpGe )
{
	int result;
	bool success( CompareInput_( state , result ) );
	T_SRDToken token( T_SRDToken::Integer( ( success && result >= 0 ) ? 1 : 0 ) );
	token.location( *state.initialLocation( ) );
	state.data( ).top( ).output( ).add( std::move( token ) );
}

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

M_SRDPP_COMMAND_EXEC( Concat )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );
	auto const& loc( *state.initialLocation( ) );

	const auto size( input.size( ) );
	if ( size == 0 ) {
		data.addError( loc , "not enough arguments" );
		T_SRDToken otok( T_SRDToken::String( T_String( "" ) ) );
		otok.location( loc );
		output.add( std::move( otok ) );
		return;
	}

	// Find out whether this is a string concatenation or a binary
	// concatenation.
	uint32_t binLength = 0;
	bool isBinary = true;
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto const& token( input[ i ] );
		if ( token.type( ) != E_SRDTokenType::BINARY ) {
			isBinary = false;
			break;
		}
		binLength += token.binary( ).size( );
	}

	// Binary concat
	if ( isBinary ) {
		auto ob( NewShared< T_Buffer< uint8_t > >( binLength ) );
		binLength = 0;
		for ( uint32_t i = 0 ; i < size ; i ++ ) {
			auto const& bin( input[ i ].binary( ) );
			memcpy( ob->data( ) + binLength ,
					bin.data( ) , bin.size( ) );
			binLength += bin.size( );
		}
		T_SRDToken otok( T_SRDToken::Binary( ob ) );
		otok.location( loc );
		output.add( std::move( otok ) );
		return;
	}

	// String concat
	T_StringBuilder sb;
	for ( uint32_t i = 0 ; i < size ; i ++ ) {
		auto const& token( input[ i ] );
		if ( !( token.isText( ) || token.isNumeric( ) ) ) {
			data.addError( token , "expected text or numeric argument" );
			continue;
		}

		switch ( token.type( ) ) {

			case E_SRDTokenType::STRING:
			case E_SRDTokenType::WORD:
				sb << token.stringValue( );
				continue;

			case E_SRDTokenType::INT:
			case E_SRDTokenType::LONG:
				sb << token.longValue( );
				break;

			case E_SRDTokenType::FLOAT:
				sb << token.floatValue( );
				break;

			default:
				assert( 0 && "we shouldn't be here" );
				continue;
				
		}
	}

	T_SRDToken otok( T_SRDToken::String( std::move( sb ) ) );
	otok.location( loc );
	output.add( std::move( otok ) );
}

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

M_SRDPP_COMMAND_EXEC( Div )
{
	DoMathOp_( state ,
		  []( auto a , auto b ) {
		return a / b;
	} ,
		  []( auto a , auto b ) {
		return a / b;
	} , true );
}

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

M_SRDPP_COMMAND_EXEC( EndsWith )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	uint32_t value = 0;
	if ( input.size( ) < 2 ) {
		data.addError( loc , "not enough arguments" );
	} else if ( input.size( ) > 2 ) {
		data.addError( loc , "too many arguments" );
	} else {
		if ( input[ 0 ].isText( ) && input[ 1 ].isText( ) ) {
			value = input[ 1 ].stringValue( ).endsWith(
					input[ 0 ].stringValue( ) );
		} else if ( input[ 0 ].type( ) == E_SRDTokenType::BINARY
				&& input[ 1 ].type( ) == E_SRDTokenType::BINARY ) {
			auto const& needle( input[ 0 ].binary( ) );
			auto const& haystack( input[ 1 ].binary( ) );
			auto const nsz( needle.size( ) );
			auto const hsz( haystack.size( ) );
			value = ( nsz <= hsz
				&& !( nsz && memcmp( needle.data( ) ,
						haystack.data( ) + hsz - nsz ,
						nsz ) ) );
		} else {
			data.addError( loc , "invalid arguments" );
		}
	}

	auto tok( T_SRDToken::Integer( value ) );
	tok.location( loc );
	data.top( ).output( ).add( std::move( tok ) );
}

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

M_SRDPP_COMMAND_EXEC( Eval )
{
	if ( state.output( ).size( ) == 0 ) {
		return;
	}
	auto& data( state.data( ) );
//	data.push( T_SRDPreprocessorState::C_CALL , state.outputPointer( ) ,
//			data.top( ).outputPointer( ) );
	auto& input( state.output( ) );
	const auto is( input.size( ) );
	if ( is ) {
		const SP_SRDLocation evalLocation( state.createChainedLocation( ) );
		SP_SRDList updated( NewShared< T_SRDList >( is ) );
		for ( size_t i = 0 ; i < is ; i ++ ) {
			T_SRDToken token( std::move( input[ i ] ) );

			auto& loc( token.location( ) );
			SP_SRDLocation el( ( i == is - 1 )
					? evalLocation
					: NewShared< T_SRDLocation >( *evalLocation ) );
			if ( loc.isChained( ) ) {
				auto const& lc( loc.chaining( ) );
				el->chain( lc.circumstances , lc.location );
			}
			loc.chain( E_SRDLocationChaining::EVALUATED , el );

			updated->add( std::move( token ) );
		}

		data.push( T_SRDPreprocessorState::C_UNSPECIFIED , updated ,
				data.top( ).outputPointer( ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( Error )
{
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );
	auto& input( state.output( ) );
	const auto nTokens( input.size( ) );
	if ( nTokens == 0 ) {
		data.addError( loc , "user error" );
	} else {
		T_StringBuilder sb;
		for ( uint32_t i = 0 ; i < nTokens ; i ++ ) {
			if ( i > 0 ) {
				sb << ' ';
			}
			auto& tok( input[ i ] );
			switch( tok.type( ) ) {

				case E_SRDTokenType::VAR:
					sb << '$';
				case E_SRDTokenType::STRING:
				case E_SRDTokenType::WORD:
					sb << tok.stringValue( );
					break;

				case E_SRDTokenType::INT:
				case E_SRDTokenType::LONG:
					sb << tok.longValue( );
					break;

				case E_SRDTokenType::FLOAT:
					sb << tok.floatValue( );
					break;

				case E_SRDTokenType::LIST:
					tok.generateFullText( );
					sb << tok.fullText( );
					break;

				default:
					sb << "(INTERNAL ERROR)";
					break;
			}
		}
		data.addError( loc , sb );
	}
}

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

M_SRDPP_COMMAND_EXEC( FromSource )
{
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	auto& input( state.output( ) );
	const auto is( input.size( ) );
	if ( is == 0 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}
	if ( is > 2 ) {
		data.addError( loc , "too many arguments" );
	}

	if ( input[ 0 ].type( ) != E_SRDTokenType::STRING ) {
		data.addError( input[ 0 ] , "string expected" );
		return;
	}
	if ( is == 2 && !input[ 1 ].isText( ) ) {
		data.addError( input[ 1 ] , "word or string expected" );
	}

	const T_String inputName( ( is == 2 && input[ 1 ].isText( ) )
			? input[ 1 ].stringValue( )
			: T_String::Pooled( "(-from-source)" ) );
	T_SRDErrors& errors( data.errorContext( ) );

	T_SRDMemoryTarget tgt( true );
	T_SRDLexer lexer( inputName , errors , tgt );
	tgt.start( errors );

	T_StringIterator it( input[ 0 ].stringValue( ) );
	while ( !it.atEnd( ) ) {
		lexer.processCharacter( it );
		it.next( );
	}
	lexer.processEnd( );

	auto const& generated( tgt.list( ) );
	auto& output( data.top( ).output( ) );
	const auto gs( generated.size( ) );
	const SP_SRDLocation gloc( state.createChainedLocation( ) );
	for ( size_t i = 0 ; i < gs ; i ++ ) {
		T_SRDToken t( generated[ i ] );
		t.location( ).chain( E_SRDLocationChaining::GENERATED , gloc );
		output.add( std::move( t ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( FromSRB )
{
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	auto& input( state.output( ) );
	const auto is( input.size( ) );
	if ( is == 0 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}
	if ( is > 2 ) {
		data.addError( loc , "too many arguments" );
	}

	if ( input[ 0 ].type( ) != E_SRDTokenType::BINARY ) {
		data.addError( input[ 0 ] , "binary array expected" );
		return;
	}
	if ( is == 2 && !input[ 1 ].isText( ) ) {
		data.addError( input[ 1 ] , "word or string expected" );
	}

	const T_String inputName( ( is == 2 && input[ 1 ].isText( ) )
			? input[ 1 ].stringValue( )
			: T_String::Pooled( "(-from-srb)" ) );
	T_SRDErrors& errors( data.errorContext( ) );

	T_SRDMemoryTarget tgt( true );
	T_SRDBinaryReader reader( tgt );
	T_MemoryInputStream istream( input[ 0 ].binary( ) );
	try {
		reader.read( inputName , istream );
	} catch ( X_SRDErrors const& srbErrors ) {
		errors.addAll( srbErrors.errors );
	}
	data.top( ).output( ).addAll( tgt.list( ) );
}

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

M_SRDPP_COMMAND_EXEC( Get )
{
	auto& data( state.data( ) );
	auto& input( state.output( ) );
	const auto sz( input.size( ) );
	auto& output( state.data( ).top( ).output( ) );

	for ( uint32_t i = 0 ; i < sz ; i ++ ) {
		auto const& vtok( input[ i ] );
		if ( vtok.type( ) != E_SRDTokenType::WORD ) {
			data.addError( vtok , "word expected" );
			continue;
		}

		auto var( data.scopes( ).get( false , vtok.stringValue( ) ) );
		if ( var == nullptr ) {
			data.addError( vtok , "unknown variable" );
		} else {
			output.addAll( *var );
		}
	}
}

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

M_SRDPP_COMMAND_EXEC( If )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );
	const auto sz( input.size( ) );

	if ( sz < 2 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}
	if ( sz > 3 ) {
		data.addError( loc , "too many arguments" );
		return;
	}

	auto const& cdTok( input[ 0 ] );
	if ( cdTok.type( ) != E_SRDTokenType::INT && cdTok.type( ) != E_SRDTokenType::LONG ) {
		data.addError( cdTok , "integer expected" );
		return;
	}
	if ( input[ 1 ].type( ) != E_SRDTokenType::LIST ) {
		data.addError( input[ 1 ] , "list expected" );
		return;
	}
	if ( sz == 3 && input[ 2 ].type( ) != E_SRDTokenType::LIST ) {
		data.addError( input[ 2 ] , "list expected" );
		return;
	}

	const bool cond( cdTok.longValue( ) != 0 );
	if ( sz == 2 && !cond ) {
		return;
	}
	data.push( T_SRDPreprocessorState::C_INNER ,
			NewShared< T_SRDList >( input[ cond ? 1 : 2 ].list( ) ) ,
			data.top( ).outputPointer( ) );
}

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

M_SRDPP_COMMAND_EXEC( Ignore )
{
	(void) state;
}

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

M_SRDPP_COMMAND_EXEC( IsBlessed )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	bool blessed;
	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		blessed = false;
	} else if ( input.size( ) > 1 ) {
		data.addError( loc , "too many arguments" );
		blessed = false;
	} else if ( input[ 0 ].type( ) != E_SRDTokenType::WORD ) {
		data.addError( input[ 0 ] , "word expected" );
		blessed = false;
	} else {
		blessed = data.scopes( ).isBlessed( input[ 0 ].stringValue( ) );
	}
	T_SRDToken output( T_SRDToken::Integer( blessed ? 1 : 0 ) );
	output.location( loc );
	state.data( ).top( ).output( ).add( std::move( output ) );
}

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

M_SRDPP_COMMAND_EXEC( IsMacro )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	bool set;
	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		set = false;
	} else if ( input.size( ) > 1 ) {
		data.addError( loc , "too many arguments" );
		set = false;
	} else if ( input[ 0 ].type( ) != E_SRDTokenType::WORD ) {
		data.addError( input[ 0 ] , "word expected" );
		set = false;
	} else {
		set = ( data.scopes( ).get( true , input[ 0 ].stringValue( ) ) != nullptr );
	}
	T_SRDToken output( T_SRDToken::Integer( set ? 1 : 0 ) );
	output.location( loc );
	state.data( ).top( ).output( ).add( std::move( output ) );
}

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

M_SRDPP_COMMAND_EXEC( IsSet )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	bool set;
	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		set = false;
	} else if ( input.size( ) > 1 ) {
		data.addError( loc , "too many arguments" );
		set = false;
	} else if ( input[ 0 ].type( ) != E_SRDTokenType::WORD ) {
		data.addError( input[ 0 ] , "word expected" );
		set = false;
	} else {
		set = ( data.scopes( ).get( false , input[ 0 ].stringValue( ) ) != nullptr );
	}
	T_SRDToken output( T_SRDToken::Integer( set ? 1 : 0 ) );
	output.location( loc );
	state.data( ).top( ).output( ).add( std::move( output ) );
}

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

M_SRDPP_COMMAND_EXEC( Length )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	int64_t length;
	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		length = -1;
	} else if ( input.size( ) > 1 ) {
		data.addError( loc , "too many arguments" );
		length = -1;
	} else if ( input[ 0 ].type( ) == E_SRDTokenType::LIST ) {
		length = input[ 0 ].list( ).size( );
	} else if ( input[ 0 ].type( ) == E_SRDTokenType::BINARY ) {
		length = input[ 0 ].binary( ).size( );
	} else if ( input[ 0 ].isText( ) ) {
		length = input[ 0 ].stringValue( ).length( );
	} else {
		data.addError( input[ 0 ] , "list, word, string or binary array expected" );
		length = -1;
	}

	T_SRDToken output( T_SRDToken::AutoInteger( length ) );
	output.location( loc );
	state.data( ).top( ).output( ).add( std::move( output ) );
}

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

M_SRDPP_COMMAND_EXEC( ListMacros )
{
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );
	if ( state.output( ).size( ) != 0 ) {
		data.addError( loc , "too many arguments" );
	}

	auto & output( data.top( ).output( ) );
	T_Array< T_String > macroNames( data.scopes( ).list( true ) );
	const auto nMacros( macroNames.size( ) );
	for ( uint32_t i = 0 ; i < nMacros ; i ++ ) {
		T_SRDToken tok( T_SRDToken::Word( macroNames[ i ] ) );
		tok.location( loc );
		output.add( std::move( tok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( ListVariables )
{
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );
	if ( state.output( ).size( ) != 0 ) {
		data.addError( loc , "too many arguments" );
	}

	auto & output( data.top( ).output( ) );
	T_Array< T_String > varNames( data.scopes( ).list( false ) );
	const auto nVars( varNames.size( ) );
	for ( uint32_t i = 0 ; i < nVars ; i ++ ) {
		T_SRDToken tok( T_SRDToken::Word( varNames[ i ] ) );
		tok.location( loc );
		output.add( std::move( tok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( Mod )
{
	DoMathOp_( state ,
		  []( auto a , auto b ) {
		return a % b;
	} ,
		  []( auto a , auto b ) {
		return std::fmod( a , b );
	} , true );
}

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

M_SRDPP_COMMAND_EXEC( Mul )
{
	DoMathOp_( state ,
		  []( auto a , auto b ) {
		return a * b;
	} ,
		  []( auto a , auto b ) {
		return a * b;
	} );
}

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

M_SRDPP_COMMAND_EXEC( Neg )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	bool of( false );
	int64_t iv( 0 );
	double fv( 0 );
	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
	} else if ( input.size( ) != 1 ) {
		data.addError( loc , "too many arguments" );
	} else if ( !input[ 0 ].isNumeric( ) ) {
		data.addError( input[ 0 ] , "numeric argument expected" );
	} else if ( input[ 0 ].isInteger( ) ) {
		iv = -input[ 0 ].longValue( );
	} else {
		fv = -input[ 0 ].floatValue( );
		of = true;
	}

	T_SRDToken output( of ? T_SRDToken::Float( fv ) : T_SRDToken::AutoInteger( iv ) );
	output.location( loc );
	state.data( ).top( ).output( ).add( std::move( output ) );
}

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

M_SRDPP_COMMAND_EXEC( Not )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	int64_t iv( 0 );
	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
	} else if ( input.size( ) != 1 ) {
		data.addError( loc , "too many arguments" );
	} else if ( !input[ 0 ].isInteger( ) ) {
		data.addError( input[ 0 ] , "integer argument expected" );
	} else {
		iv = input[ 0 ].longValue( ) ? 0 : 1;
	}

	T_SRDToken output( T_SRDToken::Integer( iv ) );
	output.location( loc );
	state.data( ).top( ).output( ).add( std::move( output ) );
}

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

M_SRDPP_COMMAND_EXEC( Or )
{
	DoLogicOp_( state ,
		  []( auto a , auto b ) {
		return ( a || b ) ? 1 : 0;
	} );
}

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

M_SRDPP_COMMAND_EXEC( Output )
{
	auto& out( state.output( ) );
	const auto n( out.size( ) );

	if ( n ) {
		auto& data( state.data( ) );
		auto& pp( data.preprocessor( ) );
		auto& ec( data.errorContext( ) );
		for ( uint32_t i = 0 ; i < n ; i ++ ) {
			pp.sendOutput( ec , out[ i ] );
		}
		pp.target( ).push( ec , T_SRDToken::Flush( ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( Raw )
{
	state.data( ).top( ).output( ).addAll( state.output( ) );
}

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

namespace {

void RethrowError_( T_SRDPreprocessorData& data , T_SRDList const& error )
{
	auto const& esTok( error[ 0 ] );
	if ( esTok.type( ) != E_SRDTokenType::STRING ) {
		data.addError( esTok , "string expected" );
		return;
	}

	const auto elSize( error.size( ) );
	SP_SRDLocation location;
	for ( uint32_t i = elSize - 1 ; i > 0 ; i -- ) {
		auto const& locEntry( error[ i ] );
		if ( locEntry.type( ) != E_SRDTokenType::LIST ) {
			data.addError( locEntry , "list expected" );
			return;
		}

		auto const& locList( locEntry.list( ) );
		const bool hasChaining( i != elSize - 1 );
		const uint32_t expectedLength( hasChaining ? 4 : 3 );
		if ( locList.size( ) != expectedLength ) {
			data.addError( locEntry , "invalid location entry" );
			return;
		}

		auto const& locName( locList[ 0 ] );
		if ( locName.type( ) != E_SRDTokenType::STRING ) {
			data.addError( locName , "string expected" );
			return;
		}
		auto const& locLine( locList[ 1 ] );
		if ( !locLine.isInteger( ) ) {
			data.addError( locLine , "integer expected" );
			return;
		}
		const int64_t line( locLine.longValue( ) );
		if ( line < 0 || line > UINT32_MAX ) {
			data.addError( locLine , "invalid line number" );
			return;
		}

		auto const& locChar( locList[ 2 ] );
		if ( !locChar.isInteger( ) ) {
			data.addError( locChar , "integer expected" );
			return;
		}
		const int64_t character( locChar.longValue( ) );
		if ( character < 0 || character > UINT32_MAX ) {
			data.addError( locChar , "invalid character/byte number" );
			return;
		}

		E_SRDLocationChaining chaining;
		if ( hasChaining ) {
			if ( locList[ 3 ].type( ) != E_SRDTokenType::WORD ) {
				data.addError( locList[ 3 ] , "word expected" );
				return;
			}
			T_String const& chainWord( locList[ 3 ].stringValue( ) );
			if ( chainWord == "generated" ) {
				chaining = E_SRDLocationChaining::GENERATED;
			} else if ( chainWord == "included" ) {
				chaining = E_SRDLocationChaining::INCLUDED;
			} else if ( chainWord == "loaded" ) {
				chaining = E_SRDLocationChaining::LOADED;
			} else if ( chainWord == "called" ) {
				chaining = E_SRDLocationChaining::CALLED;
			} else if ( chainWord == "evaluated" ) {
				chaining = E_SRDLocationChaining::EVALUATED;
			} else if ( chainWord == "expanded" ) {
				chaining = E_SRDLocationChaining::EXPANDED;
			} else if ( chainWord == "substituted" ) {
				chaining = E_SRDLocationChaining::SUBSTITUTED;
			} else {
				data.addError( locList[ 3 ] , "invalid chaining" );
				return;
			}
		} else {
			// Compiler stfu
			chaining = E_SRDLocationChaining( 0 );
		}

		T_String const& src( locName.stringValue( )  );
		SP_SRDLocation nLocation( line > 0
				? NewShared< T_SRDLocation >( src , line , character )
				: NewShared< T_SRDLocation >( src , character ) );
		if ( hasChaining ) {
			nLocation->chain( chaining , location );
		}
		location = nLocation;
	}

	data.addError( *location , esTok.stringValue( ) );
}

void DoRethrow_( T_SRDPreprocessorData& data , T_SRDList const& errors )
{
	const auto nErrors( errors.size( ) );
	for ( uint32_t i = 0 ; i < nErrors ; i ++ ) {
		auto const& errTok( errors[ i ] );
		if ( errTok.type( ) != E_SRDTokenType::LIST ) {
			data.addError( errTok , "list expected" );
			continue;
		}
		if ( errTok.list( ).size( ) < 2 ) {
			data.addError( errTok , "invalid error record" );
			continue;
		}
		RethrowError_( data , errTok.list( ) );
	}
}

}

M_SRDPP_COMMAND_EXEC( Rethrow )
{
	auto const& input( state.output( ) );
	if ( input.size( ) == 0 ) {
		return;
	}
	if ( input.size( ) == 1 && input[ 0 ].type( ) == E_SRDTokenType::LIST ) {
		auto const& innerList( input[ 0 ].list( ) );
		if ( innerList.size( ) == 0 ) {
			return;
		}
		if ( innerList[ 0 ].type( ) == E_SRDTokenType::LIST ) {
			DoRethrow_( state.data( ) , innerList );
			return;
		}
	}
	DoRethrow_( state.data( ) , input );
}

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

M_SRDPP_COMMAND_EXEC( Scope )
{
	if ( state.output( ).size( ) == 0 ) {
		return;
	}
	auto& data( state.data( ) );
	data.scopes( ).enter( );
	data.push( T_SRDPreprocessorState::C_INNER ,
			state.outputPointer( ) , data.top( ).outputPointer( ) ,
			[]( T_SRDPreprocessorState & ns ) {
				ns.data( ).scopes( ).exit( );
			} );
}

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

M_SRDPP_COMMAND_EXEC( Set )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}

	auto const& names( input[ 0 ] );
	auto& s( data.scopes( ) );
	if ( names.type( ) == E_SRDTokenType::WORD ) {
		auto const& vn( names.stringValue( ) );
		if ( !s.canSet( false , vn ) ) {
			data.addError( names , "duplicate variable name" );
			return;
		}
		s.set( false , vn , input , 1 );

	} else if ( names.type( ) == E_SRDTokenType::LIST ) {
		auto const& list( names.list( ) );
		const auto n( list.size( ) );
		if ( n == 0 ) {
			data.addError( names , "non-empty list expected" );
			return;
		}

		bool ok( true );
		for ( uint32_t i = 0 ; i < n ; i ++ ) {
			auto const& name( list[ i ] );
			if ( name.type( ) != E_SRDTokenType::WORD ) {
				data.addError( name , "word expected" );
				ok = false;
				continue;
			}

			auto const& vn( name.stringValue( ) );
			for ( uint32_t j = 0 ; j < i ; j ++ ) {
				if ( list[ j ].type( ) != E_SRDTokenType::WORD ) {
					continue;
				}
				if ( list[ j ].stringValue( ) == vn ) {
					data.addError( name , "duplicate variable name" );
					ok = false;
					goto next;
				}
			}
			if ( !s.canSet( false , vn ) ) {
				data.addError( name , "duplicate variable name" );
				ok = false;
			}

next:                   ;
		}

		if ( !ok ) {
			return;
		}
		for ( uint32_t i = 0 ; i + 1 < n ; i ++ ) {
			s.set( false , list[ i ].stringValue( ) , input , 1 + i , 1 + i );
		}
		s.set( false , list[ n - 1 ].stringValue( ) , input , n );

	} else {
		data.addError( names , "word or list expected" );
	}
}

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

M_SRDPP_COMMAND_EXEC( SetMacro )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	if ( input.size( ) < 2 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}
	if ( input.size( ) > 2 ) {
		data.addError( loc , "too many arguments" );
		return;
	}

	auto const& name( input[ 0 ] );
	if ( name.type( ) != E_SRDTokenType::WORD ) {
		data.addError( name , "word expected" );
		return;
	}
	auto const& mn( name.stringValue( ) );
	auto& s( data.scopes( ) );
	if ( !s.canSet( true , mn ) ) {
		data.addError( name , "duplicate macro name" );
		return;
	}

	auto const& value( input[ 1 ] );
	if ( !T_SRDPreprocessor::isValidFunction( value ) ) {
		data.addError( value , "invalid macro body" );
		return;
	}

	s.set( true , mn , value.list( ) );
}

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

M_SRDPP_COMMAND_EXEC( StartsWith )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	uint32_t value = 0;
	if ( input.size( ) < 2 ) {
		data.addError( loc , "not enough arguments" );
	} else if ( input.size( ) > 2 ) {
		data.addError( loc , "too many arguments" );
	} else {
		if ( input[ 0 ].isText( ) && input[ 1 ].isText( ) ) {
			value = input[ 1 ].stringValue( ).startsWith(
					input[ 0 ].stringValue( ) );
		} else if ( input[ 0 ].type( ) == E_SRDTokenType::BINARY
				&& input[ 1 ].type( ) == E_SRDTokenType::BINARY ) {
			auto const& needle( input[ 0 ].binary( ) );
			auto const& haystack( input[ 1 ].binary( ) );
			auto const nsz( needle.size( ) );
			auto const hsz( haystack.size( ) );
			value = ( nsz <= hsz
				&& !( nsz && memcmp( needle.data( ) ,
						haystack.data( ) , nsz ) ) );
		} else {
			data.addError( loc , "invalid arguments" );
		}
	}

	auto tok( T_SRDToken::Integer( value ) );
	tok.location( loc );
	data.top( ).output( ).add( std::move( tok ) );
}

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

M_SRDPP_COMMAND_EXEC( StrFind )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	int32_t value = -1;
	if ( input.size( ) < 2 ) {
		data.addError( loc , "not enough arguments" );
	} else if ( input.size( ) > 2 ) {
		data.addError( loc , "too many arguments" );
	} else {
		if ( !input[ 0 ].isText( ) ) {
			data.addError( input[ 0 ] , "text expected" );
		}
		if ( !input[ 1 ].isText( ) ) {
			data.addError( input[ 1 ] , "text expected" );
		}
		if ( input[ 0 ].isText( ) && input[ 1 ].isText( ) ) {
			value = input[ 1 ].stringValue( ).find(
					input[ 0 ].stringValue( ) );
		}
	}

	auto tok( T_SRDToken::Integer( value ) );
	tok.location( loc );
	data.top( ).output( ).add( std::move( tok ) );
}

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

M_SRDPP_COMMAND_EXEC( StrSplit )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );
	auto& output( data.top( ).output( ) );

	if ( input.size( ) < 2 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}
	if ( input.size( ) > 3 ) {
		data.addError( loc , "too many arguments" );
		return;
	}

	bool argsOk = true;
	if ( !input[ 0 ].isText( ) ) {
		data.addError( input[ 0 ] , "text expected" );
		argsOk = false;
	} else if ( !input[ 0 ].stringValue( ) ) {
		data.addError( input[ 0 ] , "invalid argument value" );
		argsOk = false;
	}
	if ( !input[ 1 ].isText( ) ) {
		data.addError( input[ 1 ] , "text expected" );
		argsOk = false;
	}
	if ( input.size( ) == 3
			&& input[ 2 ].type( ) != E_SRDTokenType::INT
			&& input[ 2 ].type( ) != E_SRDTokenType::LONG ) {
		data.addError( input[ 2 ] , "integer expected" );
		argsOk = false;
	}
	if ( !argsOk ) {
		return;
	}
	uint32_t maxOutput;
	if ( input.size( ) == 3 ) {
		int64_t n( input[ 2 ].longValue( ) );
		if ( n < 0 || n > UINT32_MAX ) {
			data.addError( input[ 2 ] , "invalid argument value" );
			maxOutput = UINT32_MAX;
		} else {
			maxOutput = n;
		}
	} else {
		maxOutput = UINT32_MAX;
	}

	int32_t nextSearch( 0 );
	auto const& needle( input[ 0 ].stringValue( ) );
	auto const& haystack( input[ 1 ].stringValue( ) );
	while ( nextSearch != -1 && maxOutput != 0 ) {
		const int32_t sepIndex( haystack.find( needle , nextSearch ) );

		const T_String os( sepIndex == -1
				? haystack.substr( nextSearch )
				: ( sepIndex == 0
					? T_String( )
					: haystack.range( nextSearch ,  sepIndex - 1 ) ) );
		T_SRDToken otok( T_SRDToken::String( os ) );
		otok.location( loc );
		output.add( std::move( otok ) );

		if ( sepIndex == -1 ) {
			nextSearch = -1;
		} else {
			nextSearch = sepIndex + needle.length( );
		}
		maxOutput --;
	}

	if ( maxOutput == 0 && nextSearch != -1 ) {
		T_SRDToken otok( T_SRDToken::String( haystack.substr( nextSearch ) ) );
		otok.location( loc );
		output.add( std::move( otok ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( Sub )
{
	DoMathOp_( state ,
		  []( auto a , auto b ) {
		return a - b;
	} ,
		  []( auto a , auto b ) {
		return a - b;
	} );
}

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

M_SRDPP_COMMAND_EXEC( Substr )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	const auto is( input.size( ) );
	if ( is < 2 ) {
		data.addError( loc , "not enough arguments" );
	} else if ( is > 4 ) {
		data.addError( loc , "too many arguments" );
	}
	if ( is == 0 ) {
		T_SRDToken otok( T_SRDToken::String( T_String( ) ) );
		otok.location( loc );
		data.top( ).output( ).add( std::move( otok ) );
		return;
	}

	const bool isBinary( input[ 0 ].type( ) == E_SRDTokenType::BINARY );
	if ( is < 2 || is > 4 ) {
		T_SRDToken otok( isBinary
				? T_SRDToken::Binary( (uint8_t const*) nullptr , 0 )
				: T_SRDToken::String( T_String( ) ) );
		otok.location( loc );
		data.top( ).output( ).add( std::move( otok ) );
		return;
	}

	const bool isRange = ( is == 4 );
	bool argsOk = true;
	uint32_t start( 0 );
	uint32_t end( UINT32_MAX );

	if ( !( input[ 0 ].isText( ) || isBinary ) ) {
		data.addError( input[ 0 ] , "binary or text expected" );
		argsOk = false;
	}

	if ( isRange && ( input[ 2 ].type( ) != E_SRDTokenType::WORD
				|| input[ 2 ].stringValue( ) != "to" ) ) {
		data.addError( input[ 2 ] , "'to' expected" );
		argsOk = false;
	}

	if ( !input[ 1 ].isNumeric( ) || input[ 1 ].type( ) == E_SRDTokenType::FLOAT ) {
		data.addError( input[ 1 ] , "integer expected" );
		argsOk = false;
	} else {
		const int64_t v( input[ 1 ].longValue( ) );
		if ( v < 0 || v > UINT32_MAX ) {
			data.addError( input[ 1 ] , "invalid argument value" );
			argsOk = false;
		} else {
			start = v;
		}
	}

	if ( is > 2 ) {
		auto const& next( input[ isRange ? 3 : 2 ] );
		if ( !next.isNumeric( ) || next.type( ) == E_SRDTokenType::FLOAT ) {
			data.addError( next , "integer expected" );
			argsOk = false;
		} else {
			const int64_t v( next.longValue( ) );
			if ( v < 0 || v > UINT32_MAX ) {
				data.addError( next , "invalid argument value" );
				argsOk = false;
			} else {
				end = v;
			}
		}
	}

	if ( !argsOk ) {
		T_SRDToken otok( isBinary
				? T_SRDToken::Binary( (uint8_t const*) nullptr , 0 )
				: T_SRDToken::String( T_String( ) ) );
		otok.location( loc );
		data.top( ).output( ).add( std::move( otok ) );
		return;
	}

	if ( !isBinary ) {
		auto const& str( input[ 0 ].stringValue( ) );
		T_SRDToken otok( T_SRDToken::String( isRange
				? str.range( start , end )
				: str.substr( start , end ) ) );
		otok.location( loc );
		data.top( ).output( ).add( std::move( otok ) );
		return;
	}

	const uint32_t oriLen( input[ 0 ].binary( ).size( ) );
	const uint32_t realEnd( std::min( oriLen , end ) );
	const uint32_t lengthMax( isRange
			? ( realEnd < start ? 0 : realEnd - start + 1 )
			: realEnd );
	const uint32_t length( start >= oriLen
			? 0 : ( std::min( oriLen , start + lengthMax ) - start ) );
	auto buffer( NewShared< T_Buffer< uint8_t > >( length ) );
	if ( length ) {
		memcpy( buffer->data( ) , input[ 0 ].binary( ).data( ) , length );
	}
	T_SRDToken otok( T_SRDToken::Binary( buffer ) );
	otok.location( loc );
	data.top( ).output( ).add( std::move( otok ) );
}

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

M_SRDPP_COMMAND_EXEC( ToSource )
{
	auto& input( state.output( ) );
	const auto n( input.size( ) );
	T_StringBuilder sb;
	for ( uint32_t i = 0 ; i < n ; i ++ ) {
		if ( i > 0 ) {
			sb << ' ';
		}

		auto& token( input[ i ] );
		token.generateFullText( );
		sb << token.fullText( );
	}

	T_SRDToken otok( T_SRDToken::String( std::move( sb ) ) );
	otok.location( *state.initialLocation( ) );
	state.data( ).top( ).output( ).add( std::move( otok ) );
}

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

namespace {

void ErrorsToList_( T_SRDLocation const& location , T_SRDErrors const& errors , T_SRDList & output )
{
	const auto nErrors( errors.size( ) );
	for ( uint32_t i = 0 ; i < nErrors ; i ++ ) {
		auto const& error( errors[ i ] );
		auto& errToken( output[ output.add( T_SRDToken::List( ) ) ] );
		auto& etl( errToken.list( ) );
		errToken.location( location );

		{
			T_SRDToken msg( T_SRDToken::String( error.error( ) ) );
			msg.location( location );
			etl.add( std::move( msg ) );
		}

		RPC_SRDLocation errLoc( &error.location( ) );
		RPC_SRDLocationChaining chaining( nullptr );
		while ( errLoc != nullptr ) {
			auto& locToken( etl[ etl.add( T_SRDToken::List( ) ) ] );
			auto& ll( locToken.list( ) );
			locToken.location( location );

			{
				T_SRDToken src( T_SRDToken::String( errLoc->source( ) ) );
				src.location( location );
				ll.add( std::move( src ) );
			}{
				T_SRDToken ln( T_SRDToken::Integer( errLoc->line( ) ) );
				ln.location( location );
				ll.add( std::move( ln ) );
			}{
				T_SRDToken cn( T_SRDToken::Integer( errLoc->character( ) ) );
				cn.location( location );
				ll.add( std::move( cn ) );
			}
			if ( chaining ) {
				T_String chain( T_String::Pooled( ( []( RPC_SRDLocationChaining c ) -> char const* {
					switch ( c->circumstances ) {
					    case E_SRDLocationChaining::INCLUDED:
						return "included";
					    case E_SRDLocationChaining::LOADED:
						return "loaded";
					    case E_SRDLocationChaining::CALLED:
						return "called";
					    case E_SRDLocationChaining::GENERATED:
						return "generated";
					    case E_SRDLocationChaining::SUBSTITUTED:
						return "substituted";
					    case E_SRDLocationChaining::EVALUATED:
						return "evaluated";
					    case E_SRDLocationChaining::EXPANDED:
						return "expanded";
					}
					return "unknown";
				} )( chaining ) ) );

				T_SRDToken ctok( T_SRDToken::Word( std::move( chain ) ) );
				ctok.location( location );
				ll.add( std::move( ctok ) );
			}

			if ( errLoc->isChained( ) ) {
				chaining = &errLoc->chaining( );
				errLoc = RPC_SRDLocation( chaining->location );
			} else {
				errLoc = nullptr;
			}
		}
	}
}

}

M_SRDPP_COMMAND_EXEC( Try )
{
	auto& data( state.data( ) );
	auto& output( data.top( ).output( ) );

	T_SRDToken otok( T_SRDToken::List( ) );
	otok.location( *state.initialLocation( ) );
	if ( state.output( ).size( ) == 0 ) {
		// No input -> empty list
		output.add( std::move( otok ) );
		return;
	}

	const SP_SRDList fullOutput( NewShared< T_SRDList >( 16 ) );
	fullOutput->add( std::move( otok ) );

	data.pushErrorContext( );
	data.push( T_SRDPreprocessorState::C_TRY , state.outputPointer( ) , fullOutput ,
			[]( T_SRDPreprocessorState & ns ) {
				const RP_SRDErrors errors( ns.data( ).popErrorContext( ) );
				auto& errTok( ns.output( )[ 0 ] );
				ErrorsToList_( errTok.location( ) , *errors , errTok.list( ) );
				delete errors;
				ns.data( ).top( ).output( ).addAll( std::move( ns.output( ) ) );
			} );
}

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

M_SRDPP_COMMAND_EXEC( TypeOf )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	char const* type;
	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		type = "none";
	} else if ( input.size( ) > 1 ) {
		data.addError( loc , "too many arguments" );
		type = "none";
	} else {
		switch ( input[ 0 ].type( ) ) {
		    case E_SRDTokenType::LIST:
			type = "list";
			break;

		    case E_SRDTokenType::WORD:
			type = "word";
			break;

		    case E_SRDTokenType::STRING:
			type = "string";
			break;

		    case E_SRDTokenType::INT:
			type = "int";
			break;

		    case E_SRDTokenType::LONG:
			type = "long";
			break;

		    case E_SRDTokenType::FLOAT:
			type = "real";
			break;

		    case E_SRDTokenType::VAR:
			type = "var";
			break;

		    case E_SRDTokenType::BINARY:
			type = "binary";
			break;

		    case E_SRDTokenType::COMMENT:
			type = "comment";
			break;

		    default:
			type = "none";
			data.addError( input[ 0 ] , "internal error" );
			break;
		}
	}

	T_SRDToken output( T_SRDToken::Word( T_String::Pooled( type ) ) );
	output.location( loc );
	state.data( ).top( ).output( ).add( std::move( output ) );
}

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

M_SRDPP_COMMAND_EXEC( Unset )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}

	const uint32_t n( input.size( ) );
	for ( uint32_t i = 0 ; i < n ; i ++ ) {
		auto const& name( input[ i ] );
		if ( name.type( ) != E_SRDTokenType::WORD ) {
			data.addError( name , "word expected" );
		} else {
			data.scopes( ).unset( false , name.stringValue( ) );
		}
	}
}

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

M_SRDPP_COMMAND_EXEC( UnsetMacro )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}

	const uint32_t n( input.size( ) );
	for ( uint32_t i = 0 ; i < n ; i ++ ) {
		auto const& name( input[ i ] );
		if ( name.type( ) != E_SRDTokenType::WORD ) {
			data.addError( name , "word expected" );
		} else {
			data.scopes( ).unset( true , name.stringValue( ) );
		}
	}
}

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

M_SRDPP_COMMAND_EXEC( Unwrap )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
	} else if ( input.size( ) > 1 ) {
		data.addError( loc , "too many arguments" );
	} else if ( input[ 0 ].type( ) != E_SRDTokenType::LIST ) {
		data.addError( input[ 0 ] , "list expected" );
	} else {
		data.top( ).output( ).addAll( std::move( input[ 0 ].list( ) ) );
	}
}

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

M_SRDPP_COMMAND_EXEC( Xor )
{
	DoLogicOp_( state ,
		  []( auto a , auto b ) {
		return ( a || b ) && !( a && b );
	} );
}


/*= VFS COMMANDS =============================================================*/

M_SRDPP_COMMAND_EXEC( VFSList )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}
	if ( input.size( ) > 1 ) {
		data.addError( loc , "too many arguments" );
		return;
	}
	if ( input[ 0 ].type( ) != E_SRDTokenType::STRING ) {
		data.addError( input[ 0 ] , "string expected" );
		return;
	}

	const T_VFSPath path( input[ 0 ].stringValue( ) );
	if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
		data.addError( input[ 0 ] , "absolute VFS path expected" );
		return;
	}

	T_Array< T_VFSPath > list( 32 );
	if ( !vfs_.list( path , list ) ) {
		return;
	}

	const uint32_t n( list.size( ) );
	T_SRDList output( std::max( 1u , n ) );
	output.ensureCapacity( n );
	for ( uint32_t i = 0 ; i < n ; i ++ ) {
		output.add( T_SRDToken::String( T_String( list[ i ] ) ) );
	}

	auto otok( T_SRDToken::List( std::move( output ) ) );
	otok.location( loc );
	data.top( ).output( ).add( std::move( otok ) );
}

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

M_SRDPP_COMMAND_EXEC( VFSLoad )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );
	auto const& loc( *state.initialLocation( ) );

	if ( input.size( ) == 0 ) {
		data.addError( loc , "not enough arguments" );
		return;
	}
	if ( input.size( ) > 1 ) {
		data.addError( loc , "too many arguments" );
		return;
	}
	if ( input[ 0 ].type( ) != E_SRDTokenType::STRING ) {
		data.addError( input[ 0 ] , "string expected" );
		return;
	}

	const T_VFSPath path( input[ 0 ].stringValue( ) );
	if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
		data.addError( input[ 0 ] , "absolute VFS path expected" );
		return;
	}

	const OP_InputStream istream( vfs_.read( path ) );
	if ( !istream ) {
		T_StringBuilder sb( "error while loading '" );
		sb << T_String( path ) << "': file not found";
		data.addError( input[ 0 ] , std::move( sb ) );
		return;
	}

	T_SharedPtr< T_Buffer< uint8_t > > buffer;
	try {
		if ( istream->isSizeKnown( ) ) {
			const auto sz( istream->size( ) );
			buffer = NewShared< T_Buffer< uint8_t > >( sz );
			if ( sz ) {
				istream->read( buffer->data( ) , sz );
			}
		} else {
			uint8_t temp[ 4096 ];
			buffer = NewShared< T_Buffer< uint8_t > >( );
			try {
				while ( 1 ) {
					const auto nRead( istream->read( temp , sizeof( temp ) ) );
					const auto prevSize( buffer->size( ) );
					assert( nRead > 0 );
					buffer->resize( prevSize + nRead );
					memcpy( buffer->data( ) + prevSize , temp , nRead );
				}
			} catch ( X_StreamError const& error ) {
				if ( error.code( ) != E_StreamError::END ) {
					throw;
				}
			}
		}
	} catch ( X_StreamError const& error ) {
		T_StringBuilder sb( "error while loading '" );
		sb << T_String( path ) << "': " << error.what( );
		data.addError( input[ 0 ] , std::move( sb ) );
	}

	auto otok( T_SRDToken::Binary( buffer ) );
	otok.location( loc );
	data.top( ).output( ).add( std::move( otok ) );
}

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

M_SRDPP_COMMAND_EXEC( VFSType )
{
	auto& input( state.output( ) );
	auto& data( state.data( ) );

	const auto is( input.size( ) );
	if ( is == 0 ) {
		return;
	}

	T_Array< T_VFSPath > paths( 4 );
	paths.ensureCapacity( is );
	for ( uint32_t i = 0 ; i < is ; i ++ ) {
		if ( input[ i ].type( ) != E_SRDTokenType::STRING ) {
			data.addError( input[ i ] , "string expected" );
			return;
		}

		T_VFSPath const& path( paths.addNew( input[ i ].stringValue( ) ) );
		if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
			data.addError( input[ i ] , "absolute VFS path expected" );
			return;
		}
	}

	auto& out( data.top( ).output( ) );
	for ( uint32_t i = 0 ; i < is ; i ++ ) {
		const E_VFSEntryType et( vfs_.typeOf( paths[ i ] ) );
		T_SRDToken token( ( []( E_VFSEntryType et ) {
			switch ( et ) {
			    case E_VFSEntryType::NONE:
				return T_SRDToken::Word( T_String::Pooled( "none" ) );

			    case E_VFSEntryType::FILE:
				return T_SRDToken::Word( T_String::Pooled( "file" ) );

			    case E_VFSEntryType::DIRECTORY:
				return T_SRDToken::Word( T_String::Pooled( "directory" ) );

			    case E_VFSEntryType::OTHER:
				return T_SRDToken::Word( T_String::Pooled( "other" ) );
			}
			return T_SRDToken::Word( T_String::Pooled( "unknown" ) );
		})( et ) );
		token.copyLocationOf( input[ i ] );
		out.add( std::move( token ) );
	}
}