//======================================================================= // Copyright XashXT Group 2007 © // parselib.c - script files parser //======================================================================= #include "launch.h" typedef struct { char filename[1024]; byte *buffer; byte *script_p; byte *end_p; int line; } script_t; // max included scripts #define MAX_INCLUDES 32 #define SC_EndOfScript( x ) SC_EndOfScript_( x, __LINE__ ) script_t scriptstack[ MAX_INCLUDES ]; script_t *script; char token[MAX_MSGLEN]; // contains token info static const char *saved_token; static int saved_scriptline; bool endofscript; bool tokenready; // only true if UnGetToken was just called bool SC_EndOfScript_ (bool newline, const int line ); /* ============== SC_AddScriptToStack ============== */ bool SC_AddScriptToStack( const char *name, byte *buffer, int size ) { if (script == &scriptstack[MAX_INCLUDES - 1]) { MsgDev( D_ERROR, "AddScriptToStack: script file limit exceeded %d\n", MAX_INCLUDES ); return false; } if(!buffer || !size) return false; script++; com_strncpy(script->filename, name, sizeof(script->filename)); script->buffer = buffer; script->line = com.com_scriptline = 1; script->script_p = script->buffer; script->end_p = script->buffer + size; return true; } bool SC_LoadScript( const char *filename, char *buf, int size ) { int result; string scriptname; if(!buf || size <= 0) { com_strncpy( scriptname, filename, MAX_STRING ); buf = FS_LoadFile( filename, &size ); } else com_strncpy( scriptname, "*sc_buffer", MAX_STRING ); script = scriptstack; result = SC_AddScriptToStack( scriptname, buf, size); endofscript = false; tokenready = false; return result; } bool SC_AddScript( const char *filename, char *buf, int size ) { string scriptname; if(!buf || size <= 0) { com_strncpy( scriptname, filename, MAX_STRING ); buf = FS_LoadFile( filename, &size ); } else com_strncpy( scriptname, "*sc_buffer", MAX_STRING ); return SC_AddScriptToStack( scriptname, buf, size ); } void SC_ResetScript( void ) { // can parsing again script->line = com.com_scriptline = saved_scriptline = 1; script->script_p = script->buffer; saved_token = NULL; } /* ================= SC_PushScript FIXME: use scriptstack ================= */ void SC_PushScript( const char **data_p ) { saved_token = *data_p; saved_scriptline = com.com_scriptline; } /* ================= SC_PopScript FIXME: use scriptstack ================= */ void SC_PopScript( const char **data_p ) { *data_p = saved_token; com.com_scriptline = saved_scriptline; } /* ============== SC_ReadToken ============== */ bool SC_ReadToken( bool newline ) { char *token_p; int c; if( tokenready ) { // is a token allready waiting? tokenready = false; return true; } if (script->script_p >= script->end_p) return SC_EndOfScript (newline); skip_whitespace: // skip whitespace while (*script->script_p <= 32) { if (script->script_p >= script->end_p) return SC_EndOfScript (newline); if (*script->script_p++ == '\n') { if (!newline) goto line_incomplete; com.com_scriptline = script->line++; } } if (script->script_p >= script->end_p) return SC_EndOfScript (newline); // ; // comments if(*script->script_p == ';' || ( script->script_p[0] == '/' && script->script_p[1] == '/') ) { if (!newline) goto line_incomplete; // ets++ if (*script->script_p == '/') script->script_p++; while (*script->script_p++ != '\n') { if (script->script_p >= script->end_p) return SC_EndOfScript (newline); } com.com_scriptline = script->line++; goto skip_whitespace; } // /* */ comments if (script->script_p[0] == '/' && script->script_p[1] == '*') { if (!newline) goto line_incomplete; script->script_p += 2; while (script->script_p[0] != '*' && script->script_p[1] != '/') { if (script->script_p >= script->end_p) return SC_EndOfScript (newline); if (*script->script_p++ == '\n') { if (!newline) goto line_incomplete; com.com_scriptline = script->line++; } } script->script_p += 2; goto skip_whitespace; } // copy token token_p = token; c = script->script_p[0]; // handle quoted strings specially if (*script->script_p == '"') { // quoted token script->script_p++; while (*script->script_p != '"') { if (token_p == &token[MAX_SYSPATH - 1]) { MsgDev(D_WARN, "GetToken: Token too large on line %i\n", com.com_scriptline); break; } *token_p++ = *script->script_p++; if (script->script_p == script->end_p) break; } script->script_p++; } else if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' || c == ',' || c == '[' || c == ']') { // parse single characters *token_p++ = *script->script_p++; } else { // parse regular word while ( *script->script_p > 32 ) { if (token_p == &token[MAX_SYSPATH - 1]) { MsgDev( D_WARN, "GetToken: Token too large on line %i\n",com.com_scriptline); break; } *token_p = c; *token_p++ = *script->script_p++; c = *script->script_p; if (script->script_p == script->end_p) break; if (c == '{' || c == '}'|| c == ')'|| c == '(' || c == '\'' || c == ':' || c == ',' || c == ';' || c == '[' || c == ']') break; } } *token_p = 0; // cutoff other symbols // quake style include & default MSVC style if (!com_strcmp(token, "$include") || !com_strcmp(token, "#include")) { SC_ReadToken( false ); SC_AddScript(token, NULL, 0 ); return SC_ReadToken (newline); } return true; line_incomplete: //invoke error return SC_EndOfScript( newline ); } /* ============== SC_ReadTokenSimple ============== */ bool SC_ReadTokenSimple( bool newline ) { char *token_p; if( tokenready ) // is a token allready waiting? { tokenready = false; return true; } if( script->script_p >= script->end_p ) return SC_EndOfScript( newline ); skip_whitespace: // skip whitespace while (*script->script_p <= 32) { if (script->script_p >= script->end_p) return SC_EndOfScript (newline); if (*script->script_p++ == '\n') { if( !newline ) return SC_EndOfScript( newline ); com.com_scriptline = script->line++; } } if (script->script_p >= script->end_p) return SC_EndOfScript (newline); // ; # // comments if( *script->script_p == ';' || *script->script_p == '#' || ( script->script_p[0] == '/' && script->script_p[1] == '/')) { if( !newline ) return SC_EndOfScript( newline ); // ets+++ if(*script->script_p == '/') script->script_p++; if( script->script_p[1] == 'T' && script->script_p[2] == 'X' ) { GI.TXcommand = script->script_p[3];//TX#"-style comment } while (*script->script_p++ != '\n') { if (script->script_p >= script->end_p) return SC_EndOfScript (newline); } com.com_scriptline = script->line++; goto skip_whitespace; } // /* */ comments if (script->script_p[0] == '/' && script->script_p[1] == '*') { if (!newline) return SC_EndOfScript( newline ); script->script_p += 2; while (script->script_p[0] != '*' && script->script_p[1] != '/') { if (script->script_p >= script->end_p) return SC_EndOfScript (newline); if (*script->script_p++ == '\n') { if( !newline ) return SC_EndOfScript( newline ); com.com_scriptline = script->line++; } } script->script_p += 2; goto skip_whitespace; } // copy token token_p = token; if (*script->script_p == '"') { // quoted token script->script_p++; while (*script->script_p != '"') { if (token_p == &token[MAX_SYSPATH - 1]) { MsgDev(D_WARN, "SC_ReadToken: Token too large on line %i\n", com.com_scriptline); break; } *token_p++ = *script->script_p++; if (script->script_p == script->end_p) break; } script->script_p++; } else // regular token { while ( *script->script_p > 32 && *script->script_p != ';') { if (token_p == &token[MAX_SYSPATH - 1]) { MsgDev( D_WARN, "GetToken: Token too large on line %i\n",com.com_scriptline); break; } *token_p++ = *script->script_p++; if (script->script_p == script->end_p) break; } } *token_p = 0; // quake style include & default MSVC style if (!com_strcmp(token, "$include")) { SC_ReadTokenSimple(false); SC_AddScript(token, NULL, 0 ); return SC_ReadTokenSimple(newline); } return true; } /* ============== SC_EndOfScript ============== */ bool SC_EndOfScript_( bool newline, const int line ) { if( !newline ) { com.com_scriptline = script->line; Sys_Break( "%s: line %i is incomplete (called at line %i)\n", script->filename, com.com_scriptline, line ); } if(!com_strcmp(script->filename, "*sc_buffer")) { endofscript = true; return false; } // FIXME: stupid xash bug if(Mem_IsAllocated( script->buffer )) Mem_Free( script->buffer ); else MsgDev( D_WARN, "attempt to free already freed script buffer '%s'\n", script->filename ); // FIXME // script->buffer = NULL; if( script == scriptstack + 1 ) { endofscript = true; return false; } script--; token[0] = 0; // clear last token com.com_scriptline = script->line; endofscript = true; return false; } /* ============== SC_TokenAvailable ============== */ bool SC_TokenAvailable (void) { char *search_p; search_p = script->script_p; if(search_p >= script->end_p) return false; while ( *search_p <= 32) { if (*search_p == '\n') return false; search_p++; if (search_p == script->end_p) return false; } if (*search_p == ';') return false; return true; } /* ================= SC_SkipWhiteSpace ================= */ const char *SC_SkipWhiteSpace( const char *data_p, bool *newline ) { int c; while((c = *data_p) <= ' ') { if( !c ) return NULL; if( c == '\n' ) { com.com_scriptline++; *newline = true; } data_p++; } return data_p; } /* ================= SC_SkipBracedSection Skips until a matching close brace is found. Internal brace depths are properly skipped. ================= */ void SC_SkipBracedSection( char **data_p, int depth ) { do { SC_ParseToken( data_p, true ); if( !token[1] ) { if( token[0] == '{' ) depth++; else if( token[0] == '}' ) depth--; } } while( depth && *data_p ); } /* ============== SC_ParseToken_Simple Parse a token out of a string, behaving like the qwcl console ============== */ bool SC_ParseToken_Simple( const char **data_p ) { int len = 0; const char *data; token[0] = 0; data = *data_p; if(!data) { endofscript = true; *data_p = NULL; return false; } // skip whitespace skipwhite: for (;*data <= ' ';data++) { if (*data == 0) { // end of file endofscript = true; *data_p = NULL; return false; } } if (*data == '/' && data[1] == '/') { // comment while (*data && *data != '\n' && *data != '\r') data++; goto skipwhite; } else if (*data == '\"') { // quoted string for (data++;*data && *data != '\"';data++) { // allow escaped " and \ case if (*data == '\\' && (data[1] == '\"' || data[1] == '\\')) data++; if (len < (int)sizeof(token) - 1) token[len++] = *data; } token[len] = 0; if (*data == '\"') data++; *data_p = data; } else { // regular word for (;*data > ' ';data++) if (len < (int)sizeof(token) - 1) token[len++] = *data; token[len] = 0; *data_p = data; } return true; } /* ============== SC_ParseToken Parse a token out of a string ============== */ char *SC_ParseToken( const char **data_p, bool allow_newline ) { int c; int len = 0; const char *data; bool newline = false; token[0] = 0; data = *data_p; if( !data ) { endofscript = true; *data_p = NULL; return token; } while( 1 ) { data = SC_SkipWhiteSpace( data, &newline ); if( !data ) { *data_p = NULL; return token; } if( newline && !allow_newline ) { *data_p = data; return token; } c = *data; if( c=='/' && data[1] == '/' ) { // skip // comments while( *data && *data != '\n' ) data++; } else if( c=='/' && data[1] == '*' ) { // skip /* comments while( data[1] && (data[0] != '*' || data[1] != '/')) { if( *data == '\n' ) com.com_scriptline++; data++; } if( *data ) data += 2; } else break; // an actual token } // handle quoted strings specially if (*data == '\"' || *data == '\'') { data++; while( 1 ) { c = *data++; if( c == '\n' ) com.com_scriptline++; if( c=='\"' || c=='\0' ) { token[len] = 0; *data_p = data; return token; } token[len++] = c; } } // parse single characters if( c == '{' || c == '}'|| c == ')' || c == '(' || c == '\'' || c == ':' || c == ',' ) { token[len] = c; data++; len++; token[len] = 0; *data_p = data; return token; } // parse a regular word do { token[len] = c; data++; len++; c = *data; if( c == '{' || c == '}'|| c == ')'|| c == '(' || c == '\'' || c == ':' || c == ',' ) break; } while( c > 32 ); token[len] = 0; *data_p = data; return token; } /* ============== SC_ParseWord Parse a word out of a string ============== */ char *SC_ParseWord( const char **data_p, bool allow_newline ) { int c; const char *data; int len = 0; bool newline = false; token[0] = 0; data = *data_p; if( !data ) { *data_p = NULL; return NULL; } while( 1 ) { data = SC_SkipWhiteSpace( data, &newline ); if( !data ) { *data_p = NULL; return NULL; } if( newline && !allow_newline ) { *data_p = data; return token; } c = *data; if( c=='/' && data[1] == '/' ) { // skip // comments while( *data && *data != '\n' ) data++; } else if( c=='/' && data[1] == '*' ) { // skip /* comments while( data[1] && (data[0] != '*' || data[1] != '/')) { if( *data == '\n' ) com.com_scriptline++; data++; } if( *data ) data += 2; } else break; // an actual token } // handle quoted strings specially if( c == '\"' ) { data++; do { c = *data++; if (c=='\"' || c=='\0') { token[len] = 0; *data_p = data; return token; } token[len] = c; len++; } while( 1 ); } // parse numbers if( c >= '0' && c <= '9' ) { if( c == '0' && data[1] == 'x' ) { // parse hex token[0] = '0'; c='x'; len=1; data++; while( 1 ) { // parse regular number token[len] = c; data++; len++; c = *data; if ((c < '0'|| c > '9') && (c < 'a'||c > 'f') && (c < 'A'|| c > 'F') && c != '.') break; } } else { while( 1 ) { // parse regular number token[len] = c; data++; len++; c = *data; if ((c < '0'|| c > '9') && c != '.') break; } } token[len] = 0; *data_p = data; return token; } // parse words else if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { do { token[len] = c; data++; len++; c = *data; } while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'); token[len] = 0; *data_p = data; return token; } else { token[len] = c; len++; token[len] = 0; *data_p = data; return token; } } /* ============================================================================= MAIN PUBLIC FUNCTIONS ============================================================================= */ /* ============== Match Token With check current token for match with user keyword ============== */ bool SC_MatchToken( const char *match ) { if (!com_stricmp( token, match )) return true; return false; } /* ============== SC_SkipToken skip current token and jump into newline ============== */ void SC_SkipToken( void ) { switch( Sys.app_name ) { case HOST_BSPLIB: case HOST_SPRITE: case HOST_STUDIO: case HOST_WADLIB: // don't handle single characters SC_ReadTokenSimple( false ); break; default: SC_ReadToken( false ); break; } tokenready = true; } /* ============== SC_FreeToken release current token to get him again with SC_GetToken() ============== */ void SC_FreeToken( void ) { tokenready = true; } /* ============== SC_TryToken check token for available on current line ============== */ bool SC_TryToken( void ) { if(!SC_TokenAvailable()) return false; switch( Sys.app_name ) { case HOST_BSPLIB: case HOST_SPRITE: case HOST_STUDIO: case HOST_WADLIB: // don't handle single characters SC_ReadTokenSimple( false ); break; default: SC_ReadToken( false ); break; } return true; } /* ============== SC_GetToken get token on current or newline ============== */ char *SC_GetToken( bool newline ) { switch( Sys.app_name ) { case HOST_BSPLIB: case HOST_SPRITE: case HOST_STUDIO: case HOST_WADLIB: // don't handle single characters if(SC_ReadTokenSimple( newline )) return token; break; default: if(SC_ReadToken( newline )) return token; break; } return NULL; } /* ============== SC_Token return current token ============== */ char *SC_Token( void ) { return token; } /* ============ SC_StringContains ============ */ char *SC_StringContains(char *str1, char *str2, int casecmp) { int len, i, j; len = com_strlen(str1) - com_strlen(str2); for (i = 0; i <= len; i++, str1++) { for (j = 0; str2[j]; j++) { if (casecmp) { if (str1[j] != str2[j]) break; } else { if(com_toupper(str1[j]) != com_toupper(str2[j])) break; } } if (!str2[j]) return str1; } return NULL; } /* ============ SC_FilterToken ============ */ bool SC_FilterToken(char *filter, char *name, int casecmp) { char buf[MAX_MSGLEN]; char *ptr; int i, found; while(*filter) { if(*filter == '*') { filter++; for (i = 0; *filter; i++) { if (*filter == '*' || *filter == '?') break; buf[i] = *filter; filter++; } buf[i] = '\0'; if (com_strlen(buf)) { ptr = SC_StringContains(name, buf, casecmp); if (!ptr) return false; name = ptr + com_strlen(buf); } } else if (*filter == '?') { filter++; name++; } else if (*filter == '[' && *(filter+1) == '[') { filter++; } else if (*filter == '[') { filter++; found = false; while(*filter && !found) { if (*filter == ']' && *(filter+1) != ']') break; if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { if (casecmp) { if (*name >= *filter && *name <= *(filter+2)) found = true; } else { if (com_toupper(*name) >= com_toupper(*filter) && com_toupper(*name) <= com_toupper(*(filter+2))) found = true; } filter += 3; } else { if (casecmp) { if (*filter == *name) found = true; } else { if (com_toupper(*filter) == com_toupper(*name)) found = true; } filter++; } } if (!found) return false; while(*filter) { if (*filter == ']' && *(filter+1) != ']') break; filter++; } filter++; name++; } else { if (casecmp) { if (*filter != *name) return false; } else { if (com_toupper(*filter) != com_toupper(*name)) return false; } filter++; name++; } } return true; } /* ================= SC_HashKey returns hash key for string ================= */ uint SC_HashKey( const char *string, uint hashSize ) { uint hashKey = 0; int i; for( i = 0; string[i]; i++ ) hashKey = (hashKey + i) * 37 + com_tolower(string[i]); return (hashKey % hashSize); }