/* =========================================================================== Copyright (C) 2015 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 =========================================================================== */ // script.cpp : // C++ implementaion of tokenizing text interpretation. Class accepts filename // to load or pointer to preloaded text data. Standard tokenizing operations // such as skip white-space, get string, get integer, get float, get token, // and skip line are implemented. // // Note: all '//', '#', and ';' are treated as comments. Probably should // make this behaviour toggleable. // #include "script.h" #if defined(GAME_DLL) # include "../fgame/g_local.h" # define FILE_FS_FreeFile gi.FS_FreeFile # define FILE_FS_ReadFile(a, b) gi.FS_ReadFile(a, b, true) # define FILE_Malloc gi.Malloc # define FILE_Free gi.Free # define FILE_Error gi.Error #elif defined(CGAME_DLL) # define FILE_FS_FreeFile cgi.FS_FreeFile # define FILE_FS_ReadFile(a, b) cgi.FS_ReadFile(a, b, qtrue) # define FILE_Malloc cgi.Malloc # define FILE_Free cgi.Free # define FILE_Error cgi.Error #else # include "qcommon.h" # define FILE_FS_FreeFile FS_FreeFile # define FILE_FS_ReadFile(a, b) FS_ReadFile(a, b) # define FILE_Malloc Z_Malloc # define FILE_Free Z_Free # define FILE_Error Com_Error #endif typedef unsigned char byte; CLASS_DECLARATION(Class, Script, NULL) { {NULL, NULL} }; Script::~Script() { Close(); } Script::Script(const char *filename /*= 0*/) { buffer = NULL; script_p = NULL; end_p = NULL; line = 0; length = 0; releaseBuffer = false; tokenready = false; token[0] = 0; if (filename != 0) { LoadFile(filename); } } Script::Script() { buffer = NULL; script_p = NULL; end_p = NULL; line = 0; length = 0; releaseBuffer = false; tokenready = false; token[0] = 0; } void Script::Close(void) { if (releaseBuffer && buffer) { FILE_Free((void *)buffer); } buffer = NULL; script_p = NULL; end_p = NULL; line = 0; releaseBuffer = false; tokenready = false; token[0] = 0; // Loop Through the macro container and delete (del33t -hehe) them all for (int i = 1; i <= macrolist.NumObjects(); i++) { if (macrolist.ObjectAt(i)) { delete macrolist.ObjectAt(i); macrolist.ObjectAt(i) = 0; } } } /* ============== = = Filename = ============== */ const char *Script::Filename(void) { return filename.c_str(); } /* ============== = = GetLineNumber = ============== */ int Script::GetLineNumber(void) { return line; } /* ============== = = Reset = ============== */ void Script::Reset(void) { script_p = buffer; line = 1; tokenready = false; hasError = false; } /* ============== = = MarkPosition = ============== */ void Script::MarkPosition(scriptmarker_t *mark) { assert(mark); mark->tokenready = tokenready; mark->offset = script_p - buffer; mark->line = line; Q_strncpyz(mark->token, token, sizeof(mark->token)); } /* ============== = = RestorePosition = ============== */ void Script::RestorePosition(const scriptmarker_t *mark) { assert(mark); tokenready = mark->tokenready; script_p = buffer + mark->offset; line = mark->line; Q_strncpyz(token, mark->token, sizeof(token)); assert(script_p <= end_p); if (script_p > end_p) { script_p = end_p; } } /* ============== = = SkipToEOL = ============== */ qboolean Script::SkipToEOL(void) { if (script_p >= end_p) { return true; } while (*script_p != TOKENEOL) { if (script_p >= end_p) { return true; } script_p++; } return false; } /* ============== = = CheckOverflow = ============== */ void Script::CheckOverflow(void) { if (script_p >= end_p) { FILE_Error(ERR_DROP, "End of token file reached prematurely reading %s\n", filename.c_str()); } } /* ============== = = SkipWhiteSpace = ============== */ void Script::SkipWhiteSpace(qboolean crossline) { // // skip space // CheckOverflow(); while (*script_p <= TOKENSPACE) { if (*script_p++ == TOKENEOL) { if (!crossline) { FILE_Error(ERR_DROP, "Line %i is incomplete in file %s\n", line, filename.c_str()); } line++; } CheckOverflow(); } } qboolean Script::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) == '/')) { return true; } return false; } /* ============== = = SkipNonToken = ============== */ void Script::SkipNonToken(qboolean crossline) { // // skip space and comments // SkipWhiteSpace(crossline); while (AtComment()) { SkipToEOL(); SkipWhiteSpace(crossline); } } /* ============================================================================= = = Token section = ============================================================================= */ /* ============== = = TokenAvailable = ============== */ qboolean Script::TokenAvailable(qboolean crossline) { if (script_p >= end_p) { return false; } while (1) { while (*script_p <= TOKENSPACE) { if (*script_p == TOKENEOL) { if (!crossline) { return (false); } line++; } script_p++; if (script_p >= end_p) { return false; } } if (AtComment()) { qboolean done; done = SkipToEOL(); if (done) { return false; } } else { break; } } return true; } /* ============== = = CommentAvailable = ============== */ qboolean Script::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; } /* ============== = = UnGet = = Signals that the current token was not used, and should be reported = for the next GetToken. Note that GetToken (true); UnGetToken (); GetToken (false); = could cross a line boundary. = ============== */ void Script::UnGetToken(void) { tokenready = true; } /* ============== = = Get = ============== */ qboolean Script::AtString(qboolean crossline) { // // skip space // SkipNonToken(crossline); return (*script_p == '"'); } qboolean Script::AtOpenParen(qboolean crossline) { // // skip space // SkipNonToken(crossline); return (*script_p == '('); } qboolean Script::AtCloseParen(qboolean crossline) { // // skip space // SkipNonToken(crossline); return (*script_p == ')'); } qboolean Script::AtComma(qboolean crossline) { // // skip space // SkipNonToken(crossline); return (*script_p == ','); } qboolean Script::AtDot(qboolean crossline) { // // skip space // SkipNonToken(crossline); return (*script_p == '.'); } qboolean Script::AtAssignment(qboolean crossline) { // // skip space // SkipNonToken(crossline); return (*script_p == '=') || ((*script_p == '+') && (*(script_p + 1) == '=')) || ((*script_p == '-') && (*(script_p + 1) == '=')) || ((*script_p == '*') && (*(script_p + 1) == '=')) || ((*script_p == '/') && (*(script_p + 1) == '=')); } /* ============== = = Get = ============== */ const char *Script::GetToken(qboolean crossline) { const char* token_p = token; qboolean is_Macro = false; // is a token already waiting? if (tokenready) { tokenready = false; return token; } is_Macro = isMacro(); token_p = GrabNextToken(crossline); if (is_Macro && (strcmp(token_p, "$include") != 0)) { // Check to see if we need to add any definitions while ((!strcmp(token_p, "$define")) || (!strcmp(token_p, "$Define"))) { AddMacroDefinition(crossline); is_Macro = isMacro(); // if ( !is_Macro ) // return ""; token_p = GrabNextToken(crossline); } // Check to see if we need return any defines strings if (is_Macro && (strcmp(token_p, "$include") != 0) && (token_p[strlen(token_p) - 1] == '$')) { return GetMacroString(token_p); } } return token; } /* ============== = = GrabNextToken = ============== */ const char *Script::GrabNextToken(qboolean crossline) { char *token_p; // // skip space // SkipNonToken(crossline); // // copy token // if (*script_p == '"') { return GetString(crossline); } token_p = token; while ((*script_p > TOKENSPACE) && !AtComment()) { if ((*script_p == '\\') && (script_p < (end_p - 1))) { script_p++; switch (*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++ = *script_p; break; } script_p++; } else { *token_p++ = *script_p++; } if (token_p == &token[SCRIPT_MAXTOKEN]) { FILE_Error(ERR_DROP, "Token too large on line %i in file %s\n", line, filename.c_str()); } if (script_p == end_p) { break; } } *token_p = 0; return token; } /* ============== = = AddMacroDefinition = ============== */ void Script::AddMacroDefinition(qboolean crossline) { macro *theMacro; // Create a new macro structure. This new macro will be deleted in the // script close() theMacro = new macro; // Grab the macro name theMacro->macroName = "$"; theMacro->macroName.append(GrabNextToken(crossline)); theMacro->macroName.append("$"); //<-- Adding closing ($) to keep formatting consistant // Grab the macro string str tmpstr; tmpstr = GrabNextToken(crossline); // Check to see if we need return any defines strings if ((tmpstr != "$include") && (tmpstr[tmpstr.length() - 1] == '$')) { theMacro->macroText = GetMacroString(tmpstr); } else { theMacro->macroText = tmpstr; } macrolist.AddObject(theMacro); } /* ============== = = GetMacroString = ============== */ const char *Script::GetMacroString(const char *theMacroName) { macro *theMacro = 0; // Initialize this puppy for (int i = 1; i <= macrolist.NumObjects(); i++) { theMacro = macrolist.ObjectAt(i); if (!theMacro->macroName.cmp(theMacro->macroName.c_str(), theMacroName)) { const char *text = theMacro->macroText.c_str(); // If our define value is another define... if (text[0] == '$') { return EvaluateMacroString(text); } else { return text; } } } char tmpstr[255], *sptr = tmpstr; Q_strncpyz(tmpstr, theMacroName, sizeof(tmpstr)); tmpstr[strlen(tmpstr) - 1] = 0; sptr++; // We didn't find what we were looking for FILE_Error(ERR_DROP, "No Macro Text found for %s in file %s\n", theMacroName, filename.c_str()); return 0; } //================================================================ // Name: AddMacro // Class: Script // // Description: Adds a macro to the definitions list. // // Parameters: const char *name -- Name of the macro // const char *value -- Value // // Returns: None // //================================================================ void Script::AddMacro(const char *name, const char *value) {} /* ============== = = EvaluateMacroString = ============== */ char *Script::EvaluateMacroString(const char *theMacroString) { static char evalText[255]; char buffer[255], *bufferptr = buffer, oper = '+', newoper = '+'; bool haveoper = false; int i; float value = 0.0f, val = 0.0f; memset(buffer, 0, 255); for (i = 0; i <= strlen(theMacroString); i++) { if (theMacroString[i] == '+') { haveoper = true; newoper = '+'; } if (theMacroString[i] == '-') { haveoper = true; newoper = '-'; } if (theMacroString[i] == '*') { haveoper = true; newoper = '*'; } if (theMacroString[i] == '/') { haveoper = true; newoper = '/'; } if (theMacroString[i] == 0) { haveoper = true; } if (haveoper) { if (buffer[0] == '$') { val = atof(GetMacroString(buffer)); } else { val = atof(buffer); } value = EvaluateMacroMath(value, val, oper); oper = newoper; // Reset everything haveoper = false; memset(buffer, 0, 255); bufferptr = buffer; continue; } *bufferptr = theMacroString[i]; bufferptr++; } Com_sprintf(evalText, sizeof(evalText), "%f", value); return evalText; } /* ============== = = EvaluateMacroMath = ============== */ float Script::EvaluateMacroMath(float value, float newval, char oper) { switch (oper) { case '+': value += newval; break; case '-': value -= newval; break; case '*': value *= newval; break; case '/': value /= newval; break; } return value; } /* ============== = = isMacro = ============== */ qboolean Script::isMacro(void) { if (!TokenAvailable(true)) { return false; } SkipNonToken(true); if (*script_p == TOKENSPECIAL) { return true; } return false; } /* ============== = = GetLine = ============== */ const char *Script::GetLine(qboolean crossline) { const char *start; int size; // is a token already waiting? if (tokenready) { tokenready = false; return token; } // // skip space // SkipNonToken(crossline); // // copy token // start = script_p; SkipToEOL(); size = script_p - start; if (size < (SCRIPT_MAXTOKEN - 1)) { memcpy(token, start, size); token[size] = '\0'; } else { FILE_Error(ERR_DROP, "Token too large on line %i in file %s\n", line, filename.c_str()); } return token; } /* ============== = = GetRaw = ============== */ const char *Script::GetRaw(void) { const char *start; int size; // // skip white space // SkipWhiteSpace(true); // // copy token // start = script_p; SkipToEOL(); size = script_p - start; if (size < (SCRIPT_MAXTOKEN - 1)) { memset(token, 0, sizeof(token)); memcpy(token, start, size); } else { FILE_Error(ERR_DROP, "Token too large on line %i in file %s\n", line, filename.c_str()); } return token; } /* ============== = = GetString = ============== */ const char *Script::GetString(qboolean crossline) { int startline; char *token_p; // is a token already waiting? if (tokenready) { tokenready = false; return token; } // // skip space // SkipNonToken(crossline); if (*script_p != '"') { FILE_Error(ERR_DROP, "Expecting string on line %i in file %s\n", line, filename.c_str()); } script_p++; startline = line; token_p = token; while (*script_p != '"') { if (*script_p == TOKENEOL) { FILE_Error(ERR_DROP, "Line %i is incomplete while reading string in file %s\n", line, filename.c_str()); } if ((*script_p == '\\') && (script_p < (end_p - 1))) { script_p++; switch (*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++ = *script_p; break; } script_p++; } else { *token_p++ = *script_p++; } if (script_p >= end_p) { FILE_Error( ERR_DROP, "End of token file reached prematurely while reading " "string on\n" "line %d in file %s\n", startline, filename.c_str() ); } if (token_p == &token[SCRIPT_MAXTOKEN]) { FILE_Error(ERR_DROP, "String too large on line %i in file %s\n", line, filename.c_str()); } } *token_p = 0; // skip last quote script_p++; return token; } /* ============== = = GetSpecific = ============== */ qboolean Script::GetSpecific(const char *string) { do { if (!TokenAvailable(true)) { return false; } GetToken(true); } while (strcmp(token, string)); return true; } //=============================================================== // Name: GetBoolean // Class: Script // // Description: Retrieves the next boolean value in the token // stream. If the next token is either "true" // or "1", then it returns true. Otherwise, it // returns false. // // Parameters: qboolean -- determines if token parsing can cross newlines // // Returns: qboolean -- true if next token was "true" (or "1") // //=============================================================== qboolean Script::GetBoolean(qboolean crossline) { GetToken(crossline); if (Q_stricmp(token, "true") == 0) { return true; } else if (Q_stricmp(token, "1") == 0) { return true; } return false; } /* ============== = = GetInteger = ============== */ int Script::GetInteger(qboolean crossline) { GetToken(crossline); return atoi(token); } /* ============== = = GetDouble = ============== */ double Script::GetDouble(qboolean crossline) { GetToken(crossline); return atof(token); } /* ============== = = GetFloat = ============== */ float Script::GetFloat(qboolean crossline) { return (float)GetDouble(crossline); } /* ============== = = GetVector = ============== */ Vector Script::GetVector(qboolean crossline) { float x = GetFloat(crossline); float y = GetFloat(crossline); float z = GetFloat(crossline); return Vector(x, y, z); } /* =================== = = LinesInFile = =================== */ int Script::LinesInFile(void) { qboolean temp_tokenready; const char *temp_script_p; int temp_line; char temp_token[SCRIPT_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 Script::Parse(const char *data, size_t length, const char *name) { Close(); buffer = data; Reset(); this->length = length; end_p = script_p + length; filename = name; } /* ============== = = Load = ============== */ void Script::LoadFile(const char *name) { int length; byte *buffer; byte *tempbuf; const char *const_buffer; Close(); length = FILE_FS_ReadFile(name, (void **)&tempbuf); if (length == -1) { error("LoadFile", "Couldn't load %s\n", name); return; } hasError = false; if (length < 0) { hasError = true; return; } // create our own space buffer = (byte *)FILE_Malloc(length + 1); // copy the file over to our space memcpy(buffer, tempbuf, length); buffer[length] = 0; // free the file FILE_FS_FreeFile(tempbuf); const_buffer = (char *)buffer; Parse(const_buffer, length, name); releaseBuffer = true; } /* ============== = = LoadFile = ============== */ void Script::LoadFile(const char *name, int length, const char *buf) { Close(); // create our own space this->buffer = (const char *)FILE_Malloc(length); this->length = length; // copy the file over to our space memcpy((void *)this->buffer, buf, length); Parse(buffer, this->length, name); releaseBuffer = true; } qboolean Script::isValid() { return !hasError; } qboolean Script::EndOfFile(void) { return script_p >= end_p; } const char *Script::Token(void) { return token; }