//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "console/console.h" #include "console/consoleInternal.h" #include "console/engineAPI.h" #include "console/ast.h" #include "core/strings/findMatch.h" #include "core/strings/stringUnit.h" #include "core/strings/unicode.h" #include "core/stream/fileStream.h" #include "console/compiler.h" #include "platform/platformInput.h" #include "core/util/journal/journal.h" #include "core/util/uuid.h" #ifdef TORQUE_DEMO_PURCHASE #include "gui/core/guiCanvas.h" #endif // This is a temporary hack to get tools using the library to // link in this module which contains no other references. bool LinkConsoleFunctions = false; // Buffer for expanding script filenames. static char scriptFilenameBuffer[1024]; //============================================================================= // String Functions. //============================================================================= // MARK: ---- String Functions ---- //----------------------------------------------------------------------------- DefineConsoleFunction( strasc, int, ( const char* chr ),, "Return the integer character code value corresponding to the first character in the given string.\n" "@param chr a (one-character) string.\n" "@return the UTF32 code value for the first character in the given string.\n" "@ingroup Strings" ) { return oneUTF8toUTF32( chr ); } //----------------------------------------------------------------------------- DefineConsoleFunction( strformat, const char*, ( const char* format, const char* value ),, "Format the given value as a string using printf-style formatting.\n" "@param format A printf-style format string.\n" "@param value The value argument matching the given format string.\n\n" "@tsexample\n" "// Convert the given integer value to a string in a hex notation.\n" "%hex = strformat( \"%x\", %value );\n" "@endtsexample\n" "@ingroup Strings\n" "@see http://en.wikipedia.org/wiki/Printf" ) { static const U32 bufSize = 64; char* pBuffer = Con::getReturnBuffer(bufSize); const char *pch = format; pBuffer[0] = '\0'; while (*pch != '\0' && *pch !='%') pch++; while (*pch != '\0' && !dIsalpha(*pch)) pch++; if (*pch == '\0') { Con::errorf("strFormat: Invalid format string!\n"); return pBuffer; } switch(*pch) { case 'c': case 'C': case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': dSprintf( pBuffer, bufSize, format, dAtoi( value ) ); break; case 'e': case 'E': case 'f': case 'g': case 'G': dSprintf( pBuffer, bufSize, format, dAtof( value ) ); break; default: Con::errorf("strFormat: Invalid format string!\n"); break; } return pBuffer; } //----------------------------------------------------------------------------- DefineConsoleFunction( strcmp, S32, ( const char* str1, const char* str2 ),, "Compares two strings using case-sensitive comparison.\n" "@param str1 The first string.\n" "@param str2 The second string.\n" "@return 0 if both strings are equal, a value <0 if the first character different in str1 has a smaller character code " "value than the character at the same position in str2, and a value >1 otherwise.\n\n" "@tsexample\n" "if( strcmp( %var, \"foobar\" ) == 0 )\n" " echo( \"%var is equal to 'foobar'\" );\n" "@endtsexample\n" "@see stricmp\n" "@see strnatcmp\n" "@ingroup Strings" ) { return dStrcmp( str1, str2 ); } //----------------------------------------------------------------------------- DefineConsoleFunction( stricmp, S32, ( const char* str1, const char* str2 ),, "Compares two strings using case-insensitive comparison.\n" "@param str1 The first string.\n" "@param str2 The second string.\n" "@return 0 if both strings are equal, a value <0 if the first character different in str1 has a smaller character code " "value than the character at the same position in str2, and a value >0 otherwise.\n\n" "@tsexample\n" "if( stricmp( \"FOObar\", \"foobar\" ) == 0 )\n" " echo( \"this is always true\" );\n" "@endtsexample\n" "@see strcmp\n" "@see strinatcmp\n" "@ingroup Strings" ) { return dStricmp( str1, str2 ); } //----------------------------------------------------------------------------- DefineConsoleFunction( strnatcmp, S32, ( const char* str1, const char* str2 ),, "Compares two strings using \"natural order\" case-sensitive comparison.\n" "Natural order means that rather than solely comparing single character code values, strings are ordered in a " "natural way. For example, the string \"hello10\" is considered greater than the string \"hello2\" even though " "the first numeric character in \"hello10\" actually has a smaller character value than the corresponding character " "in \"hello2\". However, since 10 is greater than 2, strnatcmp will put \"hello10\" after \"hello2\".\n" "@param str1 The first string.\n" "@param str2 The second string.\n\n" "@return 0 if the strings are equal, a value >0 if @a str1 comes after @a str2 in a natural order, and a value " "<0 if @a str1 comes before @a str2 in a natural order.\n\n" "@tsexample\n" "// Bubble sort 10 elements of %array using natural order\n" "do\n" "{\n" " %swapped = false;\n" " for( %i = 0; %i < 10 - 1; %i ++ )\n" " if( strnatcmp( %array[ %i ], %array[ %i + 1 ] ) > 0 )\n" " {\n" " %temp = %array[ %i ];\n" " %array[ %i ] = %array[ %i + 1 ];\n" " %array[ %i + 1 ] = %temp;\n" " %swapped = true;\n" " }\n" "}\n" "while( %swapped );\n" "@endtsexample\n" "@see strcmp\n" "@see strinatcmp\n" "@ingroup Strings" ) { return dStrnatcmp( str1, str2 ); } //----------------------------------------------------------------------------- DefineConsoleFunction( strinatcmp, S32, ( const char* str1, const char* str2 ),, "Compares two strings using \"natural order\" case-insensitive comparison.\n" "Natural order means that rather than solely comparing single character code values, strings are ordered in a " "natural way. For example, the string \"hello10\" is considered greater than the string \"hello2\" even though " "the first numeric character in \"hello10\" actually has a smaller character value than the corresponding character " "in \"hello2\". However, since 10 is greater than 2, strnatcmp will put \"hello10\" after \"hello2\".\n" "@param str1 The first string.\n" "@param str2 The second string.\n" "@return 0 if the strings are equal, a value >0 if @a str1 comes after @a str2 in a natural order, and a value " "<0 if @a str1 comes before @a str2 in a natural order.\n\n" "@tsexample\n\n" "// Bubble sort 10 elements of %array using natural order\n" "do\n" "{\n" " %swapped = false;\n" " for( %i = 0; %i < 10 - 1; %i ++ )\n" " if( strnatcmp( %array[ %i ], %array[ %i + 1 ] ) > 0 )\n" " {\n" " %temp = %array[ %i ];\n" " %array[ %i ] = %array[ %i + 1 ];\n" " %array[ %i + 1 ] = %temp;\n" " %swapped = true;\n" " }\n" "}\n" "while( %swapped );\n" "@endtsexample\n" "@see stricmp\n" "@see strnatcmp\n" "@ingroup Strings" ) { return dStrnatcasecmp( str1, str2 ); } //----------------------------------------------------------------------------- DefineConsoleFunction( strlen, S32, ( const char* str ),, "Get the length of the given string in bytes.\n" "@note This does not return a true character count for strings with multi-byte characters!\n" "@param str A string.\n" "@return The length of the given string in bytes.\n" "@ingroup Strings" ) { return dStrlen( str ); } //----------------------------------------------------------------------------- DefineConsoleFunction( strstr, S32, ( const char* string, const char* substring ),, "Find the start of @a substring in the given @a string searching from left to right.\n" "@param string The string to search.\n" "@param substring The string to search for.\n" "@return The index into @a string at which the first occurrence of @a substring was found or -1 if @a substring could not be found.\n\n" "@tsexample\n" "strstr( \"abcd\", \"c\" ) // Returns 2.\n" "@endtsexample\n" "@ingroup Strings" ) { const char* retpos = dStrstr( string, substring ); if( !retpos ) return -1; return retpos - string; } //----------------------------------------------------------------------------- DefineConsoleFunction( strpos, S32, ( const char* haystack, const char* needle, S32 offset ), ( 0 ), "Find the start of @a needle in @a haystack searching from left to right beginning at the given offset.\n" "@param haystack The string to search.\n" "@param needle The string to search for.\n" "@return The index at which the first occurrence of @a needle was found in @a haystack or -1 if no match was found.\n\n" "@tsexample\n" "strpos( \"b ab\", \"b\", 1 ) // Returns 3.\n" "@endtsexample\n" "@ingroup Strings" ) { S32 start = offset; U32 sublen = dStrlen( needle ); U32 strlen = dStrlen( haystack ); if(start < 0) return -1; if(sublen + start > strlen) return -1; for(; start + sublen <= strlen; start++) if(!dStrncmp(haystack + start, needle, sublen)) return start; return -1; } //----------------------------------------------------------------------------- DefineConsoleFunction( ltrim, const char*, ( const char* str ),, "Remove leading whitespace from the string.\n" "@param str A string.\n" "@return A string that is the same as @a str but with any leading (i.e. leftmost) whitespace removed.\n\n" "@tsexample\n" "ltrim( \" string \" ); // Returns \"string \".\n" "@endtsexample\n" "@see rtrim\n" "@see trim\n" "@ingroup Strings" ) { const char *ret = str; while(*ret == ' ' || *ret == '\n' || *ret == '\t') ret++; return ret; } //----------------------------------------------------------------------------- DefineConsoleFunction( rtrim, const char*, ( const char* str ),, "Remove trailing whitespace from the string.\n" "@param str A string.\n" "@return A string that is the same as @a str but with any trailing (i.e. rightmost) whitespace removed.\n\n" "@tsexample\n" "rtrim( \" string \" ); // Returns \" string\".\n" "@endtsexample\n" "@see ltrim\n" "@see trim\n" "@ingroup Strings" ) { S32 firstWhitespace = 0; S32 pos = 0; while(str[pos]) { if(str[pos] != ' ' && str[pos] != '\n' && str[pos] != '\t') firstWhitespace = pos + 1; pos++; } char *ret = Con::getReturnBuffer(firstWhitespace + 1); dStrncpy(ret, str, firstWhitespace); ret[firstWhitespace] = 0; return ret; } //----------------------------------------------------------------------------- DefineConsoleFunction( trim, const char*, ( const char* str ),, "Remove leading and trailing whitespace from the string.\n" "@param str A string.\n" "@return A string that is the same as @a str but with any leading (i.e. leftmost) and trailing (i.e. rightmost) whitespace removed.\n\n" "@tsexample\n" "trim( \" string \" ); // Returns \"string\".\n" "@endtsexample\n" "@ingroup Strings" ) { const char *ptr = str; while(*ptr == ' ' || *ptr == '\n' || *ptr == '\t') ptr++; S32 firstWhitespace = 0; S32 pos = 0; while(ptr[pos]) { if(ptr[pos] != ' ' && ptr[pos] != '\n' && ptr[pos] != '\t') firstWhitespace = pos + 1; pos++; } char *ret = Con::getReturnBuffer(firstWhitespace + 1); dStrncpy(ret, ptr, firstWhitespace); ret[firstWhitespace] = 0; return ret; } //----------------------------------------------------------------------------- DefineConsoleFunction( stripChars, const char*, ( const char* str, const char* chars ),, "Remove all occurrences of characters contained in @a chars from @a str.\n" "@param str The string to filter characters out from.\n" "@param chars A string of characters to filter out from @a str.\n" "@return A version of @a str with all occurrences of characters contained in @a chars filtered out.\n\n" "@tsexample\n" "stripChars( \"teststring\", \"se\" ); // Returns \"tttring\"." "@endtsexample\n" "@ingroup Strings" ) { char* ret = Con::getReturnBuffer( dStrlen( str ) + 1 ); dStrcpy( ret, str ); U32 pos = dStrcspn( ret, chars ); while ( pos < dStrlen( ret ) ) { dStrcpy( ret + pos, ret + pos + 1 ); pos = dStrcspn( ret, chars ); } return( ret ); } //----------------------------------------------------------------------------- DefineConsoleFunction( strlwr, const char*, ( const char* str ),, "Return an all lower-case version of the given string.\n" "@param str A string.\n" "@return A version of @a str with all characters converted to lower-case.\n\n" "@tsexample\n" "strlwr( \"TesT1\" ) // Returns \"test1\"\n" "@endtsexample\n" "@see strupr\n" "@ingroup Strings" ) { char *ret = Con::getReturnBuffer(dStrlen(str) + 1); dStrcpy(ret, str); return dStrlwr(ret); } //----------------------------------------------------------------------------- DefineConsoleFunction( strupr, const char*, ( const char* str ),, "Return an all upper-case version of the given string.\n" "@param str A string.\n" "@return A version of @a str with all characters converted to upper-case.\n\n" "@tsexample\n" "strupr( \"TesT1\" ) // Returns \"TEST1\"\n" "@endtsexample\n" "@see strlwr\n" "@ingroup Strings" ) { char *ret = Con::getReturnBuffer(dStrlen(str) + 1); dStrcpy(ret, str); return dStrupr(ret); } //----------------------------------------------------------------------------- DefineConsoleFunction( strchr, const char*, ( const char* str, const char* chr ),, "Find the first occurrence of the given character in @a str.\n" "@param str The string to search.\n" "@param chr The character to search for. Only the first character from the string is taken.\n" "@return The remainder of the input string starting with the given character or the empty string if the character could not be found.\n\n" "@see strrchr\n" "@ingroup Strings" ) { const char *ret = dStrchr( str, chr[ 0 ] ); return ret ? ret : ""; } //----------------------------------------------------------------------------- DefineConsoleFunction( strrchr, const char*, ( const char* str, const char* chr ),, "Find the last occurrence of the given character in @a str." "@param str The string to search.\n" "@param chr The character to search for. Only the first character from the string is taken.\n" "@return The remainder of the input string starting with the given character or the empty string if the character could not be found.\n\n" "@see strchr\n" "@ingroup Strings" ) { const char *ret = dStrrchr( str, chr[ 0 ] ); return ret ? ret : ""; } //----------------------------------------------------------------------------- DefineConsoleFunction( strreplace, const char*, ( const char* source, const char* from, const char* to ),, "Replace all occurrences of @a from in @a source with @a to.\n" "@param source The string in which to replace the occurrences of @a from.\n" "@param from The string to replace in @a source.\n" "@param to The string with which to replace occurrences of @from.\n" "@return A string with all occurrences of @a from in @a source replaced by @a to.\n\n" "@tsexample\n" "strreplace( \"aabbccbb\", \"bb\", \"ee\" ) // Returns \"aaeeccee\".\n" "@endtsexample\n" "@ingroup Strings" ) { S32 fromLen = dStrlen( from ); if(!fromLen) return source; S32 toLen = dStrlen( to ); S32 count = 0; const char *scan = source; while(scan) { scan = dStrstr(scan, from); if(scan) { scan += fromLen; count++; } } char *ret = Con::getReturnBuffer(dStrlen(source) + 1 + (toLen - fromLen) * count); U32 scanp = 0; U32 dstp = 0; for(;;) { const char *scan = dStrstr(source + scanp, from); if(!scan) { dStrcpy(ret + dstp, source + scanp); break; } U32 len = scan - (source + scanp); dStrncpy(ret + dstp, source + scanp, len); dstp += len; dStrcpy(ret + dstp, to); dstp += toLen; scanp += len + fromLen; } return ret; } //----------------------------------------------------------------------------- DefineConsoleFunction( strrepeat, const char*, ( const char* str, S32 numTimes, const char* delimiter ), ( "" ), "Return a string that repeats @a str @a numTimes number of times delimiting each occurrence with @a delimiter.\n" "@param str The string to repeat multiple times.\n" "@param numTimes The number of times to repeat @a str in the result string.\n" "@param delimiter The string to put between each repetition of @a str.\n" "@return A string containing @a str repeated @a numTimes times.\n\n" "@tsexample\n" "strrepeat( \"a\", 5, \"b\" ) // Returns \"ababababa\".\n" "@endtsexample\n" "@ingroup Strings" ) { StringBuilder result; bool isFirst = false; for( U32 i = 0; i < numTimes; ++ i ) { if( !isFirst ) result.append( delimiter ); result.append( str ); isFirst = false; } return Con::getReturnBuffer( result ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getSubStr, const char*, ( const char* str, S32 start, S32 numChars ), ( -1 ), "@brief Return a substring of @a str starting at @a start and continuing either through to the end of @a str " "(if @a numChars is -1) or for @a numChars characters (except if this would exceed the actual source " "string length).\n" "@param str The string from which to extract a substring.\n" "@param start The offset at which to start copying out characters.\n" "@param numChars Optional argument to specify the number of characters to copy. If this is -1, all characters up the end " "of the input string are copied.\n" "@return A string that contains the given portion of the input string.\n\n" "@tsexample\n" "getSubStr( \"foobar\", 1, 2 ) // Returns \"oo\".\n" "@endtsexample\n\n" "@ingroup Strings" ) { S32 baseLen = dStrlen( str ); if( numChars == -1 ) numChars = baseLen - start; if (start < 0 || numChars < 0) { Con::errorf(ConsoleLogEntry::Script, "getSubStr(...): error, starting position and desired length must be >= 0: (%d, %d)", start, numChars); return ""; } if (baseLen < start) return ""; U32 actualLen = numChars; if (start + numChars > baseLen) actualLen = baseLen - start; char *ret = Con::getReturnBuffer(actualLen + 1); dStrncpy(ret, str + start, actualLen); ret[actualLen] = '\0'; return ret; } //----------------------------------------------------------------------------- DefineConsoleFunction( strIsMatchExpr, bool, ( const char* pattern, const char* str, bool caseSensitive ), ( false ), "Match a pattern against a string.\n" "@param pattern The wildcard pattern to match against. The pattern can include characters, '*' to match " "any number of characters and '?' to match a single character.\n" "@param str The string which should be matched against @a pattern.\n" "@param caseSensitive If true, characters in the pattern are matched in case-sensitive fashion against " "this string. If false, differences in casing are ignored.\n" "@return True if @a str matches the given @a pattern.\n\n" "@tsexample\n" "strIsMatchExpr( \"f?o*R\", \"foobar\" ) // Returns true.\n" "@endtsexample\n" "@see strIsMatchMultipleExpr\n" "@ingroup Strings" ) { return FindMatch::isMatch( pattern, str, caseSensitive ); } //----------------------------------------------------------------------------- DefineConsoleFunction( strIsMatchMultipleExpr, bool, ( const char* patterns, const char* str, bool caseSensitive ), ( false ), "Match a multiple patterns against a single string.\n" "@param patterns A tab-separated list of patterns. Each pattern can include charaters, '*' to match " "any number of characters and '?' to match a single character. Each of the patterns is tried in turn.\n" "@param str The string which should be matched against @a patterns.\n" "@param caseSensitive If true, characters in the pattern are matched in case-sensitive fashion against " "this string. If false, differences in casing are ignored.\n" "@return True if @a str matches any of the given @a patterns.\n\n" "@tsexample\n" "strIsMatchMultipleExpr( \"*.cs *.gui *.mis\", \"mymission.mis\" ) // Returns true.\n" "@endtsexample\n" "@see strIsMatchExpr\n" "@ingroup Strings" ) { return FindMatch::isMatchMultipleExprs( patterns, str, caseSensitive ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getTrailingNumber, S32, ( const char* str ),, "Get the numeric suffix of the given input string.\n" "@param str The string from which to read out the numeric suffix.\n" "@return The numeric value of the number suffix of @a str or -1 if @a str has no such suffix.\n\n" "@tsexample\n" "getTrailingNumber( \"test123\" ) // Returns '123'.\n" "@endtsexample\n\n" "@see stripTrailingNumber\n" "@ingroup Strings" ) { S32 suffix = -1; String outStr( String::GetTrailingNumber( str, suffix ) ); return suffix; } //----------------------------------------------------------------------------- DefineConsoleFunction( stripTrailingNumber, String, ( const char* str ),, "Strip a numeric suffix from the given string.\n" "@param str The string from which to strip its numeric suffix.\n" "@return The string @a str without its number suffix or the original string @a str if it has no such suffix.\n\n" "@tsexample\n" "stripTrailingNumber( \"test123\" ) // Returns \"test\".\n" "@endtsexample\n\n" "@see getTrailingNumber\n" "@ingroup Strings" ) { S32 suffix; return String::GetTrailingNumber( str, suffix ); } //---------------------------------------------------------------- DefineConsoleFunction( isspace, bool, ( const char* str, S32 index ),, "Test whether the character at the given position is a whitespace character.\n" "Characters such as tab, space, or newline are considered whitespace.\n" "@param str The string to test.\n" "@param index The index of a character in @a str.\n" "@return True if the character at the given index in @a str is a whitespace character; false otherwise.\n\n" "@see isalnum\n" "@ingroup Strings" ) { if( index >= 0 && index < dStrlen( str ) ) return dIsspace( str[ index ] ); else return false; } //---------------------------------------------------------------- DefineConsoleFunction( isalnum, bool, ( const char* str, S32 index ),, "Test whether the character at the given position is an alpha-numeric character.\n" "Alpha-numeric characters are characters that are either alphabetic (a-z, A-Z) or numbers (0-9).\n" "@param str The string to test.\n" "@param index The index of a character in @a str.\n" "@return True if the character at the given index in @a str is an alpha-numeric character; false otherwise.\n\n" "@see isspace\n" "@ingroup Strings" ) { if( index >= 0 && index < dStrlen( str ) ) return dIsalnum( str[ index ] ); else return false; } //---------------------------------------------------------------- DefineConsoleFunction( startsWith, bool, ( const char* str, const char* prefix, bool caseSensitive ), ( false ), "Test whether the given string begins with the given prefix.\n" "@param str The string to test.\n" "@param prefix The potential prefix of @a str.\n" "@param caseSensitive If true, the comparison will be case-sensitive; if false, differences in casing will " "not be taken into account.\n" "@return True if the first characters in @a str match the complete contents of @a prefix; false otherwise.\n\n" "@tsexample\n" "startsWith( \"TEST123\", \"test\" ) // Returns true.\n" "@endtsexample\n" "@see endsWith\n" "@ingroup Strings" ) { // if the target string is empty, return true (all strings start with the empty string) S32 srcLen = dStrlen( str ); S32 targetLen = dStrlen( prefix ); if( targetLen == 0 ) return true; // else if the src string is empty, return false (empty src does not start with non-empty target) else if( srcLen == 0 ) return false; if( caseSensitive ) return ( dStrncmp( str, prefix, targetLen ) == 0 ); // both src and target are non empty, create temp buffers for lowercase operation char* srcBuf = new char[ srcLen + 1 ]; char* targetBuf = new char[ targetLen + 1 ]; // copy src and target into buffers dStrcpy( srcBuf, str ); dStrcpy( targetBuf, prefix ); // reassign src/target pointers to lowercase versions str = dStrlwr( srcBuf ); prefix = dStrlwr( targetBuf ); // do the comparison bool startsWith = dStrncmp( str, prefix, targetLen ) == 0; // delete temp buffers delete [] srcBuf; delete [] targetBuf; return startsWith; } //---------------------------------------------------------------- DefineConsoleFunction( endsWith, bool, ( const char* str, const char* suffix, bool caseSensitive ), ( false ), "@brief Test whether the given string ends with the given suffix.\n\n" "@param str The string to test.\n" "@param suffix The potential suffix of @a str.\n" "@param caseSensitive If true, the comparison will be case-sensitive; if false, differences in casing will " "not be taken into account.\n" "@return True if the last characters in @a str match the complete contents of @a suffix; false otherwise.\n\n" "@tsexample\n" "startsWith( \"TEST123\", \"123\" ) // Returns true.\n" "@endtsexample\n\n" "@see startsWith\n" "@ingroup Strings" ) { // if the target string is empty, return true (all strings end with the empty string) S32 srcLen = dStrlen( str ); S32 targetLen = dStrlen( suffix ); if (targetLen == 0) return true; // else if the src string is empty, return false (empty src does not end with non-empty target) else if (srcLen == 0) return false; else if( targetLen > srcLen ) return false; if( caseSensitive ) return ( dStrcmp( &str[ srcLen - targetLen ], suffix ) == 0 ); // both src and target are non empty, create temp buffers for lowercase operation char* srcBuf = new char[ srcLen + 1 ]; char* targetBuf = new char[ targetLen + 1 ]; // copy src and target into buffers dStrcpy( srcBuf, str ); dStrcpy( targetBuf, suffix ); // reassign src/target pointers to lowercase versions str = dStrlwr( srcBuf ); suffix = dStrlwr( targetBuf ); // set the src pointer to the appropriate place to check the end of the string str += srcLen - targetLen; // do the comparison bool endsWith = dStrcmp( str, suffix ) == 0; // delete temp buffers delete [] srcBuf; delete [] targetBuf; return endsWith; } //---------------------------------------------------------------- DefineConsoleFunction( strchrpos, S32, ( const char* str, const char* chr, S32 start ), ( 0 ), "Find the first occurrence of the given character in the given string.\n" "@param str The string to search.\n" "@param chr The character to look for. Only the first character of this string will be searched for.\n" "@param start The index into @a str at which to start searching for the given character.\n" "@return The index of the first occurrence of @a chr in @a str or -1 if @a str does not contain the given character.\n\n" "@tsexample\n" "strchrpos( \"test\", \"s\" ) // Returns 2.\n" "@endtsexample\n" "@ingroup Strings" ) { if( start != 0 && start >= dStrlen( str ) ) return -1; const char* ret = dStrchr( &str[ start ], chr[ 0 ] ); return ret ? ret - str : -1; } //---------------------------------------------------------------- DefineConsoleFunction( strrchrpos, S32, ( const char* str, const char* chr, S32 start ), ( 0 ), "Find the last occurrence of the given character in the given string.\n" "@param str The string to search.\n" "@param chr The character to look for. Only the first character of this string will be searched for.\n" "@param start The index into @a str at which to start searching for the given character.\n" "@return The index of the last occurrence of @a chr in @a str or -1 if @a str does not contain the given character.\n\n" "@tsexample\n" "strrchrpos( \"test\", \"t\" ) // Returns 3.\n" "@endtsexample\n" "@ingroup Strings" ) { if( start != 0 && start >= dStrlen( str ) ) return -1; const char* ret = dStrrchr( str, chr[ 0 ] ); if( !ret ) return -1; S32 index = ret - str; if( index < start ) return -1; return index; } //============================================================================= // Field Manipulators. //============================================================================= // MARK: ---- Field Manipulators ---- //----------------------------------------------------------------------------- DefineConsoleFunction( getWord, const char*, ( const char* text, S32 index ),, "Extract the word at the given @a index in the whitespace-separated list in @a text.\n" "Words in @a text must be separated by newlines, spaces, and/or tabs.\n" "@param text A whitespace-separated list of words.\n" "@param index The zero-based index of the word to extract.\n" "@return The word at the given index or \"\" if the index is out of range.\n\n" "@tsexample\n" "getWord( \"a b c\", 1 ) // Returns \"b\"\n" "@endtsexample\n\n" "@see getWords\n" "@see getWordCount\n" "@see getField\n" "@see getRecord\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::getUnit( text, index, " \t\n") ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getWords, const char*, ( const char* text, S32 startIndex, S32 endIndex ), ( -1 ), "Extract a range of words from the given @a startIndex onwards thru @a endIndex.\n" "Words in @a text must be separated by newlines, spaces, and/or tabs.\n" "@param text A whitespace-separated list of words.\n" "@param startIndex The zero-based index of the first word to extract from @a text.\n" "@param endIndex The zero-based index of the last word to extract from @a text. If this is -1, all words beginning " "with @a startIndex are extracted from @a text.\n" "@return A string containing the specified range of words from @a text or \"\" if @a startIndex " "is out of range or greater than @a endIndex.\n\n" "@tsexample\n" "getWords( \"a b c d\", 1, 2, ) // Returns \"b c\"\n" "@endtsexample\n\n" "@see getWord\n" "@see getWordCount\n" "@see getFields\n" "@see getRecords\n" "@ingroup FieldManip" ) { if( endIndex < 0 ) endIndex = 1000000; return Con::getReturnBuffer( StringUnit::getUnits( text, startIndex, endIndex, " \t\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( setWord, const char*, ( const char* text, S32 index, const char* replacement ),, "Replace the word in @a text at the given @a index with @a replacement.\n" "Words in @a text must be separated by newlines, spaces, and/or tabs.\n" "@param text A whitespace-separated list of words.\n" "@param index The zero-based index of the word to replace.\n" "@param replacement The string with which to replace the word.\n" "@return A new string with the word at the given @a index replaced by @a replacement or the original " "string if @a index is out of range.\n\n" "@tsexample\n" "setWord( \"a b c d\", 2, \"f\" ) // Returns \"a b f d\"\n" "@endtsexample\n\n" "@see getWord\n" "@see setField\n" "@see setRecord\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::setUnit( text, index, replacement, " \t\n") ); } //----------------------------------------------------------------------------- DefineConsoleFunction( removeWord, const char*, ( const char* text, S32 index ),, "Remove the word in @a text at the given @a index.\n" "Words in @a text must be separated by newlines, spaces, and/or tabs.\n" "@param text A whitespace-separated list of words.\n" "@param index The zero-based index of the word in @a text.\n" "@return A new string with the word at the given index removed or the original string if @a index is " "out of range.\n\n" "@tsexample\n" "removeWord( \"a b c d\", 2 ) // Returns \"a b d\"\n" "@endtsexample\n\n" "@see removeField\n" "@see removeRecord\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::removeUnit( text, index, " \t\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getWordCount, S32, ( const char* text ),, "Return the number of whitespace-separated words in @a text.\n" "Words in @a text must be separated by newlines, spaces, and/or tabs.\n" "@param text A whitespace-separated list of words.\n" "@return The number of whitespace-separated words in @a text.\n\n" "@tsexample\n" "getWordCount( \"a b c d e\" ) // Returns 5\n" "@endtsexample\n\n" "@see getFieldCount\n" "@see getRecordCount\n" "@ingroup FieldManip" ) { return StringUnit::getUnitCount( text, " \t\n" ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getField, const char*, ( const char* text, S32 index ),, "Extract the field at the given @a index in the newline and/or tab separated list in @a text.\n" "Fields in @a text must be separated by newlines and/or tabs.\n" "@param text A list of fields separated by newlines and/or tabs.\n" "@param index The zero-based index of the field to extract.\n" "@return The field at the given index or \"\" if the index is out of range.\n\n" "@tsexample\n" "getField( \"a b\" TAB \"c d\" TAB \"e f\", 1 ) // Returns \"c d\"\n" "@endtsexample\n\n" "@see getFields\n" "@see getFieldCount\n" "@see getWord\n" "@see getRecord\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::getUnit( text, index, "\t\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getFields, const char*, ( const char* text, S32 startIndex, S32 endIndex ), ( -1 ), "Extract a range of fields from the given @a startIndex onwards thru @a endIndex.\n" "Fields in @a text must be separated by newlines and/or tabs.\n" "@param text A list of fields separated by newlines and/or tabs.\n" "@param startIndex The zero-based index of the first field to extract from @a text.\n" "@param endIndex The zero-based index of the last field to extract from @a text. If this is -1, all fields beginning " "with @a startIndex are extracted from @a text.\n" "@return A string containing the specified range of fields from @a text or \"\" if @a startIndex " "is out of range or greater than @a endIndex.\n\n" "@tsexample\n" "getFields( \"a b\" TAB \"c d\" TAB \"e f\", 1 ) // Returns \"c d\" TAB \"e f\"\n" "@endtsexample\n\n" "@see getField\n" "@see getFieldCount\n" "@see getWords\n" "@see getRecords\n" "@ingroup FieldManip" ) { if( endIndex < 0 ) endIndex = 1000000; return Con::getReturnBuffer( StringUnit::getUnits( text, startIndex, endIndex, "\t\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( setField, const char*, ( const char* text, S32 index, const char* replacement ),, "Replace the field in @a text at the given @a index with @a replacement.\n" "Fields in @a text must be separated by newlines and/or tabs.\n" "@param text A list of fields separated by newlines and/or tabs.\n" "@param index The zero-based index of the field to replace.\n" "@param replacement The string with which to replace the field.\n" "@return A new string with the field at the given @a index replaced by @a replacement or the original " "string if @a index is out of range.\n\n" "@tsexample\n" "setField( \"a b\" TAB \"c d\" TAB \"e f\", 1, \"g h\" ) // Returns \"a b\" TAB \"g h\" TAB \"e f\"\n" "@endtsexample\n\n" "@see getField\n" "@see setWord\n" "@see setRecord\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::setUnit( text, index, replacement, "\t\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( removeField, const char*, ( const char* text, S32 index ),, "Remove the field in @a text at the given @a index.\n" "Fields in @a text must be separated by newlines and/or tabs.\n" "@param text A list of fields separated by newlines and/or tabs.\n" "@param index The zero-based index of the field in @a text.\n" "@return A new string with the field at the given index removed or the original string if @a index is " "out of range.\n\n" "@tsexample\n" "removeField( \"a b\" TAB \"c d\" TAB \"e f\", 1 ) // Returns \"a b\" TAB \"e f\"\n" "@endtsexample\n\n" "@see removeWord\n" "@see removeRecord\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::removeUnit( text, index, "\t\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getFieldCount, S32, ( const char* text ),, "Return the number of newline and/or tab separated fields in @a text.\n" "@param text A list of fields separated by newlines and/or tabs.\n" "@return The number of newline and/or tab sepearated elements in @a text.\n\n" "@tsexample\n" "getFieldCount( \"a b\" TAB \"c d\" TAB \"e f\" ) // Returns 3\n" "@endtsexample\n\n" "@see getWordCount\n" "@see getRecordCount\n" "@ingroup FieldManip" ) { return StringUnit::getUnitCount( text, "\t\n" ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getRecord, const char*, ( const char* text, S32 index ),, "Extract the record at the given @a index in the newline-separated list in @a text.\n" "Records in @a text must be separated by newlines.\n" "@param text A list of records separated by newlines.\n" "@param index The zero-based index of the record to extract.\n" "@return The record at the given index or \"\" if @a index is out of range.\n\n" "@tsexample\n" "getRecord( \"a b\" NL \"c d\" NL \"e f\", 1 ) // Returns \"c d\"\n" "@endtsexample\n\n" "@see getRecords\n" "@see getRecordCount\n" "@see getWord\n" "@see getField\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::getUnit( text, index, "\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getRecords, const char*, ( const char* text, S32 startIndex, S32 endIndex ), ( -1 ), "Extract a range of records from the given @a startIndex onwards thru @a endIndex.\n" "Records in @a text must be separated by newlines.\n" "@param text A list of records separated by newlines.\n" "@param startIndex The zero-based index of the first record to extract from @a text.\n" "@param endIndex The zero-based index of the last record to extract from @a text. If this is -1, all records beginning " "with @a startIndex are extracted from @a text.\n" "@return A string containing the specified range of records from @a text or \"\" if @a startIndex " "is out of range or greater than @a endIndex.\n\n" "@tsexample\n" "getRecords( \"a b\" NL \"c d\" NL \"e f\", 1 ) // Returns \"c d\" NL \"e f\"\n" "@endtsexample\n\n" "@see getRecord\n" "@see getRecordCount\n" "@see getWords\n" "@see getFields\n" "@ingroup FieldManip" ) { if( endIndex < 0 ) endIndex = 1000000; return Con::getReturnBuffer( StringUnit::getUnits( text, startIndex, endIndex, "\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( setRecord, const char*, ( const char* text, S32 index, const char* replacement ),, "Replace the record in @a text at the given @a index with @a replacement.\n" "Records in @a text must be separated by newlines.\n" "@param text A list of records separated by newlines.\n" "@param index The zero-based index of the record to replace.\n" "@param replacement The string with which to replace the record.\n" "@return A new string with the record at the given @a index replaced by @a replacement or the original " "string if @a index is out of range.\n\n" "@tsexample\n" "setRecord( \"a b\" NL \"c d\" NL \"e f\", 1, \"g h\" ) // Returns \"a b\" NL \"g h\" NL \"e f\"\n" "@endtsexample\n\n" "@see getRecord\n" "@see setWord\n" "@see setField\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::setUnit( text, index, replacement, "\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( removeRecord, const char*, ( const char* text, S32 index ),, "Remove the record in @a text at the given @a index.\n" "Records in @a text must be separated by newlines.\n" "@param text A list of records separated by newlines.\n" "@param index The zero-based index of the record in @a text.\n" "@return A new string with the record at the given @a index removed or the original string if @a index is " "out of range.\n\n" "@tsexample\n" "removeRecord( \"a b\" NL \"c d\" NL \"e f\", 1 ) // Returns \"a b\" NL \"e f\"\n" "@endtsexample\n\n" "@see removeWord\n" "@see removeField\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::removeUnit( text, index, "\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( getRecordCount, S32, ( const char* text ),, "Return the number of newline-separated records in @a text.\n" "@param text A list of records separated by newlines.\n" "@return The number of newline-sepearated elements in @a text.\n\n" "@tsexample\n" "getRecordCount( \"a b\" NL \"c d\" NL \"e f\" ) // Returns 3\n" "@endtsexample\n\n" "@see getWordCount\n" "@see getFieldCount\n" "@ingroup FieldManip" ) { return StringUnit::getUnitCount( text, "\n" ); } //----------------------------------------------------------------------------- DefineConsoleFunction( firstWord, const char*, ( const char* text ),, "Return the first word in @a text.\n" "@param text A list of words separated by newlines, spaces, and/or tabs.\n" "@return The word at index 0 in @a text or \"\" if @a text is empty.\n\n" "@note This is equal to \n" "@tsexample_nopar\n" "getWord( text, 0 )\n" "@endtsexample\n\n" "@see getWord\n" "@ingroup FieldManip" ) { return Con::getReturnBuffer( StringUnit::getUnit( text, 0, " \t\n" ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( restWords, const char*, ( const char* text ),, "Return all but the first word in @a text.\n" "@param text A list of words separated by newlines, spaces, and/or tabs.\n" "@return @a text with the first word removed.\n\n" "@note This is equal to \n" "@tsexample_nopar\n" "getWords( text, 1 )\n" "@endtsexample\n\n" "@see getWords\n" "@ingroup FieldManip" ) { const char* ptr = text; while( *ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '\n' ) ptr ++; // Skip separator. if( *ptr ) ptr ++; return Con::getReturnBuffer( ptr ); } //----------------------------------------------------------------------------- static bool isInSet(char c, const char *set) { if (set) while (*set) if (c == *set++) return true; return false; } ConsoleFunction( nextToken, const char *, 4, 4, "( string str, string token, string delimiters ) " "Tokenize a string using a set of delimiting characters.\n" "This function first skips all leading charaters in @a str that are contained in @a delimiters. " "From that position, it then scans for the next character in @a str that is contained in @a delimiters and stores all characters " "from the starting position up to the first delimiter in a variable in the current scope called @a token. Finally, it " "skips all characters in @a delimiters after the token and then returns the remaining string contents in @a str.\n\n" "To scan out all tokens in a string, call this function repeatedly by passing the result it returns each time as the new @a str " "until the function returns \"\".\n\n" "@param str A string.\n" "@param token The name of the variable in which to store the current token. This variable is set in the " "scope in which nextToken is called.\n" "@param delimiters A string of characters. Each character is considered a delimiter.\n" "@return The remainder of @a str after the token has been parsed out or \"\" if no more tokens were found in @a str.\n\n" "@tsexample\n" "// Prints:\n" "// a\n" "// b\n" "// c\n" "%str = \"a b c\";\n" "while ( %str !$= \"\" )\n" "{\n" " // First time, stores \"a\" in the variable %token and sets %str to \"b c\".\n" " %str = nextToken( %str, \"token\", \" \" );\n" " echo( %token );\n" "}\n" "@endtsexample\n\n" "@ingroup Strings" ) { char buffer[4096]; dStrncpy(buffer, argv[1], 4096); char *str = buffer; const char *token = argv[2]; const char *delim = argv[3]; if( str ) { // skip over any characters that are a member of delim // no need for special '\0' check since it can never be in delim while (isInSet(*str, delim)) str++; // skip over any characters that are NOT a member of delim const char *tmp = str; while (*str && !isInSet(*str, delim)) str++; // terminate the token if (*str) *str++ = 0; // set local variable if inside a function if (gEvalState.getStackDepth() > 0 && gEvalState.getCurrentFrame().scopeName) Con::setLocalVariable(token,tmp); else Con::setVariable(token,tmp); // advance str past the 'delim space' while (isInSet(*str, delim)) str++; } char *ret = Con::getReturnBuffer(dStrlen(str)+1); dStrncpy(ret, str, dStrlen(str)+1); return ret; } //============================================================================= // Tagged Strings. //============================================================================= // MARK: ---- Tagged Strings ---- //----------------------------------------------------------------------------- DefineEngineFunction( detag, const char*, ( const char* str ),, "@brief Returns the string from a tag string.\n\n" "Should only be used within the context of a function that receives a tagged " "string, and is not meant to be used outside of this context. Use getTaggedString() " "to convert a tagged string ID back into a regular string at any time.\n\n" "@tsexample\n" "// From scripts/client/message.cs\n" "function clientCmdChatMessage(%sender, %voice, %pitch, %msgString, %a1, %a2, %a3, %a4, %a5, %a6, %a7, %a8, %a9, %a10)\n" "{\n" " onChatMessage(detag(%msgString), %voice, %pitch);\n" "}\n" "@endtsexample\n\n" "@see \\ref syntaxDataTypes under Tagged %Strings\n" "@see getTag()\n" "@see getTaggedString()\n" "@ingroup Networking") { if( str[ 0 ] == StringTagPrefixByte ) { const char* word = dStrchr( str, ' ' ); if( word == NULL ) return ""; char* ret = Con::getReturnBuffer( dStrlen( word + 1 ) + 1 ); dStrcpy( ret, word + 1 ); return ret; } else return str; } ConsoleFunction(getTag, const char *, 2, 2, "(string textTagString)" "@brief Extracts the tag from a tagged string\n\n" "Should only be used within the context of a function that receives a tagged " "string, and is not meant to be used outside of this context.\n\n" "@param textTagString The tagged string to extract.\n" "@returns The tag ID of the string.\n" "@see \\ref syntaxDataTypes under Tagged %Strings\n" "@see detag()\n" "@ingroup Networking") { TORQUE_UNUSED(argc); if(argv[1][0] == StringTagPrefixByte) { const char *arg = argv[1]; const char * space = dStrchr(argv[1], ' '); U32 len; if(space) len = space - arg; else len = dStrlen(arg) + 1; char * ret = Con::getReturnBuffer(len); dStrncpy(ret, arg + 1, len - 1); ret[len - 1] = 0; return(ret); } else return(argv[1]); } //============================================================================= // Output. //============================================================================= // MARK: ---- Output ---- //----------------------------------------------------------------------------- ConsoleFunction( echo, void, 2, 0, "( string message... ) " "@brief Logs a message to the console.\n\n" "Concatenates all given arguments to a single string and prints the string to the console. " "A newline is added automatically after the text.\n\n" "@param message Any number of string arguments.\n\n" "@ingroup Logging" ) { U32 len = 0; S32 i; for(i = 1; i < argc; i++) len += dStrlen(argv[i]); char *ret = Con::getReturnBuffer(len + 1); ret[0] = 0; for(i = 1; i < argc; i++) dStrcat(ret, argv[i]); Con::printf("%s", ret); ret[0] = 0; } //----------------------------------------------------------------------------- ConsoleFunction( warn, void, 2, 0, "( string message... ) " "@brief Logs a warning message to the console.\n\n" "Concatenates all given arguments to a single string and prints the string to the console as a warning " "message (in the in-game console, these will show up using a turquoise font by default). " "A newline is added automatically after the text.\n\n" "@param message Any number of string arguments.\n\n" "@ingroup Logging" ) { U32 len = 0; S32 i; for(i = 1; i < argc; i++) len += dStrlen(argv[i]); char *ret = Con::getReturnBuffer(len + 1); ret[0] = 0; for(i = 1; i < argc; i++) dStrcat(ret, argv[i]); Con::warnf(ConsoleLogEntry::General, "%s", ret); ret[0] = 0; } //----------------------------------------------------------------------------- ConsoleFunction( error, void, 2, 0, "( string message... ) " "@brief Logs an error message to the console.\n\n" "Concatenates all given arguments to a single string and prints the string to the console as an error " "message (in the in-game console, these will show up using a red font by default). " "A newline is added automatically after the text.\n\n" "@param message Any number of string arguments.\n\n" "@ingroup Logging" ) { U32 len = 0; S32 i; for(i = 1; i < argc; i++) len += dStrlen(argv[i]); char *ret = Con::getReturnBuffer(len + 1); ret[0] = 0; for(i = 1; i < argc; i++) dStrcat(ret, argv[i]); Con::errorf(ConsoleLogEntry::General, "%s", ret); ret[0] = 0; } //----------------------------------------------------------------------------- DefineEngineFunction( debugv, void, ( const char* variableName ),, "@brief Logs the value of the given variable to the console.\n\n" "Prints a string of the form \" = \" to the console.\n\n" "@param variableName Name of the local or global variable to print.\n\n" "@tsexample\n" "%var = 1;\n" "debugv( \"%var\" ); // Prints \"%var = 1\"\n" "@endtsexample\n\n" "@ingroup Debugging" ) { if( variableName[ 0 ] == '%' ) Con::errorf( "%s = %s", variableName, Con::getLocalVariable( variableName ) ); else Con::errorf( "%s = %s", variableName, Con::getVariable( variableName ) ); } //----------------------------------------------------------------------------- DefineConsoleFunction( expandEscape, const char*, ( const char* text ),, "@brief Replace all characters in @a text that need to be escaped for the string to be a valid string literal with their " "respective escape sequences.\n\n" "All characters in @a text that cannot appear in a string literal will be replaced by an escape sequence (\\\\n, \\\\t, etc).\n\n" "The primary use of this function is for converting strings suitable for being passed as string literals " "to the TorqueScript compiler.\n\n" "@param text A string\n" "@return A duplicate of the text parameter with all unescaped characters that cannot appear in string literals replaced by their respective " "escape sequences.\n\n" "@tsxample\n" "expandEscape( \"str\" NL \"ing\" ) // Returns \"str\\ning\".\n" "@endtsxample\n\n" "@see collapseEscape\n" "@ingroup Strings") { char* ret = Con::getReturnBuffer(dStrlen( text ) * 2 + 1 ); // worst case situation expandEscape( ret, text ); return ret; } //----------------------------------------------------------------------------- DefineConsoleFunction( collapseEscape, const char*, ( const char* text ),, "Replace all escape sequences in @a text with their respective character codes.\n\n" "This function replaces all escape sequences (\\\\n, \\\\t, etc) in the given string " "with the respective characters they represent.\n\n" "The primary use of this function is for converting strings from their literal form into " "their compiled/translated form, as is normally done by the TorqueScript compiler.\n\n" "@param text A string.\n" "@return A duplicate of @a text with all escape sequences replaced by their respective character codes.\n\n" "@tsexample\n" "// Print:\n" "//\n" "// str\n" "// ing\n" "//\n" "// to the console. Note how the backslash in the string must be escaped here\n" "// in order to prevent the TorqueScript compiler from collapsing the escape\n" "// sequence in the resulting string.\n" "echo( collapseEscape( \"str\\ning\" ) );\n" "@endtsexample\n\n" "@see expandEscape\n\n" "@ingroup Strings" ) { char* ret = Con::getReturnBuffer( text ); collapseEscape( ret ); return ret; } //----------------------------------------------------------------------------- DefineEngineFunction( setLogMode, void, ( S32 mode ),, "@brief Determines how log files are written.\n\n" "Sets the operational mode of the console logging system.\n\n" "@param mode Parameter specifying the logging mode. This can be:\n" "- 1: Open and close the console log file for each seperate string of output. This will ensure that all " "parts get written out to disk and that no parts remain in intermediate buffers even if the process crashes.\n" "- 2: Keep the log file open and write to it continuously. This will make the system operate faster but " "if the process crashes, parts of the output may not have been written to disk yet and will be missing from " "the log.\n\n" "Additionally, when changing the log mode and thus opening a new log file, either of the two mode values may be " "combined by binary OR with 0x4 to cause the logging system to flush all console log messages that had already been " "issued to the console system into the newly created log file.\n\n" "@note Xbox 360 does not support logging to a file. Use Platform::OutputDebugStr in C++ instead." "@ingroup Logging" ) { Con::setLogMode( mode ); } //============================================================================= // Misc. //============================================================================= // MARK: ---- Misc ---- //----------------------------------------------------------------------------- DefineConsoleFunction( quit, void, ( ),, "Shut down the engine and exit its process.\n" "This function cleanly uninitializes the engine and then exits back to the system with a process " "exit status indicating a clean exit.\n\n" "@see quitWithErrorMessage\n\n" "@ingroup Platform" ) { Platform::postQuitMessage(0); } //----------------------------------------------------------------------------- #ifdef TORQUE_DEMO_PURCHASE ConsoleFunction( realQuit, void, 1, 1, "" ) { TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); Platform::postQuitMessage(0); } #endif //----------------------------------------------------------------------------- DefineConsoleFunction( quitWithErrorMessage, void, ( const char* message, S32 status ), (0), "Display an error message box showing the given @a message and then shut down the engine and exit its process.\n" "This function cleanly uninitialized the engine and then exits back to the system with a process " "exit status indicating an error.\n\n" "@param message The message to log to the console and show in an error message box.\n" "@param status The status code to return to the OS.\n\n" "@see quit\n\n" "@ingroup Platform" ) { Con::errorf( message ); Platform::AlertOK( "Error", message ); // [rene 03/30/10] This was previously using forceShutdown which is a bad thing // as the script code should not be allowed to pretty much hard-crash the engine // and prevent proper shutdown. Changed this to use postQuitMessage. Platform::postQuitMessage( status ); } //----------------------------------------------------------------------------- DefineConsoleFunction( quitWithStatus, void, ( S32 status ), (0), "Shut down the engine and exit its process.\n" "This function cleanly uninitializes the engine and then exits back to the system with a given " "return status code.\n\n" "@param status The status code to return to the OS.\n\n" "@see quitWithErrorMessage\n\n" "@ingroup Platform" ) { Platform::postQuitMessage(status); } //----------------------------------------------------------------------------- DefineEngineFunction( gotoWebPage, void, ( const char* address ),, "Open the given URL or file in the user's web browser.\n\n" "@param address The address to open. If this is not prefixed by a protocol specifier (\"...://\"), then " "the function checks whether the address refers to a file or directory and if so, prepends \"file://\" " "to @a adress; if the file check fails, \"http://\" is prepended to @a address.\n\n" "@tsexample\n" "gotoWebPage( \"http://www.garagegames.com\" );\n" "@endtsexample\n\n" "@ingroup Platform" ) { // If there's a protocol prefix in the address, just invoke // the browser on the given address. char* protocolSep = dStrstr( address,"://"); if( protocolSep != NULL ) { Platform::openWebBrowser( address ); return; } // If we don't see a protocol seperator, then we know that some bullethead // sent us a bad url. We'll first check to see if a file inside the sandbox // with that name exists, then we'll just glom "http://" onto the front of // the bogus url, and hope for the best. String addr; if( Platform::isFile( address ) || Platform::isDirectory( address ) ) { #ifdef TORQUE2D_TOOLS_FIXME addr = String::ToString( "file://%s", address ); #else addr = String::ToString( "file://%s/%s", Platform::getCurrentDirectory(), address ); #endif } else addr = String::ToString( "http://%s", address ); Platform::openWebBrowser( addr ); return; } //----------------------------------------------------------------------------- DefineEngineFunction( displaySplashWindow, bool, (const char* path), ("art/gui/splash.bmp"), "Display a startup splash window suitable for showing while the engine still starts up.\n\n" "@note This is currently only implemented on Windows.\n\n" "@return True if the splash window could be successfully initialized.\n\n" "@ingroup Platform" ) { return Platform::displaySplashWindow(path); } DefineEngineFunction( closeSplashWindow, void, (),, "Close our startup splash window.\n\n" "@note This is currently only implemented on Windows.\n\n" "@ingroup Platform" ) { Platform::closeSplashWindow(); } //----------------------------------------------------------------------------- DefineEngineFunction( getWebDeployment, bool, (),, "Test whether Torque is running in web-deployment mode.\n" "In this mode, Torque will usually run within a browser and certain restrictions apply (e.g. Torque will not " "be able to enter fullscreen exclusive mode).\n" "@return True if Torque is running in web-deployment mode.\n" "@ingroup Platform" ) { return Platform::getWebDeployment(); } //----------------------------------------------------------------------------- DefineEngineFunction( countBits, S32, ( S32 v ),, "Count the number of bits that are set in the given 32 bit integer.\n" "@param v An integer value.\n\n" "@return The number of bits that are set in @a v.\n\n" "@ingroup Utilities" ) { S32 c = 0; // from // http://graphics.stanford.edu/~seander/bithacks.html // for at most 32-bit values in v: c = ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f; c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f; c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f; #ifndef TORQUE_SHIPPING // since the above isn't very obvious, for debugging compute the count in a more // traditional way and assert if it is different { S32 c2 = 0; S32 v2 = v; for (c2 = 0; v2; v2 >>= 1) { c2 += v2 & 1; } if (c2 != c) Con::errorf("countBits: Uh oh bit count mismatch"); AssertFatal(c2 == c, "countBits: uh oh, bit count mismatch"); } #endif return c; } //----------------------------------------------------------------------------- DefineEngineFunction( generateUUID, Torque::UUID, (),, "Generate a new universally unique identifier (UUID).\n\n" "@return A newly generated UUID.\n\n" "@ingroup Utilities" ) { Torque::UUID uuid; uuid.generate(); return uuid; } //============================================================================= // Meta Scripting. //============================================================================= // MARK: ---- Meta Scripting ---- //----------------------------------------------------------------------------- ConsoleFunction( call, const char *, 2, 0, "( string functionName, string args... ) " "Apply the given arguments to the specified global function and return the result of the call.\n\n" "@param functionName The name of the function to call. This function must be in the global namespace, i.e. " "you cannot call a function in a namespace through #call. Use eval() for that.\n" "@return The result of the function call.\n\n" "@tsexample\n" "function myFunction( %arg )\n" "{\n" " return ( %arg SPC \"World!\" );\n" "}\n" "\n" "echo( call( \"myFunction\", \"Hello\" ) ); // Prints \"Hello World!\" to the console.\n" "@endtsexample\n\n" "@ingroup Scripting" ) { return Con::execute( argc - 1, argv + 1 ); } //----------------------------------------------------------------------------- static U32 execDepth = 0; static U32 journalDepth = 1; static StringTableEntry getDSOPath(const char *scriptPath) { #ifndef TORQUE2D_TOOLS_FIXME // [tom, 11/17/2006] Force old behavior for the player. May not want to do this. const char *slash = dStrrchr(scriptPath, '/'); if(slash != NULL) return StringTable->insertn(scriptPath, slash - scriptPath, true); slash = dStrrchr(scriptPath, ':'); if(slash != NULL) return StringTable->insertn(scriptPath, (slash - scriptPath) + 1, true); return ""; #else char relPath[1024], dsoPath[1024]; bool isPrefs = false; // [tom, 11/17/2006] Prefs are handled slightly differently to avoid dso name clashes StringTableEntry prefsPath = Platform::getPrefsPath(); if(dStrnicmp(scriptPath, prefsPath, dStrlen(prefsPath)) == 0) { relPath[0] = 0; isPrefs = true; } else { StringTableEntry strippedPath = Platform::stripBasePath(scriptPath); dStrcpy(relPath, strippedPath); char *slash = dStrrchr(relPath, '/'); if(slash) *slash = 0; } const char *overridePath; if(! isPrefs) overridePath = Con::getVariable("$Scripts::OverrideDSOPath"); else overridePath = prefsPath; if(overridePath && *overridePath) Platform::makeFullPathName(relPath, dsoPath, sizeof(dsoPath), overridePath); else { char t[1024]; dSprintf(t, sizeof(t), "compiledScripts/%s", relPath); Platform::makeFullPathName(t, dsoPath, sizeof(dsoPath), Platform::getPrefsPath()); } return StringTable->insert(dsoPath); #endif } DefineConsoleFunction( getDSOPath, const char*, ( const char* scriptFileName ),, "Get the absolute path to the file in which the compiled code for the given script file will be stored.\n" "@param scriptFileName %Path to the .cs script file.\n" "@return The absolute path to the .dso file for the given script file.\n\n" "@note The compiler will store newly compiled DSOs in the prefs path but pre-existing DSOs will be loaded " "from the current paths.\n\n" "@see compile\n" "@see getPrefsPath\n" "@ingroup Scripting" ) { Con::expandScriptFilename( scriptFilenameBuffer, sizeof(scriptFilenameBuffer), scriptFileName ); const char* filename = getDSOPath(scriptFilenameBuffer); if(filename == NULL || *filename == 0) return ""; return filename; } //----------------------------------------------------------------------------- DefineEngineFunction( compile, bool, ( const char* fileName, bool overrideNoDSO ), ( false ), "Compile a file to bytecode.\n\n" "This function will read the TorqueScript code in the specified file, compile it to internal bytecode, and, " "if DSO generation is enabled or @a overrideNoDDSO is true, will store the compiled code in a .dso file " "in the current DSO path mirrorring the path of @a fileName.\n\n" "@param fileName Path to the file to compile to bytecode.\n" "@param overrideNoDSO If true, force generation of DSOs even if the engine is compiled to not " "generate write compiled code to DSO files.\n\n" "@return True if the file was successfully compiled, false if not.\n\n" "@note The definitions contained in the given file will not be made available and no code will actually " "be executed. Use exec() for that.\n\n" "@see getDSOPath\n" "@see exec\n" "@ingroup Scripting" ) { Con::expandScriptFilename( scriptFilenameBuffer, sizeof( scriptFilenameBuffer ), fileName ); // Figure out where to put DSOs StringTableEntry dsoPath = getDSOPath(scriptFilenameBuffer); if(dsoPath && *dsoPath == 0) return false; // If the script file extention is '.ed.cs' then compile it to a different compiled extention bool isEditorScript = false; const char *ext = dStrrchr( scriptFilenameBuffer, '.' ); if( ext && ( dStricmp( ext, ".cs" ) == 0 ) ) { const char* ext2 = ext - 3; if( dStricmp( ext2, ".ed.cs" ) == 0 ) isEditorScript = true; } else if( ext && ( dStricmp( ext, ".gui" ) == 0 ) ) { const char* ext2 = ext - 3; if( dStricmp( ext2, ".ed.gui" ) == 0 ) isEditorScript = true; } const char *filenameOnly = dStrrchr(scriptFilenameBuffer, '/'); if(filenameOnly) ++filenameOnly; else filenameOnly = scriptFilenameBuffer; char nameBuffer[512]; if( isEditorScript ) dStrcpyl(nameBuffer, sizeof(nameBuffer), dsoPath, "/", filenameOnly, ".edso", NULL); else dStrcpyl(nameBuffer, sizeof(nameBuffer), dsoPath, "/", filenameOnly, ".dso", NULL); void *data = NULL; U32 dataSize = 0; Torque::FS::ReadFile(scriptFilenameBuffer, data, dataSize, true); if(data == NULL) { Con::errorf(ConsoleLogEntry::Script, "compile: invalid script file %s.", scriptFilenameBuffer); return false; } const char *script = static_cast(data); #ifdef TORQUE_DEBUG Con::printf("Compiling %s...", scriptFilenameBuffer); #endif CodeBlock *code = new CodeBlock(); code->compile(nameBuffer, scriptFilenameBuffer, script, overrideNoDSO); delete code; delete[] script; return true; } //----------------------------------------------------------------------------- DefineEngineFunction( exec, bool, ( const char* fileName, bool noCalls, bool journalScript ), ( false, false ), "Execute the given script file.\n" "@param fileName Path to the file to execute\n" "@param noCalls Deprecated\n" "@param journalScript Deprecated\n" "@return True if the script was successfully executed, false if not.\n\n" "@tsexample\n" "// Execute the init.cs script file found in the same directory as the current script file.\n" "exec( \"./init.cs\" );\n" "@endtsexample\n\n" "@see compile\n" "@see eval\n" "@ingroup Scripting" ) { bool journal = false; execDepth++; if(journalDepth >= execDepth) journalDepth = execDepth + 1; else journal = true; bool ret = false; if( journalScript && !journal ) { journal = true; journalDepth = execDepth; } // Determine the filename we actually want... Con::expandScriptFilename( scriptFilenameBuffer, sizeof( scriptFilenameBuffer ), fileName ); // since this function expects a script file reference, if it's a .dso // lets terminate the string before the dso so it will act like a .cs if(dStrEndsWith(scriptFilenameBuffer, ".dso")) { scriptFilenameBuffer[dStrlen(scriptFilenameBuffer) - dStrlen(".dso")] = '\0'; } // Figure out where to put DSOs StringTableEntry dsoPath = getDSOPath(scriptFilenameBuffer); const char *ext = dStrrchr(scriptFilenameBuffer, '.'); if(!ext) { // We need an extension! Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file name %s.", scriptFilenameBuffer); execDepth--; return false; } // Check Editor Extensions bool isEditorScript = false; // If the script file extension is '.ed.cs' then compile it to a different compiled extension if( dStricmp( ext, ".cs" ) == 0 ) { const char* ext2 = ext - 3; if( dStricmp( ext2, ".ed.cs" ) == 0 ) isEditorScript = true; } else if( dStricmp( ext, ".gui" ) == 0 ) { const char* ext2 = ext - 3; if( dStricmp( ext2, ".ed.gui" ) == 0 ) isEditorScript = true; } StringTableEntry scriptFileName = StringTable->insert(scriptFilenameBuffer); #ifndef TORQUE_OS_XENON // Is this a file we should compile? (anything in the prefs path should not be compiled) StringTableEntry prefsPath = Platform::getPrefsPath(); bool compiled = dStricmp(ext, ".mis") && !journal && !Con::getBoolVariable("Scripts::ignoreDSOs"); // [tom, 12/5/2006] stripBasePath() fucks up if the filename is not in the exe // path, current directory or prefs path. Thus, getDSOFilename() will also screw // up and so this allows the scripts to still load but without a DSO. if(Platform::isFullPath(Platform::stripBasePath(scriptFilenameBuffer))) compiled = false; // [tom, 11/17/2006] It seems to make sense to not compile scripts that are in the // prefs directory. However, getDSOPath() can handle this situation and will put // the dso along with the script to avoid name clashes with tools/game dsos. if( (dsoPath && *dsoPath == 0) || (prefsPath && prefsPath[ 0 ] && dStrnicmp(scriptFileName, prefsPath, dStrlen(prefsPath)) == 0) ) compiled = false; #else bool compiled = false; // Don't try to compile things on the 360, ignore DSO's when debugging // because PC prefs will screw up stuff like SFX. #endif // If we're in a journaling mode, then we will read the script // from the journal file. if(journal && Journal::IsPlaying()) { char fileNameBuf[256]; bool fileRead = false; U32 fileSize; Journal::ReadString(fileNameBuf); Journal::Read(&fileRead); if(!fileRead) { Con::errorf(ConsoleLogEntry::Script, "Journal script read (failed) for %s", fileNameBuf); execDepth--; return false; } Journal::Read(&fileSize); char *script = new char[fileSize + 1]; Journal::Read(fileSize, script); script[fileSize] = 0; Con::printf("Executing (journal-read) %s.", scriptFileName); CodeBlock *newCodeBlock = new CodeBlock(); newCodeBlock->compileExec(scriptFileName, script, noCalls, 0); delete [] script; execDepth--; return true; } // Ok, we let's try to load and compile the script. Torque::FS::FileNodeRef scriptFile = Torque::FS::GetFileNode(scriptFileName); Torque::FS::FileNodeRef dsoFile; // ResourceObject *rScr = gResourceManager->find(scriptFileName); // ResourceObject *rCom = NULL; char nameBuffer[512]; char* script = NULL; U32 version; Stream *compiledStream = NULL; Torque::Time scriptModifiedTime, dsoModifiedTime; // Check here for .edso bool edso = false; if( dStricmp( ext, ".edso" ) == 0 && scriptFile != NULL ) { edso = true; dsoFile = scriptFile; scriptFile = NULL; dsoModifiedTime = dsoFile->getModifiedTime(); dStrcpy( nameBuffer, scriptFileName ); } // If we're supposed to be compiling this file, check to see if there's a DSO if(compiled && !edso) { const char *filenameOnly = dStrrchr(scriptFileName, '/'); if(filenameOnly) ++filenameOnly; else filenameOnly = scriptFileName; char pathAndFilename[1024]; Platform::makeFullPathName(filenameOnly, pathAndFilename, sizeof(pathAndFilename), dsoPath); if( isEditorScript ) dStrcpyl(nameBuffer, sizeof(nameBuffer), pathAndFilename, ".edso", NULL); else dStrcpyl(nameBuffer, sizeof(nameBuffer), pathAndFilename, ".dso", NULL); dsoFile = Torque::FS::GetFileNode(nameBuffer); if(scriptFile != NULL) scriptModifiedTime = scriptFile->getModifiedTime(); if(dsoFile != NULL) dsoModifiedTime = dsoFile->getModifiedTime(); } // Let's do a sanity check to complain about DSOs in the future. // // MM: This doesn't seem to be working correctly for now so let's just not issue // the warning until someone knows how to resolve it. // //if(compiled && rCom && rScr && Platform::compareFileTimes(comModifyTime, scrModifyTime) < 0) //{ //Con::warnf("exec: Warning! Found a DSO from the future! (%s)", nameBuffer); //} // If we had a DSO, let's check to see if we should be reading from it. //MGT: fixed bug with dsos not getting recompiled correctly //Note: Using Nathan Martin's version from the forums since its easier to read and understand if(compiled && dsoFile != NULL && (scriptFile == NULL|| (dsoModifiedTime >= scriptModifiedTime))) { //MGT: end compiledStream = FileStream::createAndOpen( nameBuffer, Torque::FS::File::Read ); if (compiledStream) { // Check the version! compiledStream->read(&version); if(version != Con::DSOVersion) { Con::warnf("exec: Found an old DSO (%s, ver %d < %d), ignoring.", nameBuffer, version, Con::DSOVersion); delete compiledStream; compiledStream = NULL; } } } // If we're journalling, let's write some info out. if(journal && Journal::IsRecording()) Journal::WriteString(scriptFileName); if(scriptFile != NULL && !compiledStream) { // If we have source but no compiled version, then we need to compile // (and journal as we do so, if that's required). void *data; U32 dataSize = 0; Torque::FS::ReadFile(scriptFileName, data, dataSize, true); if(journal && Journal::IsRecording()) Journal::Write(bool(data != NULL)); if( data == NULL ) { Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file %s.", scriptFileName); execDepth--; return false; } else { if( !dataSize ) { execDepth --; return false; } script = (char *)data; if(journal && Journal::IsRecording()) { Journal::Write(dataSize); Journal::Write(dataSize, data); } } #ifndef TORQUE_NO_DSO_GENERATION if(compiled) { // compile this baddie. #ifdef TORQUE_DEBUG Con::printf("Compiling %s...", scriptFileName); #endif CodeBlock *code = new CodeBlock(); code->compile(nameBuffer, scriptFileName, script); delete code; code = NULL; compiledStream = FileStream::createAndOpen( nameBuffer, Torque::FS::File::Read ); if(compiledStream) { compiledStream->read(&version); } else { // We have to exit out here, as otherwise we get double error reports. delete [] script; execDepth--; return false; } } #endif } else { if(journal && Journal::IsRecording()) Journal::Write(bool(false)); } if(compiledStream) { // Delete the script object first to limit memory used // during recursive execs. delete [] script; script = 0; // We're all compiled, so let's run it. #ifdef TORQUE_DEBUG Con::printf("Loading compiled script %s.", scriptFileName); #endif CodeBlock *code = new CodeBlock; code->read(scriptFileName, *compiledStream); delete compiledStream; code->exec(0, scriptFileName, NULL, 0, NULL, noCalls, NULL, 0); ret = true; } else if(scriptFile) { // No compiled script, let's just try executing it // directly... this is either a mission file, or maybe // we're on a readonly volume. #ifdef TORQUE_DEBUG Con::printf("Executing %s.", scriptFileName); #endif CodeBlock *newCodeBlock = new CodeBlock(); StringTableEntry name = StringTable->insert(scriptFileName); newCodeBlock->compileExec(name, script, noCalls, 0); ret = true; } else { // Don't have anything. Con::warnf(ConsoleLogEntry::Script, "Missing file: %s!", scriptFileName); ret = false; } delete [] script; execDepth--; return ret; } ConsoleFunction(eval, const char *, 2, 2, "eval(consoleString)") { TORQUE_UNUSED(argc); return Con::evaluate(argv[1], false, NULL); } ConsoleFunction(getVariable, const char *, 2, 2, "(string varName)\n" "@brief Returns the value of the named variable or an empty string if not found.\n\n" "@varName Name of the variable to search for\n" "@return Value contained by varName, \"\" if the variable does not exist\n" "@ingroup Scripting") { return Con::getVariable(argv[1]); } ConsoleFunction(setVariable, void, 3, 3, "(string varName, string value)\n" "@brief Sets the value of the named variable.\n\n" "@param varName Name of the variable to locate\n" "@param value New value of the variable\n" "@return True if variable was successfully found and set\n" "@ingroup Scripting") { return Con::setVariable(argv[1], argv[2]); } ConsoleFunction(isFunction, bool, 2, 2, "(string funcName)" "@brief Determines if a function exists or not\n\n" "@param funcName String containing name of the function\n" "@return True if the function exists, false if not\n" "@ingroup Scripting") { return Con::isFunction(argv[1]); } ConsoleFunction(getFunctionPackage, const char*, 2, 2, "(string funcName)" "@brief Provides the name of the package the function belongs to\n\n" "@param funcName String containing name of the function\n" "@return The name of the function's package\n" "@ingroup Packages") { Namespace::Entry* nse = Namespace::global()->lookup( StringTable->insert( argv[1] ) ); if( !nse ) return ""; return nse->mPackage; } ConsoleFunction(isMethod, bool, 3, 3, "(string namespace, string method)" "@brief Determines if a class/namespace method exists\n\n" "@param namespace Class or namespace, such as Player\n" "@param method Name of the function to search for\n" "@return True if the method exists, false if not\n" "@ingroup Scripting\n") { Namespace* ns = Namespace::find( StringTable->insert( argv[1] ) ); Namespace::Entry* nse = ns->lookup( StringTable->insert( argv[2] ) ); if( !nse ) return false; return true; } ConsoleFunction(getMethodPackage, const char*, 3, 3, "(string namespace, string method)" "@brief Provides the name of the package the method belongs to\n\n" "@param namespace Class or namespace, such as Player\n" "@param method Name of the funciton to search for\n" "@return The name of the method's package\n" "@ingroup Packages") { Namespace* ns = Namespace::find( StringTable->insert( argv[1] ) ); if( !ns ) return ""; Namespace::Entry* nse = ns->lookup( StringTable->insert( argv[2] ) ); if( !nse ) return ""; return nse->mPackage; } ConsoleFunction(isDefined, bool, 2, 3, "(string varName)" "@brief Determines if a variable exists and contains a value\n" "@param varName Name of the variable to search for\n" "@return True if the variable was defined in script, false if not\n" "@tsexample\n" "isDefined( \"$myVar\" );\n" "@endtsexample\n\n" "@ingroup Scripting") { if(dStrlen(argv[1]) == 0) { Con::errorf("isDefined() - did you forget to put quotes around the variable name?"); return false; } StringTableEntry name = StringTable->insert(argv[1]); // Deal with . if (dStrchr(name, '.')) { static char scratchBuffer[4096]; S32 len = dStrlen(name); AssertFatal(len < sizeof(scratchBuffer)-1, "isDefined() - name too long"); dMemcpy(scratchBuffer, name, len+1); char * token = dStrtok(scratchBuffer, "."); if (!token || token[0] == '\0') return false; StringTableEntry objName = StringTable->insert(token); // Attempt to find the object SimObject * obj = Sim::findObject(objName); // If we didn't find the object then we can safely // assume that the field variable doesn't exist if (!obj) return false; // Get the name of the field token = dStrtok(0, ".\0"); if (!token) return false; while (token != NULL) { StringTableEntry valName = StringTable->insert(token); // Store these so we can restore them after we search for the variable bool saveModStatic = obj->canModStaticFields(); bool saveModDyn = obj->canModDynamicFields(); // Set this so that we can search both static and dynamic fields obj->setModStaticFields(true); obj->setModDynamicFields(true); const char* value = obj->getDataField(valName, 0); // Restore our mod flags to be safe obj->setModStaticFields(saveModStatic); obj->setModDynamicFields(saveModDyn); if (!value) { obj->setDataField(valName, 0, argv[2]); return false; } else { // See if we are field on a field token = dStrtok(0, ".\0"); if (token) { // The previous field must be an object obj = Sim::findObject(value); if (!obj) return false; } else { if (dStrlen(value) > 0) return true; else if (argc > 2) obj->setDataField(valName, 0, argv[2]); } } } } else if (name[0] == '%') { // Look up a local variable if( gEvalState.getStackDepth() > 0 ) { Dictionary::Entry* ent = gEvalState.getCurrentFrame().lookup(name); if (ent) return true; else if (argc > 2) gEvalState.getCurrentFrame().setVariable(name, argv[2]); } else Con::errorf("%s() - no local variable frame.", __FUNCTION__); } else if (name[0] == '$') { // Look up a global value Dictionary::Entry* ent = gEvalState.globalVars.lookup(name); if (ent) return true; else if (argc > 2) gEvalState.globalVars.setVariable(name, argv[2]); } else { // Is it an object? if (dStrcmp(argv[1], "0") && dStrcmp(argv[1], "") && (Sim::findObject(argv[1]) != NULL)) return true; else if (argc > 2) Con::errorf("%s() - can't assign a value to a variable of the form \"%s\"", __FUNCTION__, (const char*)argv[1]); } return false; } //----------------------------------------------------------------------------- ConsoleFunction( isCurrentScriptToolScript, bool, 1, 1, "() Returns true if the calling script is a tools script.\n" "@hide") { return Con::isCurrentScriptToolScript(); } ConsoleFunction(getModNameFromPath, const char *, 2, 2, "(string path)" "@brief Attempts to extract a mod directory from path. Returns empty string on failure.\n\n" "@param File path of mod folder\n" "@note This is no longer relevant in Torque 3D (which does not use mod folders), should be deprecated\n" "@internal") { StringTableEntry modPath = Con::getModNameFromPath(argv[1]); return modPath ? modPath : ""; } //----------------------------------------------------------------------------- ConsoleFunction( pushInstantGroup, void, 1, 2, "([group])" "@brief Pushes the current $instantGroup on a stack " "and sets it to the given value (or clears it).\n\n" "@note Currently only used for editors\n" "@ingroup Editors\n" "@internal") { if( argc > 1 ) Con::pushInstantGroup( (const char*)argv[ 1 ] ); else Con::pushInstantGroup(); } ConsoleFunction( popInstantGroup, void, 1, 1, "()" "@brief Pop and restore the last setting of $instantGroup off the stack.\n\n" "@note Currently only used for editors\n\n" "@ingroup Editors\n" "@internal") { Con::popInstantGroup(); } //----------------------------------------------------------------------------- ConsoleFunction(getPrefsPath, const char *, 1, 2, "([relativeFileName])" "@note Appears to be useless in Torque 3D, should be deprecated\n" "@internal") { const char *filename = Platform::getPrefsPath(argc > 1 ? (const char*)argv[1] : NULL); if(filename == NULL || *filename == 0) return ""; return filename; } //----------------------------------------------------------------------------- ConsoleFunction( execPrefs, bool, 2, 4, "( string relativeFileName, bool noCalls=false, bool journalScript=false )" "@brief Manually execute a special script file that contains game or editor preferences\n\n" "@param relativeFileName Name and path to file from project folder\n" "@param noCalls Deprecated\n" "@param journalScript Deprecated\n" "@return True if script was successfully executed\n" "@note Appears to be useless in Torque 3D, should be deprecated\n" "@ingroup Scripting") { const char *filename = Platform::getPrefsPath(argv[1]); if(filename == NULL || *filename == 0) return false; // Scripts do this a lot, so we may as well help them out if(! Platform::isFile(filename) && ! Torque::FS::IsFile(filename)) return true; argv[0] = "exec"; argv[1] = filename; return dAtob(Con::execute(argc, argv)); } //----------------------------------------------------------------------------- DefineConsoleFunction( export, void, ( const char* pattern, const char* filename, bool append ), ( "", false ), "Write out the definitions of all global variables matching the given name @a pattern.\n" "If @a fileName is not \"\", the variable definitions are written to the specified file. Otherwise the " "definitions will be printed to the console.\n\n" "The output are valid TorqueScript statements that can be executed to restore the global variable " "values.\n\n" "@param pattern A global variable name pattern. Must begin with '$'.\n" "@param filename %Path of the file to which to write the definitions or \"\" to write the definitions " "to the console.\n" "@param append If true and @a fileName is not \"\", then the definitions are appended to the specified file. " "Otherwise existing contents of the file (if any) will be overwritten.\n\n" "@tsexample\n" "// Write out all preference variables to a prefs.cs file.\n" "export( \"$prefs::*\", \"prefs.cs\" );\n" "@endtsexample\n\n" "@ingroup Scripting" ) { if( filename && filename[ 0 ] ) { #ifndef TORQUE2D_TOOLS_FIXME if(Con::expandScriptFilename(scriptFilenameBuffer, sizeof(scriptFilenameBuffer), filename)) filename = scriptFilenameBuffer; #else filename = Platform::getPrefsPath( filename ); if(filename == NULL || *filename == 0) return; #endif } else filename = NULL; gEvalState.globalVars.exportVariables( pattern, filename, append ); } //----------------------------------------------------------------------------- DefineEngineFunction( deleteVariables, void, ( const char* pattern ),, "Undefine all global variables matching the given name @a pattern.\n" "@param pattern A global variable name pattern. Must begin with '$'.\n" "@tsexample\n" "// Define a global variable in the \"My\" namespace.\n" "$My::Variable = \"value\";\n\n" "// Undefine all variable in the \"My\" namespace.\n" "deleteVariables( \"$My::*\" );\n" "@endtsexample\n\n" "@see strIsMatchExpr\n" "@ingroup Scripting" ) { gEvalState.globalVars.deleteVariables( pattern ); } //----------------------------------------------------------------------------- DefineConsoleFunction( trace, void, ( bool enable ), ( true ), "Enable or disable tracing in the script code VM.\n\n" "When enabled, the script code runtime will trace the invocation and returns " "from all functions that are called and log them to the console. This is helpful in " "observing the flow of the script program.\n\n" "@param enable New setting for script trace execution, on by default.\n" "@ingroup Debugging" ) { gEvalState.traceOn = enable; Con::printf( "Console trace %s", gEvalState.traceOn ? "enabled." : "disabled." ); } //----------------------------------------------------------------------------- #if defined(TORQUE_DEBUG) || !defined(TORQUE_SHIPPING) DefineConsoleFunction( debug, void, (),, "Drops the engine into the native C++ debugger.\n\n" "This function triggers a debug break and drops the process into the IDE's debugger. If the process is not " "running with a debugger attached it will generate a runtime error on most platforms.\n\n" "@note This function is not available in shipping builds." "@ingroup Debugging" ) { Platform::debugBreak(); } #endif //----------------------------------------------------------------------------- DefineEngineFunction( isShippingBuild, bool, (),, "Test whether the engine has been compiled with TORQUE_SHIPPING, i.e. in a form meant for final release.\n\n" "@return True if this is a shipping build; false otherwise.\n\n" "@ingroup Platform" ) { #ifdef TORQUE_SHIPPING return true; #else return false; #endif } //----------------------------------------------------------------------------- DefineEngineFunction( isDebugBuild, bool, (),, "Test whether the engine has been compiled with TORQUE_DEBUG, i.e. if it includes debugging functionality.\n\n" "@return True if this is a debug build; false otherwise.\n\n" "@ingroup Platform" ) { #ifdef TORQUE_DEBUG return true; #else return false; #endif } //----------------------------------------------------------------------------- DefineEngineFunction( isToolBuild, bool, (),, "Test whether the engine has been compiled with TORQUE_TOOLS, i.e. if it includes tool-related functionality.\n\n" "@return True if this is a tool build; false otherwise.\n\n" "@ingroup Platform" ) { #ifdef TORQUE_TOOLS return true; #else return false; #endif }