#include "externals.hh" #include "common.hh" #include "c-project.hh" #include "c-opast.hh" #include "c-ops.hh" #include "c-opcomp.hh" #include "c-syncoverrides.hh" #include #include #include #include using namespace ebcl; using namespace opast; #define M_LOGSTR_( S , L ) \ logger( [](){ return T_StringBuilder{ S }; } , L ) /*= T_Parser =================================================================*/ namespace { struct T_ParserImpl_ { enum class E_InstrType { CALL , CLEAR , COMPUTE , FRAMEBUFFER , FULLSCREEN , IF , INPUT , LOCALS , MAINOUT , ODBG , OVERRIDES , PIPELINE , PROFILE , PROGRAM , SAMPLER , SET , TEXTURE , UNIFORMS_FLT , UNIFORMS_INT , USE_FRAMEBUFFER , USE_PIPELINE , USE_PROGRAM , USE_TEXTURE , IMAGE , 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( "compute" , E_InstrType::COMPUTE ); 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( "ui-overrides" , E_InstrType::OVERRIDES ); 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( "image" , E_InstrType::IMAGE ); 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_OpsParserOutput >& output; T_Array< T_SRDError >& errors; F_OPLogger& logger; T_MultiArray< uint32_t > calls; T_Array< T_InstrRestriction > callInfo; T_SRDParserConfig ovParserConfig; T_SRDParser ovParser; T_FSPath curFile; T_FSPath baseDir; T_AutoArray< T_FSPath , 32 > inclStack; 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_SyncOverrideVisitor ovVisitor; T_ParserImpl_( T_Array< T_SRDError >* errors , T_OwnPtr< T_OpsParserOutput >* root , F_OPLogger* logger ) noexcept; // --------------------------------------------------------------------- void main( T_FSPath const& file , 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 parseTLList( T_SRDList const& list ) noexcept; void parseTLListItem( T_SRDList const& funcList ) noexcept; void handleInclude( T_SRDToken const& tFileName ) 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_( Compute ); 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_( Overrides ); 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_( Image ); 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_OpsParserOutput >* const output , F_OPLogger* logger ) noexcept : output( *output ) , errors( *errors ) , logger{ *logger } , ovParserConfig( sov::GetParserConfig( ) ) , ovParser( ovParserConfig ) { } void T_ParserImpl_::main( T_FSPath const& file , T_SRDList const& input ) noexcept { assert( file.isAbsolute( ) ); curFile = file.canonical( ); baseDir = file.parent( ); output->files.add( file ); 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 { M_LOGSTR_( "... Checking for required blocks" , 2 ); 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 { M_LOGSTR_( "... Gathering/checking calls" , 2 ); 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.size( ) ) { T_StringBuilder sb; sb << "function expects " << fn.arguments( ) << " argument" << ( fn.arguments( ) == 1 ? "" : "s" ) << ", " << call.size( ) << " argument" << ( call.size( ) == 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. Also, remove functions that are never called. */ bool T_ParserImpl_::checkInstructionRestrictions( ) noexcept { M_LOGSTR_( "... Checking instruction restrictions" , 2 ); 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( ) ; ) { if ( !callInfo[ i ] ) { output->root.removeFunction( output->root.function( i ).name( ) ); callInfo.removeSwap( i ); continue; } 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; } ); i ++; } 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 { M_LOGSTR_( "... Checking local variables" , 2 ); 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 { M_LOGSTR_( "... Collecting global declaration types" , 2 ); // 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 ); logger( [&]{ T_StringBuilder sb; sb << "id " << id << " as " << dt << " at " << location; return sb; } , 3 ); // 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 { M_LOGSTR_( "... Checking function argument types" , 2 ); // 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.size( ) != 0 ) { callInstuctions.add( &call ); } return false; } ); } #define M_TRACE_( l , x ) \ logger( [&]{ \ T_StringBuilder sb; \ sb << x; \ return sb; \ } , l ) 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 ) ); M_TRACE_( 3 , "about to check function " << f.name( ) ); if ( !argsResolved[ i ] ) { M_TRACE_( 3 , " -> arguments not resolved, skipped" ); continue; } M_TRACE_( 3 , " -> " << 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 ) ) ); M_TRACE_( 4 , " -> checking call to " << called << " (idx " << calledIdx << ") at " << call->location( ) ); assert( call->size( ) == calledFn.arguments( ) ); if ( argsResolved[ calledIdx ] ) { M_TRACE_( 4 , " argument types already resolved, checking" ); } else { M_TRACE_( 4 , " resolving arguments" ); } bool ok = true; for ( auto a = 0u ; a < call->size( ) ; a ++ ) { auto& arg( call->argument( a ).expression( ) ); 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; } M_TRACE_( 4 , " [" << 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 { M_LOGSTR_( "... Checking command argument types" , 2 ); 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_IMAGE: { auto& instr( dynamic_cast< T_ImageInstrNode& >( n ) ); checkIdentifier( instr.id( ) , instr.idLocation( ) , cfi , E_DataType::TEXTURE ); 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; } case A_Node::OP_OVERRIDES: { auto& ov{ dynamic_cast< T_OverridesInstrNode& >( n ) }; ovVisitor.visitor.visit( &ov.root( ) , [&]( T_SyncOverrideVisitor::T_Element element , bool exit ) -> bool { if ( exit || element.hasType< T_SyncOverrideSection* >( ) ) { return true; } auto& ovr( *element.value< A_SyncOverride* >( ) ); auto const& in( ovr.inputNames( ) ); const auto nin( in.size( ) ); for ( auto i = 0u ; i < nin ; i ++ ) { checkIdentifier( in[ i ] , ovr.location( ) , cfi , E_DataType::INPUT ); } return false; } ); 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 << expected << " 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 { M_LOGSTR_( "... Generating tree" , 2 ); parseTLList( input ); return errors.empty( ); } void T_ParserImpl_::parseTLList( T_SRDList const& input ) noexcept { for ( auto const& t : input ) { if ( t.type( ) == E_SRDTokenType::LIST && t.list( ).size( ) > 0 ) { parseTLListItem( t.list( ) ); } else { errors.addNew( "top-level list expected" , t.location( ) ); } } } void T_ParserImpl_::parseTLListItem( T_SRDList const& input ) noexcept { assert( input.size( ) != 0 ); auto const& ft{ input[ 0 ] }; if ( ft.type( ) != E_SRDTokenType::WORD ) { errors.addNew( "word expected" , ft.location( ) ); return; } auto const& fw{ ft.stringValue( ) }; if ( fw == "include" ) { if ( input.size( ) == 1 ) { errors.addNew( "file name expected" , ft.location( ) ); } else { handleInclude( input[ 1 ] ); if ( input.size( ) > 2 ) { errors.addNew( "too many arguments" , input[ 2 ].location( ) ); } } } else if ( fw == "init" || fw == "frame" || fw == "fn" ) { parseFunction( input ); } else { errors.addNew( "function or include expected" , ft.location( ) ); } } /*----------------------------------------------------------------------------*/ void T_ParserImpl_::handleInclude( T_SRDToken const& tFileName ) noexcept { if ( tFileName.type( ) != E_SRDTokenType::STRING ) { errors.addNew( "file name expected" , tFileName.location( ) ); return; } // Resolve included file path T_FSPath inclPath{ tFileName.stringValue( ) }; if ( !inclPath.isValid( ) ) { errors.addNew( "invalid file name" , tFileName.location( ) ); return; } if ( !inclPath.isAbsolute( ) ) { inclPath = curFile.parent( ) + inclPath; } inclPath = inclPath.canonical( ); if ( !inclPath.isUnder( baseDir ) ) { errors.addNew( "file is not in the project's directory" , tFileName.location( ) ); return; } // Check for recursive includes if ( inclStack.contains( inclPath ) ) { errors.addNew( "recursive file inclusion" , tFileName.location( ) ); return; } // Load file const auto relPath{ inclPath.makeRelative( baseDir ) }; T_SRDMemoryTarget srdOut; { T_File input{ inclPath , E_FileMode::READ_ONLY }; try { input.open( ); } catch ( X_StreamError const& e ) { T_StringBuilder sb; sb << "could not open '" << relPath << "': " << e.what( ); if ( e.code( ) == E_StreamError::SYSTEM_ERROR ) { sb << " (error code " << e.systemError( ) << ")"; } errors.addNew( std::move( sb ) , tFileName.location( ) ); return; } srdOut.clearComments( true ).clearFlushToken( true ); try { T_SRDTextReader srdReader{ srdOut }; T_FileInputStream fis{ input }; srdReader.read( relPath.toString( ) , fis ); } catch ( X_StreamError const& e ) { T_StringBuilder sb; sb << "could not read '" << relPath << "': " << e.what( ); if ( e.code( ) == E_StreamError::SYSTEM_ERROR ) { sb << " (error code " << e.systemError( ) << ")"; } errors.addNew( std::move( sb ) , tFileName.location( ) ); return; } catch ( X_SRDErrors const& e ) { const auto ne{ e.errors.size( ) }; for ( auto i = 0u ; i < ne ; i ++ ) { errors.add( e.errors[ i ] ); } return; } } // Parse included file inclStack.add( curFile ); curFile = inclPath; output->files.add( inclPath ); parseTLList( srdOut.list( ) ); curFile = inclStack.last( ); inclStack.removeLast( ); } /*----------------------------------------------------------------------------*/ void T_ParserImpl_::parseFunction( T_SRDList const& funcList ) noexcept { auto const& fw( funcList[ 0 ] ); 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_( COMPUTE , Compute ); M_CASE_( FRAMEBUFFER , Framebuffer ); M_CASE_( IF , If ); M_CASE_( INPUT , Input ); M_CASE_( LOCALS , Locals ); M_CASE_( ODBG , ODebug ); M_CASE_( OVERRIDES , Overrides ); 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_( IMAGE , Image ); 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_( Compute ) { if ( input.size( ) < 4 ) { errors.addNew( "not enough arguments" , input[ 0 ].location( ) ); } else if ( input.size( ) > 4 ) { errors.addNew( "too many arguments" , input[ 0 ].location( ) ); } auto& instr{ instructions.add< T_ComputeInstrNode >( ) }; instr.location( ) = input[ 0 ].location( ); for ( auto i = 1u ; i < std::max( 4u , 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.depthAttachment( ) ) { 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.setDefaultCase( parseBlock( cond , input[ 2 ] ) ); if ( cond.hasDefaultCase( ) ) { cond.defaultCase( ).location( ) = input[ 2 ].location( ); } if ( input.size( ) > 3 ) { cond.setCase( 0 , parseBlock( cond , input[ 3 ] ) ); if ( cond.hasCase( 0 ) ) { cond.getCase( 0 ).location( ) = input[ 3 ].location( ); } if ( input.size( ) > 4 ) { errors.addNew( "too many arguments" , input[ 4 ].location( ) ); } } else { cond.setCase( 0 , NewOwned< T_InstrListNode >( cond ) ); } } /*----------------------------------------------------------------------------*/ 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_( Overrides ) { if ( input.size( ) == 1 ) { errors.addNew( "not enough arguments" , input[ 0 ].location( ) ); return; } struct T_StackEntry_ { T_SRDList const* list; uint32_t pos; T_StackEntry_( T_SRDList const* list , const uint32_t pos ) noexcept : list( list ) , pos( pos ) {} }; T_AutoArray< T_StackEntry_ , 16 > stack; T_StackEntry_ current{ &input , 1 }; T_SRDErrors ovParserErrors; bool hadException{ false }; try { ovParser.start( ovParserErrors ); while ( current.pos < current.list->size( ) || !stack.empty( ) ) { if ( current.pos == current.list->size( ) ) { current = stack.last( ); stack.removeLast( ); T_SRDToken ls{ T_SRDToken::ListEnd( ) }; ls.location( (*current.list)[ current.pos ].location( ) ); ovParser.push( ovParserErrors , std::move( ls ) ); current.pos ++; continue; } auto const& src( (*current.list)[ current.pos ] ); if ( src.type( ) == E_SRDTokenType::LIST ) { T_SRDToken ls{ T_SRDToken::ListStart( ) }; ls.location( src.location( ) ); ovParser.push( ovParserErrors , std::move( ls ) ); stack.add( current ); current = T_StackEntry_{ &src.list( ) , 0 }; } else { ovParser.push( ovParserErrors , T_SRDToken{ src } ); current.pos ++; } } ovParser.end( ovParserErrors ); } catch ( X_SRDErrors const& e ) { hadException = true; } const auto n{ ovParserErrors.size( ) - ( hadException ? 1 : 0 ) }; if ( n != 0 ) { for ( auto i = 0u ; i < n ; i ++ ) { errors.add( ovParserErrors[ i ] ); } if ( hadException ) { errors.addNew( "too many errors in UI overrides list" , input[ 0 ].location( ) ); } return; } auto& instr{ instructions.add< T_OverridesInstrNode >( ovParser.getData< T_SharedPtr< T_SyncOverrideSection > >( ).makeOwned( ) ) }; instr.location( ) = input[ 0 ].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_FSPath path{ input[ 2 ].stringValue( ) }; if ( path.isAbsolute( ) ) { path = path.makeRelative( path.root( ) ); path = ( Common::Project( ).basePath( ) + path ).canonical( ); } else { path = ( curFile.parent( ) + path ).canonical( ); } if ( !path.isUnder( Common::Project( ).basePath( ) ) ) { errors.addNew( "shader is not in project directory" , input[ 2 ].location( ) ); } T_ProgramInstrNode& program{ instructions.add< T_ProgramInstrNode >( input[ 1 ] , std::move( path ) , input[ 2 ].location( ) ) }; 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[ 4 ] ) ); } else { errors.addNew( "height expected" , input[ 0 ].location( ) ); } if ( input.size( ) > 5 ) { instr.setLODs( parseExpression( instr , input[ 5 ] ) ); } if ( input.size( ) > 6 ) { errors.addNew( "too many arguments" , input[ 6 ].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_( Image ) { // (image [{read|write|rw}] // [(layer ( ) }; instr.location( ) = input[ 0 ].location( ); instr.id( input[ 2 ].stringValue( ) , input[ 2 ].location( ) ); instr.setParameter( T_ImageInstrNode::P_UNIT , parseExpression( instr , input[ 1 ] ) ); instr.setParameter( T_ImageInstrNode::P_LEVEL , parseExpression( instr , input[ 3 ] ) ); using E_AM_ = T_ImageInstrNode::E_AccessMode; using T_AM_ = T_ImageInstrNode::T_AccessMode; T_AM_ mode{ E_AM_::READ , E_AM_::WRITE }; uint32_t nck = 4; if ( nck < input.size( ) && input[ nck ].type( ) == E_SRDTokenType::WORD ) { auto const& v{ input[ nck ].stringValue( ) }; if ( v == "read" ) { mode = E_AM_::READ; } else if ( v == "write" ) { mode = E_AM_::WRITE; } else if ( v != "rw" ) { errors.addNew( "invalid access mode" , input[ nck ].location( ) ); } nck ++; } instr.accessMode( mode ); if ( nck < input.size( ) && input[ nck ].type( ) == E_SRDTokenType::LIST ) { auto const& lst{ input[ nck ].list( ) }; if ( lst.size( ) < 2 || lst[ 0 ].type( ) != E_SRDTokenType::WORD || lst[ 0 ].stringValue( ) != "layer" ) { errors.addNew( "invalid layer specification" , lst.size( ) ? lst[ 0 ].location( ) : input[ nck].location( ) ); } else { instr.setParameter( T_ImageInstrNode::P_LAYER , parseExpression( instr , lst[ 1 ] ) ); if ( lst.size( ) > 2 ) { errors.addNew( "too many arguments" , lst[ 2 ].location( ) ); } } nck ++; } if ( nck < input.size( ) ) { errors.addNew( "too many arguments" , input[ nck ].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 - 1 ) }; 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_OpsParser::T_OpsParser( ) noexcept : A_PrivateImplementation( new T_ParserImpl_( &errors_ , &output_ , &logger_ ) ) , errors_( 64 ) , output_{} {} bool T_OpsParser::parse( T_FSPath const& filePath , T_SRDList const& input ) noexcept { errors_.clear( ); output_ = NewOwned< T_OpsParserOutput >( ); p< T_ParserImpl_ >( ).main( filePath , input ); return errors_.empty( ); }