demotool/opparser.cc

1937 lines
55 KiB
C++

#include "externals.hh"
#include "opast.hh"
#include <ebcl/Algorithms.hh>
using namespace ebcl;
using namespace opast;
/*= T_Parser =================================================================*/
namespace {
struct T_ParserImpl_
{
enum class E_InstrType {
CALL ,
CLEAR ,
FRAMEBUFFER ,
FULLSCREEN ,
IF ,
INPUT ,
LOCALS ,
MAINOUT ,
ODBG ,
PIPELINE ,
PROFILE ,
PROGRAM ,
SAMPLER ,
SET ,
TEXTURE ,
UNIFORMS_FLT ,
UNIFORMS_INT ,
USE_FRAMEBUFFER ,
USE_PIPELINE ,
USE_PROGRAM ,
USE_TEXTURE ,
VIEWPORT ,
};
const T_KeyValueTable< T_String , E_InstrType > instrMap{ ([]() {
T_KeyValueTable< T_String , E_InstrType > temp{ 64 , 128 , 64 };
const auto add{ [&temp]( char const* name , E_InstrType it ) {
temp.add( T_String::Pooled( name ) , it );
} };
add( "call" , E_InstrType::CALL );
add( "clear" , E_InstrType::CLEAR );
add( "framebuffer" , E_InstrType::FRAMEBUFFER );
add( "fullscreen" , E_InstrType::FULLSCREEN );
add( "if" , E_InstrType::IF );
add( "input" , E_InstrType::INPUT );
add( "locals" , E_InstrType::LOCALS );
add( "main-output" , E_InstrType::MAINOUT );
add( "odbg" , E_InstrType::ODBG );
add( "pipeline" , E_InstrType::PIPELINE );
add( "profiling" , E_InstrType::PROFILE );
add( "program" , E_InstrType::PROGRAM );
add( "sampler" , E_InstrType::SAMPLER );
add( "set" , E_InstrType::SET );
add( "texture" , E_InstrType::TEXTURE );
add( "uniforms" , E_InstrType::UNIFORMS_FLT );
add( "uniforms-i" , E_InstrType::UNIFORMS_INT );
add( "use-framebuffer" , E_InstrType::USE_FRAMEBUFFER );
add( "use-pipeline" , E_InstrType::USE_PIPELINE );
add( "use-program" , E_InstrType::USE_PROGRAM );
add( "use-texture" , E_InstrType::USE_TEXTURE );
add( "viewport" , E_InstrType::VIEWPORT );
return temp;
})( ) };
const T_KeyValueTable< T_String , T_UnaryOperatorNode::E_Operator > unaryOpMap{ ([]() {
T_KeyValueTable< T_String , T_UnaryOperatorNode::E_Operator > temp{ 32 , 32 , 32 };
const auto add{ [&temp]( char const* name ,
const T_UnaryOperatorNode::E_Operator it ) {
temp.add( T_String::Pooled( name ) , it );
} };
add( "neg" , T_UnaryOperatorNode::NEG );
add( "inv" , T_UnaryOperatorNode::INV );
add( "not" , T_UnaryOperatorNode::NOT );
add( "sin" , T_UnaryOperatorNode::SIN );
add( "cos" , T_UnaryOperatorNode::COS );
add( "tan" , T_UnaryOperatorNode::TAN );
add( "sqrt" , T_UnaryOperatorNode::SQRT );
add( "exp" , T_UnaryOperatorNode::EXP );
add( "ln" , T_UnaryOperatorNode::LN );
return temp;
})( ) };
const T_KeyValueTable< T_String , E_TexType > texTypeMap{ ([]() {
T_KeyValueTable< T_String , E_TexType > temp{ 16 , 16 , 16 };
const auto add{ [&temp]( char const* name ,
const E_TexType it ) {
temp.add( T_String::Pooled( name ) , it );
} };
add( "rgba-nu8" , E_TexType::RGBA8 );
add( "rgba-f16" , E_TexType::RGBA16F );
add( "rgb-nu8" , E_TexType::RGB8 );
add( "rgb-f16" , E_TexType::RGB16F );
add( "r-nu8" , E_TexType::R8 );
add( "r-f16" , E_TexType::R16F );
return temp;
})( ) };
const T_KeyValueTable< T_String , T_BinaryOperatorNode::E_Operator > binOpMap{ ([]() {
T_KeyValueTable< T_String , T_BinaryOperatorNode::E_Operator > temp{ 32 , 32 , 32 };
const auto add{ [&temp]( char const* name ,
const T_BinaryOperatorNode::E_Operator it ) {
temp.add( T_String::Pooled( name ) , it );
} };
add( "add" , T_BinaryOperatorNode::ADD );
add( "sub" , T_BinaryOperatorNode::SUB );
add( "mul" , T_BinaryOperatorNode::MUL );
add( "div" , T_BinaryOperatorNode::DIV );
add( "pow" , T_BinaryOperatorNode::POW );
add( "cmp-eq" , T_BinaryOperatorNode::CMP_EQ );
add( "cmp-ne" , T_BinaryOperatorNode::CMP_NE );
add( "cmp-gt" , T_BinaryOperatorNode::CMP_GT );
add( "cmp-ge" , T_BinaryOperatorNode::CMP_GE );
add( "cmp-lt" , T_BinaryOperatorNode::CMP_LT );
add( "cmp-le" , T_BinaryOperatorNode::CMP_LE );
return temp;
})( ) };
const T_KeyValueTable< T_String , E_ODbgMode > odbgModes{ ([]() {
T_KeyValueTable< T_String , E_ODbgMode > temp{ 32 , 32 , 32 };
const auto add{ [&temp]( char const* name ,
const E_ODbgMode it ) {
temp.add( T_String::Pooled( name ) , it );
} };
add( "hdr" , E_ODbgMode::HDR );
add( "ldr" , E_ODbgMode::LDR );
add( "ldr-alpha" , E_ODbgMode::LDR_ALPHA );
add( "depth" , E_ODbgMode::DEPTH );
assert( temp.size( ) == uint32_t( E_ODbgMode::__COUNT__ ) );
return temp;
})( ) };
// ---------------------------------------------------------------------
T_OwnPtr< T_ParserOutput >& output;
T_Array< T_SRDError >& errors;
T_MultiArray< uint32_t > calls;
T_Array< T_InstrRestriction > callInfo;
T_Visitor< A_Node > visitor{ opast::ASTVisitorBrowser };
T_Visitor< uint32_t , uint32_t > callGraphVisitor{
[this]( uint32_t v , uint32_t child ) -> T_Optional< uint32_t > {
const uint32_t nc( calls.sizeOf( v ) );
if ( child < nc ) {
return calls.get( v , child );
}
return {};
} };
T_ParserImpl_( T_Array< T_SRDError >* errors ,
T_OwnPtr< T_ParserOutput >* root ) noexcept;
// ---------------------------------------------------------------------
void main( T_SRDList const& list ) noexcept;
// ---------------------------------------------------------------------
private:
bool checkRequiredBlocks( T_SRDList const& list ) noexcept;
bool checkCalls( ) noexcept;
bool checkInstructionRestrictions( ) noexcept;
bool collectGlobalTypes( ) noexcept;
bool checkLocalVariables( ) noexcept;
bool checkArgumentTypes( ) noexcept;
bool checkIdentifiers( ) noexcept;
void checkIdentifier(
T_String const& id ,
T_SRDLocation const& location ,
const uint32_t funcIndex ,
const E_DataType expected ) noexcept;
void ciInput( uint32_t funcIndex ,
A_Node& input ) noexcept;
void ciIdentifier( uint32_t funcIndex ,
A_Node& input ) noexcept;
E_DataType getTypeOf( T_String const& name ,
A_FuncNode const* context ) const noexcept;
// ---------------------------------------------------------------------
bool parseTopLevel( T_SRDList const& list ) noexcept;
void parseFunction( T_SRDList const& funcList ) noexcept;
void parseFunctionArguments(
T_FuncNode& function ,
T_SRDToken const& argsToken ) noexcept;
void parseInstructions(
T_InstrListNode& instructions ,
T_SRDList const& input ,
uint32_t start ) noexcept;
P_InstrListNode parseBlock(
A_Node& parent ,
T_SRDToken const& block ) noexcept;
// ---------------------------------------------------------------------
#define M_DPARSER_( NAME ) \
void parse##NAME##Instruction( \
T_InstrListNode& instructions , \
T_SRDList const& input ) noexcept
M_DPARSER_( Call );
M_DPARSER_( Clear );
M_DPARSER_( Framebuffer );
bool parseFramebufferEntry(
T_FramebufferInstrNode& instruction ,
T_SRDToken const& entry ) noexcept;
M_DPARSER_( If );
M_DPARSER_( Input );
M_DPARSER_( Locals );
M_DPARSER_( ODebug );
M_DPARSER_( Pipeline );
M_DPARSER_( Profile );
M_DPARSER_( Program );
M_DPARSER_( Sampler );
void parseSamplerSampling(
T_SamplerInstrNode& instruction ,
T_SRDList const& input );
void parseSamplerMipmaps(
T_SamplerInstrNode& instruction ,
T_SRDList const& input );
void parseSamplerWrapping(
T_SamplerInstrNode& instruction ,
T_SRDList const& input );
void parseSamplerLOD(
T_SamplerInstrNode& instruction ,
T_SRDList const& input );
M_DPARSER_( Set );
M_DPARSER_( Texture );
M_DPARSER_( Uniforms );
M_DPARSER_( UniformsI );
void parseUniformsCommon(
T_InstrListNode& instructions ,
T_SRDList const& input ,
bool integers ) noexcept;
M_DPARSER_( UseFramebuffer );
M_DPARSER_( UsePipeline );
M_DPARSER_( UseProgram );
void parseUseCommon(
T_InstrListNode& instructions ,
T_SRDList const& input ,
T_UseInstrNode::E_Type type ) noexcept;
M_DPARSER_( UseTexture );
M_DPARSER_( Viewport );
#undef M_DPARSER_
// ---------------------------------------------------------------------
P_ExpressionNode parseExpression(
A_Node& parent ,
T_SRDToken const& token ) noexcept;
P_ExpressionNode parseOperation(
A_Node& parent ,
T_SRDList const& opList ) noexcept;
P_ExpressionNode parseBinOp(
A_Node& parent ,
T_SRDList const& opList ,
T_BinaryOperatorNode::E_Operator op ) noexcept;
P_ExpressionNode parseUnaryOp(
A_Node& parent ,
T_SRDList const& opList ,
T_UnaryOperatorNode::E_Operator op ) noexcept;
};
/*----------------------------------------------------------------------------*/
inline T_ParserImpl_::T_ParserImpl_(
T_Array< T_SRDError >* const errors ,
T_OwnPtr< T_ParserOutput >* const output ) noexcept
: output( *output ) , errors( *errors )
{ }
void T_ParserImpl_::main(
T_SRDList const& input ) noexcept
{
parseTopLevel( input )
&& checkRequiredBlocks( input )
&& checkCalls( )
&& checkInstructionRestrictions( )
&& checkLocalVariables( )
&& collectGlobalTypes( )
&& checkArgumentTypes( )
&& checkIdentifiers( );
}
/*----------------------------------------------------------------------------*/
/* Check that both the initialisation and frame rendering blocks have been
* defined.
*/
bool T_ParserImpl_::checkRequiredBlocks(
T_SRDList const& input ) noexcept
{
const T_SRDLocation missingErrLoc( ([&input]() {
if ( input.size( ) != 0 ) {
return T_SRDLocation( input[ 0 ].location( ).source( ) , 1 , 1 );
}
return T_SRDLocation{};
})( ));
if ( !output->root.hasInit( ) ) {
errors.addNew( "no initialisation block" , missingErrLoc );
}
if ( !output->root.hasFrame( ) ) {
errors.addNew( "no frame rendering block" , missingErrLoc );
}
return errors.empty( );
}
/* Gather function calls into a call graph and check that function calls
* provide the correct quantity of arguments.
*/
bool T_ParserImpl_::checkCalls( ) noexcept
{
calls.clear( );
uint32_t cfi;
for ( cfi = 0 ; cfi < output->root.nFunctions( ) ; cfi ++ ) {
calls.next( );
visitor.visit( output->root.function( cfi ) , [&]( A_Node& node , bool exit ) -> bool {
if ( exit || dynamic_cast< A_ExpressionNode* >( &node ) ) {
return false;
}
if ( node.type( ) != A_Node::OP_CALL ) {
return true;
}
auto& call( (T_CallInstrNode&) node );
const auto callee( output->root.functionIndex( call.id( ) ) );
if ( callee >= 0 ) {
if ( !calls.contains( cfi , callee ) ) {
calls.add( (uint32_t) callee );
}
// Check argument count while we're at it
auto& fn( (T_FuncNode&) output->root.function( callee ) );
if ( fn.arguments( ) != call.arguments( ) ) {
T_StringBuilder sb;
sb << "function expects " << fn.arguments( )
<< " argument" << ( fn.arguments( ) == 1 ? "" : "s" )
<< ", " << call.arguments( )
<< " argument" << ( call.arguments( ) == 1 ? "" : "s" )
<< " provided";
errors.addNew( std::move( sb ) , call.location( ) );
}
} else {
errors.addNew( "unknown function" , call.idLocation( ) );
}
return false;
} );
}
return errors.empty( );
}
/* Go through the call graph, determine whether functions are called from the
* initialisation block, the frame rendering block, or both, and then enforce
* restrictions on instructions.
*/
bool T_ParserImpl_::checkInstructionRestrictions( ) noexcept
{
callInfo.clear( );
callInfo.resize( calls.size( ) );
callGraphVisitor.visit( output->root.functionIndex( "*init*" ) ,
[&]( uint32_t id , const bool exit ) -> bool {
if ( exit || callInfo[ id ] & E_InstrRestriction::INIT ) {
return false;
}
callInfo[ id ] |= E_InstrRestriction::INIT;
return true;
} );
callGraphVisitor.visit( output->root.functionIndex( "*frame*" ) ,
[&]( uint32_t id , const bool exit ) -> bool {
if ( exit || callInfo[ id ] & E_InstrRestriction::FRAME ) {
return false;
}
callInfo[ id ] |= E_InstrRestriction::FRAME;
return true;
} );
for ( auto i = 0u ; i < output->root.nFunctions( ) ; i ++ ) {
visitor.visit( output->root.function( i ) , [&]( A_Node& node , bool exit ) {
if ( exit ) {
return false;
}
auto const* instr( dynamic_cast< A_InstructionNode const* >( &node ) );
if ( instr && ( instr->restriction( ) & callInfo[ i ] ) ) {
T_StringBuilder sb;
sb << "instruction not allowed in "
<< ( ( instr->restriction( ) & E_InstrRestriction::INIT )
? "initialisation" : "frame function" );
errors.addNew( std::move( sb ) , instr->location( ) );
}
return true;
} );
}
return errors.empty( );
}
/* Find local variable declarations, add them to the function to owns them,
* add errors if duplicates or variables with names that match function
* arguments are found.
*/
bool T_ParserImpl_::checkLocalVariables( ) noexcept
{
uint32_t cfi;
T_StringBuilder esb;
for ( cfi = 0 ; cfi < output->root.nFunctions( ) ; cfi ++ ) {
auto& function( output->root.function( cfi ) );
visitor.visit( function , [&]( A_Node& n , const bool exit ) -> bool {
if ( exit || n.type( ) != A_Node::OP_LOCALS ) {
return true;
}
auto& locals( dynamic_cast< T_LocalsInstrNode& >( n ) );
for ( auto i = 0u ; i < locals.variables( ) ; i ++ ) {
auto prev{ function.addLocalVariable(
locals.varName( i ) ,
locals.varLocation( i ) ) };
if ( !prev ) {
continue;
}
esb << "duplicate local '" << locals.varName( i )
<< "'; previous declaration at "
<< *prev;
errors.addNew( std::move( esb ) , locals.varLocation( i ) );
}
return false;
} );
}
return errors.empty( );
}
/* Find and determine the type of all global declarations. This includes
* variables and resources. In addition, make sure no resources are
* declared as local and that all declarations for a global name have
* the same type.
*/
bool T_ParserImpl_::collectGlobalTypes( ) noexcept
{
// Temporary table for type / first declaration location
struct T_Decl_ {
E_DataType type;
T_SRDLocation location;
T_Decl_( const E_DataType dt ,
T_SRDLocation const& loc ) noexcept
: type( dt ) , location( loc )
{ }
T_Decl_( const E_DataType dt ) noexcept
: type( dt ) , location{}
{ }
};
T_KeyValueTable< T_String , T_Decl_ > type;
type.add( T_String::Pooled( "time" ) , T_Decl_{ E_DataType::BUILTIN } );
type.add( T_String::Pooled( "width" ) , T_Decl_{ E_DataType::BUILTIN } );
type.add( T_String::Pooled( "height" ) , T_Decl_{ E_DataType::BUILTIN } );
uint32_t cfi;
T_StringBuilder esb;
for ( cfi = 0 ; cfi < output->root.nFunctions( ) ; cfi ++ ) {
auto& function( output->root.function( cfi ) );
visitor.visit( function , [&]( A_Node& node , bool exit ) -> bool {
if ( exit ) {
return false;
}
// If the node defines or sets something, get its identifier,
// location and type.
E_DataType dt{ E_DataType::UNKNOWN };
T_String id;
T_SRDLocation location;
if ( node.type( ) == A_Node::OP_SET ) {
id = dynamic_cast< T_SetInstrNode& >( node ).id( );
dt = E_DataType::VARIABLE;
location = dynamic_cast< T_SetInstrNode& >( node ).idLocation( );
} else {
auto* np{ dynamic_cast< A_ResourceDefInstrNode* >( &node ) };
if ( !np ) {
return !dynamic_cast< A_ExpressionNode* >( &node );
}
dt = np->dataType( );
id = np->id( );
location = np->idLocation( );
}
assert( dt != E_DataType::UNKNOWN );
#ifdef INVASIVE_TRACES
esb << "id " << id << " as " << dt << " at " << location << '\n' << '\0';
printf( "%s" , esb.data( ) );
esb.clear( );
#endif
// When we find a set instruction, we need to check whether
// it is affecting a local variable. If it is we'll just skip
// it.
if ( function.hasLocal( id ) ) {
if ( function.isArgument( id ) ) {
errors.addNew( "trying to override argument",
node.location( ) );
} else if ( dt != E_DataType::VARIABLE ) {
esb << "cannot define a local " << dt;
errors.addNew( std::move( esb ) , node.location( ) );
}
return false;
}
// Add new entries
T_Decl_ const* const existing( type.get( id ) );
if ( !existing ) {
type.add( id , T_Decl_{ dt , location } );
return false;
}
// Make sure it matches previous declarations
if ( existing->type == dt ) {
return false;
}
if ( existing->type == E_DataType::BUILTIN ) {
esb << "trying to redefine built-in variable " << id;
} else {
esb << "'" << id << "' redeclared as " << dt
<< "; previous declaration as "
<< existing->type << " at "
<< existing->location;
}
errors.addNew( std::move( esb ) , location );
return false;
} );
}
// Copy type table to the output
for ( auto const& k : type.keys( ) ) {
output->types.add( k , type.get( k )->type );
}
return errors.empty( );
}
bool T_ParserImpl_::checkArgumentTypes( ) noexcept
{
// Find functions for which arguments types need to be resolved
const auto nFunctions( output->root.nFunctions( ) );
bool argsResolved[ nFunctions ];
uint32_t nSolved = 0;
for ( auto i = 0u ; i < nFunctions ; i ++ ) {
auto& fn( output->root.function( i ) );
if ( fn.type( ) == A_Node::DECL_FN ) {
auto& rfn( dynamic_cast< T_FuncNode& >( fn ) );
argsResolved[ i ] = rfn.arguments( ) == 0;
} else {
argsResolved[ i ] = true;
}
if ( argsResolved[ i ] ) {
nSolved ++;
}
}
// No functions use arguments -> we're done.
if ( nSolved == nFunctions ) {
return true;
}
// Find all calls with arguments
T_MultiArray< T_CallInstrNode* > callInstuctions;
for ( auto i = 0u ; i < nFunctions ; i ++ ) {
callInstuctions.next( );
visitor.visit( output->root.function( i ) , [&]( A_Node& node , const bool exit ) {
if ( exit || node.type( ) != A_Node::OP_CALL ) {
return true;
}
auto& call( dynamic_cast< T_CallInstrNode& >( node ) );
if ( call.arguments( ) != 0 ) {
callInstuctions.add( &call );
}
return false;
} );
}
#ifdef INVASIVE_TRACES
T_StringBuilder tracer;
#define TRACE( x ) do { tracer.clear( ) << x << '\0'; printf( "%s\n" , tracer.data( ) ); } while (0)
#else
#define TRACE( x )
#endif
T_StringBuilder esb;
bool changed = true;
while ( changed ) {
changed = false;
// Go through all functions for which argument types have been
// resolved and check all calls.
for ( auto i = 0u ; i < nFunctions ; i ++ ) {
auto& f( output->root.function( i ) );
TRACE( "about to check function " << f.name( ) );
if ( !argsResolved[ i ] ) {
TRACE( " -> arguments not resolved, skipped" );
continue;
}
TRACE( " -> " << callInstuctions.sizeOf( i ) << " calls w/ arguments" );
for ( auto c = 0u ; c < callInstuctions.sizeOf( i ) ; c ++ ) {
auto const* call( callInstuctions.get( i , c ) );
if ( ! call ) {
continue;
}
auto const& called( call->id( ) );
const auto calledIdx( output->root.functionIndex( called ) );
auto& calledFn( dynamic_cast< T_FuncNode& >(
output->root.function( calledIdx ) ) );
TRACE( " -> checking call to " << called << " (idx " << calledIdx
<< ") at " << call->location( ) );
assert( call->arguments( ) == calledFn.arguments( ) );
if ( argsResolved[ calledIdx ] ) {
TRACE( " argument types already resolved, checking" );
} else {
TRACE( " resolving arguments" );
}
bool ok = true;
for ( auto a = 0u ; a < call->arguments( ) ; a ++ ) {
auto& arg( call->argument( a ) );
E_DataType ndt{ E_DataType::UNKNOWN };
if ( arg.type( ) == A_Node::EXPR_ID ) {
auto const& idn( dynamic_cast< T_IdentifierExprNode& >( arg ) );
T_String const& id( idn.id( ) );
ndt = getTypeOf( id , &f );
if ( ndt == E_DataType::UNKNOWN ) {
errors.addNew( "unknown identifier" ,
arg.location( ) );
}
} else {
ndt = E_DataType::VARIABLE;
}
TRACE( " [" << a << "] " << ndt );
// Arguments not resolved -> try setting the argument's type
if ( !argsResolved[ calledIdx ] ) {
ok = ( ndt != E_DataType::UNKNOWN );
calledFn.setLocalType( a , ndt );
continue;
}
// Arguments resolved -> error if types don't match
if ( ndt != E_DataType::UNKNOWN && calledFn.getLocalType( a ) != ndt ) {
esb << "argument " << ( a + 1 ) << " of function '"
<< called << "' should be a "
<< calledFn.getLocalType( a )
<< " but a " << ndt << " is being passed";
errors.addNew( std::move( esb ) , arg.location( ) );
}
}
argsResolved[ calledIdx ] = ok;
changed = changed || ok;
callInstuctions.get( i , c ) = nullptr;
}
}
}
return errors.empty( );
}
/*------------------------------------------------------------------------------*/
bool T_ParserImpl_::checkIdentifiers( ) noexcept
{
uint32_t cfi;
T_StringBuilder esb;
for ( cfi = 0 ; cfi < output->root.nFunctions( ) ; cfi ++ ) {
auto& function( output->root.function( cfi ) );
visitor.visit( function , [&]( A_Node& n , const bool exit ) -> bool {
if ( exit ) {
return true;
}
switch ( n.type( ) ) {
case A_Node::EXPR_INPUT:
ciInput( cfi , n );
return false;
case A_Node::EXPR_ID:
ciIdentifier( cfi , n );
return false;
case A_Node::OP_USE_FRAMEBUFFER:
{
auto& instr( dynamic_cast< T_UseInstrNode& >( n ) );
checkIdentifier( instr.id( ) , instr.idLocation( ) , cfi ,
E_DataType::FRAMEBUFFER );
return false;
}
case A_Node::OP_USE_PROGRAM:
{
auto& instr( dynamic_cast< T_UseInstrNode& >( n ) );
checkIdentifier( instr.id( ) , instr.idLocation( ) , cfi ,
E_DataType::PROGRAM );
return false;
}
case A_Node::OP_USE_PIPELINE:
{
auto& instr( dynamic_cast< T_UseInstrNode& >( n ) );
checkIdentifier( instr.id( ) , instr.idLocation( ) , cfi ,
E_DataType::PIPELINE );
return false;
}
case A_Node::OP_USE_TEXTURE:
{
auto& instr( dynamic_cast< T_UseTextureInstrNode& >( n ) );
checkIdentifier( instr.id( ) , instr.idLocation( ) , cfi ,
E_DataType::TEXTURE );
checkIdentifier( instr.samplerId( ) , instr.samplerIdLocation( ) ,
cfi , E_DataType::SAMPLER );
return true;
}
case A_Node::OP_ODBG:
{
auto& t( dynamic_cast< T_OutputDebugInstrNode& >( n ) );
checkIdentifier( t.texture( ) , t.textureLocation( ) , cfi ,
E_DataType::TEXTURE );
return false;
}
case A_Node::OP_UNIFORMS:
{
auto& t( dynamic_cast< T_UniformsInstrNode& >( n ) );
checkIdentifier( t.progId( ) , t.progIdLocation( ) , cfi ,
E_DataType::PROGRAM );
return false;
}
default:
return true;
}
} );
}
return errors.empty( );
}
void T_ParserImpl_::checkIdentifier(
T_String const& id ,
T_SRDLocation const& location ,
const uint32_t funcIndex ,
const E_DataType expected ) noexcept
{
const E_DataType dt{ getTypeOf( id , &output->root.function( funcIndex ) ) };
if ( dt == E_DataType::UNKNOWN ) {
errors.addNew( "unknown identifier" , location );
} else if ( dt != expected ) {
T_StringBuilder esb;
esb << E_DataType::TEXTURE << " expected, " << dt << " found instead";
errors.addNew( std::move( esb ) , location );
}
}
void T_ParserImpl_::ciInput(
const uint32_t funcIndex ,
A_Node& input ) noexcept
{
auto& e( dynamic_cast< T_InputExprNode& >( input ) );
auto const* const t( output->types.get( e.id( ) ) );
if ( !t ) {
errors.addNew( "no such input" , e.idLocation( ) );
} else if ( *t != E_DataType::INPUT ) {
T_StringBuilder esb;
esb << "'" << e.id( ) << "' used as input but declared as a "
<< *t;
errors.addNew( std::move( esb ) , e.idLocation( ) );
} else if ( callInfo[ funcIndex ] & E_InstrRestriction::INIT ) {
errors.addNew( "input used in initialisation" ,
e.location( ) );
}
}
void T_ParserImpl_::ciIdentifier(
const uint32_t funcIndex ,
A_Node& input ) noexcept
{
auto& e( dynamic_cast< T_IdentifierExprNode& >( input ) );
E_DataType dt{ getTypeOf( e.id( ) , &output->root.function( funcIndex ) ) };
// Undefined identifiers
if ( dt == E_DataType::UNKNOWN ) {
errors.addNew( "unknown identifier" , e.location( ) );
return;
}
// Variables are fine
if ( dt == E_DataType::VARIABLE ) {
return;
}
// Check builtins
if ( dt == E_DataType::BUILTIN ) {
if ( ( callInfo[ funcIndex ] & E_InstrRestriction::INIT )
&& e.id( ) == "time" ) {
errors.addNew( "'time' built-in used in initialisation" ,
e.location( ) );
}
return;
}
// Allow other types to be used as an expression
// in the context of the call instruction.
if ( e.parent( ).type( ) != A_Node::OP_CALL ) {
T_StringBuilder esb;
esb << "'" << e.id( )
<< "' used as a variable but declared as a "
<< dt;
errors.addNew( std::move( esb ) , e.location( ) );
}
}
/*------------------------------------------------------------------------------*/
E_DataType T_ParserImpl_::getTypeOf(
T_String const& name ,
A_FuncNode const* context ) const noexcept
{
if ( context && context->hasLocal( name ) ) {
return context->getLocalType( name );
} else {
auto const* const ptr( output->types.get( name ) );
if ( ptr ) {
return *ptr;
} else {
return E_DataType::UNKNOWN;
}
}
}
/*------------------------------------------------------------------------------*/
bool T_ParserImpl_::parseTopLevel(
T_SRDList const& input ) noexcept
{
for ( auto const& t : input ) {
if ( t.type( ) == E_SRDTokenType::LIST && t.list( ).size( ) > 0 ) {
parseFunction( t.list( ) );
} else {
errors.addNew( "function, init or frame list expected" ,
t.location( ) );
}
}
return errors.empty( );
}
void T_ParserImpl_::parseFunction(
T_SRDList const& funcList ) noexcept
{
assert( funcList.size( ) != 0 );
auto const& fw( funcList[ 0 ] );
if ( fw.type( ) != E_SRDTokenType::WORD
|| ( fw.stringValue( ) != "init" && fw.stringValue( ) != "frame"
&& fw.stringValue( ) != "fn" ) ) {
errors.addNew( "init, frame or fn expected" , fw.location( ) );
return;
}
T_String const& ftw( fw.stringValue( ) );
T_OwnPtr< A_FuncNode > fn;
if ( ftw == "fn" ) {
if ( funcList.size( ) < 3 ) {
errors.addNew( "function name and arguments expected" ,
fw.location( ) );
return;
}
if ( funcList[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "function name expected" , funcList[ 1 ].location( ) );
return;
}
fn = NewOwned< T_FuncNode >( funcList[ 1 ].stringValue( ) , output->root );
parseFunctionArguments( dynamic_cast< T_FuncNode& >( *fn ) ,
funcList[ 2 ] );
} else {
fn = NewOwned< T_SpecialFuncNode >( ftw == "init" , output->root );
}
fn->location( ) = fw.location( );
const auto af( output->root.addFunction( fn ) );
if ( af.dupLocation ) {
T_StringBuilder esb( "duplicate " );
switch ( fn->type( ) ) {
case A_Node::DECL_FN:
esb << "function '" << fn->name( ) << "'";
break;
case A_Node::DECL_INIT:
esb << "initialisation function";
break;
case A_Node::DECL_FRAME:
esb << "frame function";
break;
default: std::abort( );
}
esb << "; previous declaration: " << *af.dupLocation;
errors.addNew( std::move( esb ) , fw.location( ) );
}
parseInstructions( af.function.instructions( ) , funcList , ftw == "fn" ? 3 : 1 );
}
void T_ParserImpl_::parseFunctionArguments(
T_FuncNode& function ,
T_SRDToken const& argsToken ) noexcept
{
if ( argsToken.type( ) != E_SRDTokenType::LIST ) {
errors.addNew( "arguments list expected" , argsToken.location( ) );
return;
}
for ( auto const& token : argsToken.list( ) ) {
if ( token.type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "argument name expected" , token.location( ) );
continue;
}
T_String const& id( token.stringValue( ) );
if ( id == "time" || id == "width" || id == "height" ) {
errors.addNew( "cannot use built-in as argument" ,
token.location( ) );
continue;
}
const auto rv( function.addArgument( token ) );
if ( rv ) {
T_StringBuilder esb;
esb << "duplicate argument '" << token.stringValue( )
<< "'; previous declaration: " << *rv;
errors.addNew( std::move( esb ) , token.location( ) );
}
}
}
/*----------------------------------------------------------------------------*/
void T_ParserImpl_::parseInstructions(
T_InstrListNode& instructions ,
T_SRDList const& input ,
const uint32_t start ) noexcept
{
for ( auto iter( input.begin( ) + start ) ; iter.valid( ) ; iter ++ ) {
T_SRDToken const& itok( *iter );
if ( itok.type( ) != E_SRDTokenType::LIST ) {
errors.addNew( "instruction expected" , itok.location( ) );
continue;
}
T_SRDList const& ilist( itok.list( ) );
if ( ilist.empty( ) ) {
errors.addNew( "instruction expected" , itok.location( ) );
continue;
}
T_SRDToken const& iname( ilist[ 0 ] );
if ( iname.type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "instruction name expected" , iname.location( ) );
continue;
}
T_String const& iword( iname.stringValue( ) );
if ( !instrMap.contains( iword ) ) {
errors.addNew( "unknown instruction" , iname.location( ) );
continue;
}
#define M_CASE_( NAME , FNAME ) case E_InstrType::NAME: parse##FNAME##Instruction( instructions , ilist ); break
switch ( *instrMap.get( iword ) ) {
M_CASE_( CALL , Call );
M_CASE_( CLEAR , Clear );
M_CASE_( FRAMEBUFFER , Framebuffer );
M_CASE_( IF , If );
M_CASE_( INPUT , Input );
M_CASE_( LOCALS , Locals );
M_CASE_( ODBG , ODebug );
M_CASE_( PIPELINE , Pipeline );
M_CASE_( PROFILE , Profile );
M_CASE_( PROGRAM , Program );
M_CASE_( SAMPLER , Sampler );
M_CASE_( SET , Set );
M_CASE_( TEXTURE , Texture );
M_CASE_( UNIFORMS_FLT , Uniforms );
M_CASE_( UNIFORMS_INT , UniformsI );
M_CASE_( USE_FRAMEBUFFER , UseFramebuffer );
M_CASE_( USE_PIPELINE , UsePipeline );
M_CASE_( USE_PROGRAM , UseProgram );
M_CASE_( USE_TEXTURE , UseTexture );
M_CASE_( VIEWPORT , Viewport );
case E_InstrType::MAINOUT:
if ( ilist.size( ) != 1 ) {
errors.addNew( "too many arguments" , iname.location( ) );
}
instructions.add< T_MainOutputInstrNode >( ).location( ) = iname.location( );
break;
case E_InstrType::FULLSCREEN:
if ( ilist.size( ) != 1 ) {
errors.addNew( "too many arguments" , iname.location( ) );
}
instructions.add< T_FullscreenInstrNode >( ).location( ) = iname.location( );
break;
}
#undef M_CASE_
}
}
P_InstrListNode T_ParserImpl_::parseBlock(
A_Node& parent ,
T_SRDToken const& block ) noexcept
{
if ( block.type( ) != E_SRDTokenType::LIST ) {
errors.addNew( "block expected" , block.location( ) );
return {};
}
P_InstrListNode rv{ NewOwned< T_InstrListNode >( parent ) };
rv->location( ) = block.location( );
parseInstructions( *rv , block.list( ) , 0 );
return rv;
}
/*----------------------------------------------------------------------------*/
#define M_INSTR_( NAME ) \
void T_ParserImpl_::parse##NAME##Instruction( \
T_InstrListNode& instructions , \
T_SRDList const& input ) noexcept
M_INSTR_( Call )
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "function identifier expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
auto& instr{ instructions.add< T_CallInstrNode >( input[ 1 ] ) };
instr.location( ) = input[ 0 ].location( );
for ( auto it = input.begin( ) + 2 ; it.valid( ) ; ++it ) {
instr.addArgument( parseExpression( instr , *it ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Clear )
{
if ( input.size( ) < 5 ) {
errors.addNew( "not enough arguments" , input[ 0 ].location( ) );
} else if ( input.size( ) > 5 ) {
errors.addNew( "too many arguments" , input[ 0 ].location( ) );
}
auto& instr{ instructions.add< T_ClearInstrNode >( ) };
instr.location( ) = input[ 0 ].location( );
for ( auto i = 1u ; i < std::max( 5u , input.size( ) ) ; i ++ ) {
instr.addComponent( parseExpression( instr , input[ i ] ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Framebuffer )
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "framebuffer identifier expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
auto& instr( instructions.add< T_FramebufferInstrNode >( input[ 1 ] ) );
instr.location( ) = input[ 0 ].location( );
bool ok( true );
for ( auto i = 2u ; i < input.size( ) ; i ++ ) {
ok = parseFramebufferEntry( instr , input[ i ] ) && ok;
}
if ( ok && instr.colorAttachments( ) == 0 && !instr.hasDepthAttachment( ) ) {
errors.addNew( "framebuffer has no attachments" ,
input[ 0 ].location( ) );
}
}
bool T_ParserImpl_::parseFramebufferEntry(
T_FramebufferInstrNode& instruction ,
T_SRDToken const& entry ) noexcept
{
if ( entry.type( ) == E_SRDTokenType::WORD ) {
const bool ok( instruction.addColorAttachment( entry ) );
if ( !ok ) {
errors.addNew( "duplicate color attachment" ,
entry.location( ) );
}
return ok;
}
if ( entry.type( ) != E_SRDTokenType::LIST ) {
errors.addNew( "framebuffer attachment expected" ,
entry.location( ) );
return false;
}
T_SRDList const& l( entry.list( ) );
if ( l.size( ) < 2 || l.size( ) > 3
|| l[ 0 ].type( ) != E_SRDTokenType::WORD
|| l[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "invalid framebuffer attachment" ,
entry.location( ) );
return false;
}
P_ExpressionNode lod{ l.size( ) > 2
? parseExpression( instruction , l[ 2 ] )
: P_ExpressionNode{} };
T_String const& atype( l[ 0 ].stringValue( ) );
if ( atype == "depth" ) {
const bool ok( instruction.setDepthAttachment(
l[ 1 ] , std::move( lod ) ) );
if ( !ok ) {
errors.addNew( "duplicate depth attachment" ,
entry.location( ) );
}
return ok;
}
if ( atype == "color" ) {
const bool ok( instruction.addColorAttachment(
l[ 1 ] , std::move( lod ) ) );
if ( !ok ) {
errors.addNew( "duplicate color attachment" ,
l[ 1 ].location( ) );
}
return ok;
}
errors.addNew( "'color' or 'depth' expected" ,
l[ 0 ].location( ) );
return false;
}
/*----------------------------------------------------------------------------*/
M_INSTR_( If )
{
if ( input.size( ) == 1 ) {
errors.addNew( "expression and 'then' block expected" ,
input[ 0 ].location( ) );
return;
}
T_CondInstrNode& cond{ instructions.add< T_CondInstrNode >( ) };
cond.location( ) = input[ 0 ].location( );
cond.setExpression( parseExpression( cond , input[ 1 ] ) );
if ( cond.hasExpression( ) ) {
cond.expression( ).location( ) = input[ 1 ].location( );
}
if ( input.size( ) == 2 ) {
errors.addNew( "'then' block expected" ,
input[ 0 ].location( ) );
return;
}
cond.setCase( 1 , parseBlock( cond , input[ 2 ] ) );
if ( cond.hasCase( 1 ) ) {
cond.getCase( 1 ).location( ) = input[ 2 ].location( );
}
if ( input.size( ) > 3 ) {
cond.setDefaultCase( parseBlock( cond , input[ 3 ] ) );
if ( cond.hasDefaultCase( ) ) {
cond.defaultCase( ).location( ) = input[ 3 ].location( );
}
if ( input.size( ) > 4 ) {
errors.addNew( "too many arguments" , input[ 4 ].location( ) );
}
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Input )
{
if ( input.size( ) < 2 || !input[ 1 ].isText( ) ) {
errors.addNew( "input identifier expected" ,
input[ input.size( ) < 2 ? 0 : 1 ].location( ) );
return;
}
if ( input.size( ) > 3 ) {
errors.addNew( "too many arguments" , input[ 3 ].location( ) );
}
if ( input.size( ) >= 3 && !input[ 2 ].isNumeric( ) ) {
errors.addNew( "default value expected" , input[ 2 ].location( ) );
}
const bool hasDefault( input.size( ) >= 3 && input[ 2 ].isNumeric( ) );
auto& instr( ([&]() -> T_InputInstrNode& {
if ( hasDefault ) {
return instructions.add< T_InputInstrNode >(
input[ 1 ] , input[ 2 ] );
}
return instructions.add< T_InputInstrNode >( input[ 1 ] );
})( ) );
instr.location( ) = input[ 0 ].location( );
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Locals )
{
const auto ni( input.size( ) );
if ( ni < 2 ) {
errors.addNew( "variable identifier expected" ,
input[ 0 ].location( ) );
return;
}
auto& instr{ instructions.add< T_LocalsInstrNode >( ) };
instr.location( ) = input[ 0 ].location( );
T_StringBuilder sb;
for ( auto i = 1u ; i < ni ; i ++ ) {
auto const& token( input[ i ] );
if ( token.type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "variable identifier expected" ,
token.location( ) );
continue;
}
T_String const& id( token.stringValue( ) );
if ( id == "time" || id == "width" || id == "height" ) {
errors.addNew( "built-in cannot be declared local" ,
token.location( ) );
continue;
}
const auto prev{ instr.addVariable( token ) };
if ( !prev ) {
continue;
}
sb << "duplicate local '" << token.stringValue( )
<< "'; previous declaration at "
<< *prev;
errors.addNew( std::move( sb ) , token.location( ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( ODebug )
{
const auto ni( input.size( ) );
if ( ni == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "texture identifier expected" ,
input[ ni == 1 ? 0 : 1 ].location( ) );
return;
}
if ( ni == 2 || input[ 2 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "display mode expected" ,
input[ ni == 2 ? 0 : 2 ].location( ) );
return;
}
auto const* const mode( odbgModes.get( input[ 2 ].stringValue( ) ) );
if ( !mode ) {
errors.addNew( "invalid display mode" , input[ 2 ].location( ) );
return;
}
if ( ni == 3 || !input[ 3 ].isText( ) ) {
errors.addNew( "description expected" ,
input[ ni == 3 ? 0 : 3 ].location( ) );
return;
}
auto& instr{ instructions.add< T_OutputDebugInstrNode >(
input[ 1 ] , *mode , input[ 2 ].location( ) ,
input[ 3 ] ) };
instr.location( ) = input[ 0 ].location( );
if ( ni > 4 ) {
errors.addNew( "too many arguments" , input[ 4 ].location( ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Pipeline )
{
if ( input.size( ) < 3 ) {
errors.addNew( "identifier and program identifiers expected" ,
input[ 0 ].location( ) );
return;
}
const bool validId( input[ 1 ].type( ) == E_SRDTokenType::WORD );
if ( !validId ) {
errors.addNew( "pipeline identifier expected" , input[ 1 ].location( ) );
return;
}
auto& pipeline{ instructions.add< T_PipelineInstrNode >( input[ 1 ] ) };
pipeline.location( ) = input[ 0 ].location( );
const auto nMax{ std::min( input.size( ) , 8u ) };
for ( auto i = 2u ; i < nMax ; i ++ ) {
T_SRDToken const& tok( input[ i ] );
if ( tok.type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "program identifier expected" ,
tok.location( ) );
continue;
}
const auto dup( pipeline.addProgram( tok ) );
if ( dup ) {
T_StringBuilder esb;
esb << "duplicate program identifier; previous use: " << *dup;
errors.addNew( std::move( esb ) , tok.location( ) );
}
}
if ( input.size( ) > 8 ) {
errors.addNew( "too many arguments" , input[ 8 ].location( ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Profile )
{
const bool hasEnough( input.size( ) < 2 );
if ( hasEnough || !input[ 1 ].isText( ) ) {
errors.addNew( "profiling section name expected" ,
hasEnough ? input[ 1 ].location( ) : T_SRDLocation{} );
if ( !hasEnough ) {
return;
}
}
const T_String text( input[ 1 ].isText( ) ? input[ 1 ].stringValue( ) : "*invalid*" );
T_ProfileInstrNode& profile{ instructions.add< T_ProfileInstrNode >( text ) };
profile.location( ) = input[ 0 ].location( );
parseInstructions( profile.instructions( ) , input , 2 );
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Program )
{
bool ok{ true };
if ( input.size( ) == 1 ) {
errors.addNew( "identifier and program name required" ,
input[ 0 ].location( ) );
return;
}
if ( input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "identifier (word) expected" , input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) == 2 ) {
errors.addNew( "program name required" , input[ 0 ].location( ) );
return;
}
if ( !input[ 2 ].isText( ) ) {
errors.addNew( "program name (string or word) expected" ,
input[ 2 ].location( ) );
ok = false;
}
if ( input.size( ) > 3 ) {
errors.addNew( "too many arguments" , input[ 3 ].location( ) );
}
if ( !ok ) {
return;
}
T_ProgramInstrNode& program{ instructions.add< T_ProgramInstrNode >(
input[ 1 ] , input[ 2 ] ) };
program.location( ) = input[ 0 ].location( );
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Sampler )
{
const auto ni{ input.size( ) };
if ( ni == 1 || input[ 0 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "sampler identifier expected" ,
input[ ni == 1 ? 0 : 1 ].location( ) );
return;
}
auto& instr{ instructions.add< T_SamplerInstrNode >( input[ 1 ] ) };
instr.location( ) = input[ 0 ].location( );
for ( auto i = 2u ; i < ni ; i ++ ) {
T_SRDToken const& token( input[ i ] );
if ( token.type( ) != E_SRDTokenType::LIST || token.list( ).empty( ) ) {
errors.addNew( "list expected" , token.location( ) );
continue;
}
T_SRDToken const& et( token.list( )[ 0 ] );
if ( et.type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "'sampling', 'mipmaps', 'wrapping' or 'lod' expected" ,
et.location( ) );
continue;
}
T_String const& etn( et.stringValue( ) );
if ( etn == "sampling" ) {
parseSamplerSampling( instr , token.list( ) );
} else if ( etn == "mipmaps" ) {
parseSamplerMipmaps( instr , token.list( ) );
} else if ( etn == "wrapping" ) {
parseSamplerWrapping( instr , token.list( ) );
} else if ( etn == "lod" ) {
parseSamplerLOD( instr , token.list( ) );
} else {
errors.addNew( "'sampling', 'mipmaps', 'wrapping' or 'lod' expected" ,
et.location( ) );
}
}
}
void T_ParserImpl_::parseSamplerSampling(
T_SamplerInstrNode& instruction ,
T_SRDList const& input )
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "sampling mode expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
bool ok = true;
T_String const& sMode{ input[ 1 ].stringValue( ) };
E_TexSampling smp{ E_TexSampling::NEAREST };
if ( sMode == "linear" ) {
smp = E_TexSampling::LINEAR;
} else if ( sMode != "nearest" ) {
errors.addNew( "'nearest' or 'linear' expected" , input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) > 2 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
const auto prev{ instruction.setSampling( smp , input[ 0 ].location( ) ) };
if ( prev && ok ) {
T_StringBuilder sb;
sb << "sampling mode already set at " << *prev;
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
}
}
void T_ParserImpl_::parseSamplerMipmaps(
T_SamplerInstrNode& instruction ,
T_SRDList const& input )
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "mipmap sampling mode expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
bool ok = true;
T_String const& sMode{ input[ 1 ].stringValue( ) };
T_Optional< E_TexSampling > smp{ };
if ( sMode == "linear" ) {
smp = E_TexSampling::LINEAR;
} else if ( sMode == "nearest" ) {
smp = E_TexSampling::NEAREST;
} else if ( sMode != "no" ) {
errors.addNew( "'no', 'nearest' or 'linear' expected" , input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) > 2 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
const auto prev{ smp
? instruction.setMipmapSampling( *smp , input[ 0 ].location( ) )
: instruction.setNoMipmap( input[ 0 ].location( ) ) };
if ( prev && ok ) {
T_StringBuilder sb;
sb << "mipmap sampling mode already set at " << *prev;
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
}
}
void T_ParserImpl_::parseSamplerWrapping(
T_SamplerInstrNode& instruction ,
T_SRDList const& input )
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "wrapping mode expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
bool ok = true;
T_String const& sMode{ input[ 1 ].stringValue( ) };
E_TexWrap smp{ E_TexWrap::REPEAT };
if ( sMode == "clamp-border" ) {
smp = E_TexWrap::CLAMP_BORDER;
} else if ( sMode == "clamp-edge" ) {
smp = E_TexWrap::CLAMP_EDGE;
} else if ( sMode != "repeat" ) {
errors.addNew( "'repeat', 'clamp-border' or 'clamp-edge' expected" ,
input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) > 2 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
const auto prev{ instruction.setWrapping( smp , input[ 0 ].location( ) ) };
if ( prev && ok ) {
T_StringBuilder sb;
sb << "wrapping mode already set at " << *prev;
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
}
}
void T_ParserImpl_::parseSamplerLOD(
T_SamplerInstrNode& instruction ,
T_SRDList const& input )
{
if ( input.size( ) < 3 ) {
errors.addNew( "min/max LODs expected" , input[ 0 ].location( ) );
return;
}
if ( input.size( ) > 3 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
P_ExpressionNode min{ parseExpression( instruction , input[ 1 ] ) };
P_ExpressionNode max{ parseExpression( instruction , input[ 2 ] ) };
const auto prev{ instruction.setLOD( input[ 0 ].location( ) ,
std::move( min ) , std::move( max ) ) };
if ( prev ) {
T_StringBuilder sb;
sb << "min/max LOD already set at " << *prev;
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Set )
{
bool ok{ true };
if ( input.size( ) == 1 ) {
errors.addNew( "identifier and expression required" ,
input[ 0 ].location( ) );
return;
}
if ( input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "variable identifier expected" , input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) == 2 ) {
errors.addNew( "expression required" , input[ 0 ].location( ) );
}
if ( input.size( ) > 3 ) {
errors.addNew( "too many arguments" , input[ 3 ].location( ) );
}
if ( !ok ) {
return;
}
T_SetInstrNode& set{ instructions.add< T_SetInstrNode >( input[ 1 ] ) };
set.location( ) = input[ 0 ].location( );
if ( input.size( ) > 2 ) {
auto expr( parseExpression( set , input[ 2 ] ) );
if ( expr ) {
set.setExpression( std::move( expr ) );
}
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Texture )
{
if ( input.size( ) < 2 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "texture identifier expected" ,
( input.size( ) < 2 ? input[ 0 ] : input[ 1 ] ).location( ) );
return;
}
if ( input.size( ) < 3 || input[ 2 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "texture type expected" ,
( input.size( ) < 3 ? input[ 0 ] : input[ 2 ] ).location( ) );
return;
}
auto const* const ttt( texTypeMap.get( input[ 2 ].stringValue( ) ) );
if ( !ttt ) {
errors.addNew( "invalid texture type" ,
( input.size( ) < 3 ? input[ 0 ] : input[ 2 ] ).location( ) );
}
const auto tt( ttt ? *ttt : E_TexType::RGB8 );
auto& instr{ instructions.add< T_TextureInstrNode >( input[ 1 ] , tt ) };
instr.location( ) = input[ 0 ].location( );
if ( input.size( ) > 4 ) {
instr.setWidth( parseExpression( instr , input[ 3 ] ) );
} else {
errors.addNew( "width expected" , input[ 0 ].location( ) );
}
if ( input.size( ) > 4 ) {
instr.setHeight( parseExpression( instr , input[ 3 ] ) );
} else {
errors.addNew( "height expected" , input[ 0 ].location( ) );
}
if ( input.size( ) > 5 ) {
instr.setLODs( parseExpression( instr , input[ 4 ] ) );
}
if ( input.size( ) > 6 ) {
errors.addNew( "too many arguments" , input[ 5 ].location( ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Uniforms )
{
parseUniformsCommon( instructions , input , false );
}
M_INSTR_( UniformsI )
{
parseUniformsCommon( instructions , input , true );
}
void T_ParserImpl_::parseUniformsCommon(
T_InstrListNode& instructions ,
T_SRDList const& input ,
const bool integers ) noexcept
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "program identifier expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
if ( input.size( ) == 2 || !input[ 2 ].isInteger( ) ) {
errors.addNew( "uniform location expected" ,
input[ input.size( ) == 2 ? 0 : 2 ].location( ) );
return;
}
if ( input[ 2 ].longValue( ) < 0 || input[ 2 ].longValue( ) > UINT32_MAX ) {
errors.addNew( "invalid uniform location" , input[ 2 ].location( ) );
return;
}
auto& instr{ instructions.add< T_UniformsInstrNode >(
integers , input[ 1 ] , input[ 2 ] ) };
instr.location( ) = input[ 0 ].location( );
if ( input.size( ) == 3 ) {
errors.addNew( "at least one value required" , input[ 0 ].location( ) );
return;
}
for ( auto i = 3u ; i < input.size( ) ; i ++ ) {
if ( i == 7 ) {
errors.addNew( "too many arguments" , input[ i ].location( ) );
return;
}
instr.addValue( parseExpression( instr , input[ i ] ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( UseFramebuffer )
{
parseUseCommon( instructions , input , T_UseInstrNode::FRAMEBUFFER );
}
M_INSTR_( UsePipeline )
{
parseUseCommon( instructions , input , T_UseInstrNode::PIPELINE );
}
M_INSTR_( UseProgram )
{
parseUseCommon( instructions , input , T_UseInstrNode::PROGRAM );
}
void T_ParserImpl_::parseUseCommon(
T_InstrListNode& instructions ,
T_SRDList const& input ,
T_UseInstrNode::E_Type type ) noexcept
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "resource identifier expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
if ( input.size( ) > 2 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
auto& instr{ instructions.add< T_UseInstrNode >( type , input[ 1 ] ) };
instr.location( ) = input[ 0 ].location( );
}
/*----------------------------------------------------------------------------*/
M_INSTR_( UseTexture )
{
if ( input.size( ) == 1 || !input[ 1 ].isInteger( ) ) {
errors.addNew( "bank number expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
if ( input[ 1 ].longValue( ) < 0 || input[ 1 ].longValue( ) > UINT32_MAX ) {
errors.addNew( "invalid bank number" , input[ 1 ].location( ) );
return;
}
if ( input.size( ) == 2 || input[ 2 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "texture identifier expected" ,
input[ input.size( ) == 2 ? 0 : 2 ].location( ) );
return;
}
if ( input.size( ) == 3 || input[ 3 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "sampler identifier expected" ,
input[ input.size( ) == 3 ? 0 : 3 ].location( ) );
return;
}
if ( input.size( ) > 4 ) {
errors.addNew( "too many arguments" , input[ 4 ].location( ) );
}
auto& instr{ instructions.add< T_UseTextureInstrNode >(
input[ 1 ] , input[ 2 ] , input[ 3 ] ) };
instr.location( ) = input[ 0 ].location( );
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Viewport )
{
auto& instr{ instructions.add< T_ViewportInstrNode >( ) };
instr.location( ) = input[ 0 ].location( );
for ( auto i = 1u ; i < 5 ; i ++ ) {
T_ViewportInstrNode::E_Parameter p{
T_ViewportInstrNode::E_Parameter( i ) };
if ( input.size( ) < i ) {
T_StringBuilder sb;
sb << "missing ";
switch ( p ) {
case T_ViewportInstrNode::PX: sb << "X"; break;
case T_ViewportInstrNode::PY: sb << "Y"; break;
case T_ViewportInstrNode::PWIDTH: sb << "width"; break;
case T_ViewportInstrNode::PHEIGHT: sb << "height"; break;
}
sb << " parameter";
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
return;
} else {
instr.setParameter( p , parseExpression( instr , input[ i ] ) );
}
}
}
/*----------------------------------------------------------------------------*/
P_ExpressionNode T_ParserImpl_::parseExpression(
A_Node& parent ,
T_SRDToken const& token ) noexcept
{
if ( token.isNumeric( ) ) {
return NewOwned< T_ConstantExprNode >( parent , token );
}
if ( token.type( ) == E_SRDTokenType::WORD || token.type( ) == E_SRDTokenType::VAR ) {
return NewOwned< T_IdentifierExprNode >( parent , token );
}
if ( token.type( ) == E_SRDTokenType::LIST && !token.list( ).empty( ) ) {
return parseOperation( parent , token.list( ) );
}
errors.addNew( "invalid expression" , token.location( ) );
return {};
}
P_ExpressionNode T_ParserImpl_::parseOperation(
A_Node& parent ,
T_SRDList const& opList ) noexcept
{
T_SRDToken const& opId( opList[ 0 ] );
if ( opId.type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "operator expected" , opId.location( ) );
return {};
}
if ( opId.stringValue( ) == "get-input" ) {
if ( opList.size( ) == 1 || !opList[ 1 ].isText( ) ) {
errors.addNew( "input identifier expected" ,
opList[ opList.size( ) == 1 ? 0 : 1 ].location( ) );
return { };
}
if ( opList.size( ) > 2 ) {
errors.addNew( "too many arguments" , opList[ 2 ].location( ) );
}
auto node{ NewOwned< T_InputExprNode >( parent , opList[ 1 ] ) };
node->location( ) = opList[ 0 ].location( );
return node;
}
if ( binOpMap.contains( opId.stringValue( ) ) ) {
return parseBinOp( parent , opList ,
*binOpMap.get( opId.stringValue( ) ) );
}
if ( unaryOpMap.contains( opId.stringValue( ) ) ) {
return parseUnaryOp( parent , opList ,
*unaryOpMap.get( opId.stringValue( ) ) );
}
errors.addNew( "unknown operator" , opId.location( ) );
return {};
}
P_ExpressionNode T_ParserImpl_::parseBinOp(
A_Node& parent ,
T_SRDList const& opList ,
T_BinaryOperatorNode::E_Operator op ) noexcept
{
if ( opList.size( ) < 3 ) {
errors.addNew( "not enough arguments" , opList[ 0 ].location( ) );
} else if ( opList.size( ) > 3 ) {
errors.addNew( "too many arguments" , opList[ 3 ].location( ) );
}
T_OwnPtr< T_BinaryOperatorNode > opNode{
NewOwned< T_BinaryOperatorNode >( parent , op ) };
opNode->location( ) = opList[ 0 ].location( );
if ( opList.size( ) > 1 ) {
auto left{ parseExpression( *opNode , opList[ 1 ] ) };
if ( left ) {
opNode->setLeft( std::move( left ) );
}
}
if ( opList.size( ) > 2 ) {
auto right{ parseExpression( *opNode , opList[ 2 ] ) };
if ( right ) {
opNode->setRight( std::move( right ) );
}
}
return opNode;
}
P_ExpressionNode T_ParserImpl_::parseUnaryOp(
A_Node& parent ,
T_SRDList const& opList ,
T_UnaryOperatorNode::E_Operator op ) noexcept
{
if ( opList.size( ) < 2 ) {
errors.addNew( "not enough arguments" , opList[ 0 ].location( ) );
} else if ( opList.size( ) > 2 ) {
errors.addNew( "too many arguments" , opList[ 2 ].location( ) );
}
T_OwnPtr< T_UnaryOperatorNode > opNode{
NewOwned< T_UnaryOperatorNode >( parent , op ) };
opNode->location( ) = opList[ 0 ].location( );
if ( opList.size( ) > 1 ) {
auto argument{ parseExpression( *opNode , opList[ 1 ] ) };
if ( argument ) {
opNode->setArgument( std::move( argument ) );
}
}
return opNode;
}
} // namespace
/*----------------------------------------------------------------------------*/
T_Parser::T_Parser( ) noexcept
: A_PrivateImplementation( new T_ParserImpl_( &errors_ , &output_ ) ) ,
errors_( 64 ) , output_{}
{}
bool T_Parser::parse(
T_SRDList const& input ) noexcept
{
errors_.clear( );
output_ = NewOwned< T_ParserOutput >( );
p< T_ParserImpl_ >( ).main( input );
return errors_.empty( );
}