demotool/c-opparser.cc
Emmanuel BENOîT 3e838627dc Parser - Preliminary work in (include)
The parser accepts (include "some file") at top level. It does nothing
so far, however, and having it do anything will require some new code
for file name / path handling.
2017-12-25 22:40:55 +01:00

2228 lines
63 KiB
C++

#include "externals.hh"
#include "c-opast.hh"
#include "c-ops.hh"
#include "c-opcomp.hh"
#include "c-syncoverrides.hh"
#include <ebcl/Algorithms.hh>
#include <ebcl/SRDParser.hh>
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_String curFile;
T_AutoArray< T_String , 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_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& 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_SRDList const& input ) noexcept
{
parseTopLevel( input )
&& checkRequiredBlocks( input )
&& checkCalls( )
&& checkInstructionRestrictions( )
&& checkLocalVariables( )
&& collectGlobalTypes( )
&& checkArgumentTypes( )
&& checkIdentifiers( );
}
/*----------------------------------------------------------------------------*/
/* Check that both the initialisation and frame rendering blocks have been
* defined.
*/
bool T_ParserImpl_::checkRequiredBlocks(
T_SRDList const& input ) noexcept
{
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 );
for ( auto const& t : input ) {
if ( t.type( ) == E_SRDTokenType::LIST && t.list( ).size( ) > 0 ) {
parseTLList( t.list( ) );
} else {
errors.addNew( "top-level list expected" , t.location( ) );
}
}
return errors.empty( );
}
void T_ParserImpl_::parseTLList(
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" ) {
// TODO
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;
}
if ( !curFile ) {
curFile = tFileName.location( ).source( );
}
// TODO: resolve included file path
// TODO: check for recursive includes
// TODO: load file
// TODO: parse it
}
/*----------------------------------------------------------------------------*/
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_ProgramInstrNode& program{ instructions.add< T_ProgramInstrNode >(
input[ 1 ] , input[ 2 ] ) };
program.location( ) = input[ 0 ].location( );
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Sampler )
{
const auto ni{ input.size( ) };
if ( ni == 1 || input[ 0 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "sampler identifier expected" ,
input[ ni == 1 ? 0 : 1 ].location( ) );
return;
}
auto& instr{ instructions.add< T_SamplerInstrNode >( input[ 1 ] ) };
instr.location( ) = input[ 0 ].location( );
for ( auto i = 2u ; i < ni ; i ++ ) {
T_SRDToken const& token( input[ i ] );
if ( token.type( ) != E_SRDTokenType::LIST || token.list( ).empty( ) ) {
errors.addNew( "list expected" , token.location( ) );
continue;
}
T_SRDToken const& et( token.list( )[ 0 ] );
if ( et.type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "'sampling', 'mipmaps', 'wrapping' or 'lod' expected" ,
et.location( ) );
continue;
}
T_String const& etn( et.stringValue( ) );
if ( etn == "sampling" ) {
parseSamplerSampling( instr , token.list( ) );
} else if ( etn == "mipmaps" ) {
parseSamplerMipmaps( instr , token.list( ) );
} else if ( etn == "wrapping" ) {
parseSamplerWrapping( instr , token.list( ) );
} else if ( etn == "lod" ) {
parseSamplerLOD( instr , token.list( ) );
} else {
errors.addNew( "'sampling', 'mipmaps', 'wrapping' or 'lod' expected" ,
et.location( ) );
}
}
}
void T_ParserImpl_::parseSamplerSampling(
T_SamplerInstrNode& instruction ,
T_SRDList const& input )
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "sampling mode expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
bool ok = true;
T_String const& sMode{ input[ 1 ].stringValue( ) };
E_TexSampling smp{ E_TexSampling::NEAREST };
if ( sMode == "linear" ) {
smp = E_TexSampling::LINEAR;
} else if ( sMode != "nearest" ) {
errors.addNew( "'nearest' or 'linear' expected" , input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) > 2 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
const auto prev{ instruction.setSampling( smp , input[ 0 ].location( ) ) };
if ( prev && ok ) {
T_StringBuilder sb;
sb << "sampling mode already set at " << *prev;
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
}
}
void T_ParserImpl_::parseSamplerMipmaps(
T_SamplerInstrNode& instruction ,
T_SRDList const& input )
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "mipmap sampling mode expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
bool ok = true;
T_String const& sMode{ input[ 1 ].stringValue( ) };
T_Optional< E_TexSampling > smp{ };
if ( sMode == "linear" ) {
smp = E_TexSampling::LINEAR;
} else if ( sMode == "nearest" ) {
smp = E_TexSampling::NEAREST;
} else if ( sMode != "no" ) {
errors.addNew( "'no', 'nearest' or 'linear' expected" , input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) > 2 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
const auto prev{ smp
? instruction.setMipmapSampling( *smp , input[ 0 ].location( ) )
: instruction.setNoMipmap( input[ 0 ].location( ) ) };
if ( prev && ok ) {
T_StringBuilder sb;
sb << "mipmap sampling mode already set at " << *prev;
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
}
}
void T_ParserImpl_::parseSamplerWrapping(
T_SamplerInstrNode& instruction ,
T_SRDList const& input )
{
if ( input.size( ) == 1 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "wrapping mode expected" ,
input[ input.size( ) == 1 ? 0 : 1 ].location( ) );
return;
}
bool ok = true;
T_String const& sMode{ input[ 1 ].stringValue( ) };
E_TexWrap smp{ E_TexWrap::REPEAT };
if ( sMode == "clamp-border" ) {
smp = E_TexWrap::CLAMP_BORDER;
} else if ( sMode == "clamp-edge" ) {
smp = E_TexWrap::CLAMP_EDGE;
} else if ( sMode != "repeat" ) {
errors.addNew( "'repeat', 'clamp-border' or 'clamp-edge' expected" ,
input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) > 2 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
const auto prev{ instruction.setWrapping( smp , input[ 0 ].location( ) ) };
if ( prev && ok ) {
T_StringBuilder sb;
sb << "wrapping mode already set at " << *prev;
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
}
}
void T_ParserImpl_::parseSamplerLOD(
T_SamplerInstrNode& instruction ,
T_SRDList const& input )
{
if ( input.size( ) < 3 ) {
errors.addNew( "min/max LODs expected" , input[ 0 ].location( ) );
return;
}
if ( input.size( ) > 3 ) {
errors.addNew( "too many arguments" , input[ 2 ].location( ) );
}
P_ExpressionNode min{ parseExpression( instruction , input[ 1 ] ) };
P_ExpressionNode max{ parseExpression( instruction , input[ 2 ] ) };
const auto prev{ instruction.setLOD( input[ 0 ].location( ) ,
std::move( min ) , std::move( max ) ) };
if ( prev ) {
T_StringBuilder sb;
sb << "min/max LOD already set at " << *prev;
errors.addNew( std::move( sb ) , input[ 0 ].location( ) );
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Set )
{
bool ok{ true };
if ( input.size( ) == 1 ) {
errors.addNew( "identifier and expression required" ,
input[ 0 ].location( ) );
return;
}
if ( input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "variable identifier expected" , input[ 1 ].location( ) );
ok = false;
}
if ( input.size( ) == 2 ) {
errors.addNew( "expression required" , input[ 0 ].location( ) );
}
if ( input.size( ) > 3 ) {
errors.addNew( "too many arguments" , input[ 3 ].location( ) );
}
if ( !ok ) {
return;
}
T_SetInstrNode& set{ instructions.add< T_SetInstrNode >( input[ 1 ] ) };
set.location( ) = input[ 0 ].location( );
if ( input.size( ) > 2 ) {
auto expr( parseExpression( set , input[ 2 ] ) );
if ( expr ) {
set.setExpression( std::move( expr ) );
}
}
}
/*----------------------------------------------------------------------------*/
M_INSTR_( Texture )
{
if ( input.size( ) < 2 || input[ 1 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "texture identifier expected" ,
( input.size( ) < 2 ? input[ 0 ] : input[ 1 ] ).location( ) );
return;
}
if ( input.size( ) < 3 || input[ 2 ].type( ) != E_SRDTokenType::WORD ) {
errors.addNew( "texture type expected" ,
( input.size( ) < 3 ? input[ 0 ] : input[ 2 ] ).location( ) );
return;
}
auto const* const ttt( texTypeMap.get( input[ 2 ].stringValue( ) ) );
if ( !ttt ) {
errors.addNew( "invalid texture type" ,
( input.size( ) < 3 ? input[ 0 ] : input[ 2 ] ).location( ) );
}
const auto tt( ttt ? *ttt : E_TexType::RGB8 );
auto& instr{ instructions.add< T_TextureInstrNode >( input[ 1 ] , tt ) };
instr.location( ) = input[ 0 ].location( );
if ( input.size( ) > 4 ) {
instr.setWidth( parseExpression( instr , input[ 3 ] ) );
} else {
errors.addNew( "width expected" , input[ 0 ].location( ) );
}
if ( input.size( ) > 4 ) {
instr.setHeight( parseExpression( instr , input[ 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 <binding expr> <tex id> <level expr> [{read|write|rw}]
// [(layer <layer expr)]
// )
if ( input.size( ) == 1 ) {
errors.addNew( "binding unit expression expected" ,
input[ 0 ].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 ) {
errors.addNew( "LOD level expression expected" ,
input[ 2 ].location( ) );
return;
}
auto& instr{ instructions.add< T_ImageInstrNode >( ) };
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_SRDList const& input ) noexcept
{
errors_.clear( );
output_ = NewOwned< T_OpsParserOutput >( );
p< T_ParserImpl_ >( ).main( input );
return errors_.empty( );
}