/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "Engine/StdH.h" #include #include #include "ParsingSymbols.h" #include #include #include #include #include #include template class CDynamicArray; // shell type used for undeclared symbols INDEX _shell_istUndeclared = -1; // pointer to global shell object CShell *_pShell = NULL; void *_pvNextToDeclare=NULL; // != NULL if declaring external symbol defined in exe code // define console variable for number of last console lines INDEX con_iLastLines = 5; extern void yy_switch_to_buffer(YY_BUFFER_STATE); // declarations for recursive shell script parsing struct BufferStackEntry { YY_BUFFER_STATE bse_bs; const char *bse_strName; const char *bse_strContents; int bse_iLineCt; BOOL bse_bParserEnd; }; static BufferStackEntry _abseBufferStack[SHELL_MAX_INCLUDE_LEVEL]; static int _ibsBufferStackTop = -1; BOOL _bExecNextBlock = 1; void ShellPushBuffer(const char *strName, const char *strBuffer, BOOL bParserEnd) { _ibsBufferStackTop++; _abseBufferStack[_ibsBufferStackTop].bse_strContents = strdup(strBuffer); _abseBufferStack[_ibsBufferStackTop].bse_strName = strdup(strName); _abseBufferStack[_ibsBufferStackTop].bse_iLineCt = 1; _abseBufferStack[_ibsBufferStackTop].bse_bParserEnd = bParserEnd; _abseBufferStack[_ibsBufferStackTop].bse_bs = yy_scan_string((char*)(const char*)strBuffer); yy_switch_to_buffer(_abseBufferStack[_ibsBufferStackTop].bse_bs); } BOOL ShellPopBuffer(void) { yy_delete_buffer( _abseBufferStack[_ibsBufferStackTop].bse_bs); free((void*)_abseBufferStack[_ibsBufferStackTop].bse_strName); free((void*)_abseBufferStack[_ibsBufferStackTop].bse_strContents); BOOL bParserEnd = _abseBufferStack[_ibsBufferStackTop].bse_bParserEnd; _ibsBufferStackTop--; if (_ibsBufferStackTop>=0) { yy_switch_to_buffer(_abseBufferStack[_ibsBufferStackTop].bse_bs); } return bParserEnd; } const char *ShellGetBufferName(void) { return _abseBufferStack[_ibsBufferStackTop].bse_strName; } int ShellGetBufferLineNumber(void) { return _abseBufferStack[_ibsBufferStackTop].bse_iLineCt; } int ShellGetBufferStackDepth(void) { return _ibsBufferStackTop; } const char *ShellGetBufferContents(void) { return _abseBufferStack[_ibsBufferStackTop].bse_strContents; } void ShellCountOneLine(void) { _abseBufferStack[_ibsBufferStackTop].bse_iLineCt++; } // temporary values for parsing CDynamicStackArray _shell_astrTempStrings; // values for extern declarations CDynamicStackArray _shell_astrExtStrings; CDynamicStackArray _shell_afExtFloats; //static const char *strCommandLine = ""; FLOAT tmp_af[10] = { 0 }; INDEX tmp_ai[10] = { 0 }; INDEX tmp_fAdd = 0; INDEX tmp_i = 0; void CShellSymbol::Clear(void) { ss_istType = -1; ss_strName.Clear(); ss_ulFlags = 0; }; BOOL CShellSymbol::IsDeclared(void) { return ss_istType>=0 && ss_istType!=_shell_istUndeclared; } CTString CShellSymbol::GetCompletionString(void) const { // get its type ShellType &st = _shell_ast[ss_istType]; // get its name if (st.st_sttType==STT_FUNCTION) { return ss_strName + "()"; } else if (st.st_sttType==STT_ARRAY) { return ss_strName + "[]"; } else { return ss_strName; } } // Constructor. CShell::CShell(void) { // allocate undefined symbol _shell_istUndeclared = _shell_ast.Allocate(); pwoCurrentWorld = NULL; }; CShell::~CShell(void) { _shell_astrExtStrings.Clear(); _shell_afExtFloats.Clear(); }; static const INDEX _bTRUE = TRUE; static const INDEX _bFALSE = FALSE; CTString ScriptEsc(const CTString &str) { CTString strResult = ""; const char *pchSrc = (const char *)str; char buf[2]; buf[1] = 0; while (*pchSrc!=0) { switch(*pchSrc) { case 10: strResult+="\\n"; break; case 13: strResult+="\\r"; break; case '\\': strResult+="\\\\"; break; case '"': strResult+="\\\""; break; default: buf[0] = *pchSrc; strResult+=buf; break; } pchSrc++; } return strResult; } #pragma inline_depth(0) void MakeAccessViolation(void* pArgs) { INDEX bDont = NEXTARGUMENT(INDEX); if( bDont) return; char *p=NULL; *p=1; } int _a=123; void MakeStackOverflow(void* pArgs) { INDEX bDont = NEXTARGUMENT(INDEX); if( bDont) return; int a[1000]; a[999] = _a; MakeStackOverflow(0); _a=a[999]; } void MakeFatalError(void* pArgs) { INDEX bDont = NEXTARGUMENT(INDEX); if( bDont) return; FatalError( "MakeFatalError()"); } extern void ReportGlobalMemoryStatus(void) { #ifdef PLATFORM_WIN32 CPrintF(TRANSV("Global memory status...\n")); MEMORYSTATUS ms; GlobalMemoryStatus(&ms); #define MB (1024*1024) CPrintF(TRANSV(" Physical memory used: %4d/%4dMB\n"), (ms.dwTotalPhys -ms.dwAvailPhys )/MB, ms.dwTotalPhys /MB); CPrintF(TRANSV(" Page file used: %4d/%4dMB\n"), (ms.dwTotalPageFile-ms.dwAvailPageFile)/MB, ms.dwTotalPageFile/MB); CPrintF(TRANSV(" Virtual memory used: %4d/%4dMB\n"), (ms.dwTotalVirtual -ms.dwAvailVirtual )/MB, ms.dwTotalVirtual /MB); CPrintF(TRANSV(" Memory load: %3d%%\n"), ms.dwMemoryLoad); DWORD dwMin; DWORD dwMax; GetProcessWorkingSetSize(GetCurrentProcess(), &dwMin, &dwMax); CPrintF(TRANSV(" Process working set: %dMB-%dMB\n\n"), dwMin/(1024*1024), dwMax/(1024*1024)); #endif } static void MemoryInfo(void) { ReportGlobalMemoryStatus(); #ifdef PLATFORM_WIN32 _HEAPINFO hinfo; int heapstatus; hinfo._pentry = NULL; SLONG slTotalUsed = 0; SLONG slTotalFree = 0; INDEX ctUsed = 0; INDEX ctFree = 0; CPrintF( "Walking heap...\n"); while( ( heapstatus = _heapwalk( &hinfo ) ) == _HEAPOK ) { if (hinfo._useflag == _USEDENTRY ) { slTotalUsed+=hinfo._size; ctUsed++; } else { slTotalFree+=hinfo._size; ctFree++; } } switch( heapstatus ) { case _HEAPEMPTY: CPrintF( "Heap empty?!?\n" ); break; case _HEAPEND: CPrintF( "Heap ok.\n" ); break; case _HEAPBADPTR: CPrintF( "ERROR - bad pointer to heap\n" ); break; case _HEAPBADBEGIN: CPrintF( "ERROR - bad start of heap\n" ); break; case _HEAPBADNODE: CPrintF( "ERROR - bad node in heap\n" ); break; } CPrintF( "Total used: %d bytes (%.2f MB) in %d blocks\n", slTotalUsed, slTotalUsed/1024.0f/1024.0f, ctUsed); CPrintF( "Total free: %d bytes (%.2f MB) in %d blocks\n", slTotalFree, slTotalFree/1024.0f/1024.0f, ctFree); #endif } // get help for a shell symbol extern CTString GetShellSymbolHelp_t(const CTString &strSymbol) { CTString strPattern = strSymbol+"*"; // open the symbol help file CTFileStream strm; strm.Open_t(CTString("Help\\ShellSymbols.txt")); // while not at the end of file while (!strm.AtEOF()) { // read the symbol name and its help CTString strSymbolInFile; strm.GetLine_t(strSymbolInFile, ':'); strSymbolInFile.TrimSpacesLeft(); strSymbolInFile.TrimSpacesRight(); CTString strHelpInFile; strm.GetLine_t(strHelpInFile, '$'); strHelpInFile.TrimSpacesLeft(); strHelpInFile.TrimSpacesRight(); // if that is the one if( strSymbolInFile.Matches(strPattern)) { // print the help return strHelpInFile; } } return ""; } // check if there is help for a shell symbol extern BOOL CheckShellSymbolHelp(const CTString &strSymbol) { try { return GetShellSymbolHelp_t(strSymbol)!=""; } catch(char *strError) { (void)strError; return FALSE; } } // print help for a shell symbol extern void PrintShellSymbolHelp(const CTString &strSymbol) { // try to try { CTString strHelp = GetShellSymbolHelp_t(strSymbol); if (strHelp!="") { CPrintF("%s\n", (const char *) strHelp); } else { CPrintF( TRANS("No help found for '%s'.\n"), (const char *) strSymbol); } // if failed } catch(char *strError) { // just print the error CPrintF( TRANS("Cannot print help for '%s': %s\n"), (const char *) strSymbol, strError); } } extern void ListSymbolsByPattern(CTString strPattern) { // synchronize access to global shell CTSingleLock slShell(&_pShell->sh_csShell, TRUE); // for each of symbols in the shell FOREACHINDYNAMICARRAY(_pShell->sh_assSymbols, CShellSymbol, itss) { CShellSymbol &ss = *itss; // if it is not visible to user, or not matching if (!(ss.ss_ulFlags&SSF_USER) || !ss.ss_strName.Matches(strPattern)) { // skip it continue; } // get its type ShellType &st = _shell_ast[ss.ss_istType]; if (ss.ss_ulFlags & SSF_CONSTANT) { CPrintF("const "); } if (ss.ss_ulFlags & SSF_PERSISTENT) { CPrintF("persistent "); } // print its declaration to the console if (st.st_sttType == STT_FUNCTION) { CPrintF("void %s(void)", (const char *) ss.ss_strName); } else if (st.st_sttType == STT_STRING) { CPrintF("CTString %s = \"%s\"", (const char *) ss.ss_strName, (const char *) (*(CTString*)ss.ss_pvValue)); } else if (st.st_sttType == STT_FLOAT) { CPrintF("FLOAT %s = %g", (const char *) ss.ss_strName, *(FLOAT*)ss.ss_pvValue); } else if (st.st_sttType == STT_INDEX) { CPrintF("INDEX %s = %d (0x%08x)", (const char *) ss.ss_strName, *(INDEX*)ss.ss_pvValue, *(INDEX*)ss.ss_pvValue); } else if (st.st_sttType == STT_ARRAY) { // get base type ShellType &stBase = _shell_ast[st.st_istBaseType]; if (stBase.st_sttType == STT_FLOAT) { CPrintF("FLOAT %s[%d]", (const char *) ss.ss_strName, st.st_ctArraySize); } else if (stBase.st_sttType == STT_INDEX) { CPrintF("INDEX %s[%d]", (const char *) ss.ss_strName, st.st_ctArraySize); } else if (stBase.st_sttType == STT_STRING) { CPrintF("CTString %s[%d]", (const char *) ss.ss_strName, st.st_ctArraySize); } else { ASSERT(FALSE); } } else { ASSERT(FALSE); } if (!CheckShellSymbolHelp(ss.ss_strName)) { CPrintF( TRANS(" help N/A")); } CPrintF("\n"); } } // Print a list of all symbols in global shell to console. static void ListSymbols(void) { // print header CPrintF( TRANS("Useful symbols:\n")); // list all symbols ListSymbolsByPattern("*"); } // output any string to console void Echo(void* pArgs) { CTString str = *NEXTARGUMENT(CTString*); CPrintF("%s", (const char *) str); } CTString UndecorateString(void* pArgs) { CTString strString = *NEXTARGUMENT(CTString*); return strString.Undecorated(); } BOOL MatchStrings(void* pArgs) { CTString strString = *NEXTARGUMENT(CTString*); CTString strPattern = *NEXTARGUMENT(CTString*); return strString.Matches(strPattern); } CTString MyLoadString(void* pArgs) { CTString strFileName = *NEXTARGUMENT(CTString*); try { CTString strString; strString.Load_t(strFileName); return strString; } catch (char *strError) { (void)strError; return ""; } } void MySaveString(void* pArgs) { CTString strFileName = *NEXTARGUMENT(CTString*); CTString strString = *NEXTARGUMENT(CTString*); try { strString.Save_t(strFileName); } catch (char *strError) { (void)strError; } } // load command batch files void LoadCommands(void) { // list all command files CDynamicStackArray afnmCmds; MakeDirList( afnmCmds, CTString("Scripts\\Commands\\"), CTString("*.ini"), DLI_RECURSIVE); // for each file for(INDEX i=0; iGetSymbol(strName, FALSE); Declaration(SSF_EXTERNAL|SSF_USER, iType, ssNew, NULL, NULL); ShellTypeDelete(iType); // get symbol type ShellTypeType stt = _shell_ast[ssNew.ss_istType].st_sttType; // if the symbol is ok if (stt == STT_STRING && !(ssNew.ss_ulFlags&SSF_CONSTANT)) { // assign value *(CTString*)ssNew.ss_pvValue = "!command "+strCmd; } else { _pShell->ErrorF("Symbol '%s' is not suitable to be a command", (const char *) ssNew.ss_strName); } } } CTString ToUpper(const CTString &strResult) { char *pch = (char*)(const char *)strResult; for(INDEX i=0; i", strDeclaration, TRUE); yyparse(); // ShellPopBuffer(); _bExecNextBlock = old_bExecNextBlock; _iParsing--; if (_iParsing<=0) { _shell_astrTempStrings.PopAll(); } // don't use that value for parsing any more _pvNextToDeclare = NULL; } // Execute command(s). void CShell::Execute(const CTString &strCommands) { // synchronize access to shell CTSingleLock slShell(&sh_csShell, TRUE); // ASSERT(_iParsing==0); _iParsing++; // parse the string const BOOL old_bExecNextBlock = _bExecNextBlock; _bExecNextBlock = 1; ShellPushBuffer("", strCommands, TRUE); yyparse(); //ShellPopBuffer(); _bExecNextBlock = old_bExecNextBlock; _iParsing--; if (_iParsing<=0) { _shell_astrTempStrings.PopAll(); } }; // Get a shell symbol by its name. CShellSymbol *CShell::GetSymbol(const CTString &strName, BOOL bDeclaredOnly) { // synchronize access to shell CTSingleLock slShell(&sh_csShell, TRUE); // for each of symbols in the shell FOREACHINDYNAMICARRAY(sh_assSymbols, CShellSymbol, itss) { // if it is the right one if (itss->ss_strName==strName) { // return it return itss; } } // if none is found... // if only declared symbols are allowed if (bDeclaredOnly) { // return nothing return NULL; // if undeclared symbols are allowed } else { // create a new one with that name and undefined type CShellSymbol &ssNew = *sh_assSymbols.New(1); ssNew.ss_strName = strName; ssNew.ss_istType = _shell_istUndeclared; ssNew.ss_pvValue = NULL; ssNew.ss_ulFlags = 0; ssNew.ss_pPreFunc = NULL; ssNew.ss_pPostFunc = NULL; return &ssNew; } }; FLOAT CShell::GetFLOAT(const CTString &strName) { // get the symbol CShellSymbol *pss = GetSymbol(strName, TRUE); // if it doesn't exist or is not of given type if (pss==NULL || _shell_ast[pss->ss_istType].st_sttType!=STT_FLOAT) { // error ASSERT(FALSE); return -666.0f; } // get it return *(FLOAT*)pss->ss_pvValue; } void CShell::SetFLOAT(const CTString &strName, FLOAT fValue) { CShellSymbol *pss = GetSymbol(strName, TRUE); // if it doesn't exist or is not of given type if (pss==NULL || _shell_ast[pss->ss_istType].st_sttType!=STT_FLOAT) { // error ASSERT(FALSE); return; } // set it *(FLOAT*)pss->ss_pvValue = fValue; } INDEX CShell::GetINDEX(const CTString &strName) { // get the symbol CShellSymbol *pss = GetSymbol(strName, TRUE); // if it doesn't exist or is not of given type if (pss==NULL || _shell_ast[pss->ss_istType].st_sttType!=STT_INDEX) { // error ASSERT(FALSE); return -666; } // get it return *(INDEX*)pss->ss_pvValue; } void CShell::SetINDEX(const CTString &strName, INDEX iValue) { CShellSymbol *pss = GetSymbol(strName, TRUE); // if it doesn't exist or is not of given type if (pss==NULL || _shell_ast[pss->ss_istType].st_sttType!=STT_INDEX) { // error ASSERT(FALSE); return; } // set it *(INDEX*)pss->ss_pvValue = iValue; } CTString CShell::GetString(const CTString &strName) { // get the symbol CShellSymbol *pss = GetSymbol(strName, TRUE); // if it doesn't exist or is not of given type if (pss==NULL || _shell_ast[pss->ss_istType].st_sttType!=STT_STRING) { // error ASSERT(FALSE); return ""; } // get it return *(CTString*)pss->ss_pvValue; } void CShell::SetString(const CTString &strName, const CTString &strValue) { CShellSymbol *pss = GetSymbol(strName, TRUE); // if it doesn't exist or is not of given type if (pss==NULL || _shell_ast[pss->ss_istType].st_sttType!=STT_STRING) { // error ASSERT(FALSE); return; } // set it *(CTString*)pss->ss_pvValue = strValue; } CTString CShell::GetValue(const CTString &strName) { // get the symbol CShellSymbol *pss = GetSymbol(strName, TRUE); // if it doesn't exist if (pss==NULL) { // error ASSERT(FALSE); return ""; } // get it ShellTypeType stt = _shell_ast[pss->ss_istType].st_sttType; CTString strValue; switch(stt) { case STT_STRING: strValue = *(CTString*)pss->ss_pvValue; break; case STT_INDEX: strValue.PrintF("%d", *(INDEX*)pss->ss_pvValue); break; case STT_FLOAT: strValue.PrintF("%g", *(FLOAT*)pss->ss_pvValue); break; default: ASSERT(FALSE); return ""; } return strValue; } void CShell::SetValue(const CTString &strName, const CTString &strValue) { // get the symbol CShellSymbol *pss = GetSymbol(strName, TRUE); // if it doesn't exist if (pss==NULL) { // error ASSERT(FALSE); return; } // get it ShellTypeType stt = _shell_ast[pss->ss_istType].st_sttType; switch(stt) { case STT_STRING: *(CTString*)pss->ss_pvValue = strValue; break; case STT_INDEX: ((CTString&)strValue).ScanF("%d", (INDEX*)pss->ss_pvValue); break; case STT_FLOAT: ((CTString&)strValue).ScanF("%g", (FLOAT*)pss->ss_pvValue); break; default: ASSERT(FALSE); } return; } /* * Report error in shell script processing. */ void CShell::ErrorF(const char *strFormat, ...) { // synchronize access to shell CTSingleLock slShell(&sh_csShell, TRUE); // print the error file and line const char *strName = ShellGetBufferName(); int iLine = ShellGetBufferLineNumber(); if (strName[0] == '<') { CPrintF("%s\n%s(%d): ", ShellGetBufferContents(), strName, iLine); } else { CPrintF("%s(%d): ", strName, iLine); } // format the message in buffer va_list arg; va_start(arg, strFormat); CTString strBuffer; strBuffer.VPrintF(strFormat, arg); va_end(arg); // print it to the main console CPrintF(strBuffer); // go to new line CPrintF("\n"); } // Save shell commands to restore persistent symbols to a script file void CShell::StorePersistentSymbols(const CTFileName &fnScript) { // synchronize access to global shell CTSingleLock slShell(&sh_csShell, TRUE); try { // open the file CTFileStream fScript; fScript.Create_t(fnScript); // print header fScript.FPrintF_t("// automatically saved persistent symbols:\n"); // for each of symbols in the shell FOREACHINDYNAMICARRAY(sh_assSymbols, CShellSymbol, itss) { CShellSymbol &ss = *itss; // if it is not persistent if (! (ss.ss_ulFlags & SSF_PERSISTENT)) { // skip it continue; } const char *strUser = (ss.ss_ulFlags & SSF_USER)?"user ":""; // get its type ShellType &st = _shell_ast[ss.ss_istType]; // if array if (st.st_sttType==STT_ARRAY) { // get base type ShellType &stBase = _shell_ast[st.st_istBaseType]; CTString strType; // if float if (stBase.st_sttType==STT_FLOAT) { // dump all members as floats for(INDEX i=0; i