#include "externals.hh" #include "opast.hh" #include using namespace ebcl; using namespace opast; /*= A_Node ===================================================================*/ A_Node::A_Node( const E_Type type , A_Node* const parent ) noexcept : type_( type ) , parent_( parent ) { assert( ( type == ROOT && !parent ) || ( type != ROOT && parent ) ); } A_Node::~A_Node( ) { } T_RootNode& A_Node::root( ) const noexcept { A_Node const* node( this ); while ( node->parent_ ) { node = node->parent_; } assert( node ); assert( node->type_ == ROOT ); return *dynamic_cast< T_RootNode* >( const_cast< A_Node* >( node ) ); } /*----------------------------------------------------------------------------*/ A_Node* opast::ASTVisitorBrowser( A_Node& node , const uint32_t child ) noexcept { switch ( node.type( ) ) { // Root node case A_Node::ROOT: { auto& n( (T_RootNode&) node ); if ( child < n.nFunctions( ) ) { return &n.function( child ); } break; } // Functions / special blocks case A_Node::DECL_FN: case A_Node::DECL_INIT: case A_Node::DECL_FRAME: if ( child == 0 ) { auto& n( (A_FuncNode&) node ); return &n.instructions( ); } break; // Instruction list case A_Node::ILIST: { auto& n( (T_InstrListNode&) node ); if ( child < n.size( ) ) { return &n.node( child ); } break; } // Unary operators case A_Node::EXPR_NEG: case A_Node::EXPR_INV: case A_Node::EXPR_NOT: case A_Node::EXPR_SIN: case A_Node::EXPR_COS: case A_Node::EXPR_TAN: case A_Node::EXPR_SQRT: case A_Node::EXPR_EXP: case A_Node::EXPR_LN: { auto& n( (T_UnaryOperatorNode&) node ); if ( child == 0 && n.hasArgument( ) ) { return &n.argument( ); } break; } // Binary operators case A_Node::EXPR_ADD: case A_Node::EXPR_SUB: case A_Node::EXPR_MUL: case A_Node::EXPR_DIV: case A_Node::EXPR_POW: case A_Node::EXPR_CMP_EQ: case A_Node::EXPR_CMP_NE: case A_Node::EXPR_CMP_GT: case A_Node::EXPR_CMP_GE: case A_Node::EXPR_CMP_LT: case A_Node::EXPR_CMP_LE: { auto& n( (T_BinaryOperatorNode&) node ); if ( child == 0 && n.hasLeft( ) ) { return &n.left( ); } if ( child < 2 && n.hasRight( ) ) { return &n.right( ); } break; } // Nodes that do not have children case A_Node::EXPR_ID: case A_Node::EXPR_CONST: case A_Node::EXPR_INPUT: case A_Node::OP_PROGRAM: case A_Node::OP_PIPELINE: case A_Node::OP_INPUT: case A_Node::OP_FULLSCREEN: case A_Node::OP_USE_FRAMEBUFFER: case A_Node::OP_USE_PIPELINE: case A_Node::OP_USE_PROGRAM: case A_Node::OP_USE_TEXTURE: break; // Profile instruction case A_Node::OP_PROFILE: if ( child == 0 ) { return &( ((T_ProfileInstrNode&) node).instructions( ) ); } break; // Call instruction case A_Node::OP_CALL: { auto& n( (T_CallInstrNode&) node ); if ( child < n.arguments( ) ) { return &n.argument( child ); } break; } // Conditional instruction case A_Node::OP_COND: { auto& n( (T_CondInstrNode&) node ); auto c = child; if ( n.hasExpression( ) ) { if ( c == 0 ) { return &n.expression( ); } c --; } if ( !n.cases( ).empty( ) ) { if ( c < n.cases( ).size( ) ) { return &n.getCase( n.cases( )[ c ] ); } c -= n.cases( ).size( ); } if ( n.hasDefaultCase( ) && c == 0 ) { return &n.defaultCase( ); } break; } // Set instruction case A_Node::OP_SET: if ( child == 0 ) { auto& n( (T_SetInstrNode&) node ); if ( n.hasExpression( ) ) { return &n.expression( ); } } break; // Texture instruction case A_Node::OP_TEXTURE: { auto& n( (T_TextureInstrNode&) node ); auto c = child; if ( n.hasWidth( ) ) { if ( c == 0 ) { return &n.width( ); } c --; } if ( n.hasHeight( ) && c == 0 ) { return &n.height( ); } break; } // Viewport instruction case A_Node::OP_VIEWPORT: { auto& n( (T_ViewportInstrNode&) node ); auto c = child; for ( int i = 0 ; i < 4 ; i ++ ) { T_ViewportInstrNode::E_Parameter p{ T_ViewportInstrNode::E_Parameter( i ) }; if ( n.hasParameter( p ) ) { if ( c == 0 ) { return &n.parameter( p ); } c --; } } break; } } return nullptr; } /*= A_FuncNode ===============================================================*/ A_FuncNode::A_FuncNode( const bool isInit , T_RootNode* const root ) noexcept : A_Node( isInit ? DECL_INIT : DECL_FRAME , root ) , name_( isInit ? "*init*" : "*frame*" ) , instructions_( *this ) { } A_FuncNode::A_FuncNode( T_String const& name , T_RootNode* const root ) noexcept : A_Node( DECL_FN , root ) , name_( name ) , instructions_( *this ) { } /*= T_RootNode ===============================================================*/ T_RootNode::T_RootNode( ) noexcept : A_Node( ROOT , nullptr ) , functions_( []( T_OwnPtr< A_FuncNode > const& f ) { return f->name( ); } ) { } T_RootNode::T_AddFunctionResult T_RootNode::addFunction( T_OwnPtr< A_FuncNode >& function ) noexcept { T_String const& fn( function->name( ) ); auto const* const pf( functions_.get( fn ) ); if ( !pf ) { auto* const rv( function.get( ) ); functions_.add( std::move( function ) ); return *rv; } T_String dfn; uint32_t dupCtr( 0 ); do { T_StringBuilder fnsb; fnsb << fn << " dup " << dupCtr ++; dfn = std::move( fnsb ); } while ( functions_.contains( dfn ) ); T_OwnPtr< A_FuncNode > df( NewOwned< T_FuncNode >( dfn , *this ) ); auto* const rv( df.get( ) ); functions_.add( std::move( df ) ); return T_AddFunctionResult{ *rv , (*pf)->location( ) }; } /*= T_FuncNode ===============================================================*/ T_Optional< T_SRDLocation > T_FuncNode::addArgument( T_SRDToken const& token ) noexcept { assert( token.type( ) == E_SRDTokenType::WORD ); assert( token.hasLocation( ) ); const auto pnp( argNames_.indexOf( token.stringValue( ) ) ); if ( pnp != -1 ) { return argLocations_[ pnp ]; } argNames_.add( token.stringValue( ) ); argLocations_.add( token.location( ) ); return {}; } /*= T_PipelineInstrNode ======================================================*/ T_Optional< T_SRDLocation > T_PipelineInstrNode::addProgram( T_SRDToken const& pidToken ) noexcept { T_String const& name( pidToken.stringValue( ) ); const auto pIndex( pids_.indexOf( name ) ); if ( pIndex != -1 ) { return pidLocations_[ pIndex ]; } pids_.add( name ); pidLocations_.add( pidToken.location( ) ); return {}; } /*= T_UnaryOperatorNode ======================================================*/ T_UnaryOperatorNode::T_UnaryOperatorNode( A_Node& parent , const E_Operator op ) noexcept : A_ExpressionNode( ([op]() { switch ( op ) { case NEG: return EXPR_NEG; case INV: return EXPR_INV; case NOT: return EXPR_NOT; case SIN: return EXPR_SIN; case COS: return EXPR_COS; case TAN: return EXPR_TAN; case SQRT: return EXPR_SQRT; case EXP: return EXPR_EXP; case LN: return EXPR_LN; } std::abort( ); })( ) , parent ) , op_( op ) { } /*= T_BinaryOperatorNode =====================================================*/ T_BinaryOperatorNode::T_BinaryOperatorNode( A_Node& parent , const E_Operator op ) noexcept : A_ExpressionNode( ([op]() { switch ( op ) { case ADD: return EXPR_ADD; case SUB: return EXPR_SUB; case MUL: return EXPR_MUL; case DIV: return EXPR_DIV; case POW: return EXPR_POW; case CMP_EQ: return EXPR_CMP_EQ; case CMP_NE: return EXPR_CMP_NE; case CMP_GT: return EXPR_CMP_GT; case CMP_GE: return EXPR_CMP_GE; case CMP_LT: return EXPR_CMP_LT; case CMP_LE: return EXPR_CMP_LE; } 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( ); }