/* =========================================================================== Copyright (C) 2023 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // tiki_parse.cpp : TIKI Script #include "q_shared.h" #include "qcommon.h" #include "tiki.h" TikiScript *TikiScript::currentScript; TikiScript::~TikiScript() { Close(); } TikiScript::TikiScript() { error = false; buffer = NULL; script_p = NULL; end_p = NULL; line = 0; releaseBuffer = false; tokenready = false; memset( token, 0, sizeof( token ) ); include = NULL; parent = NULL; nummacros = 0; mark_pos = 0; path[ 0 ] = 0; } /* ============== = = Close = ============== */ void TikiScript::Close( void ) { Uninclude(); if( this->releaseBuffer ) { if( this->buffer ) TIKI_Free( this->buffer ); } buffer = NULL; script_p = NULL; end_p = NULL; line = 0; error = false; releaseBuffer = false; tokenready = false; nummacros = 0; memset( token, 0, sizeof( token ) ); mark_pos = 0; } /* ============== = = Filename = ============== */ const char *TikiScript::Filename() { return filename; } /* ============== = = GetLineNumber = ============== */ int TikiScript::GetLineNumber() { return line; } /* ============== = = Reset = ============== */ void TikiScript::Reset( void ) { Uninclude(); nummacros = 0; error = 0; line = 1; tokenready = 0; mark_pos = 0; script_p = buffer; } /* ============== = = SkipToEOL = ============== */ qboolean TikiScript::SkipToEOL( void ) { if( script_p >= end_p ) { return true; } while( *script_p != TOKENEOL ) { if( script_p >= end_p ) { return true; } script_p++; } return false; } /* ============== = = SafeCheckOverflow = ============== */ qboolean TikiScript::SafeCheckOverflow( void ) { return script_p >= end_p; } /* ============== = = CheckOverflow = ============== */ void TikiScript::CheckOverflow( void ) { if( script_p >= end_p ) { this->error = 1; TIKI_DPrintf( "End of tiki file reached prematurely reading %s\n", Filename() ); TIKI_DPrintf( " This may be caused by having a tiki file command at the end of the file\n" ); TIKI_DPrintf( " without an 'end' at the end of the tiki file.\n" ); } } /* ============== = = SkipWhiteSpace = ============== */ void TikiScript::SkipWhiteSpace( qboolean crossline ) { // // skip space // while( !SafeCheckOverflow() ) { if( *script_p <= TOKENSPACE ) { if( *script_p == TOKENEOL ) { if( !crossline ) { TIKI_DPrintf( "Line %i is incomplete in file %s\n", line, Filename() ); return; } line++; } script_p++; } else { if( !AtComment() ) { return; } if( AtExtendedComment() ) { SkipExtendedComment(); } else { if( !crossline ) { TIKI_DPrintf( "Line %i is incomplete in file %s\n", line, Filename() ); return; } SkipToEOL(); } } } } /* ============== = = AtComment = ============== */ qboolean TikiScript::AtComment( void ) { if( script_p >= end_p ) { return false; } if( *script_p == TOKENCOMMENT ) { return true; } if( *script_p == TOKENCOMMENT2 ) { return true; } // Two or more character comment specifiers if( ( script_p + 1 ) >= end_p ) { return false; } if( ( *script_p == '/' ) && ( ( *( script_p + 1 ) == '/' ) || ( *( script_p + 1 ) == '*' ) ) ) { return true; } return false; } /* ============== = = AtExtendedComment = ============== */ qboolean TikiScript::AtExtendedComment( void ) { if( script_p >= end_p ) { return false; } // Two or more character comment specifiers if( ( script_p + 1 ) >= end_p ) { return false; } if( ( *( script_p ) == '/' ) && ( *( script_p + 1 ) == '*' ) ) { return true; } return false; } /* ============== = = SkipExtendedComment = ============== */ void TikiScript::SkipExtendedComment( void ) { CheckOverflow(); if( error ) { return; } while( ( script_p + 1 ) < end_p ) { if( ( *( script_p ) == '*' ) && ( *( script_p + 1 ) == '/' ) ) { script_p = script_p + 2; return; } if( *script_p == '\n' ) line++; script_p++; } } /* ============== = = AtCommand = ============== */ qboolean TikiScript::AtCommand( void ) { if( script_p >= end_p ) { return false; } // Two or more character comment specifiers if( ( script_p + 1 ) >= end_p ) { return false; } if( ( *script_p == '$' ) && ( *( script_p + 1 ) != '$' ) ) { return true; } return false; } /* ============== = = Uninclude = ============== */ void TikiScript::Uninclude( void ) { if( include ) { currentScript = this; include->Close(); delete include; include = NULL; } } /* ============== = = AddMacro = ============== */ void TikiScript::AddMacro( const char *name, const char *expansion ) { int i; for( i = 0; i < nummacros; i++ ) { if( !stricmp( name, macros[ i ].name ) ) return; } if( nummacros >= TIKI_MAXMACROS ) { TIKI_DPrintf( "Too many %cdefine in file %s\n", TOKENSPECIAL, Filename() ); } else { Q_strncpyz( macros[ nummacros ].name, name, sizeof( macros[ nummacros ].name ) ); Q_strncpyz( macros[ nummacros ].macro, expansion, sizeof( macros[ nummacros ].macro ) ); nummacros++; } // Add the macro to parent if( parent ) parent->AddMacro( name, expansion ); } /* ============== = = ProcessCommand = ============== */ qboolean TikiScript::ProcessCommand( qboolean crossline ) { char dummy; int i; size_t len; char command[ 256 ]; char argument1[ 256 ]; char argument2[ 256 ]; sscanf( script_p, "%c%s %s %s\n", &dummy, command, argument1, argument2 ); if( !stricmp( command, "define" ) ) { AddMacro( argument1, argument2 ); SkipToEOL(); SkipWhiteSpace( crossline ); } else if( !stricmp( command, "include" ) ) { SkipToEOL(); SkipWhiteSpace( crossline ); include = new TikiScript; if( include->LoadFile( argument1, qfalse ) ) { include->SkipNonToken( crossline ); for( i = 0; i < nummacros; i++ ) { include->AddMacro( macros[ i ].name, macros[ i ].macro ); } include->parent = this; } else { TIKI_Error( "^~^~^ Cannot find include file '%s' in %s on line %d\n", argument1, Filename(), GetLineNumber() ); Uninclude(); } } else if( !stricmp( command, "path" ) ) { Q_strncpyz( path, argument1, sizeof( path ) ); len = strlen( path ); if( path[ len - 1 ] != '/' && path[ len - 1 ] != '\\' ) { Q_strcat( path, sizeof( path ), "/" ); } SkipToEOL(); SkipWhiteSpace( crossline ); } else { return false; } return true; } /* ============== = = Completed = ============== */ qboolean TikiScript::Completed( void ) { return script_p >= end_p; } /* ============== = = FindMacro = ============== */ const char *TikiScript::FindMacro( const char *macro ) { int i; for( i = 0; i < nummacros; i++ ) { if( !stricmp( macro, macros[ i ].name ) ) return macros[ i ].macro; } return NULL; } /* ============== = = SkipNonToken = ============== */ void TikiScript::SkipNonToken( qboolean crossline ) { // // skip space and comments // SkipWhiteSpace( crossline ); while( !error ) { if( !AtCommand() ) break; if( !ProcessCommand( crossline ) ) break; if( include ) break; } } /* ============== = = TokenAvailable = ============== */ qboolean TikiScript::TokenAvailable( qboolean crossline ) { if( include ) { qboolean ret = include->TokenAvailable( crossline ); if( ret || !crossline || !include->Completed() ) return ret; Uninclude(); } if( tokenready ) { return true; } while( !SafeCheckOverflow() ) { if( *script_p <= TOKENSPACE ) { if( *script_p == TOKENEOL ) { if( !crossline ) { return false; } line++; } script_p++; } else { if( !AtComment() ) return true; if( AtExtendedComment() ) { SkipExtendedComment(); } else { if( !crossline ) return false; SkipToEOL(); } } } return false; } /* ============== = = CommentAvailable = ============== */ qboolean TikiScript::CommentAvailable( qboolean crossline ) { const char *searchptr; searchptr = script_p; if( searchptr >= end_p ) { return false; } while( *searchptr <= TOKENSPACE ) { if( ( *searchptr == TOKENEOL ) && ( !crossline ) ) { return false; } searchptr++; if( searchptr >= end_p ) { return false; } } return true; } /* ============== = = UnGetToken = ============== */ void TikiScript::UnGetToken( void ) { if( include ) { include->UnGetToken(); } else { tokenready = true; } } /* ============== = = AtString = ============== */ qboolean TikiScript::AtString( qboolean crossline ) { TikiScript *i; // // skip space // i = this; while( 1 ) { i->SkipNonToken( crossline ); if( !i->include ) break; i = i->include; } return ( !i->error ) && ( *i->script_p == '"' ); } /* ============== = = GetToken = ============== */ const char *TikiScript::GetToken( qboolean crossline ) { TikiScript *i; const char *macro_start; const char *macro_end; char *token_p; int len; const char *subst; char macro[ 256 ]; char temptoken[ 256 ]; i = this; while( 1 ) { if( i->include ) { const char *ret; ret = i->include->GetToken( crossline ); if( *ret || !crossline || !i->include->Completed() ) { return ret; } i->Uninclude(); } if( i->tokenready ) { i->tokenready = false; currentScript = i; return i->token; } i->SkipNonToken( crossline ); if( i->error ) { return ""; } if( !i->include ) { break; } i = i->include; } token_p = ( char * )i->script_p; if( *token_p == '"' ) { currentScript = i; return i->GetString( crossline ); } macro_start = NULL; macro_end = NULL; token_p = i->token; while( ( *i->script_p > TOKENSPACE ) && !i->AtComment() ) { if( ( *i->script_p == '\\' ) && ( i->script_p < ( end_p - 1 ) ) ) { i->script_p++; switch( *i->script_p ) { case 'n': *token_p++ = '\n'; break; case 'r': *token_p++ = '\n'; break; case '\'': *token_p++ = '\''; break; case '\"': *token_p++ = '\"'; break; case '\\': *token_p++ = '\\'; break; default: *token_p++ = *i->script_p; break; } i->script_p++; } else { if( *i->script_p == '$' ) { if( macro_start ) macro_end = token_p; else macro_start = token_p; } *token_p++ = *i->script_p++; } if( token_p == &i->token[ TIKI_MAXTOKEN ] ) { TIKI_DPrintf( "Token too large on line %i in file %s\n", i->line, i->Filename() ); break; } if( i->script_p == i->end_p ) { break; } } *token_p = 0; if( macro_start ) { if( !macro_end ) macro_end = token_p; len = ( macro_end - 1 ) - ( macro_start + 1 ); if( len == -1 ) { if( macro_start - i->token != 0 ) memcpy( temptoken, i->token, macro_start - i->token ); temptoken[ macro_start - i->token ] = '$'; temptoken[ macro_start - i->token + 1 ] = 0; if( *( macro_end + 1 ) ) { strcat( temptoken, macro_end + 1 ); } Q_strncpyz( i->token, temptoken, sizeof( i->token ) ); } else { memcpy( macro, ( macro_start + 1 ), len + 1 ); macro[ len + 1 ] = 0; subst = i->FindMacro( macro ); if( subst ) { if( macro_start - i->token != 0 ) memcpy( temptoken, i->token, macro_start - i->token ); temptoken[ macro_start - i->token + 1 ] = 0; strcat( temptoken, subst ); if( *( macro_end + 1 ) ) { strcat( temptoken, macro_end + 1 ); } Q_strncpyz( i->token, temptoken, sizeof( i->token ) ); } } } i->currentScript = i; return i->token; } /* ============== = = GetLine = ============== */ const char *TikiScript::GetLine( qboolean crossline ) { TikiScript *i; const char *start; int size; i = this; while( 1 ) { if( i->include ) { const char *ret; ret = i->include->GetToken( crossline ); if( *ret || !crossline || !i->include->Completed() ) { return ret; } i->Uninclude(); } if( i->tokenready ) { i->tokenready = false; return i->token; } if( !i->include ) break; i = i->include; } if( i->error ) { return ""; } // // copy token // start = i->script_p; SkipToEOL(); size = i->script_p - start; if( size < ( TIKI_MAXTOKEN - 1 ) ) { memcpy( i->token, start, size ); i->token[ size ] = '\0'; } else { TIKI_DPrintf( "Token too large on line %i in file %s\n", i->line, i->filename ); } return i->token; } /* ============== = = GetAndIgnoreLine = ============== */ const char *TikiScript::GetAndIgnoreLine( qboolean crossline ) { const char *start; int size; if( include ) { const char *ret; ret = include->GetAndIgnoreLine( crossline ); if( *ret || !crossline || !include->Completed() ) return ret; Uninclude(); } if( tokenready ) { tokenready = false; return token; } // // skip space // SkipWhiteSpace( crossline ); if( error ) { return ""; } // // copy token // start = script_p; SkipToEOL(); size = script_p - start; if( size < ( TIKI_MAXTOKEN - 1 ) ) { memcpy( token, start, size ); token[ size ] = '\0'; } else { TIKI_DPrintf( "Token too large on line %i in file %s\n", line, filename ); } return token; } /* ============== = = GetRaw = ============== */ const char *TikiScript::GetRaw( void ) { const char *start; int size; if( include ) { const char *ret; ret = include->GetRaw(); if( *ret || !include->Completed() ) return ret; Uninclude(); } // // skip white space // SkipWhiteSpace( true ); if( error ) { return ""; } // // copy token // start = script_p; SkipToEOL(); size = script_p - start; if( size < ( TIKI_MAXTOKEN - 1 ) ) { memset( token, 0, sizeof( token ) ); memcpy( token, start, size ); } else { TIKI_DPrintf( "Token too large on line %i in file %s\n", line, filename ); } return token; } /* ============== = = GetString = ============== */ const char *TikiScript::GetString( qboolean crossline ) { TikiScript *i; int startline; char *token_p; i = this; while( 1 ) { if( i->include ) { const char *ret; ret = i->include->GetString( crossline ); if( *ret || !crossline || !i->include->Completed() ) { return ret; } i->Uninclude(); } // is a token already waiting? if( i->tokenready ) { i->tokenready = false; return i->token; } i->SkipNonToken( crossline ); if( !i->include ) break; i = i->include; } if( i->error ) { return ""; } if( *i->script_p != '"' ) { TIKI_DPrintf( "Expecting string on line %i in file %s\n", i->line, i->filename ); return i->token; } i->script_p++; startline = i->line; token_p = i->token; while( *i->script_p != '"' ) { if( *i->script_p == TOKENEOL ) { TIKI_DPrintf( "Line %i is incomplete while reading string in file %s\n", i->line, i->filename ); return i->token; } if( ( *i->script_p == '\\' ) && ( i->script_p < ( end_p - 1 ) ) ) { i->script_p++; switch( *i->script_p ) { case 'n': *token_p++ = '\n'; break; case 'r': *token_p++ = '\n'; break; case '\'': *token_p++ = '\''; break; case '\"': *token_p++ = '\"'; break; case '\\': *token_p++ = '\\'; break; default: *token_p++ = *i->script_p; break; } i->script_p++; } else { *token_p++ = *i->script_p++; } if( i->script_p >= i->end_p ) { TIKI_DPrintf( "End of token file reached prematurely while reading string on\n" "line %d in file %s\n", startline, i->filename ); } if( token_p == &i->token[ TIKI_MAXTOKEN ] ) { TIKI_DPrintf( "String too large on line %i in file %s\n", i->line, i->filename ); } } *token_p = 0; // skip last quote i->script_p++; return i->token; } /* ============== = = GetSpecific = ============== */ qboolean TikiScript::GetSpecific( const char *string ) { if( include ) { qboolean ret = include->GetSpecific( string ); if( ret || !include->Completed() ) { return ret; } } do { if( !TokenAvailable( true ) ) { return false; } GetToken( true ); } while( strcmp( token, string ) ); return true; } /* ============== = = GetInteger = ============== */ int TikiScript::GetInteger( qboolean crossline ) { return atoi( GetToken( crossline ) ); } /* ============== = = GetDouble = ============== */ double TikiScript::GetDouble( qboolean crossline ) { return atof( GetToken( crossline ) ); } /* ============== = = GetFloat = ============== */ float TikiScript::GetFloat( qboolean crossline ) { return ( float )GetDouble( crossline ); } /* ============== = = GetVector = ============== */ void TikiScript::GetVector( qboolean crossline, float *vec ) { vec[ 0 ] = GetFloat( crossline ); vec[ 1 ] = GetFloat( crossline ); vec[ 2 ] = GetFloat( crossline ); } /* ============== = = LinesInFile = ============== */ int TikiScript::LinesInFile( void ) { qboolean temp_tokenready; const char *temp_script_p; int temp_line; char temp_token[ TIKI_MAXTOKEN ]; int numentries; temp_tokenready = tokenready; temp_script_p = script_p; temp_line = line; Q_strncpyz( temp_token, token, sizeof( temp_token ) ); numentries = 0; Reset(); while( TokenAvailable( true ) ) { GetLine( true ); numentries++; } tokenready = temp_tokenready; script_p = temp_script_p; line = temp_line; Q_strncpyz( token, temp_token, sizeof( token ) ); return numentries; } /* ============== = = Parse = ============== */ void TikiScript::Parse( char *data, int length, const char *name ) { Close(); buffer = data; Reset(); this->length = length; end_p = script_p + length; strncpy( filename, name, TIKI_MAXTOKEN ); } /* ============== = = LoadFile = ============== */ qboolean TikiScript::LoadFile( const char *name, qboolean quiet ) { int length; char *buf; Close(); length = TIKI_ReadFileEx( name, ( void ** )&buf, quiet ); if( length < 0 ) { if( !quiet ) TIKI_DPrintf( "Tiki:LoadFile Couldn't load %s\n", name ); return false; } // create our own space buffer = ( char * )TIKI_Alloc( length + 1 ); // copy the file over to our space memcpy( buffer, buf, length ); buffer[ length ] = 0; // free the file TIKI_FreeFile( buf ); Parse( buffer, length, name ); releaseBuffer = true; if( error ) { return false; } return true; } /* ============== = = Token = ============== */ const char *TikiScript::Token( void ) { if( include ) { return include->token; } else { return token; } } /* ============== = = MarkPos = ============== */ void TikiScript::MarkPos( void ) { mark[ mark_pos ].mark_script_p = script_p; mark[ mark_pos ].mark_tokenready = tokenready; Q_strncpyz( mark[ mark_pos ].mark_token, token, sizeof(mark[ mark_pos ].mark_token)); mark_pos++; } /* ============== = = ReturnToMark = ============== */ void TikiScript::ReturnToMark( void ) { if( mark_pos <= 0 ) { return; } mark_pos--; script_p = mark[ mark_pos ].mark_script_p; tokenready = mark[ mark_pos ].mark_tokenready; memcpy( token, mark[ mark_pos ].mark_token, sizeof( token ) ); } /* ============== = = ReplaceLineWithWhitespace = ============== */ void TikiScript::ReplaceLineWithWhitespace( bool deleteFromStartOfLine ) { char *change_p = ( char * )script_p; if( deleteFromStartOfLine && *( change_p - 1 ) != '\n' ) { while( *( change_p - 1 ) != TOKENEOL && change_p > buffer ) { change_p--; } } if( *change_p != TOKENEOL ) { if( change_p >= end_p ) { script_p = change_p; return; } do { *change_p++ = TOKENSPACE; } while( *change_p != TOKENEOL && change_p < end_p ); } if( change_p < end_p ) { script_p = change_p + 1; line++; } else { script_p = change_p; } } /* ============== = = GetParentToken = ============== */ const char *TikiScript::GetParentToken( void ) { if( parent ) return parent->token; else return ""; }