Repo init with mostly unchanged import of LW code

This commit is contained in:
Emmanuel BENOîT 2017-11-01 20:14:23 +01:00
commit 221f0c8ef8
161 changed files with 61414 additions and 0 deletions

667
src/Console-Unix.hh Normal file
View file

@ -0,0 +1,667 @@
/******************************************************************************/
/* CONSOLE SUPPORT - UNIX IMPLEMENTATION **************************************/
/******************************************************************************/
#include <lw/lib/Console.hh>
#include <unistd.h>
#include <termios.h>
#include <pty.h>
namespace {
// List of unsupported terminals
char const* const BadTerminals_[] = {
"dumb" , "cons25" , "emacs" ,
nullptr
};
// Extended input sequences
char const* const InputSequences_[] = {
"A" , // Up
"B" , // Down
"C" , // Right
"D" , // Left
"H" , // Home
"F" , // End
"3~" , // Delete
"1;5C" , // C-Right
"1;5D" , // C-Left
"15~" , // F5
"17~" , // F6
"18~" , // F7
"19~" , // F8
"20~" , // F9
"21~" , // F10
"23~" , // F11
"24~" , // F12
"1;2P" , // S-F1
"1;2Q" , // S-F2
"1;2R" , // S-F3
"1;2S" , // S-F4
"15;2~" , // S-F5
"17;2~" , // S-F6
"18;2~" , // S-F7
"19;2~" , // S-F8
"20;2~" , // S-F9
"21;2~" , // S-F10
"23;2~" , // S-F11
"24;2~" , // S-F12
"1;5P" , // C-F1
"1;5Q" , // C-F2
"1;5R" , // C-F3
"1;5S" , // C-F4
"15;5~" , // C-F5
"17;5~" , // C-F6
"18;5~" , // C-F7
"19;5~" , // C-F8
"20;5~" , // C-F9
"21;5~" , // C-F10
"23;5~" , // C-F11
"24;5~" , // C-F12
"1;6P" , // C-S-F1
"1;6Q" , // C-S-F2
"1;6R" , // C-S-F3
"1;6S" , // C-S-F4
"15;6~" , // C-S-F5
"17;6~" , // C-S-F6
"18;6~" , // C-S-F7
"19;6~" , // C-S-F8
"20;6~" , // C-S-F9
"21;6~" , // C-S-F10
"23;6~" , // C-S-F11
"24;6~" , // C-S-F12
nullptr
};
const lw::E_ConsoleKey InputKeys_[] = {
lw::E_ConsoleKey::UP ,
lw::E_ConsoleKey::DOWN ,
lw::E_ConsoleKey::RIGHT ,
lw::E_ConsoleKey::LEFT ,
lw::E_ConsoleKey::HOME ,
lw::E_ConsoleKey::END ,
lw::E_ConsoleKey::DELETE ,
lw::E_ConsoleKey::WNEXT ,
lw::E_ConsoleKey::WPREV ,
lw::E_ConsoleKey::F5 ,
lw::E_ConsoleKey::F6 ,
lw::E_ConsoleKey::F7 ,
lw::E_ConsoleKey::F8 ,
lw::E_ConsoleKey::F9 ,
lw::E_ConsoleKey::F10 ,
lw::E_ConsoleKey::F11 ,
lw::E_ConsoleKey::F12 ,
lw::E_ConsoleKey::S_F1 ,
lw::E_ConsoleKey::S_F2 ,
lw::E_ConsoleKey::S_F3 ,
lw::E_ConsoleKey::S_F4 ,
lw::E_ConsoleKey::S_F5 ,
lw::E_ConsoleKey::S_F6 ,
lw::E_ConsoleKey::S_F7 ,
lw::E_ConsoleKey::S_F8 ,
lw::E_ConsoleKey::S_F9 ,
lw::E_ConsoleKey::S_F10 ,
lw::E_ConsoleKey::S_F11 ,
lw::E_ConsoleKey::S_F12 ,
lw::E_ConsoleKey::C_F1 ,
lw::E_ConsoleKey::C_F2 ,
lw::E_ConsoleKey::C_F3 ,
lw::E_ConsoleKey::C_F4 ,
lw::E_ConsoleKey::C_F5 ,
lw::E_ConsoleKey::C_F6 ,
lw::E_ConsoleKey::C_F7 ,
lw::E_ConsoleKey::C_F8 ,
lw::E_ConsoleKey::C_F9 ,
lw::E_ConsoleKey::C_F10 ,
lw::E_ConsoleKey::C_F11 ,
lw::E_ConsoleKey::C_F12 ,
lw::E_ConsoleKey::CS_F1 ,
lw::E_ConsoleKey::CS_F2 ,
lw::E_ConsoleKey::CS_F3 ,
lw::E_ConsoleKey::CS_F4 ,
lw::E_ConsoleKey::CS_F5 ,
lw::E_ConsoleKey::CS_F6 ,
lw::E_ConsoleKey::CS_F7 ,
lw::E_ConsoleKey::CS_F8 ,
lw::E_ConsoleKey::CS_F9 ,
lw::E_ConsoleKey::CS_F10 ,
lw::E_ConsoleKey::CS_F11 ,
lw::E_ConsoleKey::CS_F12 ,
};
// Various control codes
const char CCGetTermSizeStart_[] = "\033[s\033[99999C\033[99999A";
const char CCGetTermSizeEnd_[] = "\033[u";
const char CCGetCursorPos_[] = "\033[6n";
const char CCClearScreen_[] = "\033[H\033[2J";
const char CCNextLine_[] = "\n\033[99999D\033[K";
const char CCClearLine_[] = "\033[99999D\033[K";
const char CCClearLineUp_[] = "\033[99999D\033[K\033[A";
// Are we running in an unsupported terminal?
inline bool IsBadTerminal_( )
{
char const* const t( getenv( "TERM" ) );
if ( t == nullptr ) {
return true;
}
for ( auto i = 0 ; BadTerminals_[ i ] ; i ++ ) {
if ( !strcasecmp( t , BadTerminals_[ i ] ) ) {
return true;
}
}
return false;
}
/*----------------------------------------------------------------------------*/
class T_ConsoleImpl_
{
private:
bool ok_;
termios initialTerm_;
lw::E_ConsoleInteractionMode mode_;
bool failed_;
bool interrupted_;
int sizePolling_;
uint32_t rows_ , cols_;
lw::T_StringBuilder styleSeq_;
uint8_t keyBuf_[ 16 ];
uint32_t kbPos_;
public:
T_ConsoleImpl_( );
~T_ConsoleImpl_( );
bool initConsole( );
void shutdownConsole( );
bool failed( ) const;
bool resized( uint32_t& rows , uint32_t& cols ) const;
bool getTerminalSize( uint32_t& rows , uint32_t& cols );
bool getCursorPosition( uint32_t& column , uint32_t& row );
bool readKey( lw::E_ConsoleKey& key , uint32_t& character );
bool clearScreen( );
void setTextStyle( uint8_t style , lw::T_TextColor color );
void printCharacter( lw::T_Character c );
void nextLine( );
void clearLine( );
void clearLines( uint32_t count );
void moveCursor( int32_t x , int32_t y );
private:
bool initTTY( );
bool checkInput( lw::E_ConsoleKey& key , uint32_t& character );
bool getTerminalSize( );
bool writeSequence( char const* sequence );
bool writeSequence( char const* sequence , uint32_t size );
};
/*----------------------------------------------------------------------------*/
T_ConsoleImpl_::T_ConsoleImpl_( )
{
// Buffer size:
// * "ESC[0m" always => 4
// * 2 per enabled style => 6
// * color: "38;2;" + 3 unsigned 8-bit numbers + 2 ";" separators => 16
// That would be 26, but let's go for 32 instead
styleSeq_.ensureCapacity( 32 );
}
T_ConsoleImpl_::~T_ConsoleImpl_( )
{
shutdownConsole( );
}
bool T_ConsoleImpl_::initConsole( )
{
failed_ = false;
kbPos_ = 0;
ok_ = isatty( STDIN_FILENO ) && !IsBadTerminal_( ) && initTTY( );
if ( ok_ && getTerminalSize( ) ) {
sizePolling_ = 0;
}
return ok_ && !failed_;
}
void T_ConsoleImpl_::shutdownConsole( )
{
if ( ok_ ) {
setTextStyle( 0 , lw::E_TextColor::WHITE );
tcsetattr( STDIN_FILENO , TCSAFLUSH , &initialTerm_ );
}
}
inline bool T_ConsoleImpl_::failed( ) const
{
return failed_;
}
bool T_ConsoleImpl_::resized( uint32_t& rows , uint32_t& cols ) const
{
if ( cols_ != cols || rows_ != rows ) {
cols = cols_;
rows = rows_;
return true;
}
return false;
}
bool T_ConsoleImpl_::getTerminalSize( uint32_t& rows , uint32_t& cols )
{
if ( !getTerminalSize( ) ) {
return false;
}
rows = rows_;
cols = cols_;
return true;
}
/*----------------------------------------------------------------------------*/
bool T_ConsoleImpl_::readKey( lw::E_ConsoleKey& key , uint32_t& character )
{
if ( failed_ ) {
return false;
}
do {
assert( kbPos_ < sizeof( keyBuf_ ) );
const auto n( read( STDIN_FILENO , &keyBuf_[ kbPos_ ] , 1 ) );
if ( n != 1 ) {
failed_ = failed_ || ( n == -1 );
if ( n == 0 && kbPos_ == 0 ) {
sizePolling_ = ( sizePolling_ + 1 ) % 20;
if ( sizePolling_ == 0 ) {
getTerminalSize( );
}
}
return false;
}
kbPos_ ++;
} while ( !checkInput( key , character ) );
kbPos_ = 0;
return true;
}
bool T_ConsoleImpl_::checkInput( lw::E_ConsoleKey& key , uint32_t& character )
{
assert( kbPos_ > 0 );
const uint8_t first( keyBuf_[ 0 ] );
if ( kbPos_ == 1 ) {
// Escape or UTF-8 input
if ( first == 0x1b || first > 0x7f ) {
return false;
}
switch ( first ) {
case 0x01: // Ctrl+A
key = lw::E_ConsoleKey::HOME;
return true;
case 0x02: // Ctrl+B
key = lw::E_ConsoleKey::LEFT;
return true;
case 0x03: // Ctrl+C
key = lw::E_ConsoleKey::INTERRUPT;
return true;
case 0x04: // Ctrl+D
key = lw::E_ConsoleKey::EXIT;
return true;
case 0x05: // Ctrl+E
key = lw::E_ConsoleKey::END;
return true;
case 0x06: // Ctrl+F
key = lw::E_ConsoleKey::RIGHT;
return true;
case 0x08: // Ctrl+H
case 0x7f:
key = lw::E_ConsoleKey::BACKSPACE;
return true;
case 0x09: // Ctrl+I/Tab
key = lw::E_ConsoleKey::CHARACTER;
character = ' ';
return true;
case 0x0b: // Ctrl+K
key = lw::E_ConsoleKey::KILL_REST;
return true;
case 0x0c: // Ctrl+L
key = lw::E_ConsoleKey::CLEAR;
return true;
case 0x0d: // Ctrl+M/Enter
key = lw::E_ConsoleKey::ENTER;
return true;
case 0x0e: // Ctrl+N
key = lw::E_ConsoleKey::DOWN;
return true;
case 0x10: // Ctrl+P
key = lw::E_ConsoleKey::UP;
return true;
case 0x15: // Ctrl+U
key = lw::E_ConsoleKey::KILL_LINE;
return true;
case 0x17: // Ctrl+W
key = lw::E_ConsoleKey::KILL_WORD;
return true;
case 0x19: // Ctrl+Y
key = lw::E_ConsoleKey::PASTE;
return true;
}
// Skip other control characters
if ( first < 32 ) {
kbPos_ = 0;
return false;
}
key = lw::E_ConsoleKey::CHARACTER;
character = first;
return true;
}
// Check for UTF-8 input
if ( first != 0x1b ) {
uint32_t l( 0 );
if ( !lw::UTF8BufferInfo( (char const*) keyBuf_ , kbPos_ , l ) ) {
if ( kbPos_ > 4 ) {
kbPos_ = 0;
}
return false;
}
if ( l != 1 ) {
kbPos_ = 0;
return false;
} else {
key = lw::E_ConsoleKey::CHARACTER;
character = lw::UTF8GetCodepoint( (char const*) keyBuf_ );
return true;
}
}
// Escape sequences
const uint8_t second( keyBuf_[ 1 ] );
if ( kbPos_ == 2 ) {
switch ( second ) {
case 0x7f:
key = lw::E_ConsoleKey::KILL_WORD;
return true;
case 'f':
case 'F':
key = lw::E_ConsoleKey::WNEXT;
return true;
case 'b':
case 'B':
key = lw::E_ConsoleKey::WPREV;
return true;
default:
kbPos_ = 0;
case 'O':
case '[':
return false;
}
}
// ESC-O-x sequences
if ( second == 'O' ) {
switch ( keyBuf_[ 2 ] ) {
case 'F':
key = lw::E_ConsoleKey::END;
return true;
case 'H':
key = lw::E_ConsoleKey::HOME;
return true;
case 'P':
key = lw::E_ConsoleKey::F1;
return true;
case 'Q':
key = lw::E_ConsoleKey::F2;
return true;
case 'R':
key = lw::E_ConsoleKey::F3;
return true;
case 'S':
key = lw::E_ConsoleKey::F4;
return true;
}
kbPos_ = 0;
return false;
}
// Extended sequences - ESC-[, ends with ~ or uppercase letter
const uint8_t last( keyBuf_[ kbPos_ - 1 ] );
if ( last != '~' && ( last < 'A' || last > 'Z' ) ) {
if ( kbPos_ == sizeof( keyBuf_ ) - 1 ) {
kbPos_ = 0;
}
return false;
}
// Check the list
uint32_t idx( 0 );
keyBuf_[ kbPos_ ] = 0;
while ( InputSequences_[ idx ] != nullptr ) {
if ( !strcmp( (char const*) keyBuf_ + 2 , InputSequences_[ idx ] ) ) {
key = InputKeys_[ idx ];
return true;
}
idx ++;
}
kbPos_ = 0;
return false;
}
/*----------------------------------------------------------------------------*/
bool T_ConsoleImpl_::clearScreen( )
{
return writeSequence( CCClearScreen_ , sizeof( CCClearScreen_ ) - 1 );
}
void T_ConsoleImpl_::setTextStyle( uint8_t style , lw::T_TextColor color )
{
styleSeq_.clear( );
styleSeq_ << "\033[0";
if ( ( style & uint8_t( lw::E_TextStyle::BOLD ) ) != 0 ) {
styleSeq_ << ";1";
}
if ( ( style & uint8_t( lw::E_TextStyle::ITALIC ) ) != 0 ) {
styleSeq_ << ";3";
}
if ( ( style & uint8_t( lw::E_TextStyle::UNDERLINE ) ) != 0 ) {
styleSeq_ << ";4";
}
if ( color.type == lw::E_TextColor::CUSTOM ) {
styleSeq_ << ";38;2;" << color.r
<< ';' << color.g
<< ';' << color.b;
} else {
styleSeq_ << ';' << ( 30 + uint8_t( color.type ) );
}
styleSeq_ << 'm';
writeSequence( styleSeq_.data( ) , styleSeq_.size( ) );
}
void T_ConsoleImpl_::printCharacter( lw::T_Character c )
{
char buf[ 8 ];
uint32_t l( UTF8PutCodepoint( buf , 7 , c ) );
if ( l == 0 ) {
return;
}
writeSequence( buf , l );
}
void T_ConsoleImpl_::nextLine( )
{
writeSequence( CCNextLine_ , sizeof( CCNextLine_ ) - 1 );
}
void T_ConsoleImpl_::clearLine( )
{
writeSequence( CCClearLine_ , sizeof( CCClearLine_ ) - 1 );
}
void T_ConsoleImpl_::clearLines( uint32_t count )
{
assert( count > 0 );
while ( --count ) {
writeSequence( CCClearLineUp_ , sizeof( CCClearLineUp_ ) - 1 );
}
clearLine( );
}
void T_ConsoleImpl_::moveCursor( int32_t x , int32_t y )
{
styleSeq_.clear( );
if ( x ) {
styleSeq_ << "\033["
<< ( x > 0 ? x : -x )
<< ( x > 0 ? 'C' : 'D' );
}
if ( y ) {
styleSeq_ << "\033["
<< ( y > 0 ? y : -y )
<< ( y > 0 ? 'B' : 'A' );
}
if ( styleSeq_.size( ) ) {
writeSequence( styleSeq_.data( ) , styleSeq_.size( ) );
}
}
/*----------------------------------------------------------------------------*/
bool T_ConsoleImpl_::initTTY( )
{
if ( tcgetattr( STDIN_FILENO , &initialTerm_ ) == -1 ) {
failed_ = true;
return false;
}
termios rawTerm( initialTerm_ );
rawTerm.c_iflag &= ~( BRKINT | ICRNL | INPCK | ISTRIP | IXON );
rawTerm.c_oflag &= ~OPOST;
rawTerm.c_cflag |= CS8;
rawTerm.c_lflag &= ~( ECHO | ICANON | IEXTEN | ISIG );
rawTerm.c_cc[ VMIN ] = 0;
rawTerm.c_cc[ VTIME ] = 0;
if ( tcsetattr( STDIN_FILENO , TCSAFLUSH , &rawTerm ) >= 0 ) {
return getTerminalSize( );
} else {
failed_ = true;
return false;
}
}
/*----------------------------------------------------------------------------*/
bool T_ConsoleImpl_::getTerminalSize( )
{
// Try obtaining the size using ioctl
winsize ws;
if ( ioctl( STDIN_FILENO , TIOCGWINSZ , &ws ) != -1 && ws.ws_col != 0 ) {
rows_ = ws.ws_row;
cols_ = ws.ws_col;
return true;
}
// Otherwise, we need to move the cursor to the bottom/right corner, then
// get its position.
return writeSequence( CCGetTermSizeStart_ , sizeof( CCGetTermSizeStart_ ) - 1 )
&& getCursorPosition( cols_ , rows_ )
&& writeSequence( CCGetTermSizeEnd_ , sizeof( CCGetTermSizeEnd_ ) - 1 );
}
bool T_ConsoleImpl_::getCursorPosition( uint32_t& column , uint32_t& row )
{
if ( failed_ || tcflush( STDIN_FILENO , TCIFLUSH ) != 0 ) {
failed_ = true;
return false;
}
if ( !writeSequence( CCGetCursorPos_ , sizeof( CCGetCursorPos_ ) - 1 ) ) {
return false;
}
char buf[ 32 ];
size_t i( 0 );
while ( i < sizeof( buf ) ) {
if ( read( STDIN_FILENO , buf + i , 1 ) != 1 ) {
i = sizeof( buf );
break;
}
if ( buf[ i ] == 'R' ) {
break;
}
i ++;
}
if ( i == sizeof( buf ) || i < 6 || buf[ 0 ] != 27 || buf[ 1 ] != '[' ) {
failed_ = true;
return false;
}
buf[ i ] = 0;
if ( sscanf( buf + 2 , "%d;%d" , &row , &column ) != 2 ) {
failed_ = true;
return false;
}
return true;
}
#if 0
bool T_ConsoleImpl_::writeSequence( char const* sequence )
{
if ( failed_ ) {
return false;
}
const int n( strlen( sequence ) );
if ( write( STDOUT_FILENO , sequence , n ) != n ) {
failed_ = true;
return false;
}
return true;
}
#endif
bool T_ConsoleImpl_::writeSequence( char const* sequence , uint32_t size )
{
if ( failed_ ) {
return false;
}
if ( write( STDOUT_FILENO , sequence , size ) != size ) {
failed_ = true;
return false;
}
return true;
}
}

1346
src/Console.cc Normal file

File diff suppressed because it is too large Load diff

83
src/ConsoleLogWriter.cc Normal file
View file

@ -0,0 +1,83 @@
#include <lw/lib/BuiltinLoggers.hh>
using namespace lw;
namespace {
char const* const V_Name_ = "console";
inline T_String Name_( ) { return T_String::Pooled( V_Name_ ); }
}
/*= T_ConsoleLogWriterFactory ================================================*/
T_ConsoleLogWriterFactory::T_ConsoleLogWriterFactory( T_Console& console )
: A_LogWriterFactory( Name_( ) ) , console_( console )
{ }
RP_LogWriterConfiguration T_ConsoleLogWriterFactory::createConfiguration( T_String const& name ) const
{
RP_LogWriterConfiguration p( new T_LogWriterConfiguration( Name_( ) ) );
p->setName( name );
return p;
}
OP_LogWriter T_ConsoleLogWriterFactory::createLogWriter( OP_LogWriterConfiguration&& configuration ) const
{
T_ConsoleLogWriter* p( new T_ConsoleLogWriter( std::move( configuration ) , console_ ) );
return OwnRawPointer( p );
}
/*= T_ConsoleLogWriter =======================================================*/
T_ConsoleLogWriter::T_ConsoleLogWriter( OP_LogWriterConfiguration&& configuration , T_Console& console )
: A_LogWriter( std::move( configuration ) ) , console_( console )
{ }
void T_ConsoleLogWriter::log( T_LogTimestamp const& timestamp ,
E_LogLevel level , T_LogPath const& path ,
T_LogStringData const& data , uint32_t size )
{
using namespace std::chrono;
char timeBuffer[ 128 ];
std::time_t tst( T_LogTimestamp::clock::to_time_t( timestamp ) );
std::strftime( timeBuffer , 128 , "%Y-%m-%d %H:%M:%S" , std::gmtime( &tst ) );
const auto ms( ( duration_cast< milliseconds >( timestamp - T_LogTimestamp( ) ) ).count( ) % 1000 );
T_TextBuilder sb;
sb << timeBuffer << '.';
if ( ms < 100 ) {
sb << '0';
if ( ms < 10 ) {
sb << '0';
}
}
sb << ms << ' ' << path.toString( ) << " - "
<< E_TextStyle::UNDERLINE;
switch ( level ) {
case E_LogLevel::TRACE:
case E_LogLevel::DEBUG:
case E_LogLevel::INFO:
sb << E_TextColor::CYAN;
break;
case E_LogLevel::NOTICE:
break;
case E_LogLevel::WARNING:
sb << E_TextColor::YELLOW;
break;
case E_LogLevel::CRITICAL:
sb << E_TextStyle::BOLD;
case E_LogLevel::ERROR:
sb << E_TextColor::RED;
break;
}
sb << level;
sb.reset( );
sb << " - " << T_String( &( (*data) [ 0 ] ) , size );
console_.putLine( std::move( sb ) );
}

153
src/CwdFileLogger.cc Normal file
View file

@ -0,0 +1,153 @@
/******************************************************************************/
/* LOGGING SYSTEM - BUILT-IN LOGGERS - NON-VFS FILE LOG WRITER ****************/
/******************************************************************************/
#include <lw/lib/BuiltinLoggers.hh>
#include <lw/lib/SRDParser.hh>
using namespace lw;
namespace {
char const* const V_Name_ = "preinit-file";
inline T_String Name_( ) { return T_String::Pooled( V_Name_ ); }
bool CFLCPath_( T_SRDParserData const& data )
{
auto const& ptok( (*data.input)[ 1 ] );
auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
( dynamic_cast< T_CWDFileLogWriterCfg* >( lconf ) )->setPath( ptok.stringValue( ) );
return true;
}
bool CFLCAppend_( T_SRDParserData const& data )
{
auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
( dynamic_cast< T_CWDFileLogWriterCfg* >( lconf ) )->setAppend( true );
return true;
}
bool CFLCTruncate_( T_SRDParserData const& data )
{
auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
( dynamic_cast< T_CWDFileLogWriterCfg* >( lconf ) )->setAppend( false );
return true;
}
}
/*= T_CWDFileLogWriterFactory ==================================================*/
T_CWDFileLogWriterFactory::T_CWDFileLogWriterFactory( )
: A_LogWriterFactory( Name_( ) )
{ }
RP_LogWriterConfiguration T_CWDFileLogWriterFactory::createConfiguration( T_String const& name ) const
{
RP_LogWriterConfiguration p( new T_CWDFileLogWriterCfg( ) );
p->setName( name );
return p;
}
OP_LogWriter T_CWDFileLogWriterFactory::createLogWriter( OP_LogWriterConfiguration&& configuration ) const
{
T_CWDFileLogWriter* p( new T_CWDFileLogWriter( std::move( configuration ) ) );
return OwnRawPointer( p );
}
void T_CWDFileLogWriterFactory::initializeSyntax( T_SRDParserDefs& , T_SRDContext& main ) const
{
using namespace lw::SRD;
main << ( Rule( ) << "file" << Text( ) << CFLCPath_ );
main << ( Rule( ) << "append" << CFLCAppend_ );
main << ( Rule( ) << "truncate" << CFLCTruncate_ );
}
/*= T_CWDFileLogWriterCfg ======================================================*/
T_CWDFileLogWriterCfg::T_CWDFileLogWriterCfg( )
: T_LogWriterConfiguration( Name_( ) )
{ }
T_CWDFileLogWriterCfg::T_CWDFileLogWriterCfg( T_CWDFileLogWriterCfg const& source )
: T_LogWriterConfiguration( source ) , path_( source.path_ ) ,
append_( source.append_ )
{ }
OP_LogWriterConfiguration T_CWDFileLogWriterCfg::clone( )
{
T_CWDFileLogWriterCfg* ptr( new T_CWDFileLogWriterCfg( *this ) );
return OwnRawPointer( ptr );
}
void T_CWDFileLogWriterCfg::check( T_SRDErrors& errors , T_SRDList const& input )
{
T_LogWriterConfiguration::check( errors , input );
if ( !path_ ) {
errors.add( "no file selected" , input[ 0 ] );
}
}
/*= T_CWDFileLogWriter =========================================================*/
T_CWDFileLogWriter::T_CWDFileLogWriter( OP_LogWriterConfiguration&& configuration )
: A_LogWriter( std::move( configuration ) )
{ }
void T_CWDFileLogWriter::log( T_LogTimestamp const& timestamp ,
E_LogLevel level , T_LogPath const& path ,
T_LogStringData const& data , uint32_t size )
{
using namespace std::chrono;
char timeBuffer[ 128 ];
std::time_t tst( T_LogTimestamp::clock::to_time_t( timestamp ) );
std::strftime( timeBuffer , 128 , "%Y-%m-%d %H:%M:%S" , std::gmtime( &tst ) );
const auto ms( ( duration_cast< milliseconds >( timestamp - T_LogTimestamp( ) ) ).count( ) % 1000 );
T_StringBuilder sb;
sb << timeBuffer << '.';
if ( ms < 100 ) {
sb << '0';
if ( ms < 10 ) {
sb << '0';
}
}
sb << ms << ' ' << path.toString( ) << " - " << level << ": ";
sb.append( &( (*data) [ 0 ] ) , size );
sb << '\n';
auto const& cfg( configuration< T_CWDFileLogWriterCfg >( ) );
auto const& p( cfg.path( ) );
if ( !file_ ) {
file_ = NewOwned< T_File >( p , cfg.append( ) ? E_FileMode::READ_WRITE : E_FileMode::OVERWRITE );
if ( !file_ ) {
disable( );
return;
}
try {
file_->open( );
} catch ( X_StreamError const& ) {
disable( );
file_.clear( );
return;
}
}
try {
file_->position( 0 , true );
file_->write( sb.data( ) , sb.size( ) );
file_->flush( );
} catch ( X_StreamError ) {
disable( );
file_.clear( );
return;
}
}

135
src/DynLib.cc Normal file
View file

@ -0,0 +1,135 @@
/******************************************************************************/
/* DYNAMIC LIBRARIES **********************************************************/
/******************************************************************************/
#include <lw/lib/DynLib.hh>
#include <lw/lib/Threading.hh>
using namespace lw;
#ifdef _WIN32
# error "Not implemented"
#endif
/*= T_DynLibLinux_ ===========================================================*/
#include <dlfcn.h>
#include <atomic>
namespace {
struct T_DynLibLinux_
{
T_Buffer< char > path;
T_String error;
void* lib;
explicit T_DynLibLinux_( T_String const& path ) noexcept;
explicit T_DynLibLinux_( char const* const path ) noexcept;
~T_DynLibLinux_( ) noexcept;
bool load( ) noexcept;
void unload( ) noexcept;
bool isLoaded( ) const noexcept;
void* getSymbol( char const* const symbol ) noexcept;
};
} // namespace
/*----------------------------------------------------------------------------*/
inline T_DynLibLinux_::T_DynLibLinux_(
T_String const& path ) noexcept
: path( path.toOSString( ) ) , lib( nullptr )
{ }
inline T_DynLibLinux_::T_DynLibLinux_(
char const* const path ) noexcept
: path( path , strlen( path ) ) , lib( nullptr )
{ }
inline T_DynLibLinux_::~T_DynLibLinux_( ) noexcept
{
unload( );
}
/*----------------------------------------------------------------------------*/
inline bool T_DynLibLinux_::load( ) noexcept
{
if ( lib != nullptr ) {
return true;
}
lib = dlopen( &path[ 0 ] , RTLD_LOCAL | RTLD_NOW );
error = lib ? T_String{} : T_String{ dlerror( ) };
return lib != nullptr;
}
inline void T_DynLibLinux_::unload( ) noexcept
{
if ( lib != nullptr ) {
dlclose( lib );
lib = nullptr;
}
}
inline bool T_DynLibLinux_::isLoaded( ) const noexcept
{
return lib != nullptr;
}
/*----------------------------------------------------------------------------*/
inline void* T_DynLibLinux_::getSymbol(
char const* const symbol ) noexcept
{
assert( lib != nullptr );
void* const ptr( dlsym( lib , symbol ) );
error = ( ptr == nullptr ) ? T_String{ dlerror( ) } : T_String{};
return ptr;
}
/*= T_DynLib =================================================================*/
using T_DynLibImpl_ = T_DynLibLinux_; // FIXME
T_DynLib::T_DynLib(
T_String const& name )
: A_PrivateImplementation( new T_DynLibImpl_( name ) )
{ }
T_DynLib::T_DynLib(
char const* const name )
: A_PrivateImplementation( new T_DynLibImpl_( name ) )
{ }
/*----------------------------------------------------------------------------*/
bool T_DynLib::load( )
{
return p< T_DynLibImpl_ >( ).load( );
}
void T_DynLib::unload( )
{
p< T_DynLibImpl_ >( ).unload( );
}
bool T_DynLib::isLoaded( ) const noexcept
{
return p< T_DynLibImpl_ >( ).isLoaded( );
}
T_String T_DynLib::lastError( ) const noexcept
{
return p< T_DynLibImpl_ >( ).error;
}
/*----------------------------------------------------------------------------*/
void* T_DynLib::getRawSymbol(
char const* const name ) const noexcept
{
return p< T_DynLibImpl_ >( ).getSymbol( name );
}

326
src/Files.cc Normal file
View file

@ -0,0 +1,326 @@
/******************************************************************************/
/* FILES **********************************************************************/
/******************************************************************************/
#include <lw/lib/Files.hh>
using namespace lw;
/*= T_File ==================================================================*/
T_File::T_File( )
: path_( ) , file_( nullptr )
{ }
T_File::T_File( T_String const& path , E_FileMode mode )
: path_( path ) , mode_( mode ) , file_( nullptr )
{ }
T_File::T_File( T_File&& other ) noexcept
: T_File( )
{
swap( *this , other );
}
T_File& T_File::operator= ( T_File&& other ) noexcept
{
swap( *this , other );
return *this;
}
/*---------------------------------------------------------------------------*/
void T_File::open( )
{
if ( file_ != nullptr ) {
return;
}
// Copy file path to C string
const T_Buffer< char > path( path_.toOSString( ) );
// Select the right mode
char const* mode;
switch ( mode_ ) {
case E_FileMode::READ_ONLY:
mode = "rb";
break;
case E_FileMode::READ_WRITE:
mode = "rb+";
break;
case E_FileMode::OVERWRITE:
mode = "wb+";
break;
}
// Open the file
#ifdef WIN32_
file_ = _wfopen( ( wchar_t const* ) &path[ 0 ] , mode );
#else
file_ = fopen( &path[ 0 ] , mode );
#endif
if ( file_ == nullptr ) {
throw X_StreamError( errno );
}
// Get initial size
if ( fseek( file_ , 0 , SEEK_END ) != 0 ) {
auto err( errno );
close( );
throw X_StreamError( err );
}
off_t sz( ftell( file_ ) );
if ( sz < 0 || fseek( file_ , 0 , SEEK_SET ) != 0 ) {
auto err( errno );
close( );
throw X_StreamError( err );
}
pos_ = 0;
size_ = sz;
}
void T_File::close( ) noexcept
{
if ( file_ != nullptr ) {
fclose( file_ );
file_ = nullptr;
}
}
/*---------------------------------------------------------------------------*/
void T_File::position( size_t position , bool fromEnd )
{
open( );
const auto rv( fromEnd
? fseek( file_ , -position , SEEK_END )
: fseek( file_ , +position , SEEK_SET ) );
if ( rv < 0 ) {
throw X_StreamError( errno );
}
pos_ = rv;
}
void T_File::move( ssize_t offset )
{
open( );
const auto rv( fseek( file_ , offset , SEEK_CUR ) );
if ( rv < 0 ) {
throw X_StreamError( errno );
}
pos_ = rv;
}
/*---------------------------------------------------------------------------*/
size_t T_File::read( void* data , size_t size )
{
open( );
if ( pos_ >= size_ ) {
throw X_StreamError( E_StreamError::END );
}
const auto rv( fread( data , 1 , size , file_ ) );
if ( ferror( file_ ) ) {
throw X_StreamError( errno );
} else {
pos_ += rv;
return rv;
}
}
/*---------------------------------------------------------------------------*/
size_t T_File::write( void const* data , size_t size )
{
if ( mode_ == E_FileMode::READ_ONLY ) {
throw X_StreamError( E_StreamError::NOT_SUPPORTED );
}
open( );
auto rv( fwrite( data , 1 , size , file_ ) );
if ( ferror( file_ ) ) {
throw X_StreamError( errno );
} else {
pos_ += rv;
if ( pos_ > size_ ) {
size_ = pos_;
}
return rv;
}
}
/*---------------------------------------------------------------------------*/
void T_File::flush( )
{
if ( !isOpen( ) || mode_ == E_FileMode::READ_ONLY ) {
return;
}
fflush( file_ );
}
/*= T_FileInputStream =======================================================*/
T_FileInputStream::T_FileInputStream( T_File& file , ssize_t offset , size_t limit )
: A_InputStream( 0 , 0 ) , fileRaw_( &file ) , fileOwned_( )
{
init( offset , limit );
}
T_FileInputStream::T_FileInputStream( OP_File&& file , ssize_t offset , size_t limit )
: A_InputStream( 0 , 0 ) , fileRaw_( nullptr ) , fileOwned_( std::move( file ) )
{
file.clear( );
init( offset , limit );
}
/*---------------------------------------------------------------------------*/
T_FileInputStream::T_FileInputStream( T_FileInputStream const& other )
: A_InputStream( other.position( ) , other.size( ) ) ,
fileRaw_( &( other.file( ) ) ) , fileOwned_( ) ,
start_( other.start_ )
{ }
T_FileInputStream& T_FileInputStream::operator= ( T_FileInputStream const& other )
{
position_ = other.position_;
size_ = other.size_;
fileRaw_ = &( other.file( ) );
fileOwned_ = OP_File( );
start_ = other.start_;
return *this;
}
/*---------------------------------------------------------------------------*/
void T_FileInputStream::swap( T_FileInputStream& rhs ) noexcept
{
using std::swap;
swap( size_ , rhs.size_ );
swap( position_ , rhs.position_ );
swap( fileRaw_ , rhs.fileRaw_ );
swap( fileOwned_ , rhs.fileOwned_ );
swap( start_ , rhs.start_ );
}
/*---------------------------------------------------------------------------*/
size_t T_FileInputStream::read( void* data , size_t size )
{
if ( position_ >= size_ ) {
throw X_StreamError( E_StreamError::END );
}
auto& f( file( ) );
f.open( );
if ( f.position( ) != start_ + position_ ) {
f.position( start_ + position_ );
}
const size_t rSize( std::min( size , size_ - position_ ) );
const auto r( f.read( data , rSize ) );
position_ += r;
return r;
}
/*---------------------------------------------------------------------------*/
void T_FileInputStream::init( ssize_t offset , size_t limit )
{
auto& f( file( ) );
f.open( );
const ssize_t start( f.position( ) + offset );
if ( start < 0 || start > ssize_t( f.size( ) ) ) {
throw X_StreamError( E_StreamError::INVALID_POSITION );
}
start_ = start;
size_ = std::min( f.size( ) - start , limit );
}
/*= T_FileOutputStream =======================================================*/
T_FileOutputStream::T_FileOutputStream( T_File& file , ssize_t offset )
: A_OutputStream( 0 ) , fileRaw_( &file ) , fileOwned_( )
{
init( offset );
}
T_FileOutputStream::T_FileOutputStream( OP_File&& file , ssize_t offset )
: A_OutputStream( 0 ) , fileRaw_( nullptr ) , fileOwned_( std::move( file ) )
{
init( offset );
}
/*---------------------------------------------------------------------------*/
T_FileOutputStream::T_FileOutputStream( T_FileOutputStream const& other )
: A_OutputStream( other.position( ) ) , fileRaw_( &other.file( ) ) ,
fileOwned_( ) , start_( other.start_ )
{ }
T_FileOutputStream& T_FileOutputStream::operator= ( T_FileOutputStream const& other )
{
position_ = other.position_;
fileRaw_ = &other.file( );
fileOwned_ = OP_File( );
start_ = other.start_;
return *this;
}
/*---------------------------------------------------------------------------*/
inline void T_FileOutputStream::swap( T_FileOutputStream& rhs ) noexcept
{
using std::swap;
swap( position_ , rhs.position_ );
swap( fileRaw_ , rhs.fileRaw_ );
swap( fileOwned_ , rhs.fileOwned_ );
swap( start_ , rhs.start_ );
}
/*---------------------------------------------------------------------------*/
size_t T_FileOutputStream::write( void const* data , size_t size )
{
auto& f( file( ) );
f.open( );
if ( f.position( ) != start_ + position_ ) {
f.position( start_ + position_ );
}
const auto w( f.write( data , size ) );
position_ += w;
return w;
}
/*---------------------------------------------------------------------------*/
void T_FileOutputStream::flush( )
{
file( ).flush( );
}
/*---------------------------------------------------------------------------*/
void T_FileOutputStream::init( ssize_t offset )
{
auto& f( file( ) );
if ( f.mode( ) == E_FileMode::READ_ONLY ) {
throw X_StreamError( E_StreamError::NOT_SUPPORTED );
}
const ssize_t start( f.position( ) + offset );
if ( start < 0 || start > ssize_t( f.size( ) ) ) {
throw X_StreamError( E_StreamError::INVALID_POSITION );
}
start_ = start;
}

140
src/GameLoop.cc Normal file
View file

@ -0,0 +1,140 @@
/******************************************************************************/
/* GAME'S MAIN LOOP ***********************************************************/
/******************************************************************************/
#include <lw/lib/GameLoop.hh>
#include <lw/lib/Threading.hh>
#include <lw/lib/Log.hh>
#include <lw/lib/LW.hh>
using namespace lw;
/*= T_GameLoopPrivate_ =======================================================*/
namespace {
struct T_GameLoopPrivate_
{
T_Logger logger{ "/core/loop" };
OP_Thread thread;
bool active = false;
bool forceShutdown = false;
T_Mutex mutex;
T_Condition cond;
T_RingBuffer< T_UIMessage > messages{ 64 };
/* Game loop */
void run( ) noexcept;
/* Wait for the next message, no timeout */
bool nextMessage(
T_UIMessage& message ) noexcept;
};
} // namespace
/*----------------------------------------------------------------------------*/
inline void T_GameLoopPrivate_::run( ) noexcept
{
const bool trace( logger.hasLevel( E_LogLevel::TRACE ) );
logger.debug( ) << "Game loop thread starting";
while ( !forceShutdown ) {
T_UIMessage message;
if ( !nextMessage( message ) ) {
continue;
}
const auto mt( message.type( ) );
if ( trace ) {
logger.trace( ) << "Got message " << mt;
}
switch ( mt ) {
case lw::E_GameUIMessage::QUIT:
// FIXME quit properly
LW::ui( ).putMessage( E_GameLoopMessage::TERMINATED );
forceShutdown = true;
break;
default:
logger.debug( ) << "Unhandled message " << mt;
break;
}
}
active = false;
logger.debug( ) << "Game loop thread exiting";
}
inline bool T_GameLoopPrivate_::nextMessage(
T_UIMessage& message ) noexcept
{
T_ExclusiveLock lock( mutex );
cond.wait( lock , [this]() {
return messages.size( ) != 0 || forceShutdown;
} );
return messages.readNext( message )
&& message.hasMessage( );
}
/*= T_GameLoop ===============================================================*/
#define M_PRIVATE_ \
auto& pi( p< T_GameLoopPrivate_ >( ) );
T_GameLoop::T_GameLoop( ) noexcept
: A_PrivateImplementation( new T_GameLoopPrivate_( ) )
{ }
bool T_GameLoop::active( ) const noexcept
{
M_PRIVATE_;
return bool( pi.thread ) && !pi.active;
}
void T_GameLoop::start( ) noexcept
{
M_PRIVATE_;
pi.logger.trace( ) << "Starting main loop thread";
if ( pi.thread ) {
pi.logger.warning( )
<< "The main loop thread is already active?!";
}
pi.active = true;
pi.thread = NewOwned< T_Thread >( [&]{
pi.run( );
} );
}
void T_GameLoop::shutdown( ) noexcept
{
M_PRIVATE_;
if ( !pi.thread ) {
pi.logger.trace( )
<< "The main loop thread is not present";
return;
}
if ( pi.active ) {
pi.logger.notice( ) << "Main loop is still active at shutdown!";
pi.forceShutdown = true;
T_ExclusiveLock lock( pi.mutex );
pi.cond.notify_one( );
}
pi.thread->join( );
pi.thread.clear( );
}
void T_GameLoop::putMessage( T_UIMessage&& message ) noexcept
{
M_PRIVATE_;
T_ExclusiveLock lock( pi.mutex );
pi.messages.put( std::move( message ) );
pi.cond.notify_one( );
}

231
src/HashIndex.cc Normal file
View file

@ -0,0 +1,231 @@
/******************************************************************************/
/* HASH INDEX *****************************************************************/
/******************************************************************************/
#include <lw/lib/HashIndex.hh>
#include <lw/lib/Utilities.hh>
using namespace lw;
/*= T_HashIndex =============================================================*/
constexpr uint32_t T_HashIndex::INVALID_INDEX;
uint32_t T_HashIndex::invalidIndex_ = T_HashIndex::INVALID_INDEX;
T_HashIndex::T_HashIndex( ) noexcept
: T_HashIndex( DEFAULT_SIZE , DEFAULT_SIZE )
{ }
T_HashIndex::T_HashIndex( uint32_t hashSize , uint32_t indexSize , uint32_t growth ) noexcept
: hashSize_( hashSize ) , hash_( &invalidIndex_ ) ,
indexSize_( indexSize ) , indexGrowth_( growth ) ,
indexUsed_( 0 ) , index_( &invalidIndex_ ) ,
hashMask_( hashSize - 1 ) , lookupMask_( 0 )
{
assert( IsPowerOf2( hashSize ) && "size must be a power of 2" );
assert( growth > 0 && "growth must be greater than 0" );
}
/*----------------------------------------------------------------------------*/
T_HashIndex::T_HashIndex( T_HashIndex const& other )
: hashSize_( other.hashSize_ ) , indexSize_( other.indexSize_ ) ,
indexGrowth_( other.indexGrowth_ ) , indexUsed_( other.indexUsed_ ) ,
hashMask_( other.hashMask_ ) , lookupMask_( other.lookupMask_ )
{
if ( indexUsed_ == 0 ) {
hash_ = &invalidIndex_;
index_ = &invalidIndex_;
} else {
hash_ = ( uint32_t* ) malloc( hashSize_ * sizeof( uint32_t ) );
index_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) );
indexReverse_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) );
memcpy( hash_ , other.hash_ , hashSize_ * sizeof( uint32_t ) );
memcpy( index_ , other.index_ , indexSize_ * sizeof( uint32_t ) );
memcpy( indexReverse_ , other.indexReverse_ , indexSize_ * sizeof( uint32_t ) );
}
}
T_HashIndex::T_HashIndex( T_HashIndex&& other ) noexcept
: T_HashIndex( )
{
swap( *this , other );
}
/*----------------------------------------------------------------------------*/
T_HashIndex& T_HashIndex::operator =( T_HashIndex const& other )
{
if ( hash_ != &invalidIndex_ ) {
::free( hash_ );
::free( index_ );
::free( indexReverse_ );
}
hashSize_ = other.hashSize_;
indexSize_ = other.indexSize_;
indexGrowth_ = other.indexGrowth_;
indexUsed_ = other.indexUsed_;
hashMask_ = other.hashMask_;
lookupMask_ = other.lookupMask_;
if ( indexUsed_ == 0 ) {
hash_ = &invalidIndex_;
index_ = &invalidIndex_;
} else {
hash_ = ( uint32_t* ) malloc( hashSize_ * sizeof( uint32_t ) );
index_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) );
indexReverse_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) );
memcpy( hash_ , other.hash_ , hashSize_ * sizeof( uint32_t ) );
memcpy( index_ , other.index_ , indexSize_ * sizeof( uint32_t ) );
memcpy( indexReverse_ , other.indexReverse_ , indexSize_ * sizeof( uint32_t ) );
}
return *this;
}
T_HashIndex& T_HashIndex::operator =( T_HashIndex&& other ) noexcept
{
swap( *this , other );
other.clear( );
return *this;
}
/*----------------------------------------------------------------------------*/
void lw::swap( T_HashIndex& lhs , T_HashIndex& rhs ) noexcept
{
using std::swap;
swap( lhs.hashSize_ , rhs.hashSize_ );
swap( lhs.hash_ , rhs.hash_ );
swap( lhs.indexSize_ , rhs.indexSize_ );
swap( lhs.indexGrowth_ , rhs.indexGrowth_ );
swap( lhs.indexUsed_ , rhs.indexUsed_ );
swap( lhs.index_ , rhs.index_ );
swap( lhs.indexReverse_ , rhs.indexReverse_ );
swap( lhs.hashMask_ , rhs.hashMask_ );
swap( lhs.lookupMask_ , rhs.lookupMask_ );
}
/*----------------------------------------------------------------------------*/
void T_HashIndex::free( )
{
if ( hash_ != &invalidIndex_ ) {
::free( hash_ );
::free( index_ );
::free( indexReverse_ );
hash_ = &invalidIndex_;
index_ = &invalidIndex_;
}
lookupMask_ = 0;
indexUsed_ = 0;
}
void T_HashIndex::clear( )
{
if ( hash_ != &invalidIndex_ ) {
memset( hash_ , 0xff , hashSize_ * sizeof( uint32_t ) );
memset( index_ , 0xff , indexSize_ * sizeof( uint32_t ) );
indexUsed_ = 0;
}
}
/*----------------------------------------------------------------------------*/
void T_HashIndex::enlargeIndex( uint32_t needed )
{
const uint32_t mod = needed % indexGrowth_;
const uint32_t newSize = ( mod == 0 )
? needed
: ( needed + indexGrowth_ - mod );
if ( index_ != &invalidIndex_ ) {
index_ = ( uint32_t* ) realloc( index_ ,
newSize * sizeof( uint32_t ) );
indexReverse_ = ( uint32_t* ) realloc( indexReverse_ ,
newSize * sizeof( uint32_t ) );
memset( index_ + indexSize_ , 0xff ,
( newSize - indexSize_ ) * sizeof( uint32_t ) );
memset( indexReverse_ + indexSize_ , 0xff ,
( newSize - indexSize_ ) * sizeof( uint32_t ) );
}
indexSize_ = newSize;
}
void T_HashIndex::allocateIfNecessary( )
{
if ( hash_ == &invalidIndex_ ) {
hash_ = ( uint32_t* ) malloc( hashSize_ * sizeof( uint32_t ) );
index_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) );
indexReverse_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) );
memset( hash_ , 0xff , hashSize_ * sizeof( uint32_t ) );
memset( index_ , 0xff , indexSize_ * sizeof( uint32_t ) );
memset( indexReverse_ , 0xff , indexSize_ * sizeof( uint32_t ) );
lookupMask_ = INVALID_INDEX;
}
}
/*----------------------------------------------------------------------------*/
void T_HashIndex::add( uint32_t key )
{
const uint32_t index = indexUsed_;
if ( index >= indexSize_ ) {
enlargeIndex( index + indexGrowth_ );
}
allocateIfNecessary( );
assert( index_[ index ] == INVALID_INDEX );
const uint32_t hti = key & hashMask_;
const uint32_t oldIndex = hash_[ hti ];
hash_[ hti ] = index;
index_[ index ] = oldIndex;
indexReverse_[ index ] = hti;
indexUsed_ ++;
}
void T_HashIndex::remove( uint32_t index )
{
assert( hash_ != &invalidIndex_ );
assert( index < indexUsed_ );
// Follow the chain until we find the reference to the index.
const auto key( indexReverse_[ index ] );
uint32_t* ptr = &hash_[ key & hashMask_ ];
assert( *ptr != INVALID_INDEX );
while ( *ptr != index ) {
ptr = &index_[ *ptr ];
assert( *ptr != INVALID_INDEX );
}
// Update the reference so it points to the next item in the chain
*ptr = index_[ index ];
// If the index wasn't the last used index, swap it with the last used
// value
const uint32_t last = indexUsed_ - 1;
if ( index != last ) {
const uint32_t reverse = indexReverse_[ last ];
assert( reverse != INVALID_INDEX );
index_[ index ] = index_[ last ];
indexReverse_[ index ] = reverse;
// Change the reference to the item we moved in its chain
uint32_t* ptr = &hash_[ reverse ];
assert( *ptr != INVALID_INDEX );
while ( *ptr != last ) {
ptr = &index_[ *ptr ];
assert( *ptr != INVALID_INDEX );
}
*ptr = index;
}
index_[ last ] = INVALID_INDEX;
indexReverse_[ last ] = INVALID_INDEX;
indexUsed_ = last;
}

603
src/LW.cc Normal file
View file

@ -0,0 +1,603 @@
/******************************************************************************/
/* MAIN CLASS *****************************************************************/
/******************************************************************************/
#include <lw/lib/LW.hh>
#include <lw/lib/BuiltinLoggers.hh>
#include <lw/lib/SRDBinary.hh>
#include <lw/lib/SRDParser.hh>
#include <lw/lib/SRDText.hh>
#include <lw/lib/MemoryStreams.hh>
#include <lw/lib/VFSDrivers.hh>
using namespace lw;
/*= LIBRARY DATA =============================================================*/
namespace {
#ifdef LW_BUILD
# include "lib-rom.hh"
#else
const uint8_t lw_library_rom[] = { 0 };
#endif
} // namespace
/*= INTERNALS ================================================================*/
namespace {
/* Command line arguments */
struct T_CommandLine_
{
T_String homePath;
T_Array< T_String > extraPaths;
T_String ui;
};
struct T_LWInternals_
{
T_LWComponents& components;
const T_String executablePath_;
T_CommandLine_ arguments_;
OP_LoggingSystem loggingSystem;
T_Logger lPreinit;
T_Logger logger;
T_Logger lShutdown;
bool uiInitialised;
// ---------------------------------------------------------------------
T_LWInternals_( T_String const& executablePath ,
T_LWComponents& components ) noexcept;
// ---------------------------------------------------------------------
[[noreturn]] void fatalError( T_Logger& logger , char const* text );
/* Load a logging configuration file */
OP_LoggingConfiguration loadLoggingConfigFile(
T_Logger& logger ,
char const* path ) noexcept;
// ---------------------------------------------------------------------
/* Pre-initialisation - console, logging, VFS, mods manager */
void preInit( T_String const& commandLine );
OP_LoggingConfiguration getInitialLoggingConfig( );
void parseCommandLine(
T_String const& commandLine );
OP_LoggingConfiguration loadPreinitLoggingConfig( );
OP_LoggingConfiguration getDefaultPreinitLoggingConfig( );
T_ModsManagerConfiguration getModsManagerConfiguration( );
// ---------------------------------------------------------------------
void init( );
void run( );
// ---------------------------------------------------------------------
void shutdown( );
OP_LoggingConfiguration loadShutdownLoggingConfig( );
OP_LoggingConfiguration getDefaultShutdownLoggingConfig( );
};
} // namespace
/*----------------------------------------------------------------------------*/
inline T_LWInternals_::T_LWInternals_(
T_String const& executablePath ,
T_LWComponents& components ) noexcept
: components( components ) ,
executablePath_( executablePath ) ,
lPreinit( "/core/preinit" ) ,
logger( "/core/init" ) ,
lShutdown( "/core/shutdown" )
{ }
/*----------------------------------------------------------------------------*/
void T_LWInternals_::fatalError( T_Logger& logger , char const* message )
{
logger.critical( ) << message;
// XXX UI-specific error display if possible
throw X_FatalError( );
}
OP_LoggingConfiguration T_LWInternals_::loadLoggingConfigFile(
T_Logger& logger ,
char const* const path ) noexcept
{
T_StringBuilder sb;
sb << path << ".srd";
const T_String srd( std::move( sb ) );
sb << path << ".srb";
const T_String srb( std::move( sb ) );
auto& vfs( LW::vfs( ) );
const bool useSRD( vfs.typeOf( srd ) == E_VFSEntryType::FILE );
if ( !useSRD && vfs.typeOf( srb ) != E_VFSEntryType::FILE ) {
return {};
}
T_String const& pstr( useSRD ? srd : srb );
auto cfg( LW::logWriters( ).getParserConfiguration( ) );
T_SRDParser parser( cfg );
OP_SRDReader reader( ([&]() -> OP_SRDReader {
if ( useSRD ) {
return NewOwned< T_SRDTextReader >( parser );
} else {
return NewOwned< T_SRDBinaryReader >( parser );
}
})( ) );
try {
OP_InputStream input( vfs.read( pstr ) );
if ( !input ) {
throw X_StreamError( E_StreamError::UNAVAILABLE );
}
reader->read( pstr , *input );
return NewOwned< T_LoggingConfiguration >(
std::move( *parser.getData< SP_LoggingConfiguration >( ) ) );
} catch ( X_StreamError const& e ) {
logger.warning( ) << "could not load '"
<< pstr << "': " << e.what( );
} catch ( X_SRDErrors const& errors ) {
errors.log( lPreinit );
}
return {};
}
/*= PRE-INITIALISATION =======================================================*/
void T_LWInternals_::preInit(
T_String const& commandLine )
{
// Initialise the console, if we can
components.console = NewOwned< T_Console >( );
components.console->mode( E_ConsoleMode::OPTIONAL );
// Register basic log writers
components.logWriters = NewOwned< T_LogWriterRegistry >( );
LW::logWriters( ).add< T_CWDFileLogWriterFactory >( ).automatic( false );
LW::logWriters( ).add< T_ConsoleLogWriterFactory >( LW::console( ) ).automatic( false );
// Initialise the logging system with a temporary configuration
loggingSystem = NewOwned< T_LoggingSystem >(
LW::logWriters( ) ,
getInitialLoggingConfig( ) );
// Parse command line arguments
if ( commandLine ) {
parseCommandLine( commandLine );
}
// Initialise the VFS
try {
if ( arguments_.homePath ) {
components.vfs = NewOwned< T_VFS >( arguments_.homePath );
} else {
components.vfs = NewOwned< T_VFS >( );
}
} catch ( X_VFSInitialisationFailure const& ) {
fatalError( lPreinit , "Could not initialise the VFS." );
}
// Add the VFS-based log writer, then try reconfiguring
LW::logWriters( ).add< T_TextFileLogWriterFactory >( LW::vfs( ) ).automatic( false );
loggingSystem->reconfigure( loadPreinitLoggingConfig( ) );
// Finish configuring the VFS
lPreinit.debug( ) << "Adding install path '" << executablePath_
<< "' to the VFS";
{
auto ipvfsd( LW::vfs( ).addDriver< T_VFSFilesystemDriver >(
executablePath_ ) );
if ( !ipvfsd ) {
fatalError( lPreinit , "Could not initialise the VFS." );
} else {
ipvfsd.automatic( false );
}
}
const uint32_t nExtraPaths( arguments_.extraPaths.size( ) );
for ( uint32_t i = 0 ; i < nExtraPaths ; i ++ ) {
auto const& path( arguments_.extraPaths[ i ] );
lPreinit.trace( ) << "Adding extra path '" << path << "' to the VFS";
auto vfsd( LW::vfs( ).addDriver< T_VFSFilesystemDriver >( path ) );
if ( vfsd ) {
vfsd.automatic( false );
} else {
lPreinit.warning( ) << "unable to add directory '"
<< path << "' to the VFS";
}
}
lPreinit.debug( ) << "Adding library data to the VFS";
LW::vfs( ).addDriver( LW::getLibData( ) ).automatic( false );
// Create the global preprocessor configuration
lPreinit.trace( ) << "Creating SRD preprocessor configuration";
components.ppConfig = NewOwned< T_SRDPreprocessorConfig >( );
components.ppConfig->addBuiltinCommands( );
components.ppConfig->addVFSCommands( *components.vfs );
// Initialise the mods manager
lPreinit.trace( ) << "Initialising mods manager";
components.mods = NewOwned< T_ModsManager >(
getModsManagerConfiguration( ) );
auto& mods( LW::mods( ) );
if ( !mods.scanForMods( ) ) {
// TODO later - switch to installer
fatalError( lPreinit , "No mods found" );
}
if ( !mods.resolveDependencies( ) ) {
// TODO later - switch to installer
fatalError( lPreinit ,
"Could not find a valid set of mods "
"matching the current configuration" );
}
// Pre-initialise mods
mods.preinitCommon( );
components.ui = arguments_.ui
? mods.preinitUIMods( arguments_.ui )
: mods.preinitUIMods( );
if ( !components.ui ) {
fatalError( lPreinit , "Unable to start the user interface." );
}
uiInitialised = false;
// Create the main loop object
components.gameLoop = NewOwned< T_GameLoop >( );
lPreinit.trace( ) << "UI & game loop created";
// Load main logging configuration
auto lc( loadLoggingConfigFile( lPreinit , "/logging" ) );
if ( lc ) {
loggingSystem->reconfigure( std::move( lc ) );
}
}
/*----------------------------------------------------------------------------*/
OP_LoggingConfiguration T_LWInternals_::getInitialLoggingConfig( )
{
auto logCfg( NewOwned< T_LoggingConfiguration >( ) );
logCfg->setMinLevelFor( T_LogPath( ) , E_LogLevel::NOTICE );
{
const T_String console( T_String::Pooled( "console" ) );
logCfg->addLogWriter( T_LogPath( ) , console );
auto writerConfig( LW::logWriters( )
.get( console )
->createConfiguration( console ) );
logCfg->putLogWriter( OwnRawPointer( writerConfig ) );
}
{
const T_String tfw( "preinit-file" );
T_CWDFileLogWriterCfg* writerConfig(
dynamic_cast< T_CWDFileLogWriterCfg *>(
LW::logWriters( )
.get( tfw )
->createConfiguration( tfw ) ) );
writerConfig->setPath( T_String::Pooled( "legacyworlds-preinit.log" ) );
writerConfig->setAppend( false );
logCfg->putLogWriter( OwnRawPointer( writerConfig ) );
logCfg->addLogWriter( T_LogPath( ) , tfw );
}
return logCfg;
}
/*----------------------------------------------------------------------------*/
void T_LWInternals_::parseCommandLine(
T_String const& commandLine )
{
const T_SRDParserConfig clpConfig( ([]( T_CommandLine_* cl ) {
using namespace lw::SRD;
T_SRDParserDefs defs( "default" );
defs << OnStart( [cl]( T_SRDParserData const& d ) -> bool {
*( d.currentData ) = cl;
return true;
} );
defs.context( "default" )
<< ( Rule( ) << "ui" << Text( )
<< []( T_SRDParserData const& d ) -> bool {
auto cl( d.currentData->value< T_CommandLine_* >( ) );
if ( cl->ui ) {
d.errors.add( "duplicate UI identifier" , (*d.input)[ 0 ] );
} else {
cl->ui = (*d.input)[ 1 ].stringValue( );
}
return true;
} )
<< ( Rule( ) << "home" << Text( )
<< []( T_SRDParserData const& d ) -> bool {
auto cl( d.currentData->value< T_CommandLine_* >( ) );
if ( cl->homePath ) {
d.errors.add( "duplicate home directory" , (*d.input)[ 0 ] );
} else {
cl->homePath = (*d.input)[ 1 ].stringValue( );
}
return true;
} )
<< ( Rule( ) << "extra-paths" << ( AtLeast( 1 ) << Text( ) )
<< []( T_SRDParserData const& d ) -> bool {
auto cl( d.currentData->value< T_CommandLine_* >( ) );
auto const& input( *d.input );
const auto nPaths( input.size( ) );
for ( uint32_t i = 1 ; i < nPaths ; i ++ ) {
auto const& tok( input[ i ] );
if ( cl->extraPaths.contains( tok.stringValue( ) ) ) {
d.errors.add( "duplicate data path" , tok );
} else {
cl->extraPaths.add( tok.stringValue( ) );
}
}
return true;
} );
return T_SRDParserConfig( defs );
})( &arguments_ ) );
T_SRDParser parser( clpConfig );
T_SRDTextReader reader( parser );
T_MemoryInputStream input( commandLine.data( ) , commandLine.size( ) );
try {
reader.read( T_String( "command line" ) , input );
} catch ( X_SRDErrors const& errors ) {
errors.log( lPreinit );
fatalError( lPreinit , "Invalid command line arguments" );
}
}
/*----------------------------------------------------------------------------*/
OP_LoggingConfiguration T_LWInternals_::loadPreinitLoggingConfig( )
{
auto rv( loadLoggingConfigFile( lPreinit , "/logging-pre" ) );
if ( !rv ) {
return getDefaultPreinitLoggingConfig( );
}
return rv;
}
OP_LoggingConfiguration T_LWInternals_::getDefaultPreinitLoggingConfig( )
{
auto logCfg( NewOwned< T_LoggingConfiguration >( ) );
{
const T_String console( T_String::Pooled( "console" ) );
auto writerConfig( LW::logWriters( )
.get( console )
->createConfiguration( console ) );
writerConfig->setMinLevel( E_LogLevel::NOTICE );
logCfg->putLogWriter( OwnRawPointer( writerConfig ) );
logCfg->addLogWriter( T_LogPath( ) , console );
}
{
const T_String tfw( "text-file" );
T_TextFileLogWriterCfg* writerConfig(
dynamic_cast< T_TextFileLogWriterCfg *>(
LW::logWriters( )
.get( tfw )
->createConfiguration( tfw ) ) );
writerConfig->setMinLevel( E_LogLevel::INFO );
writerConfig->setPath( "/preinit.log" );
writerConfig->setAppend( false );
logCfg->putLogWriter( OwnRawPointer( writerConfig ) );
logCfg->addLogWriter( T_LogPath( ) , tfw );
}
return logCfg;
}
/*----------------------------------------------------------------------------*/
T_ModsManagerConfiguration T_LWInternals_::getModsManagerConfiguration( )
{
using T_MMC = T_ModsManagerConfiguration;
using SP_MMC = SP_ModsManagerConfiguration;
const T_String mmcfgf( "/mods.srd" );
auto& vfs( LW::vfs( ) );
if ( vfs.typeOf( mmcfgf ) == E_VFSEntryType::FILE ) {
auto pconf( T_MMC::GetParserConfig( ) );
T_SRDParser parser( pconf );
T_SRDTextReader reader( parser );
try {
OP_InputStream input( vfs.read( mmcfgf ) );
if ( !input ) {
throw X_StreamError( E_StreamError::UNAVAILABLE );
}
reader.read( mmcfgf , *input );
return std::move( *parser.getData< SP_MMC >( ) );
} catch ( X_StreamError const& e ) {
lPreinit.warning( ) << "could not load '" << mmcfgf << "': "
<< e.what( );
} catch ( X_SRDErrors const& errors ) {
errors.log( lPreinit );
}
}
lPreinit.debug( ) << "Using default mods configuration";
return T_MMC::DefaultConfiguration( );
}
/*= INITIALISATION ===========================================================*/
void T_LWInternals_::init( )
{
// Initialise all the mods
const uint32_t nLoaded( components.mods->modsCount( ) );
uint32_t initialised = 0;
components.mods->initialise(
[&]( RPC_ModInfo mi ) -> F_UpdateInitProgress {
const T_ProgressInfoPart main{
([&](){
T_StringBuilder sb( "Initializing mod " );
sb << mi->identifier.name;
return T_String{ std::move( sb ) };
})( ) , initialised + 1 , nLoaded + 2
};
initialised ++;
components.ui->setInitProgress(
T_ProgressInfo{ main } );
return [&]( T_ProgressInfoPart part ) {
components.ui->setInitProgress(
T_ProgressInfo{ main , part } );
};
} );
// Initialise UI
const T_ProgressInfoPart uiInitMain{
T_String( "Initializing user interface" ) ,
initialised + 1 , nLoaded + 2
};
components.ui->setInitProgress( T_ProgressInfo{ uiInitMain } );
if ( ! components.ui->init( [&]( T_ProgressInfoPart part ) {
components.ui->setInitProgress(
T_ProgressInfo{ uiInitMain , part } );
}) ) {
fatalError( logger , "Failed to initialize user interface" );
}
uiInitialised = true;
// Start game loop
components.gameLoop->start( );
}
/*FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME*/
void T_LWInternals_::run( )
{
components.ui->run( );
}
/*= SHUTDOWN =================================================================*/
void T_LWInternals_::shutdown( )
{
lShutdown.trace( ) << "Starting shutdown";
// Shutdown game loop if it's still active
if ( components.gameLoop ) {
components.gameLoop->shutdown( );
}
// If the UI is present and initialised, shut it down
if ( components.ui && uiInitialised ) {
components.ui->shutdown( );
uiInitialised = false;
}
// Shut down mods
if ( components.mods ) {
components.mods->shutdown( );
}
// Reset logging configuration
lShutdown.trace( ) << "Resetting logger configuration";
loggingSystem->reconfigure( loadShutdownLoggingConfig( ) );
// Terminate the UI
if ( components.ui ) {
components.ui->postshutdown( );
components.ui.clear( );
components.gameLoop.clear( );
lShutdown.trace( ) << "UI and game loop terminated";
}
// Clear mods
if ( components.mods ) {
components.mods->unload( );
components.mods.clear( );
lShutdown.trace( ) << "Mods unloaded";
}
// Clear remaining components
lShutdown.trace( ) << "Terminating core components";
components.ppConfig.clear( );
loggingSystem.clear( );
components.logWriters.clear( );
components.console.clear( );
components.vfs.clear( );
}
/*----------------------------------------------------------------------------*/
OP_LoggingConfiguration T_LWInternals_::loadShutdownLoggingConfig( )
{
auto rv( loadLoggingConfigFile( lShutdown , "/logging-post" ) );
if ( !rv ) {
return getDefaultShutdownLoggingConfig( );
}
return rv;
}
OP_LoggingConfiguration T_LWInternals_::getDefaultShutdownLoggingConfig( )
{
auto logCfg( NewOwned< T_LoggingConfiguration >( ) );
{
const T_String console( T_String::Pooled( "console" ) );
auto writerConfig( LW::logWriters( )
.get( console )
->createConfiguration( console ) );
writerConfig->setMinLevel( E_LogLevel::NOTICE );
logCfg->putLogWriter( OwnRawPointer( writerConfig ) );
logCfg->addLogWriter( T_LogPath( ) , console );
}
{
const T_String tfw( "text-file" );
T_TextFileLogWriterCfg* writerConfig(
dynamic_cast< T_TextFileLogWriterCfg *>(
LW::logWriters( )
.get( tfw )
->createConfiguration( tfw ) ) );
writerConfig->setMinLevel( E_LogLevel::INFO );
writerConfig->setPath( "/shutdown.log" );
writerConfig->setAppend( false );
logCfg->putLogWriter( OwnRawPointer( writerConfig ) );
logCfg->addLogWriter( T_LogPath( ) , tfw );
}
return logCfg;
}
/*= MAIN CLASS ===============================================================*/
LW* LW::instance_ = nullptr;
LW::LW( T_String const& commandLine , T_String const& executablePath )
{
assert( instance_ == nullptr );
instance_ = this;
T_LWInternals_ pi( executablePath , components );
try {
pi.preInit( commandLine );
pi.init( );
pi.run( );
} catch ( X_FatalError const& ) {
pi.shutdown( );
components.~T_LWComponents( );
instance_ = nullptr;
throw;
}
pi.shutdown( );
instance_ = nullptr;
}
OP_VFSDriver LW::getLibData( ) noexcept
{
return NewOwned< T_VFSRomDriver >(
lw_library_rom , sizeof( lw_library_rom ) );
}

1041
src/Log.cc Normal file

File diff suppressed because it is too large Load diff

60
src/MemoryStreams.cc Normal file
View file

@ -0,0 +1,60 @@
/******************************************************************************/
/* MEMORY STREAMS *************************************************************/
/******************************************************************************/
#include <lw/lib/MemoryStreams.hh>
using namespace lw;
/*= T_MemoryInputStream ======================================================*/
T_MemoryInputStream::T_MemoryInputStream( void const* buffer , size_t size )
: A_InputStream( 0 , size ) ,
buffer_( reinterpret_cast< uint8_t const* >( buffer ) )
{ }
size_t T_MemoryInputStream::read( void* data , size_t size )
{
if ( position_ == size_ ) {
throw X_StreamError( E_StreamError::END );
}
const size_t readCount( position_ + size <= size_ ? size
: ( size_ - position_ ) );
memcpy( data , buffer_ + position_ , readCount );
position_ += readCount;
return readCount;
}
/*= T_MemoryOutputStream =====================================================*/
T_MemoryOutputStream::T_MemoryOutputStream( void* buffer , size_t size , F_Resizer resizer )
: A_OutputStream( 0 , size ) ,
buffer_( reinterpret_cast< uint8_t* >( buffer ) ) ,
resizer_( resizer )
{ }
size_t T_MemoryOutputStream::write( void const* data , size_t size )
{
if ( position_ == size_ && !resizer_ ) {
throw X_StreamError( E_StreamError::END );
}
size_t ok( size_ - position_ );
if ( resizer_ && ok < size ) {
size_ = size_ + size - ok;
buffer_ = resizer_( buffer_ , size_ );
ok = size;
}
const size_t count( ok > size ? size : ok );
memcpy( buffer_ + position_ , data , count );
position_ += count;
return count;
}

86
src/Messages.cc Normal file
View file

@ -0,0 +1,86 @@
/******************************************************************************/
/* UI<=>GAME MESSAGES *********************************************************/
/******************************************************************************/
#include <lw/lib/Messages.hh>
using namespace lw;
#define M_ENUM_OUT_( Type , Value ) \
case Type::Value: obj << #Value; break
/*= A_GameView ===============================================================*/
A_GameView::~A_GameView( )
{ }
/*= A_ViewBuilder ============================================================*/
A_ViewBuilder::~A_ViewBuilder( )
{ }
/*= E_GameState ==============================================================*/
namespace lw {
M_LSHIFT_OP( T_StringBuilder , E_GameState )
{
switch ( value ) {
M_ENUM_OUT_( E_GameState , NO_GAME );
M_ENUM_OUT_( E_GameState , GAME_PAUSED );
M_ENUM_OUT_( E_GameState , GAME_SLOW );
M_ENUM_OUT_( E_GameState , GAME_NORMAL );
M_ENUM_OUT_( E_GameState , GAME_FAST );
}
return obj;
}
} // namespace lw
/*= E_GameUIMessage ==========================================================*/
namespace lw {
M_LSHIFT_OP( T_StringBuilder , E_GameUIMessage )
{
switch ( value ) {
M_ENUM_OUT_( E_GameUIMessage , NEW );
M_ENUM_OUT_( E_GameUIMessage , LOAD );
M_ENUM_OUT_( E_GameUIMessage , SAVE );
M_ENUM_OUT_( E_GameUIMessage , STOP );
M_ENUM_OUT_( E_GameUIMessage , QUIT );
M_ENUM_OUT_( E_GameUIMessage , ABORT );
M_ENUM_OUT_( E_GameUIMessage , DELETE );
M_ENUM_OUT_( E_GameUIMessage , COPY_OR_RENAME );
M_ENUM_OUT_( E_GameUIMessage , SET_SPEED );
M_ENUM_OUT_( E_GameUIMessage , STEPS );
M_ENUM_OUT_( E_GameUIMessage , SET_VIEW );
M_ENUM_OUT_( E_GameUIMessage , VIEW_DISPLAYED );
M_ENUM_OUT_( E_GameUIMessage , QUERY );
M_ENUM_OUT_( E_GameUIMessage , COMMAND );
}
return obj;
}
} // namespace lw
/*= E_GameLoopMessage ========================================================*/
namespace lw {
M_LSHIFT_OP( T_StringBuilder , E_GameLoopMessage )
{
switch ( value ) {
M_ENUM_OUT_( E_GameLoopMessage , TERMINATED );
M_ENUM_OUT_( E_GameLoopMessage , PROGRESS );
M_ENUM_OUT_( E_GameLoopMessage , DONE );
M_ENUM_OUT_( E_GameLoopMessage , STATE_CHANGED );
M_ENUM_OUT_( E_GameLoopMessage , VIEW_AVAILABLE );
M_ENUM_OUT_( E_GameLoopMessage , QUERY_RESPONSE );
M_ENUM_OUT_( E_GameLoopMessage , COMMAND_OK );
M_ENUM_OUT_( E_GameLoopMessage , COMMAND_SYNTAX );
M_ENUM_OUT_( E_GameLoopMessage , COMMAND_ERROR );
}
return obj;
}
} // namespace lw

20
src/ModInterface.cc Normal file
View file

@ -0,0 +1,20 @@
/******************************************************************************/
/* MODDING SYSTEM INTERFACES **************************************************/
/******************************************************************************/
#include <lw/lib/ModInterface.hh>
using namespace lw;
/*= A_ModBase ===============================================================*/
A_ModBase::~A_ModBase( ) noexcept
{ }
/*= A_NativeMod ==============================================================*/
OP_UserInterface A_NativeMod::getUserInterface( ) const noexcept
{
return {};
}

2646
src/Mods.cc Normal file

File diff suppressed because it is too large Load diff

103
src/Pointers.cc Normal file
View file

@ -0,0 +1,103 @@
/******************************************************************************/
/* POINTERS *******************************************************************/
/******************************************************************************/
#include <lw/lib/Alloc.hh>
#include <lw/lib/Config.hh>
#include <lw/lib/Pointers.hh>
using namespace lw;
/*= T_WeakChain_ =============================================================*/
T_WeakChain_::T_WeakChain_( T_Reference_*& ref )
: ref( &ref ) , prev( nullptr ) , next( nullptr )
{ }
void T_WeakChain_::unchain( )
{
if ( prev != nullptr ) {
prev->next = next;
prev = nullptr;
} else if ( (*ref) != nullptr ) {
assert( this == (*ref)->weaks_ );
(*ref)->weaks_ = next;
}
if ( next != nullptr ) {
next->prev = prev;
next = nullptr;
}
}
void T_WeakChain_::init( )
{
if ( (*ref) == nullptr ) {
return;
}
next = (*ref)->weaks_;
if ( next != nullptr ) {
next->prev = this;
}
(*ref)->weaks_ = this;
}
/*= T_Reference_ =============================================================*/
namespace {
static T_ThreadedPoolAllocator<
sizeof( T_Reference_ ) , alignof( T_Reference_ ) ,
LW_CFG_SHAREDPTR_REFPOOL_SIZE ,
LW_CFG_SHAREDPTR_REFPOOL_MAXFREE
> ReferenceAllocator_;
}
void* T_Reference_::operator new(
const size_t count ) noexcept
{
return ReferenceAllocator_.allocate( count );
}
void T_Reference_::operator delete(
void* const object ) noexcept
{
return ReferenceAllocator_.free( object );
}
/*----------------------------------------------------------------------------*/
T_Reference_::T_Reference_(
void* const ptr ,
F_Destr_ destructor )
: pointer_( ptr ) , destr_( std::move( destructor ) ) ,
count_( 1 ) , weaks_( nullptr )
{
assert( pointer_ != nullptr );
}
T_Reference_::~T_Reference_( )
{
assert( count_ == 0 );
T_WeakChain_* wr = weaks_;
while ( wr != nullptr ) {
T_WeakChain_* const next = wr->next;
wr->unchain( );
*( wr->ref ) = nullptr;
wr = next;
}
destr_( pointer_ );
}
void* T_Reference_::extract( )
{
if ( count_ > 1 ) {
throw X_TooManyReferences( );
}
void* p( pointer_ );
count_ = 0;
pointer_ = nullptr;
delete this;
return p;
}

567
src/SRDBinary.cc Normal file
View file

@ -0,0 +1,567 @@
/******************************************************************************/
/* 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 );
}
}

657
src/SRDData.cc Normal file
View file

@ -0,0 +1,657 @@
/******************************************************************************/
/* SRD PARSER AND PREPROCESSOR - DATA REPRESENTATION **************************/
/******************************************************************************/
#include <lw/lib/SRDData.hh>
#include <lw/lib/Alloc.hh>
#include <lw/lib/Log.hh>
using namespace lw;
/*= T_SRDLocation ============================================================*/
static thread_local T_ThreadedPoolAllocator<
sizeof( T_SRDLocation ) , alignof( T_SRDLocation ) ,
128 , 2
> LocationPool_;
void* T_SRDLocation::operator new(
const size_t count ) noexcept
{
return LocationPool_.allocate( count );
}
void T_SRDLocation::operator delete(
void* const object ) noexcept
{
LocationPool_.free( object );
}
/*----------------------------------------------------------------------------*/
T_SRDLocation::T_SRDLocation(
T_String const& source ,
const uint32_t line ,
const size_t character ) noexcept
: source_( source ) , line_( line ) , character_( character )
{
assert( line > 0 );
}
T_SRDLocation::T_SRDLocation(
T_String const& source ,
const size_t byte ) noexcept
: source_( source ) , line_( 0 ) , character_( byte )
{ }
T_SRDLocation::T_SRDLocation(
T_SRDLocation const& other ) noexcept
: source_( other.source_ ) , line_( other.line_ ) ,
character_( other.character_ ) ,
chaining_( other.chaining_ )
{ }
T_SRDLocation::T_SRDLocation(
T_SRDLocation&& other ) noexcept
: T_SRDLocation( )
{
swap( *this , other );
}
namespace lw {
M_DECLARE_SWAP( T_SRDLocation )
{
using std::swap;
swap( lhs.source_ , rhs.source_ );
swap( lhs.line_ , rhs.line_ );
swap( lhs.character_ , rhs.character_ );
swap( lhs.chaining_ , rhs.chaining_ );
}
}
T_SRDLocation& T_SRDLocation::operator= (
T_SRDLocation const& other ) noexcept
{
source_ = other.source_;
line_ = other.line_;
character_ = other.character_;
chaining_ = other.chaining_;
return *this;
}
/*----------------------------------------------------------------------------*/
T_StringBuilder& operator<< (
T_StringBuilder& sb ,
T_SRDLocation const& location ) noexcept
{
if ( location.unknown( ) ) {
sb << "unknown location";
} else {
sb << location.source( ) << ' ';
if ( location.binary( ) ) {
sb << 'b' << location.character( );
} else {
sb << 'l' << location.line( )
<< ' ' << 'c' << location.character( );
}
}
return sb;
}
/*= T_SRDErrors ============================================================*/
void T_SRDErrors::checkAdded(
T_SRDError const& last )
{
if ( last.isDetails( ) || errCount_ >= MAX_ERRORS ) {
return;
}
errCount_ ++;
if ( errCount_ == T_SRDErrors::MAX_ERRORS ) {
errors_.addNew( T_String::Pooled( "too many errors" ) ,
last.location( ) );
throw X_SRDErrors( *this );
}
}
/*----------------------------------------------------------------------------*/
void T_SRDErrors::addAll(
T_SRDErrors const& source )
{
if ( errCount_ >= MAX_ERRORS ) {
return;
}
const uint32_t nErrors( source.size( ) );
RPC_SRDLocation lastLocation = nullptr;
for ( uint32_t i = 0 ; i < nErrors ; i ++ ) {
errors_.add( source[ i ] );
if ( !source[ i ].isDetails( ) ) {
lastLocation = &source[ i ].location( );
}
}
errCount_ += source.errCount_;
if ( errCount_ >= MAX_ERRORS ) {
errors_.addNew( T_String::Pooled( "too many errors" ) ,
lastLocation ? *lastLocation : T_SRDLocation( ) );
throw X_SRDErrors( *this );
}
}
/*= X_SRDErrors ============================================================*/
char const* X_SRDErrors::what( ) const noexcept
{
return "SRD read/parse errors";
}
#ifndef LW_MINLIB
void X_SRDErrors::log( T_Logger& logger ) const
{
const auto nErrors( errors.size( ) );
for ( size_t i = 0 ; i < nErrors ; i ++ ) {
auto sb( logger.error( ) );
auto const& e( errors[ i ] );
auto const& l( e.location( ) );
if ( l.unknown( ) ) {
sb << "unknown location";
} else {
sb << l.source( );
if ( l.binary( ) ) {
sb << ", b" << l.byte( );
} else {
sb << ", l" << l.line( ) << ", c" << l.character( );
}
}
sb << ": " << e.error( );
}
}
#endif // LW_MINLIB
/*= T_SRDToken =============================================================*/
T_StringBuilder& lw::operator<< ( T_StringBuilder& sb , E_SRDTokenType tt )
{
static char const* const C_TOKEN_TYPES_[] = {
"(...)" ,
"(" , ")" ,
"WORD" ,
"$WORD" ,
"STRING" ,
"BINARY" ,
"INT" ,
"LONG" ,
"FLOAT" ,
"COMMENT"
};
return sb << C_TOKEN_TYPES_[ int( tt ) ];
}
/*----------------------------------------------------------------------------*/
bool T_SRDToken::IsWord( T_String const& text )
{
enum {
INIT_ ,
AFTER_DASH_ ,
IN_WORD_
} state = INIT_;
T_StringIterator it( text );
while ( !it.atEnd( ) ) {
T_Character c( it );
it.next( );
if ( state == INIT_ ) {
if ( c == '-' ) {
state = AFTER_DASH_;
} else if ( c.isAlpha( ) ) {
state = IN_WORD_;
} else {
return false;
}
} else if ( state == AFTER_DASH_ ) {
if ( c.isAlpha( ) ) {
state = IN_WORD_;
} else {
return false;
}
} else if ( !c.isAlphanumeric( ) ) {
if ( c == '-' ) {
state = AFTER_DASH_;
} else {
return false;
}
}
}
return state == IN_WORD_;
}
/*----------------------------------------------------------------------------*/
T_SRDToken T_SRDToken::ListStart( )
{
return T_SRDToken( E_SRDTokenType::START );
}
T_SRDToken T_SRDToken::ListEnd( )
{
return T_SRDToken( E_SRDTokenType::END );
}
/*----------------------------------------------------------------------------*/
T_SRDToken T_SRDToken::List( )
{
T_SRDToken token( E_SRDTokenType::LIST );
token.list_ = NewOwned< T_SRDList >( 16 );
return token;
}
T_SRDToken T_SRDToken::List( T_SRDList const& list )
{
T_SRDToken token( E_SRDTokenType::LIST );
token.list_ = NewOwned< T_SRDList >( list );
return token;
}
T_SRDToken T_SRDToken::List( T_SRDList&& list )
{
T_SRDToken token( E_SRDTokenType::LIST );
token.list_ = NewOwned< T_SRDList >( std::move( list ) );
return token;
}
/*----------------------------------------------------------------------------*/
T_SRDToken T_SRDToken::AutoText( T_String text )
{
const bool w = IsWord( text );
T_SRDToken token( w ? E_SRDTokenType::WORD : E_SRDTokenType::STRING );
token.stringValue_ = std::move( text );
return token;
}
T_SRDToken T_SRDToken::Word( T_String word )
{
assert( IsWord( word ) );
T_SRDToken token( E_SRDTokenType::WORD );
token.stringValue_ = std::move( word );
return token;
}
T_SRDToken T_SRDToken::String( T_String string )
{
T_SRDToken token( E_SRDTokenType::STRING );
token.stringValue_ = std::move( string );
return token;
}
T_SRDToken T_SRDToken::Variable( T_String variable )
{
assert( IsWord( variable ) );
T_SRDToken token( E_SRDTokenType::VAR );
token.stringValue_ = std::move( variable );
return token;
}
T_SRDToken T_SRDToken::Comment( T_String text )
{
T_SRDToken token( E_SRDTokenType::COMMENT );
token.stringValue_ = std::move( text );
return token;
}
/*----------------------------------------------------------------------------*/
T_SRDToken T_SRDToken::Binary(
T_SharedPtr< T_Buffer< uint8_t > > const& data ) noexcept
{
T_SRDToken token( E_SRDTokenType::BINARY );
assert( bool( data ) );
token.binary_ = data;
return token;
}
/*----------------------------------------------------------------------------*/
T_SRDToken T_SRDToken::Flush( ) noexcept
{
return T_SRDToken( E_SRDTokenType::FLUSH );
}
/*----------------------------------------------------------------------------*/
T_SRDToken T_SRDToken::AutoInteger( int64_t value )
{
const bool isInt = ( value >= INT32_MIN && value <= INT32_MAX );
const E_SRDTokenType type = ([ = ]( ) {
if ( isInt ) {
return E_SRDTokenType::INT;
} else {
return E_SRDTokenType::LONG;
}
} )( );
T_SRDToken token( type );
token.longValue_ = value;
token.floatValue_ = value;
T_StringBuilder sb;
sb << value;
token.stringValue_ = std::move( sb );
return token;
}
T_SRDToken T_SRDToken::Integer( int32_t value )
{
T_SRDToken token( E_SRDTokenType::INT );
token.longValue_ = value;
token.floatValue_ = value;
T_StringBuilder sb;
sb << value;
token.stringValue_ = std::move( sb );
return token;
}
T_SRDToken T_SRDToken::Long( int64_t value )
{
T_SRDToken token( E_SRDTokenType::LONG );
token.longValue_ = value;
token.floatValue_ = value;
T_StringBuilder sb;
sb << value;
token.stringValue_ = std::move( sb );
return token;
}
T_SRDToken T_SRDToken::Float( double value )
{
T_SRDToken token( E_SRDTokenType::FLOAT );
token.longValue_ = value;
token.floatValue_ = value;
T_StringBuilder sb;
sb << value;
token.stringValue_ = std::move( sb );
return token;
}
/*----------------------------------------------------------------------------*/
T_SRDToken::T_SRDToken( T_SRDToken const& other )
: type_( other.type_ ) , text_( other.text_ ) ,
longValue_( other.longValue_ ) ,
floatValue_( other.floatValue_ ) ,
stringValue_( other.stringValue_ ) ,
binary_( other.binary_ ) ,
location_( other.location_ )
{
if ( other.list_ ) {
list_ = NewOwned< T_SRDList >( *other.list_ );
}
}
T_SRDToken& T_SRDToken::operator= ( T_SRDToken const& other )
{
type_ = other.type_;
text_ = other.text_;
if ( other.list_ ) {
list_ = NewOwned< T_SRDList >( *other.list_ );
} else {
list_.clear( );
}
longValue_ = other.longValue_;
floatValue_ = other.floatValue_;
stringValue_ = other.stringValue_;
location_ = other.location_;
binary_ = other.binary_;
return *this;
}
/*----------------------------------------------------------------------------*/
void lw::swap( T_SRDToken& lhs , T_SRDToken& rhs ) noexcept
{
using std::swap;
swap( lhs.type_ , rhs.type_ );
swap( lhs.text_ , rhs.text_ );
swap( lhs.list_ , rhs.list_ );
swap( lhs.longValue_ , rhs.longValue_ );
swap( lhs.floatValue_ , rhs.floatValue_ );
swap( lhs.stringValue_ , rhs.stringValue_ );
swap( lhs.binary_ , rhs.binary_ );
swap( lhs.location_ , rhs.location_ );
}
/*----------------------------------------------------------------------------*/
int T_SRDToken::compare( T_SRDToken const& other ) const
{
// Numeric values can be compared even if they don't have the exact same type
if ( isInteger( ) && other.isInteger( ) ) {
return T_Comparator< int64_t >::compare( longValue_ , other.longValue_ );
}
if ( isNumeric( ) && other.isNumeric( ) ) {
return T_Comparator< double >::compare( floatValue_ , other.floatValue_ );
}
// We need to have the same type to keep going
if ( type_ != other.type_ ) {
return T_Comparator< int >::compare( int( type_ ) , int( other.type_ ) );
}
// List start/end -> nothing else to do
if ( type_ == E_SRDTokenType::START || type_ == E_SRDTokenType::END ) {
return 0;
}
// List -> compare all tokens
if ( type_ == E_SRDTokenType::LIST ) {
auto const& l0( *list_ );
auto const& l1( *( other.list_ ) );
const auto lim( std::min( l0.size( ) , l1.size( ) ) );
for ( size_t i = 0 ; i < lim ; i ++ ) {
const auto tcmp( l0[ i ].compare( l1[ i ] ) );
if ( tcmp != 0 ) {
return tcmp;
}
}
return T_Comparator< size_t >::compare( l0.size( ) , l1.size( ) );
}
// Everything else: text compare
return T_Comparator< T_String >::compare( stringValue_ , other.stringValue_ );
}
/*----------------------------------------------------------------------------*/
bool T_SRDToken::hasFullText( ) const
{
return text_
|| type_ == E_SRDTokenType::WORD
|| type_ == E_SRDTokenType::START
|| type_ == E_SRDTokenType::END;
}
T_String T_SRDToken::fullText( ) const
{
switch ( type_ ) {
case E_SRDTokenType::WORD:
return stringValue_;
case E_SRDTokenType::START:
return T_String::Pooled( "(" );
case E_SRDTokenType::END:
return T_String::Pooled( ")" );
default:
return text_;
}
}
T_SRDToken& T_SRDToken::setFullText( T_String text )
{
if ( type_ != E_SRDTokenType::WORD && type_ != E_SRDTokenType::START
&& type_ != E_SRDTokenType::END ) {
text_ = std::move( text );
}
return *this;
}
T_SRDToken& T_SRDToken::generateFullText( )
{
if ( hasFullText( ) ) {
return *this;
}
T_StringBuilder sb;
switch ( type_ ) {
case E_SRDTokenType::LIST:
sb << '(';
if ( list_->size( ) != 0 ) {
sb << ' ';
}
for ( size_t i = 0 ; i < list_->size( ) ; i ++ ) {
sb << (*list_)[ i ].generateFullText( )
.fullText( )
<< ' ';
}
sb << ')';
break;
case E_SRDTokenType::VAR:
sb << '$' << stringValue_;
break;
case E_SRDTokenType::STRING:
{
T_StringIterator it( stringValue_ );
sb << '"';
while ( !it.atEnd( ) ) {
T_Character c( it );
if ( c.isControl( ) ) {
if ( c == '\n' ) {
sb << "\\n";
} else if ( c == '\t' ) {
sb << "\\t";
} else {
sb << "\\x";
if ( c < 16 ) {
sb << '0';
}
sb << c.codepoint;
}
} else if ( c == '\\' ) {
sb << "\\\\";
} else if ( c == '"' ) {
sb << "\\\"";
} else {
sb << c;
}
it.next( );
}
sb << '"';
break;
}
case E_SRDTokenType::COMMENT:
{
T_StringIterator it( stringValue_ );
bool atStart = true;
while ( !it.atEnd( ) ) {
T_Character c( it );
if ( atStart ) {
sb << '#';
atStart = false;
}
sb << c;
if ( c == '\n' ) {
atStart = true;
}
it.next( );
}
break;
}
case E_SRDTokenType::INT:
case E_SRDTokenType::LONG:
sb << longValue_;
break;
case E_SRDTokenType::FLOAT:
sb << floatValue_;
break;
case E_SRDTokenType::BINARY:
{
sb << '[';
const uint32_t sz( binary_->size( ) );
for ( uint32_t i = 0 ; i < sz ; i ++ ) {
sb << ' ';
sb.appendNumeric( uint64_t( binary_->operator[]( i ) ) , 16 );
}
sb << " ]";
break;
}
default:
assert( 0 && "This shouldn't be happening" );
}
text_ = std::move( sb );
return *this;
}
/*----------------------------------------------------------------------------*/
T_SRDToken& T_SRDToken::location( T_String const& source , size_t byte )
{
location_ = NewShared< T_SRDLocation >( source , byte );
return *this;
}
T_SRDToken& T_SRDToken::location( T_String const& source , uint32_t line , size_t character )
{
location_ = NewShared< T_SRDLocation >( source , line , character );
return *this;
}
T_SRDToken& T_SRDToken::location( T_SRDLocation const& other )
{
location_ = NewShared< T_SRDLocation >( other );
return *this;
}
T_SRDToken& T_SRDToken::location( T_SRDLocation && other )
{
location_ = NewShared< T_SRDLocation >( std::move( other ) );
return *this;
}
T_SRDToken& T_SRDToken::copyLocationOf( T_SRDToken const& other )
{
location_ = other.location_;
return *this;
}

297
src/SRDDefinitions.cc Normal file
View file

@ -0,0 +1,297 @@
/******************************************************************************/
/* SRD - PARSER DEFINITIONS ***************************************************/
/******************************************************************************/
#include <lw/lib/SRDDefinitions.hh>
using namespace lw;
/*= T_SRDEnum ================================================================*/
T_SRDEnum& T_SRDEnum::operator<< ( T_String&& word )
{
if ( (*this)[ word ] != T_HashIndex::INVALID_INDEX ) {
throw std::invalid_argument( "duplicate enum item" );
}
const uint32_t hash( ComputeHash( word ) );
index_.add( hash );
words_.add( std::move( word ) );
return *this;
}
uint32_t T_SRDEnum::operator[] ( T_String const& word ) const
{
const uint32_t hash( ComputeHash( word ) );
uint32_t idx = index_.first( hash );
while ( idx != T_HashIndex::INVALID_INDEX ) {
if ( words_[ idx ] == word ) {
break;
}
idx = index_.next( idx );
}
return idx;
}
uint32_t T_SRDEnum::operator[] ( char const* word ) const
{
const auto len = strlen( word );
const uint32_t hash( HashData( (uint8_t const*)word , len ) );
uint32_t idx = index_.first( hash );
while ( idx != T_HashIndex::INVALID_INDEX ) {
auto const& w( words_[ idx ] );
if ( w.size( ) == len && !memcmp( w.data( ) , word , len ) ) {
break;
}
idx = index_.next( idx );
}
return idx;
}
/*= T_SRDInputItem ===========================================================*/
void lw::swap( T_SRDInputItem& lhs , T_SRDInputItem& rhs ) noexcept
{
using std::swap;
swap( lhs.type_ , rhs.type_ );
swap( lhs.word_ , rhs.word_ );
swap( lhs.token_ , rhs.token_ );
swap( lhs.items_ , rhs.items_ );
swap( lhs.min_ , rhs.min_ );
swap( lhs.max_ , rhs.max_ );
}
T_SRDInputItem& T_SRDInputItem::operator<< ( T_SRDInputItem item )
{
if ( type_ != E_SRDInputItem::ALTERNATIVE
&& type_ != E_SRDInputItem::REPETITION ) {
throw std::invalid_argument(
"not an ALTERNATIVE or REPETITION" );
}
if ( item.type( ) == type_ ) {
items_.addAll( item.items( ) );
} else {
items_.add( std::move( item ) );
}
return *this;
}
/*----------------------------------------------------------------------------*/
T_StringBuilder& lw::operator<< ( T_StringBuilder& sb , T_SRDInputItem const& item )
{
switch ( item.type( ) ) {
case E_SRDInputItem::WORD:
sb << item.word( );
break;
case E_SRDInputItem::ENUM:
sb << '[' << item.word( ) << "::*]";
break;
case E_SRDInputItem::TOKEN:
sb << item.token( );
break;
case E_SRDInputItem::ALTERNATIVE:
{
auto const& alts( item.items( ) );
sb << "[ ";
for ( uint32_t i = 0 ; i < alts.size( ) ; i ++ ) {
if ( i != 0 ) {
sb << " | ";
}
sb << alts[ i ];
}
sb << " ]";
break;
}
case E_SRDInputItem::REPETITION:
{
auto const& seq( item.items( ) );
const bool listBits( seq.size( ) != 1 && ( item.min( ) != 1 || item.max( ) != 1 ) );
if ( listBits ) {
sb << "[ ";
}
for ( uint32_t i = 0 ; i < seq.size( ) ; i ++ ) {
if ( i != 0 ) {
sb << ' ';
}
sb << seq[ i ];
}
if ( listBits ) {
if ( seq.size( ) ) {
sb << ' ';
}
sb << ']';
}
if ( item.min( ) == 0 && item.max( ) == 1 ) {
sb << '?';
} else if ( item.max( ) == UINT32_MAX ) {
if ( item.min( ) == 0 ) {
sb << '*';
} else {
if ( item.min( ) != 1 ) {
sb << '{' << item.min( );
}
sb << '+';
if ( item.min( ) != 1 ) {
sb << '}';
}
}
} else if ( item.min( ) == item.max( ) && item.min( ) != 1 ) {
sb << '{' << item.min( ) << '}';
} else if ( item.min( ) != item.max( ) ) {
sb << '{' << item.min( ) << ','
<< item.max( ) << '}';
}
break;
}
}
return sb;
}
/*= T_SRDInputRule ===========================================================*/
T_SRDInputRule& T_SRDInputRule::operator<< ( T_SRDInputItem item )
{
items_.add( std::move( item ) );
return *this;
}
T_StringBuilder& lw::operator<< ( T_StringBuilder& sb , T_SRDInputRule const& item )
{
auto const& seq( item.rule( ) );
sb << '(';
for ( uint32_t i = 0 ; i < seq.size( ) ; i ++ ) {
sb << ' ' << seq[ i ];
}
if ( item.context( ) ) {
sb << " ..." << item.context( ) << "...";
}
sb << " )";
return sb;
}
/*= T_SRDContext =============================================================*/
T_SRDContext::T_SRDContext( T_String name , T_String parent )
: name_( std::move( name ) ) , parent_( std::move( parent ) ) ,
rules_( 16 )
{ }
T_SRDContext& T_SRDContext::operator<< ( T_SRDInputRule rule )
{
rules_.add( std::move( rule ) );
return *this;
}
T_StringBuilder& T_SRDContext::dump( T_StringBuilder& sb , T_String const& separator )
{
const auto nr( rules_.size( ) );
for ( uint32_t i = 0 ; i < nr ; i ++ ) {
if ( i > 0 ) {
sb << separator;
}
sb << rules_[ i ];
}
return sb;
}
/*= T_SRDParserDefs ==========================================================*/
T_SRDParserDefs::T_SRDParserDefs( T_String defaultContext )
: dfCtx_( std::move( defaultContext ) ) ,
ctx_( []( T_SRDContext const& c ) -> T_String const& {
return c.name( );
} , 64 , 64 , 64 ) ,
enums_( []( T_SRDEnum const& e ) -> T_String const& {
return e.name( );
} , 64 , 64 , 64 )
{
ctx_.add( T_SRDContext( dfCtx_ ) );
}
/*----------------------------------------------------------------------------*/
void T_SRDParserDefs::defaultContext( T_String const& name )
{
if ( name == dfCtx_ ) {
return;
}
if ( !ctx_.contains( name ) ) {
throw std::invalid_argument( "default context must exist" );
}
dfCtx_ = name;
}
uint32_t T_SRDParserDefs::contextId( T_String const& name ) const
{
return ctx_.indexOf( name );
}
T_SRDParserDefs& T_SRDParserDefs::operator<< ( SetHandler sh )
{
if ( sh.start ) {
onStart_ = sh.handler;
} else {
onFinish_ = sh.handler;
}
return *this;
}
T_SRDContext& T_SRDParserDefs::context( T_String const& name )
{
if ( !ctx_.contains( name ) ) {
ctx_.add( T_SRDContext( name ) );
}
return *( ctx_.get( name ) );
}
T_SRDContext& T_SRDParserDefs::context( T_String const& name , T_String const& parent )
{
if ( !ctx_.contains( name ) ) {
ctx_.add( T_SRDContext( name , parent ) );
}
T_SRDContext& c( *( ctx_.get( name ) ) );
if ( c.parent( ) != parent ) {
throw std::invalid_argument( "incorrect parent name" );
}
return c;
}
/*----------------------------------------------------------------------------*/
bool T_SRDParserDefs::hasEnum( T_String const& name ) const
{
return enums_.contains( name );
}
T_SRDEnum& T_SRDParserDefs::enumeration( T_String const& name )
{
if ( !enums_.contains( name ) ) {
enums_.add( T_SRDEnum( name ) );
}
return *( enums_.get( name ) );
}
T_SRDEnum const& T_SRDParserDefs::enumeration( T_String const& name ) const
{
auto eptr( enums_.get( name ) );
if ( eptr == nullptr ) {
throw std::invalid_argument( "unknown enum" );
}
return *eptr;
}

231
src/SRDIO.cc Normal file
View file

@ -0,0 +1,231 @@
/******************************************************************************/
/* SRD PARSER AND PREPROCESSOR - COMMON INPUT / OUTPUT CODE *******************/
/******************************************************************************/
#include <lw/lib/SRDIO.hh>
using namespace lw;
/*= X_SRDWriterError =========================================================*/
char const* X_SRDWriterError::what( ) const noexcept
{
return msg_;
}
/*= A_SRDWriter ==============================================================*/
A_SRDWriter& A_SRDWriter::putList( T_SRDList const& list )
{
const auto sz = list.size( );
for ( uint32_t i = 0 ; i < sz ; i ++ ) {
putToken( list[ i ] );
}
return *this;
}
A_SRDWriter& A_SRDWriter::putToken( T_SRDToken const& token )
{
switch ( token.type( ) ) {
case E_SRDTokenType::START:
return startList( );
case E_SRDTokenType::LIST:
startList( );
putList( token.list( ) );
case E_SRDTokenType::END:
return endList( );
case E_SRDTokenType::WORD:
return putWord( token.stringValue( ) );
case E_SRDTokenType::VAR:
return putVariable( token.stringValue( ) );
case E_SRDTokenType::STRING:
return putString( token.stringValue( ) );
case E_SRDTokenType::BINARY:
return putBinary( token.binary( ) );
case E_SRDTokenType::INT:
return putInt32( int32_t( token.longValue( ) ) );
case E_SRDTokenType::LONG:
return putInt64( token.longValue( ) );
case E_SRDTokenType::FLOAT:
return putFloat( token.floatValue( ) );
case E_SRDTokenType::COMMENT:
return putComment( token.stringValue( ) );
case E_SRDTokenType::FLUSH:
return *this;
}
throw std::range_error( "unknown token type" );
}
/*= T_SRDListFixer ===========================================================*/
T_SRDListFixer::T_SRDListFixer(
A_SRDReaderTarget& output ) noexcept
: output_( output )
{ }
void T_SRDListFixer::start(
T_SRDErrors& errors )
{
listStarts_.clear( );
output_.start( errors );
}
void T_SRDListFixer::push( T_SRDErrors& errors ,
T_SRDToken&& token )
{
switch ( token.type( ) ) {
case E_SRDTokenType::START:
listStarts_.add( token.location( ) );
break;
case E_SRDTokenType::END:
if ( listStarts_.size( ) == 0 ) {
errors.add( "unexpected end of list" , token.location( ) );
return;
}
listStarts_.remove( listStarts_.size( ) - 1 );
break;
default: break;
}
output_.push( errors , std::move( token ) );
}
void T_SRDListFixer::end(
T_SRDErrors& errors )
{
const auto nls( listStarts_.size( ) );
const T_String eolLoc( T_String::Pooled( "*unterminated list*" ) );
for ( auto i = 0u ; i < nls ; i ++ ) {
errors.add( "unterminated list" , listStarts_[ i ] );
auto tok( T_SRDToken::ListEnd( ) );
tok.location( eolLoc , nls - i - 1 );
output_.push( errors , std::move( tok ) );
}
output_.end( errors );
}
/*= T_SRDMemoryTarget ========================================================*/
T_SRDMemoryTarget::T_SRDMemoryTarget( bool structured )
: structured_( structured ) ,
clearFlushToken_( false ) ,
list_( T_SRDToken::List( T_SRDList( ) ) ) ,
stack_( 128 )
{ }
void T_SRDMemoryTarget::start( T_SRDErrors& )
{
list_.list( ) = T_SRDList( );
current_ = &list_;
}
void T_SRDMemoryTarget::push( T_SRDErrors& errors , T_SRDToken&& token )
{
assert( current_ != nullptr );
const auto tt( token.type( ) );
if ( tt == E_SRDTokenType::START ) {
if ( structured_ ) {
auto& c( current_->list( ) );
c.add( T_SRDToken::List( T_SRDList( ) ) );
auto& t( c[ c.size( ) - 1 ] );
t.copyLocationOf( token );
stack_.add( current_ );
current_ = &t;
} else {
auto& l( current_->list( ) );
l.add( std::move( token ) );
stack_.add( &( l[ l.size( ) - 1 ] ) );
}
} else if ( tt == E_SRDTokenType::END ) {
if ( stack_.size( ) == 0 ) {
errors.add( "unexpected ')'" , token );
} else {
const auto last = stack_.size( ) - 1;
if ( structured_ ) {
current_ = stack_[ last ];
} else {
current_->list( ).add( std::move( token ) );
}
stack_.remove( last );
}
} else if ( tt != E_SRDTokenType::FLUSH || !clearFlushToken_ ) {
current_->list( ).add( std::move( token ) );
}
}
void T_SRDMemoryTarget::end( T_SRDErrors& errors )
{
const auto stackSize( stack_.size( ) );
if ( stackSize != 0 ) {
try {
const uint32_t start( structured_ ? 1 : 0 );
for ( uint32_t i = start ; i < stackSize ; i ++ ) {
errors.add( "unterminated list" ,
*stack_[ i ] );
}
if ( structured_ ) {
errors.add( "unterminated list" , *current_ );
}
} catch ( ... ) {
stack_.free( );
throw;
}
}
stack_.free( );
}
/*= T_SRDWriterTarget ========================================================*/
T_SRDWriterTarget::T_SRDWriterTarget( A_SRDWriter& writer )
: writer_( writer )
{ }
void T_SRDWriterTarget::start( T_SRDErrors& errors )
{
try {
writer_.start( );
} catch ( X_SRDWriterError const& e ) {
errors.add( e.what( ) , T_SRDLocation( ) );
}
}
void T_SRDWriterTarget::push( T_SRDErrors& errors , T_SRDToken&& token )
{
try {
writer_.putToken( token );
} catch ( X_SRDWriterError const& e ) {
errors.add( e.what( ) , token );
}
}
void T_SRDWriterTarget::end( T_SRDErrors& errors )
{
try {
writer_.end( );
} catch ( X_SRDWriterError const& e ) {
errors.add( e.what( ) , T_SRDLocation( ) );
}
}

2518
src/SRDPPCommands.cc Normal file

File diff suppressed because it is too large Load diff

686
src/SRDParser.cc Normal file
View file

@ -0,0 +1,686 @@
/******************************************************************************/
/* SRD PARSER AND PREPROCESSOR - PARSER ***************************************/
/******************************************************************************/
#include <lw/lib/SRDParser.hh>
using namespace lw;
/*= T_SRDParserData ==========================================================*/
T_SRDParserData::T_SRDParserData(
T_SRDParserConfig const& config ,
T_SRDErrors& errors )
: config( config ) , errors( errors )
{ }
/*= T_SRDParserPrivate_ ======================================================*/
namespace {
struct T_SRDParserPrivate_
{
// T_State_ - Internal state information
struct T_State_
{
// Current context
uint32_t context;
// Are we parsing a rule ?
bool inRule;
// Rule start location
T_SRDLocation ruleStart;
// Current state
uint32_t state;
// Current depth (in-rule)
uint32_t depth;
// Last rule list output
SP_SRDList list;
// List/context ambiguity resolution - initial state ID
uint32_t lcarState = T_SRDParserConfig::INVALID_END;
// List/context ambiguity resolution - tokens to replay
SP_SRDList lcarReplay;
};
// E_ESAction_ - Stack actions for the execution
enum class E_ESAction_
{
NONE ,
PUSH ,
POP
};
// T_Exec_ - Execution list entry
struct T_Exec_
{
// Handler to call
F_SRDHandler handler;
// Name of the current context
T_String currentContext;
// Data that matched the rule
SP_SRDList input;
// Name of the target context
T_String targetContext;
// Execution data stack action
E_ESAction_ stackAction;
T_Exec_( F_SRDHandler const& handler , T_String const& currentContext );
T_Exec_( F_SRDHandler const& handler , T_String const& currentContext , SP_SRDList input );
T_Exec_( F_SRDHandler const& handler , T_String const& currentContext , SP_SRDList input ,
T_String const& targetContext , E_ESAction_ stackAction );
};
// E_Recovery_ - Error recovery mode
enum class E_Recovery_
{
NONE ,
SOL ,
EOL ,
LIST
};
// Parser configuration data
T_SRDParserConfig const& cfg_;
// Current state
T_State_ current_;
// Memory target for accumulation of input
T_SRDMemoryTarget accum_;
// State stack, changed when entering / exiting contexts
T_Array< T_State_ > stack_;
// Current error recovery mode
E_Recovery_ recovery_;
// Flush mode
E_SRDFlushMode flushMode_;
// Accept flush tokens?
bool handleFlushTokens_;
// Execution list
T_Array< T_Exec_ > exec_;
// Execution stack
T_Array< T_Variant > execStack_;
// Current execution data, if any
OP_SRDParserData execData_;
// ---------------------------------------------------------------------
explicit T_SRDParserPrivate_( T_SRDParserConfig const& cfg );
// Add a token's description to a string builder
static void addDescription( T_StringBuilder & sb ,
T_SRDToken const& token );
// Flush the list/context ambiguity resolution data to the accumulator
void flushLCARReplay( T_SRDErrors& errors );
// End the current input list
void endList( T_SRDErrors & errors );
// Add a match error caused by a specific token
void addMatchError( T_SRDErrors & errors ,
T_SRDToken const& token ) const;
// Enter a context
void enterContext( T_SRDErrors& errors , uint32_t endId ,
uint32_t ctxId ,
T_SRDLocation const& startLocation );
// Handle error recovery if necessary
bool handleRecovery( T_SRDToken const& token );
// Check for rules starting
void checkRuleStart( T_SRDErrors & errors , T_SRDToken const& token );
// Check for rules ending
bool checkRuleEnd( T_SRDErrors & errors , T_SRDToken const& token ,
uint32_t eid );
// Get the data at the top of the execution stack
T_Variant const& getExecStackTop( ) const;
// Public methods
void start( T_SRDErrors & errors );
void push( T_SRDErrors & errors , T_SRDToken && token );
void end( T_SRDErrors & errors );
void flush( );
};
} // namespace
/*----------------------------------------------------------------------------*/
T_SRDParserPrivate_::T_Exec_::T_Exec_(
F_SRDHandler const& handler ,
T_String const& currentContext )
: handler( handler ) , currentContext( currentContext ) ,
stackAction( E_ESAction_::NONE )
{ }
T_SRDParserPrivate_::T_Exec_::T_Exec_(
F_SRDHandler const& handler ,
T_String const& currentContext ,
SP_SRDList input )
: handler( handler ) , currentContext( currentContext ) ,
input( input ) , stackAction( E_ESAction_::NONE )
{ }
T_SRDParserPrivate_::T_Exec_::T_Exec_(
F_SRDHandler const& handler ,
T_String const& currentContext ,
SP_SRDList input ,
T_String const& targetContext ,
E_ESAction_ stackAction )
: handler( handler ) , currentContext( currentContext ) ,
input( input ) , targetContext( targetContext ) ,
stackAction( stackAction )
{ }
/*----------------------------------------------------------------------------*/
inline T_SRDParserPrivate_::T_SRDParserPrivate_(
T_SRDParserConfig const& cfg )
: cfg_( cfg ) , stack_( 16 ) , flushMode_( E_SRDFlushMode::END ) ,
handleFlushTokens_( false ) , execStack_( 16 )
{ }
/*----------------------------------------------------------------------------*/
void T_SRDParserPrivate_::flushLCARReplay( T_SRDErrors& errors )
{
auto& list( *current_.lcarReplay );
const auto size( list.size( ) );
for ( uint32_t i = 0 ; i < size ; i ++ ) {
accum_.push( errors , std::move( list[ i ] ) );
}
}
void T_SRDParserPrivate_::endList( T_SRDErrors& errors )
{
if ( current_.lcarReplay ) {
flushLCARReplay( errors );
current_.lcarReplay = SP_SRDList( );
}
accum_.end( errors );
current_.list = NewShared< T_SRDList >( accum_.list( ) );
}
void T_SRDParserPrivate_::addDescription( T_StringBuilder& sb , T_SRDToken const& token )
{
auto tt( token.type( ) );
switch ( tt ) {
case E_SRDTokenType::WORD:
sb << '\'' << token.stringValue( ) << '\'';
break;
case E_SRDTokenType::START:
sb << "'('";
break;
case E_SRDTokenType::END:
sb << "')'";
break;
default:
sb << token.type( ) << " token";
break;
}
}
void T_SRDParserPrivate_::addMatchError( T_SRDErrors& errors , T_SRDToken const& token ) const
{
const auto nTrans( cfg_.transitions.sizeOf( current_.state ) );
const auto eid( cfg_.endStates[ current_.state ] );
T_StringBuilder sb;
if ( nTrans == 0 ) {
assert( eid != T_SRDParserConfig::INVALID_END );
const auto nctx( cfg_.ruleContexts[ eid ] );
if ( nctx != T_SRDParserConfig::INVALID_END ) {
sb << "'(' or ";
}
sb << "')'";
} else {
if ( eid != T_SRDParserConfig::INVALID_END ) {
const auto nctx( cfg_.ruleContexts[ eid ] );
if ( nctx != T_SRDParserConfig::INVALID_END ) {
sb << "'(', ";
}
sb << "')'";
if ( nTrans > 1 ) {
sb << ", ";
} else {
sb << " or ";
}
}
for ( uint32_t i = 0 ; i < nTrans ; i ++ ) {
auto const& t( cfg_.transitions.get( current_.state , i ) );
if ( t.type == E_SRDTransitionType::WORD ) {
sb << '\'' << cfg_.words[ t.index ] << '\'';
} else if ( t.type == E_SRDTransitionType::ENUM ) {
sb << '\'' << cfg_.enums[ t.index ].name( )
<< "' enum value";
} else {
sb << t.tokenType << " token";
}
if ( i + 2 < nTrans ) {
sb << ", ";
} else if ( i + 1 < nTrans ) {
sb << " or ";
}
}
}
sb << " expected, ";
addDescription( sb , token );
sb << " found instead.";
errors.add( sb , token );
}
void T_SRDParserPrivate_::enterContext( T_SRDErrors& errors , uint32_t endId , uint32_t ctxId ,
T_SRDLocation const& startLocation )
{
endList( errors );
auto const& cctx( cfg_.contexts[ current_.context ] );
auto const& rule( cctx.rules( )[ endId ] );
auto const& nctx( cfg_.contexts[ ctxId ] );
if ( rule.onEnter( ) ) {
const auto action( rule.onExit( )
? E_ESAction_::PUSH
: E_ESAction_::NONE );
exec_.addNew( rule.onEnter( ) , cctx.name( ) ,
current_.list , nctx.name( ) ,
action );
}
stack_.add( std::move( current_ ) );
current_.context = ctxId;
current_.inRule = true;
current_.state = cfg_.startStates[ ctxId ];
current_.depth = 0;
current_.ruleStart = startLocation;
accum_.start( errors );
}
/*----------------------------------------------------------------------------*/
bool T_SRDParserPrivate_::handleRecovery( T_SRDToken const& token )
{
const auto tt( token.type( ) );
switch ( recovery_ ) {
case E_Recovery_::NONE:
return true;
case E_Recovery_::SOL:
// Wait for '(', process it
if ( tt == E_SRDTokenType::START ) {
recovery_ = E_Recovery_::NONE;
return true;
}
break;
case E_Recovery_::EOL:
// Wait for the required ')'. Don't process it.
if ( tt == E_SRDTokenType::START ) {
current_.depth ++;
} else if ( tt == E_SRDTokenType::END ) {
if ( current_.depth == 0 ) {
current_.inRule = false;
recovery_ = E_Recovery_::NONE;
} else {
current_.depth --;
}
}
break;
case E_Recovery_::LIST:
// Process both '(' and ')'
if ( tt == E_SRDTokenType::START || tt == E_SRDTokenType::END ) {
recovery_ = E_Recovery_::NONE;
return true;
}
break;
default:
throw std::logic_error( "invalid error recovery state" );
}
return false;
}
void T_SRDParserPrivate_::checkRuleStart( T_SRDErrors& errors , T_SRDToken const& token )
{
const auto tt( token.type( ) );
const auto ss( stack_.size( ) );
if ( tt == E_SRDTokenType::START ) {
// Entering a rule
current_.inRule = true;
current_.state = cfg_.startStates[ current_.context ];
current_.depth = 0;
current_.ruleStart = token.location( );
accum_.start( errors );
} else if ( ss != 0 && tt == E_SRDTokenType::END ) {
// Exiting a context
const auto oldCtx( current_.context );
current_ = std::move( stack_[ ss - 1 ] );
stack_.remove( ss - 1 );
const auto eid( cfg_.endStates[ current_.state ] );
auto const& cctx( cfg_.contexts[ current_.context ] );
auto const& rule( cctx.rules( )[ eid ] );
auto const& octx( cfg_.contexts[ oldCtx ] );
if ( rule.onExit( ) ) {
const auto action( rule.onEnter( )
? E_ESAction_::POP
: E_ESAction_::NONE );
exec_.addNew( rule.onExit( ) , octx.name( ) ,
current_.list , cctx.name( ) ,
action );
}
if ( rule.handler( ) ) {
exec_.addNew( rule.handler( ) , cctx.name( ) , current_.list );
}
if ( flushMode_ == E_SRDFlushMode::SENTENCE
&& stack_.size( ) == 0 ) {
flush( );
}
current_.inRule = false;
} else {
T_StringBuilder sb( "'(' " );
if ( ss == 0 ) {
recovery_ = E_Recovery_::SOL;
} else {
sb << "or ')' ";
recovery_ = E_Recovery_::LIST;
}
sb << "expected, ";
addDescription( sb , token );
sb << " found instead.";
errors.add( sb , token );
}
}
bool T_SRDParserPrivate_::checkRuleEnd( T_SRDErrors& errors , T_SRDToken const& token , uint32_t eid )
{
const auto tt( token.type( ) );
if ( tt == E_SRDTokenType::END ) {
endList( errors );
auto const& cctx( cfg_.contexts[ current_.context ] );
auto const& rule( cctx.rules( )[ eid ] );
if ( rule.handler( ) ) {
exec_.addNew( rule.handler( ) , cctx.name( ) , current_.list );
}
current_.inRule = false;
return true;
}
if ( tt != E_SRDTokenType::START ) {
return false;
}
const auto cid( cfg_.ruleContexts.get( current_.context , eid ) );
if ( cid == T_SRDParserConfig::INVALID_END ) {
return false;
}
enterContext( errors , eid , cid , token.location( ) );
return true;
}
/*----------------------------------------------------------------------------*/
inline T_Variant const& T_SRDParserPrivate_::getExecStackTop( ) const
{
assert( execStack_.size( ) != 0 );
const auto stackLast( execStack_.size( ) - 1 );
return execStack_[ stackLast ];
}
/*----------------------------------------------------------------------------*/
inline void T_SRDParserPrivate_::start( T_SRDErrors& errors )
{
execData_ = NewOwned< T_SRDParserData >( cfg_ , errors );
execStack_.clear( );
execStack_.addNew( );
exec_.clear( );
if ( cfg_.onStart ) {
auto const& dc( cfg_.contexts[ 0 ] );
exec_.addNew( cfg_.onStart , dc.name( ) );
if ( flushMode_ == E_SRDFlushMode::SENTENCE ) {
flush( );
}
}
stack_.clear( );
current_.context = 0;
current_.inRule = false;
recovery_ = E_Recovery_::NONE;
}
inline void T_SRDParserPrivate_::push( T_SRDErrors& errors , T_SRDToken&& token )
{
const auto tt( token.type( ) );
if ( tt == E_SRDTokenType::FLUSH ) {
if ( handleFlushTokens_ ) {
flush( );
}
return;
}
if ( tt == E_SRDTokenType::COMMENT || !handleRecovery( token ) ) {
return;
}
if ( !current_.inRule ) {
checkRuleStart( errors , token );
return;
}
const auto prevState( current_.state );
const auto eid( cfg_.endStates[ prevState ] );
if ( cfg_.transition( current_.state , token ) ) {
// Handling of list/context ambiguities
if ( tt == E_SRDTokenType::START && eid != T_SRDParserConfig::INVALID_END ) {
assert( current_.depth == 0 );
const auto cid( cfg_.ruleContexts.get( current_.context , eid ) );
if ( cid != T_SRDParserConfig::INVALID_END ) {
current_.lcarState = prevState;
if ( current_.lcarReplay ) {
flushLCARReplay( errors );
current_.lcarReplay->clear( );
} else {
current_.lcarReplay = NewShared< T_SRDList >( 16 );
}
}
}
// Handle depth
if ( tt == E_SRDTokenType::START ) {
current_.depth ++;
} else if ( tt == E_SRDTokenType::END ) {
assert( current_.depth != 0 );
current_.depth --;
}
// Accumulate token
if ( current_.lcarState == T_SRDParserConfig::INVALID_END ) {
accum_.push( errors , std::move( token ) );
} else {
current_.lcarReplay->add( std::move( token ) );
}
return;
}
// Check for "normal" end of rule
if ( current_.depth == 0 && eid != T_SRDParserConfig::INVALID_END
&& checkRuleEnd( errors , token , eid ) ) {
return;
}
// If we had found a list/context ambiguity, enter the context then
// replay what we had.
if ( current_.lcarState != T_SRDParserConfig::INVALID_END ) {
auto lcarState( current_.lcarState );
auto lcarEnd( cfg_.endStates[ lcarState ] );
auto lcarContext( cfg_.ruleContexts.get( current_.context , lcarEnd ) );
SP_SRDList replay( std::move( current_.lcarReplay ) );
current_.lcarState = T_SRDParserConfig::INVALID_END;
current_.state = lcarState;
const auto nTokens( replay->size( ) );
enterContext( errors , lcarEnd , lcarContext , (*replay)[ 0 ].location( ) );
for ( uint32_t i = 1 ; i < nTokens ; i ++ ) {
push( errors , std::move( (*replay)[ i ] ) );
}
push( errors , std::move( token ) );
return;
}
addMatchError( errors , token );
if ( token.type( ) == E_SRDTokenType::END ) {
if ( current_.depth == 0 ) {
// Don't go into recovery mode
current_.inRule = false;
return;
}
current_.depth --;
}
recovery_ = E_Recovery_::EOL;
}
inline void T_SRDParserPrivate_::end( T_SRDErrors& errors )
{
const auto ss( stack_.size( ) );
for ( uint32_t i = 0 ; i < ss ; i ++ ) {
auto const& si( stack_[ i ] );
errors.add( "Unterminated list." , si.ruleStart );
}
if ( current_.inRule ) {
errors.add( "Unterminated list." , current_.ruleStart );
}
if ( cfg_.onFinish ) {
auto const& dc( cfg_.contexts[ 0 ] );
exec_.addNew( cfg_.onFinish , dc.name( ) );
}
}
/*----------------------------------------------------------------------------*/
void T_SRDParserPrivate_::flush( )
{
const auto nExec( exec_.size( ) );
if ( !nExec ) {
return;
}
auto& data( *execData_ );
if ( data.errors.size( ) != 0 ) {
exec_.clear( );
return;
}
bool enabled = true;
for ( uint32_t i = 0 ; i < nExec ; i ++ ) {
auto const& step( exec_[ i ] );
if ( step.stackAction == E_ESAction_::PUSH && enabled ) {
execStack_.addNew( );
}
const auto stackLast( execStack_.size( ) - 1 );
if ( enabled ) {
data.input = RPC_SRDList( step.input );
data.currentContext = step.currentContext;
data.targetContext = step.targetContext;
if ( step.stackAction == E_ESAction_::PUSH ) {
data.currentData = &execStack_[ stackLast - 1 ];
data.targetData = &execStack_[ stackLast ];
} else {
data.currentData = &execStack_[ stackLast ];
if ( step.stackAction == E_ESAction_::POP ) {
data.targetData = &execStack_[ stackLast - 1 ];
}
}
enabled = step.handler( data );
enabled = enabled && data.errors.size( ) == 0;
}
if ( step.stackAction == E_ESAction_::POP
&& ( enabled || stackLast > 0 ) ) {
execStack_.remove( stackLast );
}
}
exec_.clear( );
}
/*= T_SRDParser ==============================================================*/
T_SRDParser::T_SRDParser( T_SRDParserConfig const& cfg )
: A_PrivateImplementation( new T_SRDParserPrivate_( cfg ) )
{ }
/*----------------------------------------------------------------------------*/
void T_SRDParser::start( T_SRDErrors& errors )
{
p< T_SRDParserPrivate_ >( ).start( errors );
}
void T_SRDParser::push( T_SRDErrors& errors , T_SRDToken&& token )
{
p< T_SRDParserPrivate_ >( ).push( errors , std::move( token ) );
}
void T_SRDParser::end( T_SRDErrors& errors )
{
auto& pi( p< T_SRDParserPrivate_ >( ) );
pi.end( errors );
if ( pi.flushMode_ != E_SRDFlushMode::MANUAL ) {
pi.flush( );
}
}
void T_SRDParser::flush( )
{
p< T_SRDParserPrivate_ >( ).flush( );
}
/*----------------------------------------------------------------------------*/
void T_SRDParser::flushMode(
const E_SRDFlushMode mode ) noexcept
{
p< T_SRDParserPrivate_ >( ).flushMode_ = mode;
}
E_SRDFlushMode T_SRDParser::flushMode( ) const noexcept
{
return p< T_SRDParserPrivate_ >( ).flushMode_;
}
void T_SRDParser::handleFlushToken( bool handleIt ) noexcept
{
p< T_SRDParserPrivate_ >( ).handleFlushTokens_ = handleIt;
}
bool T_SRDParser::handleFlushToken( ) const noexcept
{
return p< T_SRDParserPrivate_ >( ).handleFlushTokens_;
}
/*----------------------------------------------------------------------------*/
T_Variant const& T_SRDParser::getExecStackTop( ) const
{
return p< T_SRDParserPrivate_ >( ).getExecStackTop( );
}

982
src/SRDParserConfig.cc Normal file
View file

@ -0,0 +1,982 @@
/******************************************************************************/
/* SRD - PARSER CONFIGURATION *************************************************/
/******************************************************************************/
#include <lw/lib/SRDParserConfig.hh>
using namespace lw;
namespace {
/*= TEMPORARY CONVERSION DATA ================================================*/
// E_NodeType_ - Node types for the intermediary RE representation
enum class E_NodeType_
{ CAT , ALT , STAR , SYM , EPS };
// E_SymNodeType_ - Symbol types for the intermediary RE representation
// This includes END symbols, which are not present in the actual
// configuration.
enum class E_SymNodeType_
{ WORD , ENUM , TOKEN , END };
// T_RuleNode_ - A node in the intermediary RE tree
struct T_RuleNode_
{
uint32_t index;
E_NodeType_ type;
uint32_t left;
uint32_t right;
uint32_t label = -1;
};
// T_Symbol_ - Symbol data for the intermediary representation
struct T_Symbol_
{
E_SymNodeType_ type;
uint32_t idx;
explicit T_Symbol_( T_RuleNode_ const& node )
: type( E_SymNodeType_( node.left ) ) ,
idx( node.right )
{ }
};
// T_RuleSource_ - Initial context and rule indices
struct T_RuleSource_
{
uint32_t context;
uint32_t rule;
T_RuleSource_( uint32_t context , uint32_t rule )
: context( context ) , rule( rule )
{ }
};
// T_ContextConverter_ - Definitions to configuration converter
struct T_ContextConverter_
{
typedef T_Array< T_SRDInputItem > T_InSeq_;
typedef T_Array< T_RuleNode_ > T_Nodes_;
T_SRDParserConfig& cfg;
T_SRDParserDefs const& defs;
// Data used to transform definition contexts into actual contexts
T_Array< uint32_t > usedContexts;
T_MultiArray< T_RuleSource_ > ruleSources;
// Current context & rule
uint32_t cid;
uint32_t curRule;
// RE representation of a context's rules
T_Nodes_ nodes;
T_Array< uint32_t > symNodes;
uint32_t maxLabel;
// Automaton construction internals
T_Array< bool > cancellable;
T_MultiArray< uint32_t > firstPos;
T_MultiArray< uint32_t > lastPos;
T_MultiArray< uint32_t > nextPos;
T_Array< uint32_t > nameAccum;
T_MultiArray< uint32_t > stateNames;
// "Alphabet" for a set of rules
T_Array< T_Symbol_ > symbols;
T_MultiArray< uint32_t > symLabels;
T_ContextConverter_( T_SRDParserConfig& cfg , T_SRDParserDefs const& defs );
// Go through all rules to check for unbalanced lists
void checkUnbalancedRules( ) const;
// Unbalanced list check - sequence
int checkUBSequence( T_String const& ctx , uint32_t cRule , T_InSeq_ const& sequence ) const;
// Unbalanced list check - alternatives
int checkUBAlternatives( T_String const& ctx , uint32_t cRule , T_InSeq_ const& alternatives ) const;
// Unbalanced list check - single item
int checkUBItem( T_String const& ctx , uint32_t cRule , T_SRDInputItem const& item ) const;
// Unbalanced list check - throw the error
void errorUnbalanced( T_String const& ctx , uint32_t cRule ) const;
// Find all contexts that are used by another; also check inheritance
void findUsedContexts( );
// Build the context for the parser's configuration, including parents
void buildContext( uint32_t context );
// Add a context to the list of contexts to copy
void addUsedContext( T_String const& current , uint32_t cRule , T_String const& next );
// Find all context IDs required to build a context
int findContextsFor( uint32_t sourceContext , uint32_t* output );
// Add a node, set its index and type, then return it
T_RuleNode_& addNode( E_NodeType_ type );
// Add a symbol node
T_RuleNode_& addSymbolNode( E_SymNodeType_ type , uint32_t idx );
// Add a word node. If the word is not in the words index, add it there.
T_RuleNode_& addWord( T_String const& word );
// Add an enumeration node.
T_RuleNode_& addEnum( T_String const& eName );
// Add a token node.
T_RuleNode_& addToken( E_SRDTokenType token );
// Add a branch that represents alternatives
T_RuleNode_& addAlternatives( T_InSeq_ const& alts );
// Add a branch that represents a sequence of items, using CAT nodes
T_RuleNode_& addSequence( T_InSeq_ const& sequence );
// Generate a branch that represents repetitions of a sequence.
T_RuleNode_& convertRepetitions( T_SRDInputItem const& item );
// Convert a rule item into a tree branch
T_RuleNode_& convertItem( T_SRDInputItem const& item );
// Convert all rules in the current context into a RE tree
void convertRules( );
// Generate the "cancellable" value for all nodes
void computeCancellable( );
// Implementation for compute{First|Last}Pos
void computePos( T_MultiArray< uint32_t >& output , bool first );
// Generate first positions for all nodes
void computeFirstPos( );
// Generate last positions for all nodes
void computeLastPos( );
// Generate next positions for all labels
void computeNextPos( );
// Gather all symbols and their associated labels
void gatherSymbols( );
// Set the rule index for a state
void setEndState( uint32_t state , uint32_t end );
// Add values from a label to the state name accumulator
void addNameParts( uint32_t label );
// Check symbol labels in a state name
void checkStateName( uint32_t state , uint32_t first , uint32_t end , uint32_t si );
// Try to find an existing state matching the accumulated name.
// Returns T_HashIndex::INVALID_INDEX if not found.
uint32_t findState( uint32_t firstState );
// Add a transition
void addTransition( T_Symbol_ const& sym , uint32_t state );
// Build the automaton for the current context
void buildAutomaton( );
};
} // namespace
/*= T_ContextConverter_ ======================================================*/
namespace {
T_ContextConverter_::T_ContextConverter_( T_SRDParserConfig& cfg , T_SRDParserDefs const& defs )
: cfg( cfg ) , defs( defs ) , usedContexts( defs.contexts( ) )
{ }
/*----------------------------------------------------------------------------*/
void T_ContextConverter_::checkUnbalancedRules( ) const
{
const auto nContexts( defs.contexts( ) );
for ( uint32_t i = 0 ; i < nContexts ; i ++ ) {
auto const& ctx( defs[ i ] );
auto const& cName( ctx.name( ) );
auto const& rules( ctx.rules( ) );
const auto nRules( rules.size( ) );
for ( uint32_t j = 0 ; j < nRules ; j ++ ) {
auto const& rule( rules[ j ] );
if ( checkUBSequence( cName , j + 1 , rule.rule( ) ) != 0 ) {
errorUnbalanced( cName , j + 1 );
}
}
}
}
int T_ContextConverter_::checkUBSequence( T_String const& ctx , uint32_t cRule ,
T_InSeq_ const& sequence ) const
{
const auto length( sequence.size( ) );
int sum( 0 );
for ( uint32_t i = 0 ; i < length ; i ++ ) {
sum += checkUBItem( ctx , cRule , sequence[ i ] );
}
return sum;
}
int T_ContextConverter_::checkUBAlternatives( T_String const& ctx , uint32_t cRule ,
T_InSeq_ const& alternatives ) const
{
const auto length( alternatives.size( ) );
int current( 0 );
for ( uint32_t i = 0 ; i < length ; i ++ ) {
const int value( checkUBItem( ctx , cRule , alternatives[ i ] ) );
if ( i == 0 ) {
current = value;
} else if ( value != current ) {
errorUnbalanced( ctx , cRule );
}
}
return current;
}
int T_ContextConverter_::checkUBItem( T_String const& ctx , uint32_t cRule ,
T_SRDInputItem const& item ) const
{
const auto iType( item.type( ) );
switch ( iType ) {
case E_SRDInputItem::TOKEN:
if ( item.token( ) == E_SRDTokenType::START ) {
return 1;
} else if ( item.token( ) == E_SRDTokenType::END ) {
return -1;
}
break;
case E_SRDInputItem::ALTERNATIVE:
return checkUBAlternatives( ctx , cRule , item.items( ) );
case E_SRDInputItem::REPETITION:
{
const int sv( checkUBSequence( ctx , cRule , item.items( ) ) );
if ( sv != 0 && item.min( ) != item.max( ) ) {
errorUnbalanced( ctx , cRule );
} else {
return sv * item.min( );
}
}
break;
case E_SRDInputItem::WORD:
case E_SRDInputItem::ENUM:
break;
}
return 0;
}
void T_ContextConverter_::errorUnbalanced( T_String const& ctx , uint32_t cRule ) const
{
T_StringBuilder sb;
sb << "In context `" << ctx << "`: rule #" << cRule << " is unbalanced.";
throw X_SRDParserConfig( sb );
}
/*----------------------------------------------------------------------------*/
void T_ContextConverter_::findUsedContexts( )
{
uint32_t pos = 0;
usedContexts.add( defs.contextId( defs.defaultContext( ) ) );
while ( pos < usedContexts.size( ) ) {
buildContext( usedContexts[ pos ] );
pos ++;
}
}
void T_ContextConverter_::buildContext( uint32_t context )
{
uint32_t list[ defs.contexts( ) ];
const auto nContexts( findContextsFor( context , list ) );
auto const& name( defs[ context ].name( ) );
cfg.contexts.addNew( name );
auto& output( cfg.contexts[ cfg.contexts.size( ) - 1 ] );
ruleSources.next( );
cfg.ruleContexts.next( );
for ( auto c = nContexts - 1 ; c >= 0 ; c -- ) {
auto const& sCtx( defs[ list[ c ] ] );
auto const& src( sCtx.rules( ) );
for ( uint32_t i = 0 ; i < src.size( ) ; i ++ ) {
ruleSources.addNew( list[ c ] , i + 1 );
auto const& r( src[ i ] );
output << r;
addUsedContext( sCtx.name( ) , i + 1 , r.context( ) );
}
}
}
void T_ContextConverter_::addUsedContext( T_String const& current , uint32_t cRule , T_String const& next )
{
if ( !next ) {
cfg.ruleContexts.add( T_SRDParserConfig::INVALID_END );
return;
}
const auto id( defs.contextId( next ) );
if ( id == T_HashIndex::INVALID_INDEX ) {
T_StringBuilder sb;
sb << "In context `" << current << "`: rule #" << cRule
<< " refers to missing context `" << next << "`.";
throw X_SRDParserConfig( sb );
}
for ( uint32_t i = 0 ; i < usedContexts.size( ) ; i ++ ) {
if ( usedContexts[ i ] == id ) {
cfg.ruleContexts.add( i );
return;
}
}
cfg.ruleContexts.add( usedContexts.size( ) );
usedContexts.add( id );
}
int T_ContextConverter_::findContextsFor( uint32_t sourceContext , uint32_t* output )
{
auto spos = 1;
output[ 0 ] = sourceContext;
do {
auto const& c( defs[ output[ spos - 1 ] ] );
if ( !c.parent( ) ) {
break;
}
const auto pid( defs.contextId( c.parent( ) ) );
if ( pid == T_HashIndex::INVALID_INDEX ) {
T_StringBuilder sb;
sb << "Context `" << c.name( ) << "`: unknown parent `"
<< c.parent( ) << "`";
throw X_SRDParserConfig( sb );
}
for ( auto i = 0 ; i < spos ; i ++ ) {
if ( output[ i ] == pid ) {
T_StringBuilder sb;
sb << "Context `" << c.parent( )
<< "`: recursive inheritance";
throw X_SRDParserConfig( sb );
}
}
output[ spos ++ ] = pid;
} while ( 1 );
return spos;
}
/*----------------------------------------------------------------------------*/
T_RuleNode_& T_ContextConverter_::addNode( E_NodeType_ type )
{
const uint32_t index( nodes.size( ) );
auto& node( nodes.addNew( ) );
node.index = index;
node.type = type;
return node;
}
T_RuleNode_& T_ContextConverter_::addSymbolNode( E_SymNodeType_ type , uint32_t idx )
{
auto& node( addNode( E_NodeType_::SYM ) );
node.left = uint32_t( type );
node.right = uint32_t( idx );
node.label = ++ maxLabel;
symNodes.add( node.index );
return node;
}
T_RuleNode_& T_ContextConverter_::addWord( T_String const& word )
{
const auto hash = ComputeHash( word );
uint32_t idx = cfg.wordsIndex.first( hash );
while ( idx != T_HashIndex::INVALID_INDEX ) {
if ( cfg.words[ idx ] == word ) {
break;
}
idx = cfg.wordsIndex.next( idx );
}
if ( idx == T_HashIndex::INVALID_INDEX ) {
idx = cfg.words.size( );
cfg.wordsIndex.add( hash );
cfg.words.add( word );
}
return addSymbolNode( E_SymNodeType_::WORD , idx );
}
T_RuleNode_& T_ContextConverter_::addEnum( T_String const& eName )
{
if ( !cfg.enums.contains( eName ) ) {
if ( !defs.hasEnum( eName ) ) {
T_RuleSource_ const& src( ruleSources.get( cid , curRule - 1 ) );
T_SRDContext const& sCtx( defs[ src.context ] );
T_StringBuilder sb;
sb << "In context `" << sCtx.name( ) << "`: rule #"
<< src.rule << " refers to unknown enumeration `" << eName << "`.";
throw X_SRDParserConfig( sb );
}
cfg.enums.add( defs.enumeration( eName ) );
}
return addSymbolNode( E_SymNodeType_::ENUM ,
cfg.enums.indexOf( eName ) );
}
inline T_RuleNode_& T_ContextConverter_::addToken( E_SRDTokenType token )
{
return addSymbolNode( E_SymNodeType_::TOKEN , uint32_t( token ) );
}
T_RuleNode_& T_ContextConverter_::addAlternatives( T_InSeq_ const& alts )
{
auto n( alts.size( ) );
if ( n == 0 ) {
T_RuleSource_ const& src( ruleSources.get( cid , curRule - 1 ) );
T_SRDContext const& sCtx( defs[ src.context ] );
T_StringBuilder sb;
sb << "In context `" << sCtx.name( ) << "`: rule #" << src.rule
<< " features an empty alternation.";
throw X_SRDParserConfig( sb );
}
uint32_t indices[ n ];
for ( uint32_t i = 0 ; i < n ; i ++ ) {
auto& node( convertItem( alts[ i ] ) );
indices[ i ] = node.index;
}
uint32_t nextLeft = indices[ 0 ];
for ( uint32_t i = 1 ; i < n ; i ++ ) {
auto& node( addNode( E_NodeType_::ALT ) );
node.left = nextLeft;
node.right = indices[ i ];
nextLeft = node.index;
}
return nodes[ nextLeft ];
}
T_RuleNode_& T_ContextConverter_::addSequence( T_InSeq_ const& sequence )
{
auto n( sequence.size( ) );
if ( n == 0 ) {
T_RuleSource_ const& src( ruleSources.get( cid , curRule - 1 ) );
T_SRDContext const& sCtx( defs[ src.context ] );
T_StringBuilder sb;
sb << "In context `" << sCtx.name( ) << "`: rule #" << src.rule
<< " features an empty sequence.";
throw X_SRDParserConfig( sb );
}
uint32_t indices[ n ];
for ( uint32_t i = 0 ; i < n ; i ++ ) {
auto& node( convertItem( sequence[ i ] ) );
indices[ i ] = node.index;
}
uint32_t nextLeft = indices[ 0 ];
for ( uint32_t i = 1 ; i < n ; i ++ ) {
auto& node( addNode( E_NodeType_::CAT ) );
node.left = nextLeft;
node.right = indices[ i ];
nextLeft = node.index;
}
return nodes[ nextLeft ];
}
T_RuleNode_& T_ContextConverter_::convertRepetitions( T_SRDInputItem const& item )
{
const auto min( item.min( ) );
const auto max( item.max( ) );
auto const& seq( item.items( ) );
// Minimum amount of repetitions
uint32_t nextLeft = UINT32_MAX;
for ( uint32_t i = 0 ; i < min ; i ++ ) {
auto const& node( addSequence( seq ) );
if ( i > 0 ) {
auto& cat( addNode( E_NodeType_::CAT ) );
cat.left = nextLeft;
cat.right = node.index;
nextLeft = cat.index;
} else {
nextLeft = node.index;
}
}
uint32_t nextRight = UINT32_MAX;
if ( max == UINT32_MAX ) {
// Unlimited repetitions
auto const& node( addSequence( seq ) );
auto& star( addNode( E_NodeType_::STAR ) );
star.left = node.index;
nextRight = star.index;
} else if ( max > min ) {
// First item: (alt $seq epsilon)
auto const& seq0( addSequence( seq ) );
auto const& epsilon0( addNode( E_NodeType_::EPS ) );
auto& alt0( addNode( E_NodeType_::ALT ) );
alt0.left = seq0.index;
alt0.right = epsilon0.index;
nextRight = alt0.index;
// Other items: (alt (cat $seq $previous) epsilon)
const uint32_t count( max - min );
for ( uint32_t i = 1 ; i < count ; i ++ ) {
auto const& nseq( addSequence( seq ) );
auto& cat( addNode( E_NodeType_::CAT ) );
cat.left = nseq.index;
cat.right = nextRight;
auto const& eps( addNode( E_NodeType_::EPS ) );
auto& alt( addNode( E_NodeType_::ALT ) );
alt.left = cat.index;
alt.right = eps.index;
nextRight = alt.index;
}
}
assert( nextRight != UINT32_MAX || nextLeft != UINT32_MAX );
// Combine fixed part and variable part if both exist
if ( nextRight != UINT32_MAX && nextLeft != UINT32_MAX ) {
auto& cat( addNode( E_NodeType_::CAT ) );
cat.left = nextLeft;
cat.right = nextRight;
return cat;
}
if ( nextRight == UINT32_MAX ) {
return nodes[ nextLeft ];
} else {
return nodes[ nextRight ];
}
}
T_RuleNode_& T_ContextConverter_::convertItem( T_SRDInputItem const& item )
{
switch ( item.type( ) ) {
case E_SRDInputItem::WORD:
return addWord( item.word( ) );
case E_SRDInputItem::ENUM:
return addEnum( item.word( ) );
case E_SRDInputItem::TOKEN:
return addToken( item.token( ) );
case E_SRDInputItem::ALTERNATIVE:
return addAlternatives( item.items( ) );
case E_SRDInputItem::REPETITION:
return convertRepetitions( item );
}
throw std::domain_error( "unknown item type" );
}
/*----------------------------------------------------------------------------*/
void T_ContextConverter_::convertRules( )
{
auto const& ctx( cfg.contexts[ cid ] );
nodes.clear( );
symNodes.clear( );
maxLabel = 0;
// Access all rules
auto const& rules( ctx.rules( ) );
const uint32_t nRules( rules.size( ) );
if ( nRules == 0 ) {
T_StringBuilder sb;
sb << "Context `" << ctx.name( )
<< "` is empty.";
throw X_SRDParserConfig( sb );
}
// Convert each rule
uint32_t ruleRoots[ nRules ];
for ( curRule = 1 ; curRule <= nRules ; curRule ++ ) {
// Convert the rule
auto& rNode( addSequence( rules[ curRule - 1 ].rule( ) ) );
auto const& ruleEnd( addSymbolNode( E_SymNodeType_::END , curRule - 1 ) );
auto& ruleRoot( addNode( E_NodeType_::CAT ) );
ruleRoot.left = rNode.index;
ruleRoot.right = ruleEnd.index;
ruleRoots[ curRule - 1 ] = ruleRoot.index;
}
// Add alternations between the rules
uint32_t nextLeft = ruleRoots[ 0 ];
for ( uint32_t i = 1 ; i < nRules ; i ++ ) {
auto& alt( addNode( E_NodeType_::ALT ) );
alt.left = nextLeft;
alt.right = ruleRoots[ i ];
nextLeft = alt.index;
}
}
/*----------------------------------------------------------------------------*/
void T_ContextConverter_::computeCancellable( )
{
const uint32_t nNodes( nodes.size( ) );
cancellable.clear( );
for ( uint32_t i = 0 ; i < nNodes ; i ++ ) {
auto const& node( nodes[ i ] );
switch ( node.type ) {
case E_NodeType_::ALT:
{
bool l( cancellable[ node.left ] );
bool r( cancellable[ node.right ] );
cancellable.add( l || r );
break;
}
case E_NodeType_::CAT:
{
bool l( cancellable[ node.left ] );
bool r( cancellable[ node.right ] );
cancellable.add( l && r );
break;
}
case E_NodeType_::SYM:
cancellable.add( false );
break;
case E_NodeType_::STAR:
case E_NodeType_::EPS:
cancellable.add( true );
break;
}
}
}
void T_ContextConverter_::computePos( T_MultiArray< uint32_t >& output , const bool first )
{
const auto n( nodes.size( ) );
output.clear( );
for ( uint32_t i = 0 ; i < n ; i ++ ) {
auto const& node( nodes[ i ] );
output.next( );
switch ( node.type ) {
case E_NodeType_::SYM:
output.add( node.label );
break;
case E_NodeType_::STAR:
output.copyFrom( node.left );
break;
case E_NodeType_::ALT:
case E_NodeType_::CAT:
{
const auto n1( first ? node.left : node.right );
const auto n2( first ? node.right : node.left );
output.copyFrom( n1 );
if ( node.type == E_NodeType_::ALT
|| cancellable[ n1 ] ) {
output.copyUnique( n2 );
}
break;
}
case E_NodeType_::EPS:
// Do nothing.
break;
}
}
}
inline void T_ContextConverter_::computeFirstPos( )
{
computePos( firstPos , true );
}
inline void T_ContextConverter_::computeLastPos( )
{
computePos( lastPos , false );
}
void T_ContextConverter_::computeNextPos( )
{
const auto lp( maxLabel + 1 );
const auto nn( nodes.size( ) );
nextPos.clear( );
for ( uint32_t pos = 1 ; pos < lp ; pos ++ ) {
nextPos.next( );
for ( uint32_t i = 0 ; i < nn ; i ++ ) {
auto const& node( nodes[ i ] );
if ( node.type == E_NodeType_::CAT ) {
if ( lastPos.contains( node.left , pos ) ) {
nextPos.copyUnique( firstPos , node.right );
}
} else if ( node.type == E_NodeType_::STAR ) {
if ( lastPos.contains( i , pos ) ) {
nextPos.copyUnique( firstPos , i );
}
}
}
}
}
void T_ContextConverter_::gatherSymbols( )
{
symbols.clear( );
symLabels.clear( );
while ( symNodes.size( ) ) {
auto const& first( nodes[ symNodes[ 0 ] ] );
symNodes.removeSwap( 0 );
symbols.addNew( first );
symLabels.next( );
symLabels.add( first.label );
uint32_t i = 0;
while ( i < symNodes.size( ) ) {
auto const& node( nodes[ symNodes[ i ] ] );
if ( first.left == node.left && first.right == node.right ) {
symLabels.add( node.label );
symNodes.removeSwap( i );
} else {
i ++;
}
}
}
}
/*----------------------------------------------------------------------------*/
void T_ContextConverter_::setEndState( uint32_t state , uint32_t end )
{
const auto curEnd( cfg.endStates[ state ] );
if ( curEnd != T_SRDParserConfig::INVALID_END && curEnd != end ) {
T_StringBuilder sb;
sb << "In context `" << defs[ cid ].name( ) << "`: rule #"
<< ( curEnd + 1 ) << " and rule #" << ( end + 1 ) << " are ambiguous.";
throw X_SRDParserConfig( sb );
}
cfg.endStates[ state ] = end;
}
void T_ContextConverter_::addNameParts( uint32_t label )
{
const auto npf( nextPos.firstOf( label - 1 ) );
const auto npl( nextPos.sizeOf( label - 1 ) + npf );
for ( uint32_t i = npf ; i < npl ; i ++ ) {
const auto p( nextPos[ i ] );
const auto nas( nameAccum.size( ) );
bool found = false;
for ( uint32_t j = 0 ; j < nas && !found ; j ++ ) {
found = nameAccum[ j ] == p;
}
if ( !found ) {
nameAccum.add( p );
}
}
}
void T_ContextConverter_::checkStateName( uint32_t state , uint32_t first , uint32_t end , uint32_t si )
{
auto const& sym( symbols[ si ] );
nameAccum.clear( );
for ( uint32_t i = first ; i < end ; i ++ ) {
const auto snVal( stateNames[ i ] );
if ( !symLabels.contains( si , snVal ) ) {
continue;
}
if ( sym.type == E_SymNodeType_::END ) {
setEndState( state , sym.idx );
} else {
addNameParts( snVal );
}
}
}
uint32_t T_ContextConverter_::findState( uint32_t firstState )
{
const uint32_t nsize( nameAccum.size( ) );
const uint32_t snsize( stateNames.size( ) );
for ( uint32_t i = 0 ; i < snsize ; i ++ ) {
if ( stateNames.sizeOf( i ) != nsize ) {
continue;
}
if ( !memcmp( &stateNames[ stateNames.firstOf( i ) ] , &nameAccum[ 0 ] ,
nsize * sizeof( uint32_t ) ) ) {
return i + firstState;
}
}
return T_HashIndex::INVALID_INDEX;
}
void T_ContextConverter_::addTransition( T_Symbol_ const& sym , uint32_t state )
{
if ( sym.type == E_SymNodeType_::TOKEN ) {
cfg.transitions.addNew( E_SRDTokenType( sym.idx ) , state );
} else {
const auto type( sym.type == E_SymNodeType_::WORD
? E_SRDTransitionType::WORD
: E_SRDTransitionType::ENUM );
cfg.transitions.addNew( type , sym.idx , state );
}
}
/*----------------------------------------------------------------------------*/
void T_ContextConverter_::buildAutomaton( )
{
static const F_Comparator< T_SRDTransition > transSorter =
[]( T_SRDTransition const& a , T_SRDTransition const& b ) -> int {
return T_Comparator< decltype( a.type ) >::compare( a.type , b.type );
};
const auto firstState( cfg.endStates.size( ) );
cfg.endStates.add( T_SRDParserConfig::INVALID_END );
cfg.startStates.add( firstState );
stateNames.clear( );
stateNames.copyFrom( firstPos , firstPos.size( ) - 1 );
stateNames.sort( 0 );
const auto nsyms( symbols.size( ) );
auto state = firstState;
while ( state < cfg.endStates.size( ) ) {
cfg.transitions.next( );
const auto snFirst( stateNames.firstOf( state - firstState ) );
const auto snEnd( snFirst + stateNames.sizeOf( state - firstState ) );
for ( uint32_t si = 0 ; si < nsyms ; si ++ ) {
// Find the next state's name
checkStateName( state , snFirst , snEnd , si );
if ( nameAccum.size( ) == 0 ) {
continue;
}
nameAccum.sort( );
// Check if the state exists
uint32_t found( findState( firstState ) );
// Add new state if needed
if ( found == T_HashIndex::INVALID_INDEX ) {
found = cfg.endStates.size( );
cfg.endStates.add( T_SRDParserConfig::INVALID_END );
stateNames.next( );
stateNames.copyFrom( nameAccum );
}
// Add the transition
addTransition( symbols[ si ] , found );
}
cfg.transitions.sort( state , transSorter );
state ++;
}
}
} // namespace
/*= X_SRDParserConfig ========================================================*/
X_SRDParserConfig::X_SRDParserConfig( T_StringBuilder const& error )
: errorString_( error.size( ) + 1 )
{
memcpy( &errorString_[ 0 ] , error.data( ) , error.size( ) );
errorString_[ error.size( ) ] = 0;
}
X_SRDParserConfig::X_SRDParserConfig( T_String const& error )
: errorString_( error.size( ) + 1 )
{
memcpy( &errorString_[ 0 ] , error.data( ) , error.size( ) );
errorString_[ error.size( ) ] = 0;
}
char const* X_SRDParserConfig::what( ) const noexcept
{
return &errorString_[ 0 ];
}
/*= T_SRDParserConfig ========================================================*/
T_SRDParserConfig::T_SRDParserConfig( T_SRDParserDefs const& defs )
: onStart( defs.onStart( ) ) , onFinish( defs.onFinish( ) ) , contexts( defs.contexts( ) ) ,
enums( []( T_SRDEnum const& e ) -> T_String const& {
return e.name( );
} , 64 , 64 , 64 ) , wordsIndex( 256 , 256 , 256 ) , words( 256 ) , startStates( defs.contexts( ) )
{
T_ContextConverter_ converter( *this , defs );
converter.checkUnbalancedRules( );
converter.findUsedContexts( );
const auto nc( contexts.size( ) );
for ( uint32_t i = 0 ; i < nc ; i ++ ) {
converter.cid = i;
converter.convertRules( );
converter.computeCancellable( );
converter.computeFirstPos( );
converter.computeLastPos( );
converter.computeNextPos( );
converter.gatherSymbols( );
converter.buildAutomaton( );
}
}
bool T_SRDParserConfig::transition( uint32_t& state , T_SRDToken const& token ) const
{
const auto first( transitions.firstOf( state ) );
const auto end( first + transitions.sizeOf( state ) );
for ( uint32_t i = first ; i < end ; i ++ ) {
auto const& t( transitions[ i ] );
if ( t.type == E_SRDTransitionType::TOKEN ) {
if ( token.type( ) == t.tokenType ) {
state = t.next;
return true;
}
continue;
}
if ( token.type( ) != E_SRDTokenType::WORD ) {
continue;
}
if ( t.type == E_SRDTransitionType::WORD ) {
if ( token.stringValue( ) == words[ t.index ] ) {
state = t.next;
return true;
}
continue;
}
assert( t.type == E_SRDTransitionType::ENUM );
if ( enums[ t.index ].contains( token.stringValue( ) ) ) {
state = t.next;
return true;
}
}
return false;
}
T_Optional< uint32_t > T_SRDParserConfig::enumValue(
T_String const& name , T_String const& member ) const noexcept
{
T_SRDEnum const* const enumData( enums.get( name ) );
if ( enumData && enumData->contains( member ) ) {
return (*enumData)[ member ];
}
return T_Optional< uint32_t >( );
}

1416
src/SRDPreproc.cc Normal file

File diff suppressed because it is too large Load diff

1329
src/SRDText.cc Normal file

File diff suppressed because it is too large Load diff

42
src/Streams.cc Normal file
View file

@ -0,0 +1,42 @@
/******************************************************************************/
/* STREAMS ********************************************************************/
/******************************************************************************/
#include <lw/lib/Streams.hh>
using namespace lw;
/*= X_StreamError ============================================================*/
char const* X_StreamError::what( ) const noexcept
{
switch ( error_ ) {
case E_StreamError::NOT_SUPPORTED:
return "operation not supported";
case E_StreamError::INVALID_POSITION:
return "invalid position";
case E_StreamError::END:
return "stream ended";
case E_StreamError::BAD_DATA:
return "incomplete read/write";
case E_StreamError::SYSTEM_ERROR:
return "system error";
case E_StreamError::UNAVAILABLE:
return "stream no longer available";
default:
return "unknown error";
}
}
/*= A_OutputStream ===========================================================*/
void A_OutputStream::flush( )
{ }

2015
src/Strings.cc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
/******************************************************************************/
/* INSTANTIATION OF VARIOUS TEMPLATE CLASSES **********************************/
/******************************************************************************/
#include <lw/lib/Utilities.hh>
#include <lw/lib/Arrays.hh>
#include <lw/lib/Strings.hh>
#include <lw/lib/SRDData.hh>
#include <lw/lib/SRDDefinitions.hh>
namespace lw {
/*----------------------------------------------------------------------------*/
template struct T_Comparator< uint32_t >;
/*----------------------------------------------------------------------------*/
template class T_Array< uint32_t >;
template class T_Array< bool >;
template class T_Array< T_String >;
template class T_Array< T_SRDToken >;
template class T_Array< T_SRDInputItem >;
/*----------------------------------------------------------------------------*/
template class T_MultiArray< uint32_t >;
template class T_MultiArray< T_String >;
/*----------------------------------------------------------------------------*/
}

159
src/TextFileLogger.cc Normal file
View file

@ -0,0 +1,159 @@
/******************************************************************************/
/* LOGGING SYSTEM - BUILT-IN LOGGERS - TEXT FILE LOGGER ***********************/
/******************************************************************************/
#include <lw/lib/BuiltinLoggers.hh>
#include <lw/lib/SRDParser.hh>
using namespace lw;
namespace {
char const* const V_Name_ = "text-file";
inline T_String Name_( ) { return T_String::Pooled( V_Name_ ); }
bool TFLCPath_( T_SRDParserData const& data )
{
auto const& ptok( (*data.input)[ 1 ] );
T_VFSPath path( ptok.stringValue( ) );
path = path.normalize( );
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
data.errors.add( "invalid path" , ptok );
} else {
auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setPath( std::move( path ) );
}
return true;
}
bool TFLCAppend_( T_SRDParserData const& data )
{
auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setAppend( true );
return true;
}
bool TFLCTruncate_( T_SRDParserData const& data )
{
auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setAppend( false );
return true;
}
}
/*= T_TextFileLogWriterFactory ==================================================*/
T_TextFileLogWriterFactory::T_TextFileLogWriterFactory( T_VFS& vfs )
: A_LogWriterFactory( Name_( ) ) , vfs_( vfs )
{ }
RP_LogWriterConfiguration T_TextFileLogWriterFactory::createConfiguration( T_String const& name ) const
{
RP_LogWriterConfiguration p( new T_TextFileLogWriterCfg( ) );
p->setName( name );
return p;
}
OP_LogWriter T_TextFileLogWriterFactory::createLogWriter( OP_LogWriterConfiguration&& configuration ) const
{
T_TextFileLogWriter* p( new T_TextFileLogWriter( std::move( configuration ) , vfs_ ) );
return OwnRawPointer( p );
}
void T_TextFileLogWriterFactory::initializeSyntax( T_SRDParserDefs& , T_SRDContext& main ) const
{
using namespace lw::SRD;
main << ( Rule( ) << "file" << Text( ) << TFLCPath_ );
main << ( Rule( ) << "append" << TFLCAppend_ );
main << ( Rule( ) << "truncate" << TFLCTruncate_ );
}
/*= T_TextFileLogWriterCfg ======================================================*/
T_TextFileLogWriterCfg::T_TextFileLogWriterCfg( )
: T_LogWriterConfiguration( Name_( ) )
{ }
T_TextFileLogWriterCfg::T_TextFileLogWriterCfg( T_TextFileLogWriterCfg const& source )
: T_LogWriterConfiguration( source ) , path_( source.path_ ) ,
append_( source.append_ )
{ }
OP_LogWriterConfiguration T_TextFileLogWriterCfg::clone( )
{
T_TextFileLogWriterCfg* ptr( new T_TextFileLogWriterCfg( *this ) );
return OwnRawPointer( ptr );
}
void T_TextFileLogWriterCfg::check( T_SRDErrors& errors , T_SRDList const& input )
{
T_LogWriterConfiguration::check( errors , input );
if ( path_.type( ) == E_VFSPathType::UNKNOWN || path_.elements( ) == 0 ) {
errors.add( "no file selected" , input[ 0 ] );
}
}
/*= T_TextFileLogWriter =========================================================*/
T_TextFileLogWriter::T_TextFileLogWriter( OP_LogWriterConfiguration&& configuration , T_VFS& vfs )
: A_LogWriter( std::move( configuration ) ) , vfs_( vfs )
{ }
void T_TextFileLogWriter::log( T_LogTimestamp const& timestamp ,
E_LogLevel level , T_LogPath const& path ,
T_LogStringData const& data , uint32_t size )
{
using namespace std::chrono;
char timeBuffer[ 128 ];
std::time_t tst( T_LogTimestamp::clock::to_time_t( timestamp ) );
std::strftime( timeBuffer , 128 , "%Y-%m-%d %H:%M:%S" , std::gmtime( &tst ) );
const auto ms( ( duration_cast< milliseconds >( timestamp - T_LogTimestamp( ) ) ).count( ) % 1000 );
T_StringBuilder sb;
sb << timeBuffer << '.';
if ( ms < 100 ) {
sb << '0';
if ( ms < 10 ) {
sb << '0';
}
}
sb << ms << ' ' << path.toString( ) << " - " << level << ": ";
sb.append( &( (*data) [ 0 ] ) , size );
sb << '\n';
auto const& cfg( configuration< T_TextFileLogWriterCfg >( ) );
auto const& p( cfg.path( ) );
if ( !file_ ) {
vfs_.mkdir( p.parent( ) );
file_ = vfs_.file( p , cfg.append( ) ? E_FileMode::READ_WRITE : E_FileMode::OVERWRITE );
if ( !file_ ) {
disable( );
return;
}
try {
file_->open( );
} catch ( X_StreamError const& ) {
disable( );
file_.clear( );
return;
}
}
try {
file_->position( 0 , true );
file_->write( sb.data( ) , sb.size( ) );
file_->flush( );
} catch ( X_StreamError ) {
disable( );
file_.clear( );
return;
}
}

160
src/Utilities.cc Normal file
View file

@ -0,0 +1,160 @@
/******************************************************************************/
/* VARIOUS UTILITIES **********************************************************/
/******************************************************************************/
#include <lw/lib/Utilities.hh>
using namespace lw;
namespace {
/*= SORTING INTERNALS - QUICKSORT ============================================*/
static const int32_t C_MIN_QS_ITEMS_ = 4;
/*----------------------------------------------------------------------------*/
// T_Stack_ - Quicksort stack
struct T_Stack_
{
uint8_t* low;
uint8_t* high;
};
inline void QSPush_( T_Stack_*& stack , uint8_t* low , uint8_t* high )
{
stack->low = low;
stack->high = high;
stack ++;
}
inline void QSPop_( T_Stack_*& stack , uint8_t*& low , uint8_t*& high )
{
stack --;
low = stack->low;
high = stack->high;
}
/*----------------------------------------------------------------------------*/
inline void Quicksort_( uint8_t* data , uint32_t itemSize , uint32_t items , T_Sorter_::F_Swap swap ,
T_Sorter_::F_Cmp cmp )
{
const int32_t minSize = itemSize * C_MIN_QS_ITEMS_;
uint8_t* low( data );
uint8_t* high( low + itemSize * ( items - 1 ) );
T_Stack_ stackData[ 8 * sizeof( uint32_t ) ];
T_Stack_* stack( &stackData[ 0 ] );
QSPush_( stack , nullptr , nullptr );
while ( stack > &stackData[ 0 ] ) {
// Select pivot, sort low/pivot/high
uint8_t* mid( low + itemSize * ( ( high - low ) / itemSize >> 1 ) );
if ( cmp( mid , low ) < 0 ) {
swap( mid , low );
}
if ( cmp( high , mid ) < 0 ) {
swap( mid , high );
if ( cmp( mid , low ) < 0 ) {
swap( mid , low );
}
}
// Partitioning
uint8_t* left( low + itemSize ) ,
*right( high - itemSize );
do {
while ( cmp( left , mid ) < 0 ) {
left += itemSize;
}
while ( cmp( mid , right ) < 0 ) {
right -= itemSize;
}
if ( left < right ) {
swap( left , right );
if ( left == mid ) {
mid = right;
} else if ( right == mid ) {
mid = left;
}
}
if ( left <= right ) {
left += itemSize;
right -= itemSize;
}
} while ( left <= right );
// Setup for next iteration
if ( right - low <= minSize ) {
if ( high - left <= minSize ) {
QSPop_( stack , low , high );
} else {
low = left;
}
} else if ( high - left <= minSize ) {
high = right;
} else if ( right - low > high - left ) {
QSPush_( stack , low , right );
low = left;
} else {
QSPush_( stack , left , high );
high = right;
}
}
}
/*= SORTING INTERNALS - INSERTION SORT =======================================*/
inline void InsertionSort_( uint8_t* data , uint32_t itemSize , uint32_t items , T_Sorter_::F_Swap swap ,
T_Sorter_::F_Cmp cmp )
{
uint8_t* const end( data + itemSize* items );
for ( uint8_t* i( data + itemSize ) ; i < end ; i += itemSize ) {
auto j = i;
while ( j != data && cmp( j - itemSize , j ) > 0 ) {
swap( j , j - itemSize );
j -= itemSize;
}
}
}
} // namespace
/*= T_Sorter_ ================================================================*/
void T_Sorter_::sort( uint8_t* data , uint32_t itemSize , uint32_t items , F_Swap swap , F_Cmp cmp )
{
if ( items != 0 ) {
if ( items > C_MIN_QS_ITEMS_ ) {
Quicksort_( data , itemSize , items , swap , cmp );
}
InsertionSort_( data , itemSize , items , swap , cmp );
}
}
/*= HashData( ) ==============================================================*/
uint32_t lw::HashData( uint8_t const* data , uint32_t size )
{
uint32_t hash = 111119;
while ( size > 0 ) {
hash += *data;
hash += ( hash << 10 );
hash ^= ( hash >> 6 );
data ++;
size --;
}
hash += ( hash << 3 );
hash ^= ( hash >> 11 );
hash += ( hash << 15 );
return hash;
}

930
src/VFS.cc Normal file
View file

@ -0,0 +1,930 @@
/******************************************************************************/
/* VIRTUAL FILE SYSTEM ********************************************************/
/******************************************************************************/
#include <lw/lib/VFS.hh>
#ifndef _WIN32
# include <sys/stat.h>
# include <dirent.h>
# include <unistd.h>
#endif
using namespace lw;
/*= VARIOUS INTERNALS ========================================================*/
namespace {
bool MkDir_( T_String const& path )
{
const auto chars( path.toOSString( ) );
#ifdef _WIN32
return CreateDirectoryW( ( wchar_t const* ) &chars[ 0 ] , nullptr );
#else
return mkdir( ( char const* ) &chars[ 0 ] , 0755 ) == 0;
#endif
}
E_VFSEntryType TypeOfPath_( T_String const& path )
{
const auto chars( path.toOSString( ) );
#ifdef _WIN32
uint32_t fa( GetFileAttributesW( (wchar_t const*) & chars[ 0 ] ) );
if ( fa == INVALID_FILE_ATTRIBUTES ) {
return E_VFSEntryType::NONE;
} else if ( ( fa & FILE_ATTRIBUTE_DIRECTORY ) != 0 ) {
return E_VFSEntryType::DIRECTORY;
} else if ( ( fa & FILE_ATTRIBUTE_NORMAL ) != 0 ) {
return E_VFSEntryType::FILE;
} else {
return E_VFSEntryType::OTHER;
}
#else
struct stat fa;
if ( stat( ( char const* ) &chars[ 0 ] , &fa ) ) {
return E_VFSEntryType::NONE;
}
const auto masked( fa.st_mode & S_IFMT );
switch ( masked ) {
case S_IFREG:
return E_VFSEntryType::FILE;
case S_IFDIR:
return E_VFSEntryType::DIRECTORY;
default:
return E_VFSEntryType::OTHER;
}
#endif
}
} // namespace
/*= T_DirLister_ =============================================================*/
namespace {
class T_DirLister_
{
private:
T_Buffer< char > path_;
#ifdef _WIN32
HANDLE handle_;
WIN32_FIND_DATAW output_;
#else
DIR* dir_;
struct dirent* output_;
#endif
public:
explicit T_DirLister_( T_Buffer< char > path );
~T_DirLister_( );
bool start( );
void next( );
bool hasValue( ) const;
T_String getName( ) const;
};
} // namespace
/*----------------------------------------------------------------------------*/
inline T_DirLister_::T_DirLister_( T_Buffer< char > path )
: path_( std::move( path ) )
{
#ifdef _WIN32
# error "Not implemented"
#else
dir_ = nullptr;
output_ = nullptr;
#endif
}
T_DirLister_::~T_DirLister_( )
{
#ifdef _WIN32
# error "Not implemented"
#else
if ( dir_ != nullptr ) {
closedir( dir_ );
}
#endif
}
inline bool T_DirLister_::start( )
{
#ifdef _WIN32
# error "Not implemented"
#else
dir_ = opendir( &path_[ 0 ] );
if ( dir_ == nullptr ) {
return false;
}
output_ = readdir( dir_ );
return true;
#endif
}
inline void T_DirLister_::next( )
{
#ifdef _WIN32
# error "Not implemented"
#else
output_ = readdir( dir_ );
#endif
}
inline bool T_DirLister_::hasValue( ) const
{
#ifdef _WIN32
# error "Not implemented"
#else
return output_ != nullptr;
#endif
}
inline T_String T_DirLister_::getName( ) const
{
#ifdef _WIN32
# error "Not implemented"
#else
return T_String( output_->d_name , strlen( output_->d_name ) );
#endif
}
/*= T_VFSPath ================================================================*/
T_VFSPath::T_VFSPath( E_VFSPathType type )
: type_( type ) , elements_( 16 )
{ }
T_VFSPath::T_VFSPath( T_String const& path )
: T_VFSPath( )
{
extractPath( path );
}
/*----------------------------------------------------------------------------*/
T_VFSPath::T_VFSPath( T_VFSPath const& parent , T_VFSPath const& child )
: T_VFSPath( parent.type_ )
{
if ( child.type( ) == E_VFSPathType::UNKNOWN ) {
type_ = E_VFSPathType::UNKNOWN;
}
if ( type_ == E_VFSPathType::UNKNOWN || child.type( ) == E_VFSPathType::UNKNOWN ) {
return;
}
if ( child.type_ == E_VFSPathType::INVALID ) {
type_ = E_VFSPathType::INVALID;
}
elements_.ensureCapacity( parent.elements( ) + child.elements( ) );
elements_.addAll( parent.elements_ );
elements_.addAll( child.elements_ );
}
/*----------------------------------------------------------------------------*/
T_VFSPath::T_VFSPath( T_VFSPath const& other )
: type_( other.type_ ) , elements_( other.elements_ )
{ }
T_VFSPath::T_VFSPath( T_VFSPath&& other ) noexcept
: T_VFSPath( )
{
swap( *this , other );
}
/*----------------------------------------------------------------------------*/
T_VFSPath& T_VFSPath::operator= ( T_VFSPath const& other )
{
type_ = other.type_;
elements_ = other.elements_;
return *this;
}
T_VFSPath& T_VFSPath::operator= ( T_VFSPath&& other ) noexcept
{
swap( *this , other );
return *this;
}
/*----------------------------------------------------------------------------*/
T_VFSPath::operator T_String ( ) const
{
T_StringBuilder sb;
if ( type_ == E_VFSPathType::ABSOLUTE ) {
sb << '/';
}
const auto n( elements_.size( ) );
for ( uint32_t i = 0 ; i < n ; i ++ ) {
if ( i > 0 ) {
sb << '/';
}
sb << elements_[ i ];
}
return T_String( std::move( sb ) );
}
/*----------------------------------------------------------------------------*/
void lw::swap( T_VFSPath& lhs , T_VFSPath& rhs ) noexcept
{
using std::swap;
swap( lhs.type_ , rhs.type_ );
swap( lhs.elements_ , rhs.elements_ );
}
/*----------------------------------------------------------------------------*/
T_VFSPath T_VFSPath::normalize( ) const
{
T_VFSPath normed;
if ( type_ == E_VFSPathType::UNKNOWN || type_ == E_VFSPathType::INVALID ) {
return normed;
}
normed.type_ = type_;
const auto absolute( type_ == E_VFSPathType::ABSOLUTE );
const auto n( elements_.size( ) );
for ( uint32_t i = 0 ; i < n ; i ++ ) {
auto const& e( elements_[ i ] );
// Simply remove .'s
if ( e == "." ) {
continue;
}
if ( e == ".." ) {
auto const te( normed.elements( ) );
// Remove ..'s at the start of an absolute path
if ( te == 0 && absolute ) {
continue;
}
// Use ..'s to remove stuff like "dir/../wat/../wut/.."
if ( te != 0 && normed.elements_[ te - 1 ] != ".." ) {
normed.elements_.remove( te - 1 );
continue;
}
}
normed.elements_.add( e );
}
if ( normed.elements( ) == 0 && normed.type( ) == E_VFSPathType::RELATIVE ) {
normed.elements_.add( T_String::Pooled( "." ) );
}
return normed;
}
/*----------------------------------------------------------------------------*/
T_VFSPath T_VFSPath::parent( ) const
{
if ( type_ == E_VFSPathType::UNKNOWN || type_ == E_VFSPathType::INVALID
|| elements_.size( ) == 0 ) {
return *this;
}
if ( type_ == E_VFSPathType::RELATIVE && elements_.size( ) == 1 ) {
T_VFSPath p( E_VFSPathType::RELATIVE );
p.elements_.add( T_String::Pooled( "." ) );
return p;
}
T_VFSPath p( *this );
p.elements_.remove( elements_.size( ) - 1 );
return p;
}
/*----------------------------------------------------------------------------*/
bool T_VFSPath::operator== ( T_VFSPath const& other ) const
{
if ( this == &other ) {
return true;
}
const auto n( elements( ) );
if ( type_ != other.type_ || n != other.elements( ) ) {
return false;
}
for ( uint32_t i = 0 ; i < n ; i ++ ) {
if ( elements_[ i ] != other[ i ] ) {
return false;
}
}
return true;
}
/*----------------------------------------------------------------------------*/
void T_VFSPath::extractPath( T_String const& path )
{
// Extract the path's elements
T_StringIterator it( path );
bool elementStarted( false ) , absolute( false ) , invalid( false );
uint32_t startIndex , index( 0 );
while ( !it.atEnd( ) ) {
T_Character c( it );
if ( c == '/' ) {
if ( elementStarted ) {
elements_.add( path.range( startIndex , it.index( ) - 1 ) );
elementStarted = false;
}
if ( it.index( ) == 0 ) {
absolute = true;
}
} else {
if ( !( c.isNumeric( ) || c.isLowercase( )
|| c == '-' || c == '_' || c == '.' ) ) {
invalid = true;
}
if ( !elementStarted ) {
elementStarted = true;
startIndex = it.index( );
}
}
it.next( );
index ++;
}
if ( elementStarted ) {
elements_.add( path.substr( startIndex ) );
}
// Only "." and ".." should start with a dot
if ( !invalid ) {
const auto n( elements_.size( ) );
for ( uint32_t i = 0 ; i < n && !invalid ; i ++ ) {
T_String const& s( elements_[ i ] );
if ( s[ 0 ] == '.' ) {
invalid = ( s != "." && s != ".." );
}
}
}
// Set the type
if ( invalid ) {
type_ = E_VFSPathType::INVALID;
} else if ( absolute ) {
type_ = E_VFSPathType::ABSOLUTE;
} else if ( elements_.size( ) == 0 ) {
type_ = E_VFSPathType::UNKNOWN;
} else {
type_ = E_VFSPathType::RELATIVE;
}
}
/*----------------------------------------------------------------------------*/
M_DEFINE_HASH( T_VFSPath )
{
uint32_t hash( uint32_t( item.type( ) ) << 16 );
const auto n( item.elements( ) );
hash ^= n;
for ( uint32_t i = 0 ; i < n ; i ++ ) {
hash = ( hash << 11 ) | ( hash >> 21 );
hash ^= ComputeHash( item[ i ] );
}
return hash;
}
/*= A_VFSDriver ==============================================================*/
bool A_VFSDriver::init( )
{
return true;
}
void A_VFSDriver::shutdown( )
{ }
OP_File A_VFSDriver::file( T_VFSPath const& )
{
return OP_File( );
}
/*= T_VFSFilesystemDriver ====================================================*/
#ifdef _WIN32
# define C_SEPARATOR_ '\\'
#else
# define C_SEPARATOR_ '/'
#endif
T_VFSFilesystemDriver::T_VFSFilesystemDriver( T_String root )
: root_( root )
{ }
/*----------------------------------------------------------------------------*/
bool T_VFSFilesystemDriver::init( )
{
const auto path( root_.toOSString( ) );
#ifdef _WIN32
uint32_t fa( GetFileAttributesW( (wchar_t const*) & path[ 0 ] ) );
return fa != INVALID_FILE_ATTRIBUTES && ( fa & FILE_ATTRIBUTE_DIRECTORY ) != 0;
#else
struct stat fa;
if ( stat( ( char const* ) &path[ 0 ] , &fa ) ) {
return false;
}
return ( fa.st_mode & S_IFMT ) == S_IFDIR;
#endif
}
/*----------------------------------------------------------------------------*/
E_VFSEntryType T_VFSFilesystemDriver::typeOf( T_VFSPath const& path )
{
return TypeOfPath_( getFullPath( path ) );
}
bool T_VFSFilesystemDriver::list( T_VFSPath const& path , T_Array< T_VFSPath >& values , T_HashIndex& index )
{
T_DirLister_ dl( getOSPath( path ) );
if ( !dl.start( ) ) {
return false;
}
while ( dl.hasValue( ) ) {
const auto v( dl.getName( ) );
dl.next( );
if ( v == "." || v == ".." ) {
continue;
}
const T_VFSPath entry( v );
if ( entry.type( ) == E_VFSPathType::INVALID ) {
continue;
}
const uint32_t hash( ComputeHash( entry ) );
uint32_t idx( index.first( hash ) );
while ( idx != T_HashIndex::INVALID_INDEX ) {
if ( values[ idx ] == entry ) {
break;
}
idx = index.next( idx );
}
if ( idx == T_HashIndex::INVALID_INDEX ) {
index.add( hash );
values.add( entry );
}
}
return true;
}
OP_InputStream T_VFSFilesystemDriver::read( T_VFSPath const& path )
{
if ( typeOf( path ) != E_VFSEntryType::FILE ) {
return OP_InputStream( );
} else {
auto f( file( path ) );
if ( f ) {
return NewOwned< T_FileInputStream >( std::move( f ) );
} else {
return OP_InputStream( );
}
}
}
OP_File T_VFSFilesystemDriver::file( T_VFSPath const& path )
{
if ( typeOf( path ) != E_VFSEntryType::FILE ) {
return OP_File( );
}
return NewOwned< T_File >( getFullPath( path ) , E_FileMode::READ_ONLY );
}
/*----------------------------------------------------------------------------*/
T_String T_VFSFilesystemDriver::getFullPath( T_VFSPath const& path ) const
{
T_StringBuilder sb( root_ );
const auto n( path.elements( ) );
for ( uint32_t i = 0 ; i < n ; i ++ ) {
sb << C_SEPARATOR_ << path[ i ];
}
return T_String( std::move( sb ) );
}
inline T_Buffer< char > T_VFSFilesystemDriver::getOSPath( T_VFSPath const& path ) const
{
return getFullPath( path ).toOSString( );
}
/*= T_VFSUserDirectory_ ======================================================*/
namespace {
class T_VFSUserDirectory_ : public T_VFSFilesystemDriver
{
public:
explicit T_VFSUserDirectory_( T_String const& base );
bool init( ) override;
OP_File file( T_VFSPath const& path ) override;
OP_File file( T_VFSPath const& path , E_FileMode mode );
OP_OutputStream write( T_VFSPath const& path );
bool mkdir( T_VFSPath const& path ) const;
bool rmdir( T_VFSPath const& path ) const;
bool rm( T_VFSPath const& path ) const;
bool move( T_VFSPath const& from , T_VFSPath const& to ) const;
};
} // namespace
/*----------------------------------------------------------------------------*/
T_VFSUserDirectory_::T_VFSUserDirectory_( T_String const& base )
: T_VFSFilesystemDriver( base )
{ }
bool T_VFSUserDirectory_::init( )
{
if ( T_VFSFilesystemDriver::init( ) ) {
return true;
}
// Try creating the user directory
T_String const& r( root( ) );
int32_t nextSep( r.find( C_SEPARATOR_ ) );
while ( 1 ) {
const T_String p( nextSep == -1 ? r : r.substr( 0 , nextSep ) );
if ( p ) {
const auto t( TypeOfPath_( p ) );
if ( ( t != E_VFSEntryType::DIRECTORY && t != E_VFSEntryType::NONE )
|| ( t == E_VFSEntryType::NONE && !MkDir_( p ) ) ) {
return false;
}
}
if ( nextSep == -1 ) {
break;
} else {
nextSep = r.find( C_SEPARATOR_ , nextSep + 1 );
}
}
return true;
}
/*----------------------------------------------------------------------------*/
OP_File T_VFSUserDirectory_::file( T_VFSPath const& path )
{
return T_VFSFilesystemDriver::file( path );
}
OP_File T_VFSUserDirectory_::file( T_VFSPath const& path , E_FileMode mode )
{
if ( mode == E_FileMode::READ_ONLY ) {
return file( path );
}
const auto fp( getFullPath( path ) );
const auto ft( TypeOfPath_( fp ) );
if ( ft != E_VFSEntryType::FILE && ft != E_VFSEntryType::NONE ) {
return OP_File( );
}
return NewOwned< T_File >( fp , mode );
}
/*----------------------------------------------------------------------------*/
OP_OutputStream T_VFSUserDirectory_::write( T_VFSPath const& path )
{
auto f( file( path , E_FileMode::OVERWRITE ) );
if ( f ) {
return NewOwned< T_FileOutputStream >( std::move( f ) );
} else {
return OP_OutputStream( );
}
}
/*----------------------------------------------------------------------------*/
bool T_VFSUserDirectory_::mkdir( T_VFSPath const& path ) const
{
T_StringBuilder sb( root( ) );
const auto n( path.elements( ) );
for ( uint32_t i = 0 ; i < n ; i ++ ) {
sb << C_SEPARATOR_ << path[ i ];
if ( !MkDir_( sb ) ) {
return false;
}
}
return true;
}
bool T_VFSUserDirectory_::rmdir( T_VFSPath const& path ) const
{
const auto chars( getOSPath( path ) );
#ifdef _WIN32
return RemoveDirectoryW( ( wchar_t const* ) &chars[ 0 ] );
#else
return ::rmdir( ( char const* ) &chars[ 0 ] ) == 0;
#endif
}
bool T_VFSUserDirectory_::rm( T_VFSPath const& path ) const
{
const auto chars( getOSPath( path ) );
#ifdef _WIN32
return DeleteFileW( ( wchar_t const* ) &chars[ 0 ] );
#else
return unlink( ( char const* ) &chars[ 0 ] ) == 0;
#endif
}
bool T_VFSUserDirectory_::move( T_VFSPath const& from , T_VFSPath const& to ) const
{
const auto charsFrom( getOSPath( from ) );
const auto charsTo( getOSPath( to ) );
#ifdef _WIN32
return MoveFileW( ( wchar_t const* ) &charsFrom[ 0 ] ,
( wchar_t const* ) &charsTo[ 0 ] );
#else
return rename( ( char const* ) &charsFrom[ 0 ] ,
( char const* ) &charsTo[ 0 ] ) == 0;
#endif
}
/*= T_VFSPrivate_ ============================================================*/
namespace {
struct T_VFSPrivate_
{
T_RegisteredItem::SP_Unregister unregisterFunction_{
NewShared< T_RegisteredItem::F_Unregister >(
[this]( void* data ) {
auto const n( drivers_.size( ) );
for ( uint32_t i = 0 ; i < n ; i ++ ) {
auto& p( drivers_[ i ] );
if ( p.get( ) == data ) {
p->shutdown( );
drivers_.removeSwap( i );
break;
}
}
}
) };
OP_VFSFilesystemDriver userDir_;
T_Array< OP_VFSDriver > drivers_{ 16 };
static T_String findUserDir( );
void initUserDir( T_String const& dir );
};
} // namespace
/*----------------------------------------------------------------------------*/
T_String T_VFSPrivate_::findUserDir( )
{
#ifdef _WIN32
# error "Not implemented!"
#else
T_String base;
char const* const lwHome( getenv( "LWHOME" ) );
if ( lwHome == nullptr ) {
char const* const userHome( getenv( "HOME" ) );
if ( userHome == nullptr ) {
throw X_VFSInitialisationFailure( );
} else {
T_StringBuilder sb( userHome );
sb << "/.local/share/LegacyWorlds";
base = std::move( sb );
}
} else {
base = T_String( lwHome , strlen( lwHome ) );
}
return base;
#endif
}
void T_VFSPrivate_::initUserDir( T_String const& dir )
{
assert( !userDir_ );
userDir_ = NewOwned< T_VFSUserDirectory_ >( dir );
if ( !userDir_->init( ) ) {
throw X_VFSInitialisationFailure( );
}
}
/*= T_VFS ====================================================================*/
T_VFS::T_VFS( )
: A_PrivateImplementation( new T_VFSPrivate_( ) )
{
p< T_VFSPrivate_ >( ).initUserDir( T_VFSPrivate_::findUserDir( ) );
}
T_VFS::T_VFS( T_String const& userDir )
: A_PrivateImplementation( new T_VFSPrivate_( ) )
{
p< T_VFSPrivate_ >( ).initUserDir( userDir );
}
T_RegisteredItem T_VFS::addDriver( OP_VFSDriver&& driver )
{
assert( driver );
const bool ok( driver->init( ) );
if ( ok ) {
auto& pi( p< T_VFSPrivate_ >( ) );
void* const ptr( driver.get( ) );
pi.drivers_.add( std::move( driver ) );
return T_RegisteredItem( pi.unregisterFunction_ , ptr );
}
return T_RegisteredItem( );
}
/*----------------------------------------------------------------------------*/
E_VFSEntryType T_VFS::typeOf( T_VFSPath const& path ) const
{
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return E_VFSEntryType::NONE;
}
auto& pi( p< T_VFSPrivate_ >( ) );
const auto np( path.normalize( ) );
E_VFSEntryType rv( pi.userDir_->typeOf( np ) );
const auto nd( pi.drivers_.size( ) );
for ( uint32_t i = nd ; i != 0 && rv == E_VFSEntryType::NONE ; i -- ) {
rv = pi.drivers_[ i - 1 ]->typeOf( np );
}
return rv;
}
bool T_VFS::list( T_VFSPath const& path , T_Array< T_VFSPath >& output ) const
{
output.clear( );
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return false;
}
const auto np( path.normalize( ) );
auto& pi( p< T_VFSPrivate_ >( ) );
T_HashIndex index( 1024 , 256 , 256 );
bool rv( pi.userDir_->list( np , output , index ) );
const auto nd( pi.drivers_.size( ) );
for ( uint32_t i = nd ; i != 0 ; i -- ) {
rv = pi.drivers_[ i - 1 ]->list( np , output , index ) || rv;
}
return rv;
}
OP_InputStream T_VFS::read( T_VFSPath const& path ) const
{
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return OP_InputStream( );
}
const auto np( path.normalize( ) );
auto& pi( p< T_VFSPrivate_ >( ) );
OP_InputStream rv( pi.userDir_->read( np ) );
const auto nd( pi.drivers_.size( ) );
for ( uint32_t i = nd ; i != 0 && !rv ; i -- ) {
rv = pi.drivers_[ i - 1 ]->read( np );
}
return rv;
}
OP_File T_VFS::file( T_VFSPath const& path ) const
{
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return OP_File( );
}
const auto np( path.normalize( ) );
auto& pi( p< T_VFSPrivate_ >( ) );
OP_File rv( pi.userDir_->file( np ) );
const auto nd( pi.drivers_.size( ) );
for ( uint32_t i = nd ; i != 0 && !rv ; i -- ) {
rv = pi.drivers_[ i - 1 ]->file( np );
}
return rv;
}
/*----------------------------------------------------------------------------*/
OP_File T_VFS::file( T_VFSPath const& path , E_FileMode mode ) const
{
if ( mode == E_FileMode::READ_ONLY ) {
return file( path );
} else if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return OP_File( );
} else {
return dynamic_cast< T_VFSUserDirectory_* >(
p< T_VFSPrivate_ >( ).userDir_.get( ) )
->file( path.normalize( ) , mode );
}
}
OP_OutputStream T_VFS::write( T_VFSPath const& path ) const
{
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return OP_OutputStream( );
} else {
return dynamic_cast< T_VFSUserDirectory_* >(
p< T_VFSPrivate_ >( ).userDir_.get( ) )
->write( path.normalize( ) );
}
}
bool T_VFS::mkdir( T_VFSPath const& path ) const
{
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return false;
} else {
return dynamic_cast< T_VFSUserDirectory_* >(
p< T_VFSPrivate_ >( ).userDir_.get( ) )
->mkdir( path.normalize( ) );
}
}
bool T_VFS::rmdir( T_VFSPath const& path ) const
{
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return false;
} else {
return dynamic_cast< T_VFSUserDirectory_* >(
p< T_VFSPrivate_ >( ).userDir_.get( ) )
->rmdir( path.normalize( ) );
}
}
bool T_VFS::rm( T_VFSPath const& path ) const
{
if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
return false;
} else {
return dynamic_cast< T_VFSUserDirectory_* >(
p< T_VFSPrivate_ >( ).userDir_.get( ) )
->rm( path.normalize( ) );
}
}
bool T_VFS::move( T_VFSPath const& from , T_VFSPath const& to ) const
{
if ( from.type( ) != E_VFSPathType::ABSOLUTE ) {
return false;
}
const auto nFrom( from.normalize( ) );
if ( from.elements( ) == 0 ) {
return false;
}
T_VFSPath nTo;
if ( to.type( ) == E_VFSPathType::ABSOLUTE ) {
nTo = to.normalize( );
} else if ( to.type( ) == E_VFSPathType::RELATIVE ) {
nTo = T_VFSPath( nFrom , T_VFSPath( ".." , to ) ).normalize( );
} else {
return false;
}
if ( nTo.elements( ) == 0 ) {
return false;
}
return dynamic_cast< T_VFSUserDirectory_* >(
p< T_VFSPrivate_ >( ).userDir_.get( ) )
->move( nFrom , nTo );
}

614
src/VFSDrivers.cc Normal file
View file

@ -0,0 +1,614 @@
/******************************************************************************/
/* VIRTUAL FILE SYSTEM - NON-ESSENTIAL DRIVERS ********************************/
/******************************************************************************/
#include <lw/lib/VFSDrivers.hh>
#include <lw/lib/Threading.hh>
#include <lw/lib/MemoryStreams.hh>
#include <lw/lib/BinaryStreams.hh>
using namespace lw;
/*= T_VFSDataIndexPrivate_ ===================================================*/
namespace {
struct T_VFSDataIndexPrivate_
{
// Information about an entry.
struct T_EntryInfo {
uint32_t index;
bool isFile;
};
const uint32_t magic_;
T_KeyValueTable< T_VFSPath , T_EntryInfo > entries_;
T_MultiArray< T_String > dirEntries_;
T_Array< T_VFSDataIndex::T_FileInfo > files_;
// ---------------------------------------------------------------------
explicit T_VFSDataIndexPrivate_(
uint32_t magic );
// ---------------------------------------------------------------------
// Read the index from a stream
bool readIndex(
T_BinaryReader& reader );
// Free the index
void free( );
// ---------------------------------------------------------------------
// Get the type of an entry
E_VFSEntryType typeOf( T_VFSPath const& path ) const;
// List a directory
bool list(
T_VFSPath const& path ,
T_Array< T_VFSPath >& values ,
T_HashIndex& index ) const;
// Returns the file information record for a given path
T_VFSDataIndex::T_FileInfo const* getFileInfo(
T_VFSPath const& path ) const;
};
} // namespace
/*----------------------------------------------------------------------------*/
inline T_VFSDataIndexPrivate_::T_VFSDataIndexPrivate_(
uint32_t magic )
: magic_( magic )
{ }
/*----------------------------------------------------------------------------*/
inline bool T_VFSDataIndexPrivate_::readIndex(
T_BinaryReader& reader )
{
/*
* FIXME clean this up later
*
* Format:
*
* magic
* version
* root directory
* other directories
* file data
*
* a directory:
* number of entries
* list of entries
*
* a directory entry:
* Entry's name (T_String)
* is file? (0/1)
* if it's a file:
* size (uint32_t)
* offset relative to start of data (uint32_t)
*
* size of a directory entry:
* size( name ) + 4 + isFile ? 8 : 0
*
* size of a directory list:
* 4 + sum( size( entry ) for all entries )
*
*/
if ( reader.read< uint32_t >( ) != magic_ ) {
return false;
}
if ( reader.read< uint32_t >( ) != 0 ) {
return false;
}
const auto inSize( reader.stream( ).size( ) );
T_RingBuffer< T_VFSPath > directories;
directories.put( T_VFSPath( "/" ) );
entries_.add( T_VFSPath( "/" ) , T_EntryInfo{ 0 , false } );
uint32_t dirReserved( 1 );
T_VFSPath current;
while ( directories.size( ) ) {
directories.readNext( current );
dirEntries_.next( );
const auto nEntries( reader.read< uint32_t >( ) );
for ( uint32_t i = 0 ; i < nEntries ; i ++ ) {
T_String name( reader.read< T_String >( ) );
const T_VFSPath path( current , name );
const bool isFile( reader.read< uint8_t >( ) );
const uint32_t index( isFile ? files_.size( ) : dirReserved );
dirEntries_.add( name );
if ( !entries_.add( path , T_EntryInfo{ index , isFile } ) ) {
return false;
}
if ( isFile ) {
const auto size( reader.read< uint32_t >( ) );
const auto offset( reader.read< uint32_t >( ) );
if ( offset >= inSize || offset + size > inSize ) {
return false;
}
files_.add( T_VFSDataIndex::T_FileInfo{ size , offset } );
} else {
directories.put( path );
dirReserved ++;
}
}
}
return dirReserved == dirEntries_.size( );
}
inline void T_VFSDataIndexPrivate_::free( )
{
entries_.free( );
dirEntries_.free( );
files_.free( );
}
/*----------------------------------------------------------------------------*/
inline E_VFSEntryType T_VFSDataIndexPrivate_::typeOf(
T_VFSPath const& path ) const
{
T_EntryInfo const* eptr( entries_.get( path ) );
if ( eptr == nullptr ) {
return E_VFSEntryType::NONE;
}
return eptr->isFile ? E_VFSEntryType::FILE : E_VFSEntryType::DIRECTORY;
}
inline bool T_VFSDataIndexPrivate_::list(
T_VFSPath const& path ,
T_Array< T_VFSPath >& values ,
T_HashIndex& index ) const
{
T_EntryInfo const* eptr( entries_.get( path ) );
if ( eptr == nullptr || eptr->isFile ) {
return false;
}
const auto first( dirEntries_.firstOf( eptr->index ) );
const auto size( dirEntries_.sizeOf( eptr->index ) );
for ( uint32_t i = 0 ; i < size ; i ++ ) {
const T_VFSPath p( dirEntries_[ i + first ] );
const uint32_t hash( ComputeHash( p ) );
uint32_t idx( index.first( hash ) );
while ( idx != T_HashIndex::INVALID_INDEX ) {
if ( values[ idx ] == p ) {
break;
}
idx = index.next( idx );
}
if ( idx == T_HashIndex::INVALID_INDEX ) {
index.add( hash );
values.add( p );
}
}
return true;
}
inline T_VFSDataIndex::T_FileInfo const* T_VFSDataIndexPrivate_::getFileInfo(
T_VFSPath const& path ) const
{
T_EntryInfo const* eptr( entries_.get( path ) );
if ( eptr == nullptr || !eptr->isFile ) {
return nullptr;
}
return &files_[ eptr->index ];
}
/*= T_VFSDataIndex ===========================================================*/
T_VFSDataIndex::T_VFSDataIndex(
uint32_t magic )
: A_PrivateImplementation( new T_VFSDataIndexPrivate_( magic ) )
{ }
/*----------------------------------------------------------------------------*/
bool T_VFSDataIndex::readIndex(
T_BinaryReader& reader )
{
return p< T_VFSDataIndexPrivate_ >( ).readIndex( reader );
}
void T_VFSDataIndex::free( )
{
return p< T_VFSDataIndexPrivate_ >( ).free( );
}
/*----------------------------------------------------------------------------*/
E_VFSEntryType T_VFSDataIndex::typeOf(
T_VFSPath const& path ) const
{
return p< T_VFSDataIndexPrivate_ >( ).typeOf( path );
}
bool T_VFSDataIndex::list(
T_VFSPath const& path ,
T_Array< T_VFSPath >& values ,
T_HashIndex& index ) const
{
return p< T_VFSDataIndexPrivate_ >( ).list( path , values , index );
}
T_VFSDataIndex::T_FileInfo const* T_VFSDataIndex::getFileInfo(
T_VFSPath const& path ) const
{
return p< T_VFSDataIndexPrivate_ >( ).getFileInfo( path );
}
/*= T_VFSRomDriverStream_ ====================================================*/
namespace lw {
M_CLASS_POINTERS( VFSRomDriverStream_ );
class T_VFSRomDriverStream_ : public A_InputStream
{
private:
RP_VFSRomDriverStream_& head_;
T_ReadWriteMutex& mutex_;
RP_VFSRomDriverStream_ prev_;
RP_VFSRomDriverStream_ next_;
OP_MemoryInputStream actual_;
public:
T_VFSRomDriverStream_( ) = delete;
T_VFSRomDriverStream_( T_VFSRomDriverStream_ const& ) = delete;
T_VFSRomDriverStream_( T_VFSRomDriverStream_&& ) = delete;
T_VFSRomDriverStream_( RP_VFSRomDriverStream_& head ,
T_ReadWriteMutex& mutex ,
OP_MemoryInputStream&& stream );
~T_VFSRomDriverStream_( );
size_t read( void* data , size_t size ) override;
void disable( );
};
}
/*----------------------------------------------------------------------------*/
T_VFSRomDriverStream_::T_VFSRomDriverStream_( RP_VFSRomDriverStream_& head ,
T_ReadWriteMutex& mutex ,
OP_MemoryInputStream&& stream )
: A_InputStream( stream->position( ) , stream->size( ) ) ,
head_( head ) , mutex_( mutex ) ,
prev_( nullptr ) , next_( head ) ,
actual_( std::move( stream ) )
{
head = this;
if ( next_ ) {
next_->prev_ = this;
}
}
T_VFSRomDriverStream_::~T_VFSRomDriverStream_( )
{
T_WriteLock lock( mutex_ );
disable( );
}
size_t T_VFSRomDriverStream_::read( void* data , size_t size )
{
T_ReadLock lock( mutex_ );
if ( actual_ ) {
return actual_->read( data , size );
} else {
throw X_StreamError( E_StreamError::UNAVAILABLE );
}
}
void T_VFSRomDriverStream_::disable( )
{
actual_.clear( );
if ( prev_ == nullptr ) {
head_ = next_;
} else {
prev_->next_ = next_;
}
if ( next_ != nullptr ) {
next_->prev_ = prev_;
}
}
/*= T_VFSRomDriverPrivate_ ===================================================*/
struct T_VFSRomDriverPrivate_
{
uint8_t const* data_;
uint32_t size_;
T_VFSDataIndex index_;
T_ReadWriteMutex mutex_;
T_VFSRomDriverStream_* streams_;
T_VFSRomDriverPrivate_(
uint8_t const* const data ,
uint32_t const size );
};
/*----------------------------------------------------------------------------*/
inline T_VFSRomDriverPrivate_::T_VFSRomDriverPrivate_(
uint8_t const* const data ,
uint32_t const size )
: data_( data ) , size_( size ) ,
index_( T_VFSRomDriver::C_MAGIC ) ,
streams_( nullptr )
{
assert( data_ != nullptr );
assert( size_ > 0 );
}
/*= T_VFSRomDriver ===========================================================*/
constexpr uint32_t T_VFSRomDriver::C_MAGIC;
/*----------------------------------------------------------------------------*/
T_VFSRomDriver::T_VFSRomDriver(
uint8_t const* const data ,
uint32_t const size )
: A_PrivateImplementation( new T_VFSRomDriverPrivate_( data , size ) )
{ }
/*----------------------------------------------------------------------------*/
uint8_t const* T_VFSRomDriver::data( ) const
{
return p< T_VFSRomDriverPrivate_ >( ).data_;
}
uint32_t T_VFSRomDriver::size( ) const
{
return p< T_VFSRomDriverPrivate_ >( ).size_;
}
/*----------------------------------------------------------------------------*/
bool T_VFSRomDriver::init( )
{
auto& pi( p< T_VFSRomDriverPrivate_ >( ) );
T_MemoryInputStream input( pi.data_ , pi.size_ );
T_BinaryReader reader( input , E_Endian::NATIVE );
bool rv;
try {
rv = pi.index_.readIndex( reader );
} catch ( X_StreamError const& ) {
rv = false;
}
if ( !rv ) {
pi.index_.free( );
}
return rv;
}
void T_VFSRomDriver::shutdown( )
{
auto& pi( p< T_VFSRomDriverPrivate_ >( ) );
T_WriteLock lock( pi.mutex_ );
while ( pi.streams_ != nullptr ) {
pi.streams_->disable( );
}
pi.index_.free( );
}
/*----------------------------------------------------------------------------*/
E_VFSEntryType T_VFSRomDriver::typeOf( T_VFSPath const& path )
{
auto& pi( p< T_VFSRomDriverPrivate_ >( ) );
T_ReadLock lock( pi.mutex_ );
return pi.index_.typeOf( path );
}
bool T_VFSRomDriver::list( T_VFSPath const& path , T_Array< T_VFSPath >& values , T_HashIndex& index )
{
auto& pi( p< T_VFSRomDriverPrivate_ >( ) );
T_ReadLock lock( pi.mutex_ );
return pi.index_.list( path , values , index );
}
OP_InputStream T_VFSRomDriver::read( T_VFSPath const& path )
{
auto& pi( p< T_VFSRomDriverPrivate_ >( ) );
T_ReadLock lock( pi.mutex_ );
T_VFSDataIndex::T_FileInfo const* p( pi.index_.getFileInfo( path ) );
if ( p == nullptr ) {
return OP_InputStream( );
}
T_WriteLock wLock( lock.upgrade( ) );
return NewOwned< T_VFSRomDriverStream_ >( pi.streams_ , pi.mutex_ ,
NewOwned< T_MemoryInputStream >( pi.data_ + p->offset , p->size ) );
}
/*= T_VFSDataFileDriverStream_ ===============================================*/
namespace lw {
M_CLASS_POINTERS( VFSDataFileDriverStream_ );
class T_VFSDataFileDriverStream_ : public A_InputStream
{
private:
RP_VFSDataFileDriverStream_& head_;
T_Mutex& mutex_;
RP_VFSDataFileDriverStream_ prev_;
RP_VFSDataFileDriverStream_ next_;
OP_FileInputStream actual_;
public:
T_VFSDataFileDriverStream_( ) = delete;
T_VFSDataFileDriverStream_( T_VFSDataFileDriverStream_ const& ) = delete;
T_VFSDataFileDriverStream_( T_VFSDataFileDriverStream_&& ) = delete;
T_VFSDataFileDriverStream_( RP_VFSDataFileDriverStream_& head , T_Mutex& mutex ,
OP_FileInputStream&& stream );
~T_VFSDataFileDriverStream_( );
size_t read( void* data , size_t size ) override;
void disable( );
};
}
/*----------------------------------------------------------------------------*/
T_VFSDataFileDriverStream_::T_VFSDataFileDriverStream_(
RP_VFSDataFileDriverStream_& head , T_Mutex& mutex ,
OP_FileInputStream&& stream )
: A_InputStream( stream->position( ) , stream->size( ) ) ,
head_( head ) , mutex_( mutex ) ,
prev_( nullptr ) , next_( head ) ,
actual_( std::move( stream ) )
{
head = this;
if ( next_ ) {
next_->prev_ = this;
}
}
T_VFSDataFileDriverStream_::~T_VFSDataFileDriverStream_( )
{
T_ScopeLock lock( mutex_ );
disable( );
}
size_t T_VFSDataFileDriverStream_::read( void* data , size_t size )
{
T_ScopeLock lock( mutex_ );
if ( actual_ ) {
return actual_->read( data , size );
} else {
throw X_StreamError( E_StreamError::UNAVAILABLE );
}
}
void T_VFSDataFileDriverStream_::disable( )
{
actual_.clear( );
if ( prev_ == nullptr ) {
head_ = next_;
} else {
prev_->next_ = next_;
}
if ( next_ != nullptr ) {
next_->prev_ = prev_;
}
}
/*= T_VFSDataFileDriverPrivate_ ==============================================*/
namespace {
struct T_VFSDataFileDriverPrivate_
{
T_Mutex mutex_;
OP_File file_;
T_VFSDataIndex index_;
T_VFSDataFileDriverStream_* streams_;
explicit T_VFSDataFileDriverPrivate_(
OP_File&& file );
};
} // namespace
/*----------------------------------------------------------------------------*/
T_VFSDataFileDriverPrivate_::T_VFSDataFileDriverPrivate_(
OP_File&& file )
: file_( std::move( file ) ) ,
index_( T_VFSDataFileDriver::C_MAGIC ) ,
streams_( nullptr )
{
assert( file_ );
}
/*= T_VFSDataFileDriver ======================================================*/
T_VFSDataFileDriver::T_VFSDataFileDriver( OP_File&& file )
: A_PrivateImplementation( new T_VFSDataFileDriverPrivate_( std::move( file ) ) )
{ }
/*----------------------------------------------------------------------------*/
T_File const& T_VFSDataFileDriver::dataFile( ) const
{
return *( p< T_VFSDataFileDriverPrivate_ >( ).file_ );
}
/*----------------------------------------------------------------------------*/
bool T_VFSDataFileDriver::init( )
{
auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) );
bool rv;
try {
T_FileInputStream input( *pi.file_ , 0 );
T_BinaryReader reader( input , E_Endian::NATIVE );
rv = pi.index_.readIndex( reader );
} catch ( X_StreamError const& ) {
rv = false;
}
if ( !rv ) {
pi.index_.free( );
}
return rv;
}
void T_VFSDataFileDriver::shutdown( )
{
auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) );
T_ScopeLock lock( pi.mutex_ );
while ( pi.streams_ != nullptr ) {
pi.streams_->disable( );
}
pi.index_.free( );
pi.file_->close( );
}
/*----------------------------------------------------------------------------*/
E_VFSEntryType T_VFSDataFileDriver::typeOf( T_VFSPath const& path )
{
auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) );
T_ScopeLock lock( pi.mutex_ );
return pi.index_.typeOf( path );
}
bool T_VFSDataFileDriver::list( T_VFSPath const& path , T_Array< T_VFSPath >& values , T_HashIndex& index )
{
auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) );
T_ScopeLock lock( pi.mutex_ );
return pi.index_.list( path , values , index );
}
OP_InputStream T_VFSDataFileDriver::read( T_VFSPath const& path )
{
auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) );
T_ScopeLock lock( pi.mutex_ );
T_VFSDataIndex::T_FileInfo const* p( pi.index_.getFileInfo( path ) );
if ( p == nullptr ) {
return OP_InputStream( );
}
pi.file_->position( p->offset );
return NewOwned< T_VFSDataFileDriverStream_ >( pi.streams_ , pi.mutex_ ,
NewOwned< T_FileInputStream >( *pi.file_ , 0 , p->size ) );
}

36
src/files.mk Normal file
View file

@ -0,0 +1,36 @@
MINLIB_SOURCES = \
src/lib/Files.cc \
src/lib/HashIndex.cc \
src/lib/MemoryStreams.cc \
src/lib/Pointers.cc \
src/lib/SRDBinary.cc \
src/lib/SRDData.cc \
src/lib/SRDDefinitions.cc \
src/lib/SRDIO.cc \
src/lib/SRDParser.cc \
src/lib/SRDParserConfig.cc \
src/lib/SRDPreproc.cc \
src/lib/SRDPPCommands.cc \
src/lib/SRDText.cc \
src/lib/Streams.cc \
src/lib/Strings.cc \
src/lib/TemplateInstantiation.cc \
src/lib/Utilities.cc \
src/lib/VFS.cc \
src/lib/VFSDrivers.cc
# FIXME: VFSDrivers shouldn't be in the minlib
# (but we need it for srdpp at this point)
LIB_SOURCES = \
$(MINLIB_SOURCES) \
src/lib/Console.cc \
src/lib/ConsoleLogWriter.cc \
src/lib/CwdFileLogger.cc \
src/lib/DynLib.cc \
src/lib/GameLoop.cc \
src/lib/LW.cc \
src/lib/Log.cc \
src/lib/Messages.cc \
src/lib/ModInterface.cc \
src/lib/Mods.cc \
src/lib/TextFileLogger.cc