diff --git a/Makefile b/Makefile index 1980fd7..344f32f 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ COMMON = \ sync.cc \ control.cc \ opast.cc \ + opparser.cc \ # END COMMON DEMO = \ diff --git a/opast.cc b/opast.cc index 7e00c2a..b6437c5 100644 --- a/opast.cc +++ b/opast.cc @@ -325,937 +325,3 @@ T_BinaryOperatorNode::T_BinaryOperatorNode( std::abort( ); })( ) , parent ) , op_( op ) { } - - -/*= T_Parser =================================================================*/ - -namespace { - -struct T_ParserImpl_ -{ - enum class E_InstrType { - CALL , - FULLSCREEN , - IF , - INPUT , - PIPELINE , - PROFILE , - PROGRAM , - SET , - TEXTURE , - USE_FRAMEBUFFER , - USE_PIPELINE , - USE_PROGRAM , - USE_TEXTURE , - VIEWPORT , - }; - - const T_KeyValueTable< T_String , E_InstrType > instrMap{ ([]() { - T_KeyValueTable< T_String , E_InstrType > temp{ 256 , 64 , 64 }; - const auto add{ [&temp]( char const* name , E_InstrType it ) { - temp.add( T_String::Pooled( name ) , it ); - } }; - - add( "call" , E_InstrType::CALL ); - add( "fullscreen" , E_InstrType::FULLSCREEN ); - add( "if" , E_InstrType::IF ); - add( "input" , E_InstrType::INPUT ); - add( "pipeline" , E_InstrType::PIPELINE ); - add( "profiling" , E_InstrType::PROFILE ); - add( "program" , E_InstrType::PROGRAM ); - add( "set" , E_InstrType::SET ); - add( "texture" , E_InstrType::TEXTURE ); - 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{ 64 , 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{ 64 , 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{ 64 , 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; - })( ) }; - - // --------------------------------------------------------------------- - - T_OwnPtr< T_RootNode >& root; - T_Array< T_SRDError >& errors; - T_Visitor< A_Node > visitor{ opast::ASTVisitorBrowser }; - T_MultiArray< uint32_t > calls; - 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_RootNode >* root ) noexcept; - - // --------------------------------------------------------------------- - - void 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_( If ); - M_DPARSER_( Input ); - M_DPARSER_( Pipeline ); - M_DPARSER_( Profile ); - M_DPARSER_( Program ); - M_DPARSER_( Set ); - M_DPARSER_( Texture ); - 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_RootNode >* const root ) noexcept - : root( *root ) , errors( *errors ) -{ } - -/*----------------------------------------------------------------------------*/ - -void 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( ) ); - } - } - if ( !errors.empty( ) ) { - return; - } - - const T_SRDLocation missingErrLoc( ([&input]() { - if ( input.size( ) != 0 ) { - return T_SRDLocation( input[ 0 ].location( ).source( ) , 1 , 1 ); - } - return T_SRDLocation{}; - })( )); - if ( !root->hasInit( ) ) { - errors.addNew( "no initialisation block" , missingErrLoc ); - } - if ( !root->hasFrame( ) ) { - errors.addNew( "no initialisation block" , missingErrLoc ); - } - if ( !errors.empty( ) ) { - return; - } - - calls.clear( ); - uint32_t cfi; - for ( cfi = 0 ; cfi < root->nFunctions( ) ; cfi ++ ) { - calls.next( ); - visitor.visit( 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( 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&) 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; - } ); - } - if ( !errors.empty( ) ) { - return; - } - - T_InstrRestriction callInfo[ calls.size( ) ]; - callGraphVisitor.visit( 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( 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 < root->nFunctions( ) ; i ++ ) { - visitor.visit( 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; - } ); - } -} - -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( ) , *root ); - parseFunctionArguments( dynamic_cast< T_FuncNode& >( *fn ) , - funcList[ 2 ] ); - } else { - fn = NewOwned< T_SpecialFuncNode >( ftw == "init" , *root ); - } - fn->location( ) = fw.location( ); - - const auto af( 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; - } - - 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_( IF , If ); - M_CASE_( INPUT , Input ); - M_CASE_( PIPELINE , Pipeline ); - M_CASE_( PROFILE , Profile ); - M_CASE_( PROGRAM , Program ); - M_CASE_( SET , Set ); - M_CASE_( TEXTURE , Texture ); - 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::FULLSCREEN: - 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_( 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 ( input.size( ) == 2 ) { - errors.addNew( "'then' block expected" , - input[ 0 ].location( ) ); - return; - } - - cond.setCase( 1 , parseBlock( cond , input[ 2 ] ) ); - if ( input.size( ) > 3 ) { - cond.setDefaultCase( parseBlock( cond , input[ 3 ] ) ); - 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_( 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( ) ); - } - - T_PipelineInstrNode& pipeline{ ([&instructions,&input,validId]( ) -> T_PipelineInstrNode& { - if ( validId ) { - return instructions.add< T_PipelineInstrNode >( input[ 0 ] ); - } - return instructions.add< T_PipelineInstrNode >( ); - })() }; - 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_( 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 ) { - errors.addNew( "too many arguments" , input[ 5 ].location( ) ); - } -} - -/*----------------------------------------------------------------------------*/ - -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_ , &rootNode_ ) ) , - errors_( 64 ) , rootNode_{} -{} - -bool T_Parser::parse( - T_SRDList const& input ) noexcept -{ - errors_.clear( ); - rootNode_ = NewOwned< T_RootNode >( ); - p< T_ParserImpl_ >( ).parseTopLevel( input ); - return errors_.empty( ); -} diff --git a/opparser.cc b/opparser.cc new file mode 100644 index 0000000..6b9276c --- /dev/null +++ b/opparser.cc @@ -0,0 +1,940 @@ +#include "externals.hh" +#include "opast.hh" +#include + +using namespace ebcl; +using namespace opast; + + +/*= T_Parser =================================================================*/ + +namespace { + +struct T_ParserImpl_ +{ + enum class E_InstrType { + CALL , + FULLSCREEN , + IF , + INPUT , + PIPELINE , + PROFILE , + PROGRAM , + SET , + TEXTURE , + USE_FRAMEBUFFER , + USE_PIPELINE , + USE_PROGRAM , + USE_TEXTURE , + VIEWPORT , + }; + + const T_KeyValueTable< T_String , E_InstrType > instrMap{ ([]() { + T_KeyValueTable< T_String , E_InstrType > temp{ 256 , 64 , 64 }; + const auto add{ [&temp]( char const* name , E_InstrType it ) { + temp.add( T_String::Pooled( name ) , it ); + } }; + + add( "call" , E_InstrType::CALL ); + add( "fullscreen" , E_InstrType::FULLSCREEN ); + add( "if" , E_InstrType::IF ); + add( "input" , E_InstrType::INPUT ); + add( "pipeline" , E_InstrType::PIPELINE ); + add( "profiling" , E_InstrType::PROFILE ); + add( "program" , E_InstrType::PROGRAM ); + add( "set" , E_InstrType::SET ); + add( "texture" , E_InstrType::TEXTURE ); + 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{ 64 , 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{ 64 , 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{ 64 , 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; + })( ) }; + + // --------------------------------------------------------------------- + + T_OwnPtr< T_RootNode >& root; + T_Array< T_SRDError >& errors; + T_Visitor< A_Node > visitor{ opast::ASTVisitorBrowser }; + T_MultiArray< uint32_t > calls; + 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_RootNode >* root ) noexcept; + + // --------------------------------------------------------------------- + + void 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_( If ); + M_DPARSER_( Input ); + M_DPARSER_( Pipeline ); + M_DPARSER_( Profile ); + M_DPARSER_( Program ); + M_DPARSER_( Set ); + M_DPARSER_( Texture ); + 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_RootNode >* const root ) noexcept + : root( *root ) , errors( *errors ) +{ } + +/*----------------------------------------------------------------------------*/ + +void 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( ) ); + } + } + if ( !errors.empty( ) ) { + return; + } + + const T_SRDLocation missingErrLoc( ([&input]() { + if ( input.size( ) != 0 ) { + return T_SRDLocation( input[ 0 ].location( ).source( ) , 1 , 1 ); + } + return T_SRDLocation{}; + })( )); + if ( !root->hasInit( ) ) { + errors.addNew( "no initialisation block" , missingErrLoc ); + } + if ( !root->hasFrame( ) ) { + errors.addNew( "no initialisation block" , missingErrLoc ); + } + if ( !errors.empty( ) ) { + return; + } + + calls.clear( ); + uint32_t cfi; + for ( cfi = 0 ; cfi < root->nFunctions( ) ; cfi ++ ) { + calls.next( ); + visitor.visit( 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( 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&) 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; + } ); + } + if ( !errors.empty( ) ) { + return; + } + + T_InstrRestriction callInfo[ calls.size( ) ]; + callGraphVisitor.visit( 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( 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 < root->nFunctions( ) ; i ++ ) { + visitor.visit( 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; + } ); + } +} + +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( ) , *root ); + parseFunctionArguments( dynamic_cast< T_FuncNode& >( *fn ) , + funcList[ 2 ] ); + } else { + fn = NewOwned< T_SpecialFuncNode >( ftw == "init" , *root ); + } + fn->location( ) = fw.location( ); + + const auto af( 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; + } + + 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_( IF , If ); + M_CASE_( INPUT , Input ); + M_CASE_( PIPELINE , Pipeline ); + M_CASE_( PROFILE , Profile ); + M_CASE_( PROGRAM , Program ); + M_CASE_( SET , Set ); + M_CASE_( TEXTURE , Texture ); + 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::FULLSCREEN: + 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_( 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 ( input.size( ) == 2 ) { + errors.addNew( "'then' block expected" , + input[ 0 ].location( ) ); + return; + } + + cond.setCase( 1 , parseBlock( cond , input[ 2 ] ) ); + if ( input.size( ) > 3 ) { + cond.setDefaultCase( parseBlock( cond , input[ 3 ] ) ); + 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_( 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( ) ); + } + + T_PipelineInstrNode& pipeline{ ([&instructions,&input,validId]( ) -> T_PipelineInstrNode& { + if ( validId ) { + return instructions.add< T_PipelineInstrNode >( input[ 0 ] ); + } + return instructions.add< T_PipelineInstrNode >( ); + })() }; + 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_( 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 ) { + errors.addNew( "too many arguments" , input[ 5 ].location( ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +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_ , &rootNode_ ) ) , + errors_( 64 ) , rootNode_{} +{} + +bool T_Parser::parse( + T_SRDList const& input ) noexcept +{ + errors_.clear( ); + rootNode_ = NewOwned< T_RootNode >( ); + p< T_ParserImpl_ >( ).parseTopLevel( input ); + return errors_.empty( ); +}