/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 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 3 of the License, or (at your option) any later version. Doom 3 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "idlib/Lib.h" #include "framework/Common.h" #include "idlib/Parser.h" //#define DEBUG_EVAL #define MAX_DEFINEPARMS 128 #define DEFINEHASHSIZE 2048 #define TOKEN_FL_RECURSIVE_DEFINE 1 define_t * idParser::globaldefines; /* ================ idParser::SetBaseFolder ================ */ void idParser::SetBaseFolder( const char *path) { idLexer::SetBaseFolder(path); } /* ================ idParser::AddGlobalDefine ================ */ int idParser::AddGlobalDefine( const char *string ) { define_t *define; define = idParser::DefineFromString(string); if (!define) { return false; } define->next = globaldefines; globaldefines = define; return true; } /* ================ idParser::RemoveGlobalDefine ================ */ int idParser::RemoveGlobalDefine( const char *name ) { define_t *d, *prev; for ( prev = NULL, d = idParser::globaldefines; d; prev = d, d = d->next ) { if ( !strcmp( d->name, name ) ) { break; } } if ( d ) { if ( prev ) { prev->next = d->next; } else { idParser::globaldefines = d->next; } idParser::FreeDefine( d ); return true; } return false; } /* ================ idParser::RemoveAllGlobalDefines ================ */ void idParser::RemoveAllGlobalDefines( void ) { define_t *define; for ( define = globaldefines; define; define = globaldefines ) { globaldefines = globaldefines->next; idParser::FreeDefine(define); } } /* =============================================================================== idParser =============================================================================== */ /* ================ idParser::PrintDefine ================ */ void idParser::PrintDefine( define_t *define ) { idLib::common->Printf("define->name = %s\n", define->name); idLib::common->Printf("define->flags = %d\n", define->flags); idLib::common->Printf("define->builtin = %d\n", define->builtin); idLib::common->Printf("define->numparms = %d\n", define->numparms); } /* ================ PC_PrintDefineHashTable ================ * / static void PC_PrintDefineHashTable(define_t **definehash) { int i; define_t *d; for (i = 0; i < DEFINEHASHSIZE; i++) { Log_Write("%4d:", i); for (d = definehash[i]; d; d = d->hashnext) { Log_Write(" %s", d->name); } Log_Write("\n"); } } */ /* ================ PC_NameHash ================ */ ID_INLINE int PC_NameHash( const char *name ) { int hash, i; hash = 0; for ( i = 0; name[i] != '\0'; i++ ) { hash += name[i] * (119 + i); } hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); return hash; } /* ================ idParser::AddDefineToHash ================ */ void idParser::AddDefineToHash( define_t *define, define_t **definehash ) { int hash; hash = PC_NameHash(define->name); define->hashnext = definehash[hash]; definehash[hash] = define; } /* ================ FindHashedDefine ================ */ define_t *idParser::FindHashedDefine( define_t **definehash, const char *name ) { define_t *d; int hash; hash = PC_NameHash(name); for ( d = definehash[hash]; d; d = d->hashnext ) { if ( !strcmp(d->name, name) ) { return d; } } return NULL; } /* ================ idParser::FindDefine ================ */ define_t *idParser::FindDefine( define_t *defines, const char *name ) { define_t *d; for ( d = defines; d; d = d->next ) { if ( !strcmp(d->name, name) ) { return d; } } return NULL; } /* ================ idParser::FindDefineParm ================ */ int idParser::FindDefineParm( define_t *define, const char *name ) { idToken *p; int i; i = 0; for ( p = define->parms; p; p = p->next ) { if ( (*p) == name ) { return i; } i++; } return -1; } /* ================ idParser::CopyDefine ================ */ define_t *idParser::CopyDefine( define_t *define ) { define_t *newdefine; idToken *token, *newtoken, *lasttoken; newdefine = (define_t *) Mem_Alloc(sizeof(define_t) + strlen(define->name) + 1); //copy the define name newdefine->name = (char *) newdefine + sizeof(define_t); strcpy(newdefine->name, define->name); newdefine->flags = define->flags; newdefine->builtin = define->builtin; newdefine->numparms = define->numparms; //the define is not linked newdefine->next = NULL; newdefine->hashnext = NULL; //copy the define tokens newdefine->tokens = NULL; for (lasttoken = NULL, token = define->tokens; token; token = token->next) { newtoken = new idToken(token); newtoken->next = NULL; if (lasttoken) lasttoken->next = newtoken; else newdefine->tokens = newtoken; lasttoken = newtoken; } //copy the define parameters newdefine->parms = NULL; for (lasttoken = NULL, token = define->parms; token; token = token->next) { newtoken = new idToken(token); newtoken->next = NULL; if (lasttoken) lasttoken->next = newtoken; else newdefine->parms = newtoken; lasttoken = newtoken; } return newdefine; } /* ================ idParser::FreeDefine ================ */ void idParser::FreeDefine( define_t *define ) { idToken *t, *next; //free the define parameters for (t = define->parms; t; t = next) { next = t->next; delete t; } //free the define tokens for (t = define->tokens; t; t = next) { next = t->next; delete t; } //free the define Mem_Free( define ); } /* ================ idParser::DefineFromString ================ */ define_t *idParser::DefineFromString( const char *string ) { idParser src; define_t *def; if ( !src.LoadMemory(string, strlen(string), "*defineString") ) { return NULL; } // create a define from the source if ( !src.Directive_define() ) { src.FreeSource(); return NULL; } def = src.CopyFirstDefine(); src.FreeSource(); //if the define was created succesfully return def; } /* ================ idParser::Error ================ */ void idParser::Error( const char *str, ... ) const { char text[MAX_STRING_CHARS]; va_list ap; va_start(ap, str); vsprintf(text, str, ap); va_end(ap); if ( idParser::scriptstack ) { idParser::scriptstack->Error( "%s", text ); } } /* ================ idParser::Warning ================ */ void idParser::Warning( const char *str, ... ) const { char text[MAX_STRING_CHARS]; va_list ap; va_start(ap, str); vsprintf(text, str, ap); va_end(ap); if ( idParser::scriptstack ) { idParser::scriptstack->Warning( "%s", text ); } } /* ================ idParser::PushIndent ================ */ void idParser::PushIndent( int type, int skip ) { indent_t *indent; indent = (indent_t *) Mem_Alloc(sizeof(indent_t)); indent->type = type; indent->script = idParser::scriptstack; indent->skip = (skip != 0); idParser::skip += indent->skip; indent->next = idParser::indentstack; idParser::indentstack = indent; } /* ================ idParser::PopIndent ================ */ void idParser::PopIndent( int *type, int *skip ) { indent_t *indent; *type = 0; *skip = 0; indent = idParser::indentstack; if (!indent) return; // must be an indent from the current script if (idParser::indentstack->script != idParser::scriptstack) { return; } *type = indent->type; *skip = indent->skip; idParser::indentstack = idParser::indentstack->next; idParser::skip -= indent->skip; Mem_Free( indent ); } /* ================ idParser::PushScript ================ */ void idParser::PushScript( idLexer *script ) { idLexer *s; for ( s = idParser::scriptstack; s; s = s->next ) { if ( !idStr::Icmp(s->GetFileName(), script->GetFileName()) ) { idParser::Warning( "'%s' recursively included", script->GetFileName() ); return; } } //push the script on the script stack script->next = idParser::scriptstack; idParser::scriptstack = script; } /* ================ idParser::ReadSourceToken ================ */ int idParser::ReadSourceToken( idToken *token ) { idToken *t; idLexer *script; int type, skip, changedScript; if ( !idParser::scriptstack ) { idLib::common->FatalError( "idParser::ReadSourceToken: not loaded" ); return false; } changedScript = 0; // if there's no token already available while( !idParser::tokens ) { // if there's a token to read from the script if ( idParser::scriptstack->ReadToken( token ) ) { token->linesCrossed += changedScript; // set the marker based on the start of the token read in if ( !marker_p ) { marker_p = token->whiteSpaceEnd_p; } return true; } // if at the end of the script if ( idParser::scriptstack->EndOfFile() ) { // remove all indents of the script while( idParser::indentstack && idParser::indentstack->script == idParser::scriptstack ) { idParser::Warning( "missing #endif" ); idParser::PopIndent( &type, &skip ); } changedScript = 1; } // if this was the initial script if ( !idParser::scriptstack->next ) { return false; } // remove the script and return to the previous one script = idParser::scriptstack; idParser::scriptstack = idParser::scriptstack->next; delete script; } // copy the already available token *token = idParser::tokens; // remove the token from the source t = idParser::tokens; idParser::tokens = idParser::tokens->next; delete t; return true; } /* ================ idParser::UnreadSourceToken ================ */ int idParser::UnreadSourceToken( idToken *token ) { idToken *t; t = new idToken(token); t->next = idParser::tokens; idParser::tokens = t; return true; } /* ================ idParser::ReadDefineParms ================ */ int idParser::ReadDefineParms( define_t *define, idToken **parms, int maxparms ) { define_t *newdefine; idToken token, *t, *last; int i, done, lastcomma, numparms, indent; if ( !idParser::ReadSourceToken( &token ) ) { idParser::Error( "define '%s' missing parameters", define->name ); return false; } if ( define->numparms > maxparms ) { idParser::Error( "define with more than %d parameters", maxparms ); return false; } for ( i = 0; i < define->numparms; i++ ) { parms[i] = NULL; } // if no leading "(" if ( token != "(" ) { idParser::UnreadSourceToken( &token ); idParser::Error( "define '%s' missing parameters", define->name ); return false; } // read the define parameters for ( done = 0, numparms = 0, indent = 1; !done; ) { if ( numparms >= maxparms ) { idParser::Error( "define '%s' with too many parameters", define->name ); return false; } parms[numparms] = NULL; lastcomma = 1; last = NULL; while( !done ) { if ( !idParser::ReadSourceToken( &token ) ) { idParser::Error( "define '%s' incomplete", define->name ); return false; } if ( token == "," ) { if ( indent <= 1 ) { if ( lastcomma ) { idParser::Warning( "too many comma's" ); } if ( numparms >= define->numparms ) { idParser::Warning( "too many define parameters" ); } lastcomma = 1; break; } } else if ( token == "(" ) { indent++; } else if ( token == ")" ) { indent--; if ( indent <= 0 ) { if ( !parms[define->numparms-1] ) { idParser::Warning( "too few define parameters" ); } done = 1; break; } } else if ( token.type == TT_NAME ) { newdefine = FindHashedDefine( idParser::definehash, token.c_str() ); if ( newdefine ) { if ( !idParser::ExpandDefineIntoSource( &token, newdefine ) ) { return false; } continue; } } lastcomma = 0; if ( numparms < define->numparms ) { t = new idToken( token ); t->next = NULL; if (last) last->next = t; else parms[numparms] = t; last = t; } } numparms++; } return true; } /* ================ idParser::StringizeTokens ================ */ int idParser::StringizeTokens( idToken *tokens, idToken *token ) { idToken *t; token->type = TT_STRING; token->whiteSpaceStart_p = NULL; token->whiteSpaceEnd_p = NULL; (*token) = ""; for ( t = tokens; t; t = t->next ) { token->Append( t->c_str() ); } return true; } /* ================ idParser::MergeTokens ================ */ int idParser::MergeTokens( idToken *t1, idToken *t2 ) { // merging of a name with a name or number if ( t1->type == TT_NAME && (t2->type == TT_NAME || (t2->type == TT_NUMBER && !(t2->subtype & TT_FLOAT))) ) { t1->Append( t2->c_str() ); return true; } // merging of two strings if (t1->type == TT_STRING && t2->type == TT_STRING) { t1->Append( t2->c_str() ); return true; } // merging of two numbers if ( t1->type == TT_NUMBER && t2->type == TT_NUMBER && !(t1->subtype & (TT_HEX|TT_BINARY)) && !(t2->subtype & (TT_HEX|TT_BINARY)) && (!(t1->subtype & TT_FLOAT) || !(t2->subtype & TT_FLOAT)) ) { t1->Append( t2->c_str() ); return true; } return false; } /* ================ idParser::AddBuiltinDefines ================ */ void idParser::AddBuiltinDefines( void ) { int i; define_t *define; struct builtin { const char *string; int id; } builtin[] = { { "__LINE__", BUILTIN_LINE }, { "__FILE__", BUILTIN_FILE }, { "__DATE__", BUILTIN_DATE }, { "__TIME__", BUILTIN_TIME }, { "__STDC__", BUILTIN_STDC }, { NULL, 0 } }; for (i = 0; builtin[i].string; i++) { define = (define_t *) Mem_Alloc(sizeof(define_t) + strlen(builtin[i].string) + 1); define->name = (char *) define + sizeof(define_t); strcpy(define->name, builtin[i].string); define->flags = DEFINE_FIXED; define->builtin = builtin[i].id; define->numparms = 0; define->parms = NULL; define->tokens = NULL; // add the define to the source AddDefineToHash(define, idParser::definehash); } } /* ================ idParser::CopyFirstDefine ================ */ define_t *idParser::CopyFirstDefine( void ) { int i; for ( i = 0; i < DEFINEHASHSIZE; i++ ) { if ( idParser::definehash[i] ) { return CopyDefine(idParser::definehash[i]); } } return NULL; } // simple abstraction around localtime()/localtime_r() or whatever static bool my_localtime(const time_t* t, struct tm* ts) { // TODO: does any non-windows platform not support localtime_r()? // then add them here (or handle them specially with their own #elif) #ifdef _WIN32 // localtime() is C89 so it should be available everywhere, but it *may* not be threadsafe. // It *is* threadsafe on Windows though (there it returns a thread-local variable). // (yes, Windows has localtime_s(), but it's unclear if MinGW supports that // or localtime_r() and in the end, *on Windows* localtime() is safe so use it) struct tm* tmp = localtime(t); if(tmp != NULL) { *ts = *tmp; return true; } #else // localtime_r() assumed available, use it (all Unix-likes incl. macOS support it, AROS as well) if(localtime_r(t, ts) != NULL) return true; #endif // if we get here, the localtime() (or localtime_r() or whatever) call failed memset(ts, 0, sizeof(*ts)); // make sure ts has deterministic content return false; } /* ================ idParser::ExpandBuiltinDefine ================ */ int idParser::ExpandBuiltinDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ) { idToken *token; ID_TIME_T t; char buf[MAX_STRING_CHARS]; token = new idToken(deftoken); switch( define->builtin ) { case BUILTIN_LINE: { sprintf( buf, "%d", deftoken->line ); (*token) = buf; token->intvalue = deftoken->line; token->floatvalue = deftoken->line; token->type = TT_NUMBER; token->subtype = TT_DECIMAL | TT_INTEGER | TT_VALUESVALID; token->line = deftoken->line; token->linesCrossed = deftoken->linesCrossed; token->flags = 0; *firsttoken = token; *lasttoken = token; break; } case BUILTIN_FILE: { (*token) = idParser::scriptstack->GetFileName(); token->type = TT_NAME; token->subtype = token->Length(); token->line = deftoken->line; token->linesCrossed = deftoken->linesCrossed; token->flags = 0; *firsttoken = token; *lasttoken = token; break; } case BUILTIN_DATE: { t = time(NULL); // DG: apparently this was supposed to extract the date part of "Wed Jun 30 21:49:08 1993\n" // (like "Jun 30 1993") - it was both ugly and broken before I changed it, though // Originally it copied stuff out of ctime(), which is ugly but ok, but also // free'd the returned value (wrong) and tried to modify token in ways that should've crashed // (like token[7] = NULL; which should've been (*token)[7] = '\0';) struct tm ts; my_localtime(&t, &ts); strftime(buf, sizeof(buf), "\"%b %d %Y\"", &ts); (*token) = buf; token->type = TT_STRING; token->subtype = token->Length(); token->line = deftoken->line; token->linesCrossed = deftoken->linesCrossed; token->flags = 0; *firsttoken = token; *lasttoken = token; break; } case BUILTIN_TIME: { t = time(NULL); // DG: apparently this was supposed to extract the time part of "Wed Jun 30 21:49:08 1993\n" // (like "21:49:08") - it was both ugly and broken before I changed it, though // (had basicaly the same problems as BUILTIN_DATE) struct tm ts; my_localtime(&t, &ts); strftime(buf, sizeof(buf), "\"%H:%M:%S\"", &ts); (*token) = buf; token->type = TT_STRING; token->subtype = token->Length(); token->line = deftoken->line; token->linesCrossed = deftoken->linesCrossed; token->flags = 0; *firsttoken = token; *lasttoken = token; break; } case BUILTIN_STDC: { idParser::Warning( "__STDC__ not supported\n" ); delete token; // DG: we probably shouldn't leak it, right? *firsttoken = NULL; *lasttoken = NULL; break; } default: { delete token; // DG: we probably shouldn't leak it, right? *firsttoken = NULL; *lasttoken = NULL; break; } } return true; } /* ================ idParser::ExpandDefine ================ */ int idParser::ExpandDefine( idToken *deftoken, define_t *define, idToken **firsttoken, idToken **lasttoken ) { idToken *parms[MAX_DEFINEPARMS], *dt, *pt, *t; idToken *t1, *t2, *first, *last, *nextpt, token; int parmnum, i; // if it is a builtin define if ( define->builtin ) { return idParser::ExpandBuiltinDefine( deftoken, define, firsttoken, lasttoken ); } // if the define has parameters if ( define->numparms ) { if ( !idParser::ReadDefineParms( define, parms, MAX_DEFINEPARMS ) ) { return false; } #ifdef DEBUG_EVAL for ( i = 0; i < define->numparms; i++ ) { Log_Write("define parms %d:", i); for ( pt = parms[i]; pt; pt = pt->next ) { Log_Write( "%s", pt->c_str() ); } } #endif //DEBUG_EVAL } // empty list at first first = NULL; last = NULL; // create a list with tokens of the expanded define for ( dt = define->tokens; dt; dt = dt->next ) { parmnum = -1; // if the token is a name, it could be a define parameter if ( dt->type == TT_NAME ) { parmnum = FindDefineParm( define, dt->c_str() ); } // if it is a define parameter if ( parmnum >= 0 ) { for ( pt = parms[parmnum]; pt; pt = pt->next ) { t = new idToken(pt); //add the token to the list t->next = NULL; if (last) last->next = t; else first = t; last = t; } } else { // if stringizing operator if ( (*dt) == "#" ) { // the stringizing operator must be followed by a define parameter if ( dt->next ) { parmnum = FindDefineParm( define, dt->next->c_str() ); } else { parmnum = -1; } if ( parmnum >= 0 ) { // step over the stringizing operator dt = dt->next; // stringize the define parameter tokens if ( !idParser::StringizeTokens( parms[parmnum], &token ) ) { idParser::Error( "can't stringize tokens" ); return false; } t = new idToken(token); t->line = deftoken->line; } else { idParser::Warning( "stringizing operator without define parameter" ); continue; } } else { t = new idToken(dt); t->line = deftoken->line; } // add the token to the list t->next = NULL; // the token being read from the define list should use the line number of // the original file, not the header file t->line = deftoken->line; if ( last ) last->next = t; else first = t; last = t; } } // check for the merging operator for ( t = first; t; ) { if ( t->next ) { // if the merging operator if ( (*t->next) == "##" ) { t1 = t; t2 = t->next->next; if ( t2 ) { if ( !idParser::MergeTokens( t1, t2 ) ) { idParser::Error( "can't merge '%s' with '%s'", t1->c_str(), t2->c_str() ); return false; } delete t1->next; t1->next = t2->next; if ( t2 == last ) last = t1; delete t2; continue; } } } t = t->next; } // store the first and last token of the list *firsttoken = first; *lasttoken = last; // free all the parameter tokens for ( i = 0; i < define->numparms; i++ ) { for ( pt = parms[i]; pt; pt = nextpt ) { nextpt = pt->next; delete pt; } } return true; } /* ================ idParser::ExpandDefineIntoSource ================ */ int idParser::ExpandDefineIntoSource( idToken *deftoken, define_t *define ) { idToken *firsttoken, *lasttoken; if ( !idParser::ExpandDefine( deftoken, define, &firsttoken, &lasttoken ) ) { return false; } // if the define is not empty if ( firsttoken && lasttoken ) { firsttoken->linesCrossed += deftoken->linesCrossed; lasttoken->next = idParser::tokens; idParser::tokens = firsttoken; } return true; } /* ================ idParser::ReadLine reads a token from the current line, continues reading on the next line only if a backslash '\' is found ================ */ int idParser::ReadLine( idToken *token ) { int crossline; crossline = 0; do { if (!idParser::ReadSourceToken( token )) { return false; } if (token->linesCrossed > crossline) { idParser::UnreadSourceToken( token ); return false; } crossline = 1; } while( (*token) == "\\" ); return true; } /* ================ idParser::Directive_include ================ */ int idParser::Directive_include( void ) { idLexer *script; idToken token; idStr path; if ( !idParser::ReadSourceToken( &token ) ) { idParser::Error( "#include without file name" ); return false; } if ( token.linesCrossed > 0 ) { idParser::Error( "#include without file name" ); return false; } if ( token.type == TT_STRING ) { script = new idLexer; // try relative to the current file path = scriptstack->GetFileName(); path.StripFilename(); path += "/"; path += token; if ( !script->LoadFile( path, OSPath ) ) { // try absolute path path = token; if ( !script->LoadFile( path, OSPath ) ) { // try from the include path path = includepath + token; if ( !script->LoadFile( path, OSPath ) ) { delete script; script = NULL; } } } } else if ( token.type == TT_PUNCTUATION && token == "<" ) { path = idParser::includepath; while( idParser::ReadSourceToken( &token ) ) { if ( token.linesCrossed > 0 ) { idParser::UnreadSourceToken( &token ); break; } if ( token.type == TT_PUNCTUATION && token == ">" ) { break; } path += token; } if ( token != ">" ) { idParser::Warning( "#include missing trailing >" ); } if ( !path.Length() ) { idParser::Error( "#include without file name between < >" ); return false; } if ( idParser::flags & LEXFL_NOBASEINCLUDES ) { return true; } script = new idLexer; if ( !script->LoadFile( includepath + path, OSPath ) ) { delete script; script = NULL; } } else { idParser::Error( "#include without file name" ); return false; } if (!script) { idParser::Error( "file '%s' not found", path.c_str() ); return false; } script->SetFlags( idParser::flags ); script->SetPunctuations( idParser::punctuations ); idParser::PushScript( script ); return true; } /* ================ idParser::Directive_undef ================ */ int idParser::Directive_undef( void ) { idToken token; define_t *define, *lastdefine; int hash; // if (!idParser::ReadLine( &token )) { idParser::Error( "undef without name" ); return false; } if (token.type != TT_NAME) { idParser::UnreadSourceToken( &token ); idParser::Error( "expected name but found '%s'", token.c_str() ); return false; } hash = PC_NameHash( token.c_str() ); for (lastdefine = NULL, define = idParser::definehash[hash]; define; define = define->hashnext) { if (!strcmp(define->name, token.c_str())) { if (define->flags & DEFINE_FIXED) { idParser::Warning( "can't undef '%s'", token.c_str() ); } else { if (lastdefine) { lastdefine->hashnext = define->hashnext; } else { idParser::definehash[hash] = define->hashnext; } FreeDefine(define); } break; } lastdefine = define; } return true; } /* ================ idParser::Directive_define ================ */ int idParser::Directive_define( void ) { idToken token, *t, *last; define_t *define; if (!idParser::ReadLine( &token )) { idParser::Error( "#define without name" ); return false; } if (token.type != TT_NAME) { idParser::UnreadSourceToken( &token ); idParser::Error( "expected name after #define, found '%s'", token.c_str() ); return false; } // check if the define already exists define = FindHashedDefine(idParser::definehash, token.c_str()); if (define) { if (define->flags & DEFINE_FIXED) { idParser::Error( "can't redefine '%s'", token.c_str() ); return false; } idParser::Warning( "redefinition of '%s'", token.c_str() ); // unread the define name before executing the #undef directive idParser::UnreadSourceToken( &token ); if (!idParser::Directive_undef()) return false; // if the define was not removed (define->flags & DEFINE_FIXED) define = FindHashedDefine(idParser::definehash, token.c_str()); } // allocate define define = (define_t *) Mem_ClearedAlloc(sizeof(define_t) + token.Length() + 1); define->name = (char *) define + sizeof(define_t); strcpy(define->name, token.c_str()); // add the define to the source AddDefineToHash(define, idParser::definehash); // if nothing is defined, just return if ( !idParser::ReadLine( &token ) ) { return true; } // if it is a define with parameters if ( token.WhiteSpaceBeforeToken() == 0 && token == "(" ) { // read the define parameters last = NULL; if ( !idParser::CheckTokenString(")") ) { while(1) { if ( !idParser::ReadLine( &token ) ) { idParser::Error( "expected define parameter" ); return false; } // if it isn't a name if (token.type != TT_NAME) { idParser::Error( "invalid define parameter" ); return false; } if (FindDefineParm(define, token.c_str()) >= 0) { idParser::Error( "two the same define parameters" ); return false; } // add the define parm t = new idToken(token); t->ClearTokenWhiteSpace(); t->next = NULL; if (last) last->next = t; else define->parms = t; last = t; define->numparms++; // read next token if (!idParser::ReadLine( &token )) { idParser::Error( "define parameters not terminated" ); return false; } if ( token == ")" ) { break; } // then it must be a comma if ( token != "," ) { idParser::Error( "define not terminated" ); return false; } } } if ( !idParser::ReadLine( &token ) ) { return true; } } // read the defined stuff last = NULL; do { t = new idToken(token); if ( t->type == TT_NAME && !strcmp( t->c_str(), define->name ) ) { t->flags |= TOKEN_FL_RECURSIVE_DEFINE; idParser::Warning( "recursive define (removed recursion)" ); } t->ClearTokenWhiteSpace(); t->next = NULL; if ( last ) last->next = t; else define->tokens = t; last = t; } while( idParser::ReadLine( &token ) ); if ( last ) { // check for merge operators at the beginning or end if ( (*define->tokens) == "##" || (*last) == "##" ) { idParser::Error( "define with misplaced ##" ); return false; } } return true; } /* ================ idParser::AddDefine ================ */ int idParser::AddDefine( const char *string ) { define_t *define; define = DefineFromString( string ); if (!define) { return false; } AddDefineToHash(define, idParser::definehash); return true; } /* ================ idParser::AddGlobalDefinesToSource ================ */ void idParser::AddGlobalDefinesToSource( void ) { define_t *define, *newdefine; for (define = globaldefines; define; define = define->next) { newdefine = CopyDefine( define ); AddDefineToHash(newdefine, idParser::definehash); } } /* ================ idParser::Directive_if_def ================ */ int idParser::Directive_if_def( int type ) { idToken token; define_t *d; int skip; if ( !idParser::ReadLine( &token ) ) { idParser::Error( "#ifdef without name" ); return false; } if (token.type != TT_NAME) { idParser::UnreadSourceToken( &token ); idParser::Error( "expected name after #ifdef, found '%s'", token.c_str() ); return false; } d = FindHashedDefine(idParser::definehash, token.c_str()); skip = (type == INDENT_IFDEF) == (d == NULL); idParser::PushIndent( type, skip ); return true; } /* ================ idParser::Directive_ifdef ================ */ int idParser::Directive_ifdef( void ) { return idParser::Directive_if_def( INDENT_IFDEF ); } /* ================ idParser::Directive_ifndef ================ */ int idParser::Directive_ifndef( void ) { return idParser::Directive_if_def( INDENT_IFNDEF ); } /* ================ idParser::Directive_else ================ */ int idParser::Directive_else( void ) { int type, skip; idParser::PopIndent( &type, &skip ); if (!type) { idParser::Error( "misplaced #else" ); return false; } if (type == INDENT_ELSE) { idParser::Error( "#else after #else" ); return false; } idParser::PushIndent( INDENT_ELSE, !skip ); return true; } /* ================ idParser::Directive_endif ================ */ int idParser::Directive_endif( void ) { int type, skip; idParser::PopIndent( &type, &skip ); if (!type) { idParser::Error( "misplaced #endif" ); return false; } return true; } /* ================ idParser::EvaluateTokens ================ */ typedef struct operator_s { int op; int priority; int parentheses; struct operator_s *prev, *next; } operator_t; typedef struct value_s { int intvalue; double floatvalue; int parentheses; struct value_s *prev, *next; } value_t; int PC_OperatorPriority(int op) { switch(op) { case P_MUL: return 15; case P_DIV: return 15; case P_MOD: return 15; case P_ADD: return 14; case P_SUB: return 14; case P_LOGIC_AND: return 7; case P_LOGIC_OR: return 6; case P_LOGIC_GEQ: return 12; case P_LOGIC_LEQ: return 12; case P_LOGIC_EQ: return 11; case P_LOGIC_UNEQ: return 11; case P_LOGIC_NOT: return 16; case P_LOGIC_GREATER: return 12; case P_LOGIC_LESS: return 12; case P_RSHIFT: return 13; case P_LSHIFT: return 13; case P_BIN_AND: return 10; case P_BIN_OR: return 8; case P_BIN_XOR: return 9; case P_BIN_NOT: return 16; case P_COLON: return 5; case P_QUESTIONMARK: return 5; } return false; } //#define AllocValue() GetClearedMemory(sizeof(value_t)); //#define FreeValue(val) FreeMemory(val) //#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); //#define FreeOperator(op) FreeMemory(op); #define MAX_VALUES 64 #define MAX_OPERATORS 64 #define AllocValue(val) \ if ( numvalues >= MAX_VALUES ) { \ idParser::Error( "out of value space\n" ); \ error = 1; \ break; \ } \ else { \ val = &value_heap[numvalues++]; \ } #define FreeValue(val) #define AllocOperator(op) \ if ( numoperators >= MAX_OPERATORS ) { \ idParser::Error( "out of operator space\n" ); \ error = 1; \ break; \ } \ else { \ op = &operator_heap[numoperators++]; \ } #define FreeOperator(op) int idParser::EvaluateTokens( idToken *tokens, int *intvalue, double *floatvalue, int integer ) { operator_t *o, *firstoperator, *lastoperator; value_t *v, *firstvalue, *lastvalue, *v1, *v2; idToken *t; int brace = 0; int parentheses = 0; int error = 0; int lastwasvalue = 0; int negativevalue = 0; int questmarkintvalue = 0; double questmarkfloatvalue = 0; int gotquestmarkvalue = false; // operator_t operator_heap[MAX_OPERATORS]; int numoperators = 0; value_t value_heap[MAX_VALUES]; int numvalues = 0; firstoperator = lastoperator = NULL; firstvalue = lastvalue = NULL; if (intvalue) *intvalue = 0; if (floatvalue) *floatvalue = 0; for ( t = tokens; t; t = t->next ) { switch( t->type ) { case TT_NAME: { if ( lastwasvalue || negativevalue ) { idParser::Error( "syntax error in #if/#elif" ); error = 1; break; } if ( (*t) != "defined" ) { idParser::Error( "undefined name '%s' in #if/#elif", t->c_str() ); error = 1; break; } t = t->next; if ( (*t) == "(" ) { brace = true; t = t->next; } if (!t || t->type != TT_NAME) { idParser::Error( "defined() without name in #if/#elif" ); error = 1; break; } //v = (value_t *) GetClearedMemory(sizeof(value_t)); AllocValue(v); if (FindHashedDefine(idParser::definehash, t->c_str())) { v->intvalue = 1; v->floatvalue = 1; } else { v->intvalue = 0; v->floatvalue = 0; } v->parentheses = parentheses; v->next = NULL; v->prev = lastvalue; if (lastvalue) lastvalue->next = v; else firstvalue = v; lastvalue = v; if (brace) { t = t->next; if (!t || (*t) != ")" ) { idParser::Error( "defined missing ) in #if/#elif" ); error = 1; break; } } brace = false; // defined() creates a value lastwasvalue = 1; break; } case TT_NUMBER: { if (lastwasvalue) { idParser::Error( "syntax error in #if/#elif" ); error = 1; break; } //v = (value_t *) GetClearedMemory(sizeof(value_t)); AllocValue(v); if (negativevalue) { v->intvalue = - t->GetIntValue(); v->floatvalue = - t->GetFloatValue(); } else { v->intvalue = t->GetIntValue(); v->floatvalue = t->GetFloatValue(); } v->parentheses = parentheses; v->next = NULL; v->prev = lastvalue; if (lastvalue) lastvalue->next = v; else firstvalue = v; lastvalue = v; //last token was a value lastwasvalue = 1; // negativevalue = 0; break; } case TT_PUNCTUATION: { if (negativevalue) { idParser::Error( "misplaced minus sign in #if/#elif" ); error = 1; break; } if (t->subtype == P_PARENTHESESOPEN) { parentheses++; break; } else if (t->subtype == P_PARENTHESESCLOSE) { parentheses--; if (parentheses < 0) { idParser::Error( "too many ) in #if/#elsif" ); error = 1; } break; } //check for invalid operators on floating point values if ( !integer ) { if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || t->subtype == P_BIN_XOR) { idParser::Error( "illigal operator '%s' on floating point operands\n", t->c_str() ); error = 1; break; } } switch( t->subtype ) { case P_LOGIC_NOT: case P_BIN_NOT: { if (lastwasvalue) { idParser::Error( "! or ~ after value in #if/#elif" ); error = 1; break; } break; } case P_INC: case P_DEC: { idParser::Error( "++ or -- used in #if/#elif" ); break; } case P_SUB: { if (!lastwasvalue) { negativevalue = 1; break; } } case P_MUL: case P_DIV: case P_MOD: case P_ADD: case P_LOGIC_AND: case P_LOGIC_OR: case P_LOGIC_GEQ: case P_LOGIC_LEQ: case P_LOGIC_EQ: case P_LOGIC_UNEQ: case P_LOGIC_GREATER: case P_LOGIC_LESS: case P_RSHIFT: case P_LSHIFT: case P_BIN_AND: case P_BIN_OR: case P_BIN_XOR: case P_COLON: case P_QUESTIONMARK: { if (!lastwasvalue) { idParser::Error( "operator '%s' after operator in #if/#elif", t->c_str() ); error = 1; break; } break; } default: { idParser::Error( "invalid operator '%s' in #if/#elif", t->c_str() ); error = 1; break; } } if (!error && !negativevalue) { //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); AllocOperator(o); o->op = t->subtype; o->priority = PC_OperatorPriority(t->subtype); o->parentheses = parentheses; o->next = NULL; o->prev = lastoperator; if (lastoperator) lastoperator->next = o; else firstoperator = o; lastoperator = o; lastwasvalue = 0; } break; } default: { idParser::Error( "unknown '%s' in #if/#elif", t->c_str() ); error = 1; break; } } if (error) { break; } } if (!error) { if (!lastwasvalue) { idParser::Error( "trailing operator in #if/#elif" ); error = 1; } else if (parentheses) { idParser::Error( "too many ( in #if/#elif" ); error = 1; } } // gotquestmarkvalue = false; questmarkintvalue = 0; questmarkfloatvalue = 0; //while there are operators while( !error && firstoperator ) { v = firstvalue; for (o = firstoperator; o->next; o = o->next) { //if the current operator is nested deeper in parentheses //than the next operator if (o->parentheses > o->next->parentheses) { break; } //if the current and next operator are nested equally deep in parentheses if (o->parentheses == o->next->parentheses) { //if the priority of the current operator is equal or higher //than the priority of the next operator if (o->priority >= o->next->priority) { break; } } //if the arity of the operator isn't equal to 1 if (o->op != P_LOGIC_NOT && o->op != P_BIN_NOT) { v = v->next; } //if there's no value or no next value if (!v) { idParser::Error( "mising values in #if/#elif" ); error = 1; break; } } if (error) { break; } v1 = v; v2 = v->next; #ifdef DEBUG_EVAL if (integer) { Log_Write("operator %s, value1 = %d", idParser::scriptstack->getPunctuationFromId(o->op), v1->intvalue); if (v2) Log_Write("value2 = %d", v2->intvalue); } else { Log_Write("operator %s, value1 = %f", idParser::scriptstack->getPunctuationFromId(o->op), v1->floatvalue); if (v2) Log_Write("value2 = %f", v2->floatvalue); } #endif //DEBUG_EVAL switch(o->op) { case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; v1->floatvalue = !v1->floatvalue; break; case P_BIN_NOT: v1->intvalue = ~v1->intvalue; break; case P_MUL: v1->intvalue *= v2->intvalue; v1->floatvalue *= v2->floatvalue; break; case P_DIV: if (!v2->intvalue || !v2->floatvalue) { idParser::Error( "divide by zero in #if/#elif\n" ); error = 1; break; } v1->intvalue /= v2->intvalue; v1->floatvalue /= v2->floatvalue; break; case P_MOD: if (!v2->intvalue) { idParser::Error( "divide by zero in #if/#elif\n" ); error = 1; break; } v1->intvalue %= v2->intvalue; break; case P_ADD: v1->intvalue += v2->intvalue; v1->floatvalue += v2->floatvalue; break; case P_SUB: v1->intvalue -= v2->intvalue; v1->floatvalue -= v2->floatvalue; break; case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; v1->floatvalue = v1->floatvalue && v2->floatvalue; break; case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; v1->floatvalue = v1->floatvalue || v2->floatvalue; break; case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; v1->floatvalue = v1->floatvalue == v2->floatvalue; break; case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; v1->floatvalue = v1->floatvalue != v2->floatvalue; break; case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; v1->floatvalue = v1->floatvalue > v2->floatvalue; break; case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; v1->floatvalue = v1->floatvalue < v2->floatvalue; break; case P_RSHIFT: v1->intvalue >>= v2->intvalue; break; case P_LSHIFT: v1->intvalue <<= v2->intvalue; break; case P_BIN_AND: v1->intvalue &= v2->intvalue; break; case P_BIN_OR: v1->intvalue |= v2->intvalue; break; case P_BIN_XOR: v1->intvalue ^= v2->intvalue; break; case P_COLON: { if (!gotquestmarkvalue) { idParser::Error( ": without ? in #if/#elif" ); error = 1; break; } if (integer) { if (!questmarkintvalue) v1->intvalue = v2->intvalue; } else { if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; } gotquestmarkvalue = false; break; } case P_QUESTIONMARK: { if (gotquestmarkvalue) { idParser::Error( "? after ? in #if/#elif" ); error = 1; break; } questmarkintvalue = v1->intvalue; questmarkfloatvalue = v1->floatvalue; gotquestmarkvalue = true; break; } } #ifdef DEBUG_EVAL if (integer) Log_Write("result value = %d", v1->intvalue); else Log_Write("result value = %f", v1->floatvalue); #endif //DEBUG_EVAL if (error) break; //if not an operator with arity 1 if (o->op != P_LOGIC_NOT && o->op != P_BIN_NOT) { //remove the second value if not question mark operator if (o->op != P_QUESTIONMARK) { v = v->next; } // if (v->prev) v->prev->next = v->next; else firstvalue = v->next; if (v->next) v->next->prev = v->prev; else lastvalue = v->prev; //FreeMemory(v); FreeValue(v); } //remove the operator if (o->prev) o->prev->next = o->next; else firstoperator = o->next; if (o->next) o->next->prev = o->prev; else lastoperator = o->prev; //FreeMemory(o); FreeOperator(o); } if (firstvalue) { if (intvalue) *intvalue = firstvalue->intvalue; if (floatvalue) *floatvalue = firstvalue->floatvalue; } for (o = firstoperator; o; o = lastoperator) { lastoperator = o->next; //FreeMemory(o); FreeOperator(o); } for (v = firstvalue; v; v = lastvalue) { lastvalue = v->next; //FreeMemory(v); FreeValue(v); } if (!error) { return true; } if (intvalue) { *intvalue = 0; } if (floatvalue) { *floatvalue = 0; } return false; } /* ================ idParser::Evaluate ================ */ int idParser::Evaluate( int *intvalue, double *floatvalue, int integer ) { idToken token, *firsttoken, *lasttoken; idToken *t, *nexttoken; define_t *define; int defined = false; if (intvalue) { *intvalue = 0; } if (floatvalue) { *floatvalue = 0; } // if ( !idParser::ReadLine( &token ) ) { idParser::Error( "no value after #if/#elif" ); return false; } firsttoken = NULL; lasttoken = NULL; do { //if the token is a name if (token.type == TT_NAME) { if (defined) { defined = false; t = new idToken(token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } else if ( token == "defined" ) { defined = true; t = new idToken(token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } else { //then it must be a define define = FindHashedDefine(idParser::definehash, token.c_str()); if (!define) { idParser::Error( "can't Evaluate '%s', not defined", token.c_str() ); return false; } if ( !idParser::ExpandDefineIntoSource( &token, define ) ) { return false; } } } //if the token is a number or a punctuation else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { t = new idToken(token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } else { idParser::Error( "can't Evaluate '%s'", token.c_str() ); return false; } } while(idParser::ReadLine( &token )); // if ( !idParser::EvaluateTokens( firsttoken, intvalue, floatvalue, integer ) ) { return false; } // #ifdef DEBUG_EVAL Log_Write("eval:"); #endif //DEBUG_EVAL for (t = firsttoken; t; t = nexttoken) { #ifdef DEBUG_EVAL Log_Write(" %s", t->c_str()); #endif //DEBUG_EVAL nexttoken = t->next; delete t; } //end for #ifdef DEBUG_EVAL if (integer) Log_Write("eval result: %d", *intvalue); else Log_Write("eval result: %f", *floatvalue); #endif //DEBUG_EVAL // return true; } /* ================ idParser::DollarEvaluate ================ */ int idParser::DollarEvaluate( int *intvalue, double *floatvalue, int integer) { int indent, defined = false; idToken token, *firsttoken, *lasttoken; idToken *t, *nexttoken; define_t *define; if (intvalue) { *intvalue = 0; } if (floatvalue) { *floatvalue = 0; } // if ( !idParser::ReadSourceToken( &token ) ) { idParser::Error( "no leading ( after $evalint/$evalfloat" ); return false; } if ( !idParser::ReadSourceToken( &token ) ) { idParser::Error( "nothing to Evaluate" ); return false; } indent = 1; firsttoken = NULL; lasttoken = NULL; do { //if the token is a name if (token.type == TT_NAME) { if (defined) { defined = false; t = new idToken(token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } else if ( token == "defined" ) { defined = true; t = new idToken(token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } else { //then it must be a define define = FindHashedDefine(idParser::definehash, token.c_str()); if (!define) { idParser::Warning( "can't Evaluate '%s', not defined", token.c_str() ); return false; } if ( !idParser::ExpandDefineIntoSource( &token, define ) ) { return false; } } } //if the token is a number or a punctuation else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { if ( token[0] == '(' ) indent++; else if ( token[0] == ')' ) indent--; if (indent <= 0) { break; } t = new idToken(token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } else { idParser::Error( "can't Evaluate '%s'", token.c_str() ); return false; } } while(idParser::ReadSourceToken( &token )); // if (!idParser::EvaluateTokens( firsttoken, intvalue, floatvalue, integer)) { return false; } // #ifdef DEBUG_EVAL Log_Write("$eval:"); #endif //DEBUG_EVAL for (t = firsttoken; t; t = nexttoken) { #ifdef DEBUG_EVAL Log_Write(" %s", t->c_str()); #endif //DEBUG_EVAL nexttoken = t->next; delete t; } //end for #ifdef DEBUG_EVAL if (integer) Log_Write("$eval result: %d", *intvalue); else Log_Write("$eval result: %f", *floatvalue); #endif //DEBUG_EVAL // return true; } /* ================ idParser::Directive_elif ================ */ int idParser::Directive_elif( void ) { int value; int type, skip; idParser::PopIndent( &type, &skip ); if (!type || type == INDENT_ELSE) { idParser::Error( "misplaced #elif" ); return false; } if ( !idParser::Evaluate( &value, NULL, true ) ) { return false; } skip = (value == 0); idParser::PushIndent( INDENT_ELIF, skip ); return true; } /* ================ idParser::Directive_if ================ */ int idParser::Directive_if( void ) { int value; int skip; if ( !idParser::Evaluate( &value, NULL, true ) ) { return false; } skip = (value == 0); idParser::PushIndent( INDENT_IF, skip ); return true; } /* ================ idParser::Directive_line ================ */ int idParser::Directive_line( void ) { idToken token; idParser::Error( "#line directive not supported" ); while( idParser::ReadLine( &token ) ) { } return true; } /* ================ idParser::Directive_error ================ */ int idParser::Directive_error( void ) { idToken token; if ( !idParser::ReadLine( &token) || token.type != TT_STRING ) { idParser::Error( "#error without string" ); return false; } idParser::Error( "#error: %s", token.c_str() ); return true; } /* ================ idParser::Directive_warning ================ */ int idParser::Directive_warning( void ) { idToken token; if ( !idParser::ReadLine( &token) || token.type != TT_STRING ) { idParser::Warning( "#warning without string" ); return false; } idParser::Warning( "#warning: %s", token.c_str() ); return true; } /* ================ idParser::Directive_pragma ================ */ int idParser::Directive_pragma( void ) { idToken token; idParser::Warning( "#pragma directive not supported" ); while( idParser::ReadLine( &token ) ) { } return true; } /* ================ idParser::UnreadSignToken ================ */ void idParser::UnreadSignToken( void ) { idToken token; token.line = idParser::scriptstack->GetLineNum(); token.whiteSpaceStart_p = NULL; token.whiteSpaceEnd_p = NULL; token.linesCrossed = 0; token.flags = 0; token = "-"; token.type = TT_PUNCTUATION; token.subtype = P_SUB; idParser::UnreadSourceToken( &token ); } /* ================ idParser::Directive_eval ================ */ int idParser::Directive_eval( void ) { int value; idToken token; char buf[128]; if ( !idParser::Evaluate( &value, NULL, true ) ) { return false; } token.line = idParser::scriptstack->GetLineNum(); token.whiteSpaceStart_p = NULL; token.whiteSpaceEnd_p = NULL; token.linesCrossed = 0; token.flags = 0; sprintf(buf, "%d", abs(value)); token = buf; token.type = TT_NUMBER; token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; idParser::UnreadSourceToken( &token ); if ( value < 0 ) { idParser::UnreadSignToken(); } return true; } /* ================ idParser::Directive_evalfloat ================ */ int idParser::Directive_evalfloat( void ) { double value; idToken token; char buf[128]; if ( !idParser::Evaluate( NULL, &value, false ) ) { return false; } token.line = idParser::scriptstack->GetLineNum(); token.whiteSpaceStart_p = NULL; token.whiteSpaceEnd_p = NULL; token.linesCrossed = 0; token.flags = 0; sprintf(buf, "%1.2f", idMath::Fabs(value)); token = buf; token.type = TT_NUMBER; token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; idParser::UnreadSourceToken( &token ); if (value < 0) { idParser::UnreadSignToken(); } return true; } /* ================ idParser::ReadDirective ================ */ int idParser::ReadDirective( void ) { idToken token; //read the directive name if ( !idParser::ReadSourceToken( &token ) ) { idParser::Error( "found '#' without name" ); return false; } //directive name must be on the same line if (token.linesCrossed > 0) { idParser::UnreadSourceToken( &token ); idParser::Error( "found '#' at end of line" ); return false; } //if if is a name if (token.type == TT_NAME) { if ( token == "if" ) { return idParser::Directive_if(); } else if ( token == "ifdef" ) { return idParser::Directive_ifdef(); } else if ( token == "ifndef" ) { return idParser::Directive_ifndef(); } else if ( token == "elif" ) { return idParser::Directive_elif(); } else if ( token == "else" ) { return idParser::Directive_else(); } else if ( token == "endif" ) { return idParser::Directive_endif(); } else if (idParser::skip > 0) { // skip the rest of the line while( idParser::ReadLine( &token ) ) { } return true; } else { if ( token == "include" ) { return idParser::Directive_include(); } else if ( token == "define" ) { return idParser::Directive_define(); } else if ( token == "undef" ) { return idParser::Directive_undef(); } else if ( token == "line" ) { return idParser::Directive_line(); } else if ( token == "error" ) { return idParser::Directive_error(); } else if ( token == "warning" ) { return idParser::Directive_warning(); } else if ( token == "pragma" ) { return idParser::Directive_pragma(); } else if ( token == "eval" ) { return idParser::Directive_eval(); } else if ( token == "evalfloat" ) { return idParser::Directive_evalfloat(); } } } idParser::Error( "unknown precompiler directive '%s'", token.c_str() ); return false; } /* ================ idParser::DollarDirective_evalint ================ */ int idParser::DollarDirective_evalint( void ) { int value; idToken token; char buf[128]; if ( !idParser::DollarEvaluate( &value, NULL, true ) ) { return false; } token.line = idParser::scriptstack->GetLineNum(); token.whiteSpaceStart_p = NULL; token.whiteSpaceEnd_p = NULL; token.linesCrossed = 0; token.flags = 0; sprintf( buf, "%d", abs( value ) ); token = buf; token.type = TT_NUMBER; token.subtype = TT_INTEGER | TT_LONG | TT_DECIMAL | TT_VALUESVALID; token.intvalue = abs( value ); token.floatvalue = abs( value ); idParser::UnreadSourceToken( &token ); if ( value < 0 ) { idParser::UnreadSignToken(); } return true; } /* ================ idParser::DollarDirective_evalfloat ================ */ int idParser::DollarDirective_evalfloat( void ) { double value; idToken token; char buf[128]; if ( !idParser::DollarEvaluate( NULL, &value, false ) ) { return false; } token.line = idParser::scriptstack->GetLineNum(); token.whiteSpaceStart_p = NULL; token.whiteSpaceEnd_p = NULL; token.linesCrossed = 0; token.flags = 0; sprintf( buf, "%1.2f", fabs( value ) ); token = buf; token.type = TT_NUMBER; token.subtype = TT_FLOAT | TT_LONG | TT_DECIMAL | TT_VALUESVALID; token.intvalue = (unsigned int) fabs( value ); token.floatvalue = fabs( value ); idParser::UnreadSourceToken( &token ); if ( value < 0 ) { idParser::UnreadSignToken(); } return true; } /* ================ idParser::ReadDollarDirective ================ */ int idParser::ReadDollarDirective( void ) { idToken token; // read the directive name if ( !idParser::ReadSourceToken( &token ) ) { idParser::Error( "found '$' without name" ); return false; } // directive name must be on the same line if ( token.linesCrossed > 0 ) { idParser::UnreadSourceToken( &token ); idParser::Error( "found '$' at end of line" ); return false; } // if if is a name if (token.type == TT_NAME) { if ( token == "evalint" ) { return idParser::DollarDirective_evalint(); } else if ( token == "evalfloat" ) { return idParser::DollarDirective_evalfloat(); } } idParser::UnreadSourceToken( &token ); return false; } /* ================ idParser::ReadToken ================ */ int idParser::ReadToken( idToken *token ) { define_t *define; while(1) { if ( !idParser::ReadSourceToken( token ) ) { return false; } // check for precompiler directives if ( token->type == TT_PUNCTUATION && (*token)[0] == '#' && (*token)[1] == '\0' ) { // read the precompiler directive if ( !idParser::ReadDirective() ) { return false; } continue; } // if skipping source because of conditional compilation if ( idParser::skip ) { continue; } // recursively concatenate strings that are behind each other still resolving defines if ( token->type == TT_STRING && !(idParser::scriptstack->GetFlags() & LEXFL_NOSTRINGCONCAT) ) { idToken newtoken; if ( idParser::ReadToken( &newtoken ) ) { if ( newtoken.type == TT_STRING ) { token->Append( newtoken.c_str() ); } else { idParser::UnreadSourceToken( &newtoken ); } } } // if ( !(idParser::scriptstack->GetFlags() & LEXFL_NODOLLARPRECOMPILE) ) { // check for special precompiler directives if ( token->type == TT_PUNCTUATION && (*token)[0] == '$' && (*token)[1] == '\0' ) { // read the precompiler directive if ( idParser::ReadDollarDirective() ) { continue; } } } // if the token is a name if ( token->type == TT_NAME && !( token->flags & TOKEN_FL_RECURSIVE_DEFINE ) ) { // check if the name is a define macro define = FindHashedDefine( idParser::definehash, token->c_str() ); // if it is a define macro if ( define ) { // expand the defined macro if ( !idParser::ExpandDefineIntoSource( token, define ) ) { return false; } continue; } } // found a token return true; } } /* ================ idParser::ExpectTokenString ================ */ int idParser::ExpectTokenString( const char *string ) { idToken token; if ( !idParser::ReadToken( &token ) ) { idParser::Error( "couldn't find expected '%s'", string ); return false; } if ( token != string ) { idParser::Error( "expected '%s' but found '%s'", string, token.c_str() ); return false; } return true; } /* ================ idParser::ExpectTokenType ================ */ int idParser::ExpectTokenType( int type, int subtype, idToken *token ) { idStr str; if ( !idParser::ReadToken( token ) ) { idParser::Error( "couldn't read expected token" ); return 0; } if ( token->type != type ) { switch( type ) { case TT_STRING: str = "string"; break; case TT_LITERAL: str = "literal"; break; case TT_NUMBER: str = "number"; break; case TT_NAME: str = "name"; break; case TT_PUNCTUATION: str = "punctuation"; break; default: str = "unknown type"; break; } idParser::Error( "expected a %s but found '%s'", str.c_str(), token->c_str() ); return 0; } if ( token->type == TT_NUMBER ) { if ( (token->subtype & subtype) != subtype ) { str.Clear(); if ( subtype & TT_DECIMAL ) str = "decimal "; if ( subtype & TT_HEX ) str = "hex "; if ( subtype & TT_OCTAL ) str = "octal "; if ( subtype & TT_BINARY ) str = "binary "; if ( subtype & TT_UNSIGNED ) str += "unsigned "; if ( subtype & TT_LONG ) str += "long "; if ( subtype & TT_FLOAT ) str += "float "; if ( subtype & TT_INTEGER ) str += "integer "; str.StripTrailing( ' ' ); idParser::Error( "expected %s but found '%s'", str.c_str(), token->c_str() ); return 0; } } else if ( token->type == TT_PUNCTUATION ) { if ( subtype < 0 ) { idParser::Error( "BUG: wrong punctuation subtype" ); return 0; } if ( token->subtype != subtype ) { idParser::Error( "expected '%s' but found '%s'", scriptstack->GetPunctuationFromId( subtype ), token->c_str() ); return 0; } } return 1; } /* ================ idParser::ExpectAnyToken ================ */ int idParser::ExpectAnyToken( idToken *token ) { if (!idParser::ReadToken( token )) { idParser::Error( "couldn't read expected token" ); return false; } else { return true; } } /* ================ idParser::CheckTokenString ================ */ int idParser::CheckTokenString( const char *string ) { idToken tok; if ( !ReadToken( &tok ) ) { return false; } //if the token is available if ( tok == string ) { return true; } UnreadSourceToken( &tok ); return false; } /* ================ idParser::CheckTokenType ================ */ int idParser::CheckTokenType( int type, int subtype, idToken *token ) { idToken tok; if ( !ReadToken( &tok ) ) { return false; } //if the type matches if (tok.type == type && (tok.subtype & subtype) == subtype) { *token = tok; return true; } UnreadSourceToken( &tok ); return false; } /* ================ idParser::PeekTokenString ================ */ int idParser::PeekTokenString( const char *string ) { idToken tok; if ( !ReadToken( &tok ) ) { return false; } UnreadSourceToken( &tok ); // if the token is available if ( tok == string ) { return true; } return false; } /* ================ idParser::PeekTokenType ================ */ int idParser::PeekTokenType( int type, int subtype, idToken *token ) { idToken tok; if ( !ReadToken( &tok ) ) { return false; } UnreadSourceToken( &tok ); // if the type matches if ( tok.type == type && ( tok.subtype & subtype ) == subtype ) { *token = tok; return true; } return false; } /* ================ idParser::SkipUntilString ================ */ int idParser::SkipUntilString( const char *string ) { idToken token; while(idParser::ReadToken( &token )) { if ( token == string ) { return true; } } return false; } /* ================ idParser::SkipRestOfLine ================ */ int idParser::SkipRestOfLine( void ) { idToken token; while(idParser::ReadToken( &token )) { if ( token.linesCrossed ) { idParser::UnreadSourceToken( &token ); return true; } } return false; } /* ================= idParser::SkipBracedSection Skips until a matching close brace is found. Internal brace depths are properly skipped. ================= */ int idParser::SkipBracedSection( bool parseFirstBrace ) { idToken token; int depth; depth = parseFirstBrace ? 0 : 1; do { if ( !ReadToken( &token ) ) { return false; } if( token.type == TT_PUNCTUATION ) { if( token == "{" ) { depth++; } else if ( token == "}" ) { depth--; } } } while( depth ); return true; } /* ================= idParser::ParseBracedSectionExact The next token should be an open brace. Parses until a matching close brace is found. Maintains the exact formating of the braced section FIXME: what about precompilation ? ================= */ const char *idParser::ParseBracedSectionExact( idStr &out, int tabs ) { return scriptstack->ParseBracedSectionExact( out, tabs ); } /* ================= idParser::ParseBracedSection The next token should be an open brace. Parses until a matching close brace is found. Internal brace depths are properly skipped. ================= */ const char *idParser::ParseBracedSection( idStr &out, int tabs ) { idToken token; int i, depth; bool doTabs = false; if (tabs >= 0) { doTabs = true; } out.Empty(); if ( !idParser::ExpectTokenString( "{" ) ) { return out.c_str(); } out = "{"; depth = 1; do { if ( !idParser::ReadToken( &token ) ) { Error( "missing closing brace" ); return out.c_str(); } // if the token is on a new line for ( i = 0; i < token.linesCrossed; i++ ) { out += "\r\n"; } if (doTabs && token.linesCrossed) { i = tabs; if (token[0] == '}' && i > 0) { i--; } while (i-- > 0) { out += "\t"; } } if ( token.type == TT_PUNCTUATION ) { if ( token[0] == '{' ) { depth++; if (doTabs) { tabs++; } } else if ( token[0] == '}' ) { depth--; if (doTabs) { tabs--; } } } if ( token.type == TT_STRING ) { out += "\"" + token + "\""; } else { out += token; } out += " "; } while( depth ); return out.c_str(); } /* ================= idParser::ParseRestOfLine parse the rest of the line ================= */ const char *idParser::ParseRestOfLine( idStr &out ) { idToken token; out.Empty(); while(idParser::ReadToken( &token )) { if ( token.linesCrossed ) { idParser::UnreadSourceToken( &token ); break; } if ( out.Length() ) { out += " "; } out += token; } return out.c_str(); } /* ================ idParser::UnreadToken ================ */ void idParser::UnreadToken( idToken *token ) { idParser::UnreadSourceToken( token ); } /* ================ idParser::ReadTokenOnLine ================ */ int idParser::ReadTokenOnLine( idToken *token ) { idToken tok; if (!idParser::ReadToken( &tok )) { return false; } // if no lines were crossed before this token if ( !tok.linesCrossed ) { *token = tok; return true; } // idParser::UnreadSourceToken( &tok ); return false; } /* ================ idParser::ParseInt ================ */ int idParser::ParseInt( void ) { idToken token; if ( !idParser::ReadToken( &token ) ) { idParser::Error( "couldn't read expected integer" ); return 0; } if ( token.type == TT_PUNCTUATION && token == "-" ) { idParser::ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ); return -((signed int) token.GetIntValue()); } else if ( token.type != TT_NUMBER || token.subtype == TT_FLOAT ) { idParser::Error( "expected integer value, found '%s'", token.c_str() ); } return token.GetIntValue(); } /* ================ idParser::ParseBool ================ */ bool idParser::ParseBool( void ) { idToken token; if ( !idParser::ExpectTokenType( TT_NUMBER, 0, &token ) ) { idParser::Error( "couldn't read expected boolean" ); return false; } return ( token.GetIntValue() != 0 ); } /* ================ idParser::ParseFloat ================ */ float idParser::ParseFloat( void ) { idToken token; if ( !idParser::ReadToken( &token ) ) { idParser::Error( "couldn't read expected floating point number" ); return 0.0f; } if ( token.type == TT_PUNCTUATION && token == "-" ) { idParser::ExpectTokenType( TT_NUMBER, 0, &token ); return -token.GetFloatValue(); } else if ( token.type != TT_NUMBER ) { idParser::Error( "expected float value, found '%s'", token.c_str() ); } return token.GetFloatValue(); } /* ================ idParser::Parse1DMatrix ================ */ int idParser::Parse1DMatrix( int x, float *m ) { int i; if ( !idParser::ExpectTokenString( "(" ) ) { return false; } for ( i = 0; i < x; i++ ) { m[i] = idParser::ParseFloat(); } if ( !idParser::ExpectTokenString( ")" ) ) { return false; } return true; } /* ================ idParser::Parse2DMatrix ================ */ int idParser::Parse2DMatrix( int y, int x, float *m ) { int i; if ( !idParser::ExpectTokenString( "(" ) ) { return false; } for ( i = 0; i < y; i++ ) { if ( !idParser::Parse1DMatrix( x, m + i * x ) ) { return false; } } if ( !idParser::ExpectTokenString( ")" ) ) { return false; } return true; } /* ================ idParser::Parse3DMatrix ================ */ int idParser::Parse3DMatrix( int z, int y, int x, float *m ) { int i; if ( !idParser::ExpectTokenString( "(" ) ) { return false; } for ( i = 0 ; i < z; i++ ) { if ( !idParser::Parse2DMatrix( y, x, m + i * x*y ) ) { return false; } } if ( !idParser::ExpectTokenString( ")" ) ) { return false; } return true; } /* ================ idParser::GetLastWhiteSpace ================ */ int idParser::GetLastWhiteSpace( idStr &whiteSpace ) const { if ( scriptstack ) { scriptstack->GetLastWhiteSpace( whiteSpace ); } else { whiteSpace.Clear(); } return whiteSpace.Length(); } /* ================ idParser::SetMarker ================ */ void idParser::SetMarker( void ) { marker_p = NULL; } /* ================ idParser::GetStringFromMarker FIXME: this is very bad code, the script isn't even garrenteed to still be around ================ */ void idParser::GetStringFromMarker( idStr& out, bool clean ) { char* p; char save; if ( marker_p == NULL ) { marker_p = scriptstack->buffer; } if ( tokens ) { p = (char*)tokens->whiteSpaceStart_p; } else { p = (char*)scriptstack->script_p; } // Set the end character to NULL to give us a complete string save = *p; *p = 0; // If cleaning then reparse if ( clean ) { idParser temp( marker_p, strlen( marker_p ), "temp", flags ); idToken token; while ( temp.ReadToken ( &token ) ) { out += token; } } else { out = marker_p; } // restore the character we set to NULL *p = save; } /* ================ idParser::SetIncludePath ================ */ void idParser::SetIncludePath( const char *path ) { idParser::includepath = path; // add trailing path seperator if (idParser::includepath[idParser::includepath.Length()-1] != '\\' && idParser::includepath[idParser::includepath.Length()-1] != '/') { idParser::includepath += PATHSEPERATOR_STR; } } /* ================ idParser::SetPunctuations ================ */ void idParser::SetPunctuations( const punctuation_t *p ) { idParser::punctuations = p; } /* ================ idParser::SetFlags ================ */ void idParser::SetFlags( int flags ) { idLexer *s; idParser::flags = flags; for ( s = idParser::scriptstack; s; s = s->next ) { s->SetFlags( flags ); } } /* ================ idParser::GetFlags ================ */ int idParser::GetFlags( void ) const { return idParser::flags; } /* ================ idParser::LoadFile ================ */ int idParser::LoadFile( const char *filename, bool OSPath ) { idLexer *script; if ( idParser::loaded ) { idLib::common->FatalError("idParser::loadFile: another source already loaded"); return false; } script = new idLexer( filename, 0, OSPath ); if ( !script->IsLoaded() ) { delete script; return false; } script->SetFlags( idParser::flags ); script->SetPunctuations( idParser::punctuations ); script->next = NULL; idParser::OSPath = OSPath; idParser::filename = filename; idParser::scriptstack = script; idParser::tokens = NULL; idParser::indentstack = NULL; idParser::skip = 0; idParser::loaded = true; if ( !idParser::definehash ) { idParser::defines = NULL; idParser::definehash = (define_t **) Mem_ClearedAlloc( DEFINEHASHSIZE * sizeof(define_t *) ); idParser::AddGlobalDefinesToSource(); } return true; } /* ================ idParser::LoadMemory ================ */ int idParser::LoadMemory(const char *ptr, int length, const char *name ) { idLexer *script; if ( idParser::loaded ) { idLib::common->FatalError("idParser::loadMemory: another source already loaded"); return false; } script = new idLexer( ptr, length, name ); if ( !script->IsLoaded() ) { delete script; return false; } script->SetFlags( idParser::flags ); script->SetPunctuations( idParser::punctuations ); script->next = NULL; idParser::filename = name; idParser::scriptstack = script; idParser::tokens = NULL; idParser::indentstack = NULL; idParser::skip = 0; idParser::loaded = true; if ( !idParser::definehash ) { idParser::defines = NULL; idParser::definehash = (define_t **) Mem_ClearedAlloc( DEFINEHASHSIZE * sizeof(define_t *) ); idParser::AddGlobalDefinesToSource(); } return true; } /* ================ idParser::FreeSource ================ */ void idParser::FreeSource( bool keepDefines ) { idLexer *script; idToken *token; define_t *define; indent_t *indent; int i; // free all the scripts while( scriptstack ) { script = scriptstack; scriptstack = scriptstack->next; delete script; } // free all the tokens while( tokens ) { token = tokens; tokens = tokens->next; delete token; } // free all indents while( indentstack ) { indent = indentstack; indentstack = indentstack->next; Mem_Free( indent ); } if ( !keepDefines ) { // free hash table if ( definehash ) { // free defines for ( i = 0; i < DEFINEHASHSIZE; i++ ) { while( definehash[i] ) { define = definehash[i]; definehash[i] = definehash[i]->hashnext; FreeDefine(define); } } defines = NULL; Mem_Free( idParser::definehash ); definehash = NULL; } } loaded = false; } /* ================ idParser::GetPunctuationFromId ================ */ const char *idParser::GetPunctuationFromId( int id ) { int i; if ( !idParser::punctuations ) { idLexer lex; return lex.GetPunctuationFromId( id ); } for (i = 0; idParser::punctuations[i].p; i++) { if ( idParser::punctuations[i].n == id ) { return idParser::punctuations[i].p; } } return "unknown punctuation"; } /* ================ idParser::GetPunctuationId ================ */ int idParser::GetPunctuationId( const char *p ) { int i; if ( !idParser::punctuations ) { idLexer lex; return lex.GetPunctuationId( p ); } for (i = 0; idParser::punctuations[i].p; i++) { if ( !strcmp(idParser::punctuations[i].p, p) ) { return idParser::punctuations[i].n; } } return 0; } /* ================ idParser::idParser ================ */ idParser::idParser() { this->loaded = false; this->OSPath = false; this->punctuations = 0; this->flags = 0; this->scriptstack = NULL; this->indentstack = NULL; this->definehash = NULL; this->defines = NULL; this->tokens = NULL; this->marker_p = NULL; } /* ================ idParser::idParser ================ */ idParser::idParser( int flags ) { this->loaded = false; this->OSPath = false; this->punctuations = 0; this->flags = flags; this->scriptstack = NULL; this->indentstack = NULL; this->definehash = NULL; this->defines = NULL; this->tokens = NULL; this->marker_p = NULL; } /* ================ idParser::idParser ================ */ idParser::idParser( const char *filename, int flags, bool OSPath ) { this->loaded = false; this->OSPath = true; this->punctuations = 0; this->flags = flags; this->scriptstack = NULL; this->indentstack = NULL; this->definehash = NULL; this->defines = NULL; this->tokens = NULL; this->marker_p = NULL; LoadFile( filename, OSPath ); } /* ================ idParser::idParser ================ */ idParser::idParser( const char *ptr, int length, const char *name, int flags ) { this->loaded = false; this->OSPath = false; this->punctuations = 0; this->flags = flags; this->scriptstack = NULL; this->indentstack = NULL; this->definehash = NULL; this->defines = NULL; this->tokens = NULL; this->marker_p = NULL; LoadMemory( ptr, length, name ); } /* ================ idParser::~idParser ================ */ idParser::~idParser( void ) { idParser::FreeSource( false ); }