568 lines
12 KiB
C++
568 lines
12 KiB
C++
|
/******************************************************************************/
|
||
|
/* SRD PARSER AND PREPROCESSOR - BINARY FORM **********************************/
|
||
|
/******************************************************************************/
|
||
|
|
||
|
#include <lw/lib/SRDBinary.hh>
|
||
|
using namespace lw;
|
||
|
|
||
|
|
||
|
/*= MAGIC NUMBERS, VERSIONS AND TAGS =========================================*/
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
// Magic number for binary SRD
|
||
|
static constexpr uint32_t C_MAGIC_ = 0xea7ca1ce;
|
||
|
|
||
|
// E_Versions_ - Binary SRD version numbers
|
||
|
enum class E_Version_ : uint32_t {
|
||
|
V1 = 0xc0ffee00 ,
|
||
|
};
|
||
|
|
||
|
// E_V1Tag_ - Version 1 SRD tags
|
||
|
enum class E_V1Tag_ : uint8_t {
|
||
|
END = 0x00 ,
|
||
|
LIST = 0x01 ,
|
||
|
WORD_NEW = 0x02 ,
|
||
|
WORD_KNOWN = 0x03 ,
|
||
|
VAR_WORD = 0x04 ,
|
||
|
STRING = 0x05 ,
|
||
|
INT = 0x06 ,
|
||
|
LONG = 0x07 ,
|
||
|
FLOAT = 0x08 ,
|
||
|
COMMENT = 0x09 ,
|
||
|
BINARY = 0x0a ,
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
|
||
|
namespace lw {
|
||
|
|
||
|
// Writer for version numbers
|
||
|
template< >
|
||
|
struct T_ObjectWriter< E_Version_ >
|
||
|
{
|
||
|
static void write( T_BinaryWriter const& writer , E_Version_ const& v )
|
||
|
{
|
||
|
writer.write( ( uint32_t ) v );
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Writer for version 1 tags
|
||
|
template< >
|
||
|
struct T_ObjectWriter< E_V1Tag_ >
|
||
|
{
|
||
|
static void write( T_BinaryWriter const& writer , E_V1Tag_ const& v )
|
||
|
{
|
||
|
writer.write( ( uint8_t ) v );
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Reader for version 1 tags
|
||
|
template< >
|
||
|
struct T_ObjectReader< E_V1Tag_ >
|
||
|
{
|
||
|
static E_V1Tag_ read( T_BinaryReader const& reader )
|
||
|
{
|
||
|
return ( E_V1Tag_ ) reader.read< uint8_t >( );
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
|
||
|
/*= VERSION 1 BINARY READER ==================================================*/
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
class T_SRDBRVersion1_
|
||
|
{
|
||
|
private:
|
||
|
T_BinaryReader& reader_;
|
||
|
T_String const& name_;
|
||
|
T_SRDErrors& errors_;
|
||
|
A_SRDReaderTarget& target_;
|
||
|
|
||
|
T_HashIndex wordsIndex_;
|
||
|
T_Array< T_String > words_;
|
||
|
uint32_t lastTagPos_;
|
||
|
|
||
|
typedef std::function< T_SRDToken( T_String const& ) > F_StringTok;
|
||
|
|
||
|
size_t curPos( ) const
|
||
|
{
|
||
|
return reader_.stream( ).position( );
|
||
|
}
|
||
|
|
||
|
void push( T_SRDToken&& token )
|
||
|
{
|
||
|
token.location( name_ , lastTagPos_ );
|
||
|
target_.push( errors_ , std::move( token ) );
|
||
|
}
|
||
|
|
||
|
void readNewWord( F_StringTok mt );
|
||
|
void readKnownWord( F_StringTok mt );
|
||
|
void readString( F_StringTok mt );
|
||
|
|
||
|
void readLoop( );
|
||
|
|
||
|
public:
|
||
|
T_SRDBRVersion1_( T_BinaryReader& reader , T_String const& name , T_SRDErrors& errors ,
|
||
|
A_SRDReaderTarget& target )
|
||
|
: reader_( reader ) , name_( name ) , errors_( errors ) ,
|
||
|
target_( target )
|
||
|
{ }
|
||
|
|
||
|
void read( );
|
||
|
};
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
void T_SRDBRVersion1_::readNewWord( F_StringTok mt )
|
||
|
{
|
||
|
const auto wordPos( curPos( ) );
|
||
|
const T_String word( reader_.read< T_String >( ).usePool( ) );
|
||
|
if ( word.size( ) == 0 ) {
|
||
|
errors_.add( "empty word" , name_ , uint32_t( wordPos ) );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const bool valid = T_SRDToken::IsWord( word );
|
||
|
if ( !valid ) {
|
||
|
errors_.add( "invalid word" , name_ , wordPos );
|
||
|
}
|
||
|
|
||
|
const auto hash( ComputeHash( word ) );
|
||
|
uint32_t index( wordsIndex_.first( hash ) );
|
||
|
while ( index != T_HashIndex::INVALID_INDEX ) {
|
||
|
if ( words_[ index ] == word ) {
|
||
|
errors_.add( "duplicate word" , name_ , wordPos );
|
||
|
return;
|
||
|
}
|
||
|
index = wordsIndex_.next( index );
|
||
|
}
|
||
|
|
||
|
wordsIndex_.add( hash );
|
||
|
words_.add( word );
|
||
|
if ( valid ) {
|
||
|
push( mt( word ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void T_SRDBRVersion1_::readKnownWord( F_StringTok mt )
|
||
|
{
|
||
|
const auto pos( curPos( ) );
|
||
|
const auto widx( reader_.read< uint32_t >( ) );
|
||
|
|
||
|
if ( widx >= words_.size( ) ) {
|
||
|
errors_.add( "unregistered word" , name_ , pos );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
T_String const& word( words_[ widx ] );
|
||
|
if ( T_SRDToken::IsWord( word ) ) {
|
||
|
push( mt( word ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void T_SRDBRVersion1_::readString( F_StringTok mt )
|
||
|
{
|
||
|
const auto str( reader_.read< T_String >( ).usePool( ) );
|
||
|
push( mt( str ) );
|
||
|
}
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
void T_SRDBRVersion1_::readLoop( )
|
||
|
{
|
||
|
bool pendingVariable = false;
|
||
|
uint32_t depth = 1;
|
||
|
while ( depth > 0 ) {
|
||
|
lastTagPos_ = reader_.stream( ).position( );
|
||
|
const auto tag( reader_.read< E_V1Tag_ >( ) );
|
||
|
|
||
|
if ( pendingVariable && tag != E_V1Tag_::WORD_NEW && tag != E_V1Tag_::WORD_KNOWN ) {
|
||
|
pendingVariable = false;
|
||
|
errors_.add( "spurious variable tag" , name_ , lastTagPos_ - 1 );
|
||
|
}
|
||
|
|
||
|
switch ( tag ) {
|
||
|
|
||
|
case E_V1Tag_::LIST:
|
||
|
depth ++;
|
||
|
push( T_SRDToken::ListStart( ) );
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::END:
|
||
|
depth --;
|
||
|
if ( depth > 0 ) {
|
||
|
push( T_SRDToken::ListEnd( ) );
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::WORD_NEW:
|
||
|
if ( pendingVariable ) {
|
||
|
lastTagPos_ --;
|
||
|
readNewWord( T_SRDToken::Variable );
|
||
|
pendingVariable = false;
|
||
|
} else {
|
||
|
readNewWord( T_SRDToken::Word );
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::WORD_KNOWN:
|
||
|
if ( pendingVariable ) {
|
||
|
lastTagPos_ --;
|
||
|
readKnownWord( T_SRDToken::Variable );
|
||
|
pendingVariable = false;
|
||
|
} else {
|
||
|
readKnownWord( T_SRDToken::Word );
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::VAR_WORD:
|
||
|
pendingVariable = true;
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::STRING:
|
||
|
readString( T_SRDToken::String );
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::COMMENT:
|
||
|
readString( T_SRDToken::Comment );
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::INT:
|
||
|
push( T_SRDToken::Integer( reader_.read< int32_t >( ) ) );
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::LONG:
|
||
|
push( T_SRDToken::Long( reader_.read< int64_t >( ) ) );
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::FLOAT:
|
||
|
push( T_SRDToken::Float( reader_.read< double >( ) ) );
|
||
|
break;
|
||
|
|
||
|
case E_V1Tag_::BINARY:
|
||
|
{
|
||
|
const uint32_t size( reader_.read< uint32_t >( ) );
|
||
|
T_SharedPtr< T_Buffer< uint8_t > > buffer(
|
||
|
NewShared< T_Buffer< uint8_t > >( size ) );
|
||
|
if ( size ) {
|
||
|
reader_.stream( ).read( buffer->data( ) , size );
|
||
|
}
|
||
|
push( T_SRDToken::Binary( buffer ) );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void T_SRDBRVersion1_::read( )
|
||
|
{
|
||
|
T_SRDReaderTargetHelper rth( target_ , errors_ );
|
||
|
try {
|
||
|
readLoop( );
|
||
|
} catch ( X_StreamError const& e ) {
|
||
|
switch ( e.code( ) ) {
|
||
|
|
||
|
case E_StreamError::BAD_DATA:
|
||
|
errors_.add( "invalid data" , name_ , reader_.stream( ).position( ) );
|
||
|
break;
|
||
|
|
||
|
case E_StreamError::END:
|
||
|
errors_.add( "unexpected end of file" , name_ , reader_.stream( ).position( ) );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
errors_.add( "read error" , name_ , reader_.stream( ).position( ) );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
|
||
|
/*= T_SRDBinaryWriter ========================================================*/
|
||
|
|
||
|
T_SRDBinaryWriter::T_SRDBinaryWriter( A_OutputStream& output )
|
||
|
: writer_( output , E_Endian::LITTLE ) , depth_( 0 )
|
||
|
{ }
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
void T_SRDBinaryWriter::flushComment( )
|
||
|
{
|
||
|
if ( comment_.size( ) != 0 ) {
|
||
|
writer_.write( E_V1Tag_::COMMENT );
|
||
|
writer_.write( comment_ );
|
||
|
comment_.clear( );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void T_SRDBinaryWriter::writeWord( T_String const& word )
|
||
|
{
|
||
|
// Try to find the word in the index
|
||
|
const uint32_t hash = ComputeHash( word );
|
||
|
uint32_t index = wordsIndex_.first( hash );
|
||
|
while ( index != T_HashIndex::INVALID_INDEX ) {
|
||
|
if ( word == words_[ index ] ) {
|
||
|
break;
|
||
|
}
|
||
|
index = wordsIndex_.next( index );
|
||
|
}
|
||
|
|
||
|
if ( index == T_HashIndex::INVALID_INDEX ) {
|
||
|
// New word
|
||
|
wordsIndex_.add( hash );
|
||
|
words_.add( word );
|
||
|
writer_.write( E_V1Tag_::WORD_NEW );
|
||
|
writer_.write( word );
|
||
|
} else {
|
||
|
// Known word, use the index
|
||
|
writer_.write( E_V1Tag_::WORD_KNOWN );
|
||
|
writer_.write( index );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::start( )
|
||
|
{
|
||
|
if ( depth_ != 0 ) {
|
||
|
throw X_SRDWriterError( "already started" );
|
||
|
}
|
||
|
writer_.write( C_MAGIC_ );
|
||
|
writer_.write( E_Version_::V1 );
|
||
|
depth_ = 1;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::end( )
|
||
|
{
|
||
|
if ( depth_ != 1 ) {
|
||
|
throw X_SRDWriterError( depth_ == 0 ? "already ended" : "unterminated lists" );
|
||
|
}
|
||
|
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::END );
|
||
|
depth_ = 0;
|
||
|
|
||
|
comment_.free( );
|
||
|
wordsIndex_.free( );
|
||
|
words_.free( );
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::startList( )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::LIST );
|
||
|
depth_ ++;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::endList( )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::END );
|
||
|
depth_ --;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putText( T_String const& text )
|
||
|
{
|
||
|
if ( T_SRDToken::IsWord( text ) ) {
|
||
|
return putWord( text );
|
||
|
} else {
|
||
|
return putString( text );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putWord( T_String const& word )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writeWord( word );
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putString( T_String const& string )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::STRING );
|
||
|
writer_.write( string );
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putComment( T_String const& comment )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
comment_ << comment;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putVariable( T_String const& variable )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::VAR_WORD );
|
||
|
writeWord( variable );
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putBinary(
|
||
|
T_Buffer< uint8_t > const& binary )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::BINARY );
|
||
|
writer_.write( uint32_t( binary.bytes( ) ) );
|
||
|
if ( binary.size( ) != 0 ) {
|
||
|
writer_.stream( ).write( binary.data( ) , binary.bytes( ) );
|
||
|
}
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putInteger( int64_t value )
|
||
|
{
|
||
|
if ( value >= INT32_MIN && value <= INT32_MAX ) {
|
||
|
return putInt32( int32_t( value ) );
|
||
|
} else {
|
||
|
return putInt64( int64_t( value ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putInt32( int32_t value )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::INT );
|
||
|
writer_.write( value );
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putInt64( int64_t value )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::LONG );
|
||
|
writer_.write( value );
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
T_SRDBinaryWriter& T_SRDBinaryWriter::putFloat( double value )
|
||
|
{
|
||
|
if ( depth_ == 0 ) {
|
||
|
throw X_SRDWriterError( "not started" );
|
||
|
}
|
||
|
flushComment( );
|
||
|
writer_.write( E_V1Tag_::FLOAT );
|
||
|
writer_.write( value );
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*= T_SRDBinaryReader ========================================================*/
|
||
|
|
||
|
void T_SRDBinaryReader::read( T_String const& name , A_InputStream& input )
|
||
|
{
|
||
|
T_BinaryReader rd( input , E_Endian::LITTLE );
|
||
|
T_SRDErrors errors;
|
||
|
|
||
|
// Read/check magic number
|
||
|
const auto magicPos( input.position( ) );
|
||
|
const uint32_t magic( ([&]( ) {
|
||
|
try {
|
||
|
return rd.read< uint32_t >( );
|
||
|
} catch ( X_StreamError& e ) {
|
||
|
switch ( e.code( ) ) {
|
||
|
|
||
|
case E_StreamError::BAD_DATA:
|
||
|
case E_StreamError::END:
|
||
|
errors.add( "missing magic" , name , input.position( ) );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
errors.add( "read error" , name , input.position( ) );
|
||
|
break;
|
||
|
}
|
||
|
throw X_SRDErrors( errors );
|
||
|
}
|
||
|
})( ) );
|
||
|
if ( magic != C_MAGIC_ ) {
|
||
|
errors.add( "invalid magic number" , name , magicPos );
|
||
|
throw X_SRDErrors( errors );
|
||
|
}
|
||
|
|
||
|
// Read version number
|
||
|
const uint32_t versionPos( input.position( ) );
|
||
|
const uint32_t version( ([&]( ) {
|
||
|
try {
|
||
|
return rd.read< uint32_t >( );
|
||
|
} catch ( X_StreamError& e ) {
|
||
|
switch ( e.code( ) ) {
|
||
|
|
||
|
case E_StreamError::BAD_DATA:
|
||
|
case E_StreamError::END:
|
||
|
errors.add( "missing version ID" , name , input.position( ) );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
errors.add( "read error" , name , input.position( ) );
|
||
|
break;
|
||
|
}
|
||
|
throw X_SRDErrors( errors );
|
||
|
}
|
||
|
})( ) );
|
||
|
switch ( version ) {
|
||
|
|
||
|
case uint32_t( E_Version_::V1 ):
|
||
|
T_SRDBRVersion1_( rd , name , errors , target_ ).read( );
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
errors.add( "invalid version ID" , name , versionPos );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( errors.size( ) != 0 ) {
|
||
|
throw X_SRDErrors( errors );
|
||
|
}
|
||
|
}
|