consoleInternal.cpp
Engine/source/console/consoleInternal.cpp
Classes:
Namespaces:
namespace
Public Defines
define
ST_INIT_SIZE() 15
Public Variables
std::unordered_map< std::pair< StringTableEntry, StringTableEntry >, Namespace * >
char
scratchBuffer [1024]
char *
Public Functions
bool
canTabComplete(const char * prevText, const char * bestMatch, const char * newText, S32 baseLen, bool fForward)
compareEntries(const void * a, const void * b)
DefineEngineFunction(activatePackage , void , (String packageName) , "@brief Activates an existing <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">package.\n\n</a>" "The activation occurs by updating the namespace linkage of existing functions and methods. " "If the package is already activated the function does <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">nothing.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Packages\n</a>" )
DefineEngineFunction(backtrace , void , () , "@brief Prints the scripting call stack <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the console <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">log.\n\n</a>" "Used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trace functions called from within functions. Can help discover what functions were called " "(and not yet exited) before the current point in <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n\n</a>" "@ingroup Debugging" )
DefineEngineFunction(deactivatePackage , void , (String packageName) , "@brief Deactivates <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> previously activated <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">package.\n\n</a>" "The package is deactivated by removing its namespace linkages <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> any function or method. " "If there are any packages above this one in the stack they are deactivated as well. " "If the package is not on the stack this function does <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">nothing.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Packages\n</a>" )
DefineEngineFunction(getPackageList , const char * , () , "@brief Returns <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> space delimited list of the active packages in stack <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">order.\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Packages\n</a>" )
DefineEngineFunction(isPackage , bool , (String identifier) , "@brief Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the identifier is the name of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> declared <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">package.\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Packages\n</a>" )
executeBlock(StmtNode * block, ExprEvalState * state)
varCompare(const void * a, const void * b)
Detailed Description
Public Defines
ST_INIT_SIZE() 15
Public Variables
std::unordered_map< std::pair< StringTableEntry, StringTableEntry >, Namespace * > gNamespaceCache
char scratchBuffer [1024]
char * typeValueEmpty
Public Functions
canTabComplete(const char * prevText, const char * bestMatch, const char * newText, S32 baseLen, bool fForward)
compareEntries(const void * a, const void * b)
DefineEngineFunction(activatePackage , void , (String packageName) , "@brief Activates an existing <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">package.\n\n</a>" "The activation occurs by updating the namespace linkage of existing functions and methods. " "If the package is already activated the function does <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">nothing.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Packages\n</a>" )
DefineEngineFunction(backtrace , void , () , "@brief Prints the scripting call stack <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the console <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">log.\n\n</a>" "Used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> trace functions called from within functions. Can help discover what functions were called " "(and not yet exited) before the current point in <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n\n</a>" "@ingroup Debugging" )
DefineEngineFunction(deactivatePackage , void , (String packageName) , "@brief Deactivates <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> previously activated <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">package.\n\n</a>" "The package is deactivated by removing its namespace linkages <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> any function or method. " "If there are any packages above this one in the stack they are deactivated as well. " "If the package is not on the stack this function does <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">nothing.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Packages\n</a>" )
DefineEngineFunction(getPackageList , const char * , () , "@brief Returns <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> space delimited list of the active packages in stack <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">order.\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Packages\n</a>" )
DefineEngineFunction(isPackage , bool , (String identifier) , "@brief Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the identifier is the name of <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> declared <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">package.\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Packages\n</a>" )
executeBlock(StmtNode * block, ExprEvalState * state)
HashPointer(StringTableEntry ptr)
varCompare(const void * a, const void * b)
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include <unordered_map> 25 26#include "platform/platform.h" 27#include "console/console.h" 28 29#include "console/ast.h" 30#include "core/tAlgorithm.h" 31 32#include "core/strings/findMatch.h" 33#include "console/consoleInternal.h" 34#include "core/stream/fileStream.h" 35#include "console/compiler.h" 36#include "console/engineAPI.h" 37 38//#define DEBUG_SPEW 39 40#define ST_INIT_SIZE 15 41 42static char scratchBuffer[1024]; 43U32 Namespace::mCacheSequence = 0; 44DataChunker Namespace::mCacheAllocator; 45DataChunker Namespace::mAllocator; 46Namespace *Namespace::mNamespaceList = NULL; 47Namespace *Namespace::mGlobalNamespace = NULL; 48 49namespace std 50{ 51 template<> struct hash<std::pair<StringTableEntry, StringTableEntry>> 52 { 53 typedef std::pair<StringTableEntry, StringTableEntry> argument_type; 54 typedef size_t result_type; 55 result_type operator()(argument_type const& s) const 56 { 57 return HashPointer(s.first) ^ (HashPointer(s.second) << 1); 58 } 59 }; 60}; 61 62std::unordered_map<std::pair<StringTableEntry, StringTableEntry>, Namespace*> gNamespaceCache; 63 64bool canTabComplete(const char *prevText, const char *bestMatch, 65 const char *newText, S32 baseLen, bool fForward) 66{ 67 // test if it matches the first baseLen chars: 68 if (dStrnicmp(newText, prevText, baseLen)) 69 return false; 70 71 if (fForward) 72 { 73 if (!bestMatch) 74 return dStricmp(newText, prevText) > 0; 75 else 76 return (dStricmp(newText, prevText) > 0) && 77 (dStricmp(newText, bestMatch) < 0); 78 } 79 else 80 { 81 if (dStrlen(prevText) == (U32)baseLen) 82 { 83 // look for the 'worst match' 84 if (!bestMatch) 85 return dStricmp(newText, prevText) > 0; 86 else 87 return dStricmp(newText, bestMatch) > 0; 88 } 89 else 90 { 91 if (!bestMatch) 92 return (dStricmp(newText, prevText) < 0); 93 else 94 return (dStricmp(newText, prevText) < 0) && 95 (dStricmp(newText, bestMatch) > 0); 96 } 97 } 98} 99 100//--------------------------------------------------------------- 101// 102// Dictionary functions 103// 104//--------------------------------------------------------------- 105struct StringValue 106{ 107 S32 size; 108 char *val; 109 110 operator char *() { return val; } 111 StringValue &operator=(const char *string); 112 113 StringValue() { size = 0; val = NULL; } 114 ~StringValue() { dFree(val); } 115}; 116 117 118StringValue & StringValue::operator=(const char *string) 119{ 120 if (!val) 121 { 122 val = dStrdup(string); 123 size = dStrlen(val); 124 } 125 else 126 { 127 S32 len = dStrlen(string); 128 if (len < size) 129 dStrcpy(val, string, size); 130 else 131 { 132 size = len; 133 dFree(val); 134 val = dStrdup(string); 135 } 136 } 137 return *this; 138} 139 140static S32 QSORT_CALLBACK varCompare(const void* a, const void* b) 141{ 142 return dStricmp((*((Dictionary::Entry **) a))->name, (*((Dictionary::Entry **) b))->name); 143} 144 145void Dictionary::exportVariables(const char *varString, const char *fileName, bool append) 146{ 147 const char *searchStr = varString; 148 Vector<Entry*> sortList(__FILE__, __LINE__); 149 150 for (S32 i = 0; i < hashTable->size; i++) 151 { 152 Entry *walk = hashTable->data[i]; 153 while (walk) 154 { 155 if (FindMatch::isMatch((char *)searchStr, (char *)walk->name)) 156 sortList.push_back(walk); 157 158 walk = walk->nextEntry; 159 } 160 } 161 162 if (!sortList.size()) 163 return; 164 165 dQsort((void *)&sortList[0], sortList.size(), sizeof(Entry *), varCompare); 166 167 Vector<Entry *>::iterator s; 168 char expandBuffer[1024]; 169 FileStream *strm = NULL; 170 171 if (fileName) 172 { 173 if ((strm = FileStream::createAndOpen(fileName, append ? Torque::FS::File::ReadWrite : Torque::FS::File::Write)) == NULL) 174 { 175 Con::errorf(ConsoleLogEntry::General, "Unable to open file '%s for writing.", fileName); 176 return; 177 } 178 if (append) 179 strm->setPosition(strm->getStreamSize()); 180 } 181 182 char buffer[1024]; 183 const char *cat = fileName ? "\r\n" : ""; 184 185 for (s = sortList.begin(); s != sortList.end(); s++) 186 { 187 switch ((*s)->value.type) 188 { 189 case ConsoleValue::TypeInternalInt: 190 dSprintf(buffer, sizeof(buffer), "%s = %d;%s", (*s)->name, (*s)->value.ival, cat); 191 break; 192 case ConsoleValue::TypeInternalFloat: 193 dSprintf(buffer, sizeof(buffer), "%s = %g;%s", (*s)->name, (*s)->value.fval, cat); 194 break; 195 default: 196 expandEscape(expandBuffer, (*s)->getStringValue()); 197 dSprintf(buffer, sizeof(buffer), "%s = \"%s\";%s", (*s)->name, expandBuffer, cat); 198 break; 199 } 200 if (strm) 201 strm->write(dStrlen(buffer), buffer); 202 else 203 Con::printf("%s", buffer); 204 } 205 if (strm) 206 delete strm; 207} 208 209void Dictionary::exportVariables(const char *varString, Vector<String> *names, Vector<String> *values) 210{ 211 const char *searchStr = varString; 212 Vector<Entry*> sortList(__FILE__, __LINE__); 213 214 for (S32 i = 0; i < hashTable->size; i++) 215 { 216 Entry *walk = hashTable->data[i]; 217 while (walk) 218 { 219 if (FindMatch::isMatch((char*)searchStr, (char*)walk->name)) 220 sortList.push_back(walk); 221 222 walk = walk->nextEntry; 223 } 224 } 225 226 if (!sortList.size()) 227 return; 228 229 dQsort((void *)&sortList[0], sortList.size(), sizeof(Entry *), varCompare); 230 231 if (names) 232 names->reserve(sortList.size()); 233 if (values) 234 values->reserve(sortList.size()); 235 236 char expandBuffer[1024]; 237 238 Vector<Entry *>::iterator s; 239 240 for (s = sortList.begin(); s != sortList.end(); s++) 241 { 242 if (names) 243 names->push_back(String((*s)->name)); 244 245 if (values) 246 { 247 switch ((*s)->value.type) 248 { 249 case ConsoleValue::TypeInternalInt: 250 values->push_back(String::ToString((*s)->value.ival)); 251 break; 252 case ConsoleValue::TypeInternalFloat: 253 values->push_back(String::ToString((*s)->value.fval)); 254 break; 255 default: 256 expandEscape(expandBuffer, (*s)->getStringValue()); 257 values->push_back(expandBuffer); 258 break; 259 } 260 } 261 } 262} 263 264void Dictionary::deleteVariables(const char *varString) 265{ 266 const char *searchStr = varString; 267 268 for (S32 i = 0; i < hashTable->size; i++) 269 { 270 Entry *walk = hashTable->data[i]; 271 while (walk) 272 { 273 Entry *matchedEntry = (FindMatch::isMatch((char *)searchStr, (char *)walk->name)) ? walk : NULL; 274 walk = walk->nextEntry; 275 if (matchedEntry) 276 remove(matchedEntry); // assumes remove() is a stable remove (will not reorder entries on remove) 277 } 278 } 279} 280 281U32 HashPointer(StringTableEntry ptr) 282{ 283 return (U32)(((dsize_t)ptr) >> 2); 284} 285 286Dictionary::Entry *Dictionary::lookup(StringTableEntry name) 287{ 288 Entry *walk = hashTable->data[HashPointer(name) % hashTable->size]; 289 while (walk) 290 { 291 if (walk->name == name) 292 return walk; 293 else 294 walk = walk->nextEntry; 295 } 296 297 return NULL; 298} 299 300Dictionary::Entry *Dictionary::add(StringTableEntry name) 301{ 302 // Try to find an existing match. 303 //printf("Add Variable %s\n", name); 304 305 Entry* ret = lookup(name); 306 if (ret) 307 return ret; 308 309 // Rehash if the table get's too crowded. Be aware that this might 310 // modify a table that we don't own. 311 312 hashTable->count++; 313 if (hashTable->count > hashTable->size * 2) 314 { 315 // Allocate a new table. 316 317 const U32 newTableSize = hashTable->size * 4 - 1; 318 Entry** newTableData = new Entry*[newTableSize]; 319 dMemset(newTableData, 0, newTableSize * sizeof(Entry*)); 320 321 // Move the entries over. 322 323 for (U32 i = 0; i < hashTable->size; ++i) 324 for (Entry* entry = hashTable->data[i]; entry != NULL; ) 325 { 326 Entry* next = entry->nextEntry; 327 U32 index = HashPointer(entry->name) % newTableSize; 328 329 entry->nextEntry = newTableData[index]; 330 newTableData[index] = entry; 331 332 entry = next; 333 } 334 335 // Switch the tables. 336 337 delete[] hashTable->data; 338 hashTable->data = newTableData; 339 hashTable->size = newTableSize; 340 } 341 342#ifdef DEBUG_SPEW 343 Platform::outputDebugString("[ConsoleInternal] Adding entry '%s'", name); 344#endif 345 346 // Add the new entry. 347 348 ret = hashTable->mChunker.alloc(); 349 constructInPlace(ret, name); 350 U32 idx = HashPointer(name) % hashTable->size; 351 ret->nextEntry = hashTable->data[idx]; 352 hashTable->data[idx] = ret; 353 354 return ret; 355} 356 357// deleteVariables() assumes remove() is a stable remove (will not reorder entries on remove) 358void Dictionary::remove(Dictionary::Entry *ent) 359{ 360 Entry **walk = &hashTable->data[HashPointer(ent->name) % hashTable->size]; 361 while (*walk != ent) 362 walk = &((*walk)->nextEntry); 363 364#ifdef DEBUG_SPEW 365 Platform::outputDebugString("[ConsoleInternal] Removing entry '%s'", ent->name); 366#endif 367 368 *walk = (ent->nextEntry); 369 370 destructInPlace(ent); 371 hashTable->mChunker.free(ent); 372 373 hashTable->count--; 374} 375 376Dictionary::Dictionary() 377 : hashTable(NULL), 378#pragma warning( disable : 4355 ) 379 ownHashTable(this), // Warning with VC++ but this is safe. 380#pragma warning( default : 4355 ) 381 exprState(NULL), 382 scopeName(NULL), 383 scopeNamespace(NULL), 384 code(NULL), 385 ip(0) 386{ 387} 388 389void Dictionary::setState(ExprEvalState *state, Dictionary* ref) 390{ 391 exprState = state; 392 393 if (ref) 394 { 395 hashTable = ref->hashTable; 396 return; 397 } 398 399 if (!ownHashTable.data) 400 { 401 ownHashTable.count = 0; 402 ownHashTable.size = ST_INIT_SIZE; 403 ownHashTable.data = new Entry *[ownHashTable.size]; 404 405 dMemset(ownHashTable.data, 0, ownHashTable.size * sizeof(Entry*)); 406 } 407 408 hashTable = &ownHashTable; 409} 410 411Dictionary::~Dictionary() 412{ 413 reset(); 414 if (ownHashTable.data) 415 delete[] ownHashTable.data; 416} 417 418void Dictionary::reset() 419{ 420 if (hashTable && hashTable->owner != this) 421 { 422 hashTable = NULL; 423 return; 424 } 425 426 for (U32 i = 0; i < ownHashTable.size; ++i) 427 { 428 Entry* walk = ownHashTable.data[i]; 429 while (walk) 430 { 431 Entry* temp = walk->nextEntry; 432 destructInPlace(walk); 433 walk = temp; 434 } 435 } 436 437 dMemset(ownHashTable.data, 0, ownHashTable.size * sizeof(Entry*)); 438 ownHashTable.mChunker.freeBlocks(true); 439 440 ownHashTable.count = 0; 441 hashTable = NULL; 442 443 scopeName = NULL; 444 scopeNamespace = NULL; 445 code = NULL; 446 ip = 0; 447} 448 449 450const char *Dictionary::tabComplete(const char *prevText, S32 baseLen, bool fForward) 451{ 452 S32 i; 453 454 const char *bestMatch = NULL; 455 for (i = 0; i < hashTable->size; i++) 456 { 457 Entry *walk = hashTable->data[i]; 458 while (walk) 459 { 460 if (canTabComplete(prevText, bestMatch, walk->name, baseLen, fForward)) 461 bestMatch = walk->name; 462 walk = walk->nextEntry; 463 } 464 } 465 return bestMatch; 466} 467 468 469char *typeValueEmpty = ""; 470 471Dictionary::Entry::Entry(StringTableEntry in_name) 472{ 473 name = in_name; 474 value.type = ConsoleValue::TypeInternalString; 475 notify = NULL; 476 nextEntry = NULL; 477 mUsage = NULL; 478 mIsConstant = false; 479 mNext = NULL; 480 // NOTE: This is data inside a nameless 481 // union, so we don't need to init the rest. 482 value.init(); 483} 484 485Dictionary::Entry::~Entry() 486{ 487 value.cleanup(); 488 489 if (notify) 490 delete notify; 491} 492 493const char *Dictionary::getVariable(StringTableEntry name, bool *entValid) 494{ 495 Entry *ent = lookup(name); 496 if (ent) 497 { 498 if (entValid) 499 *entValid = true; 500 return ent->getStringValue(); 501 } 502 if (entValid) 503 *entValid = false; 504 505 // Warn users when they access a variable that isn't defined. 506 if (gWarnUndefinedScriptVariables) 507 Con::warnf(" *** Accessed undefined variable '%s'", name); 508 509 return ""; 510} 511 512void ConsoleValue::setStringValue(const char * value) 513{ 514 if (value == NULL) value = typeValueEmpty; 515 516 if (type <= ConsoleValue::TypeInternalString) 517 { 518 // Let's not remove empty-string-valued global vars from the dict. 519 // If we remove them, then they won't be exported, and sometimes 520 // it could be necessary to export such a global. There are very 521 // few empty-string global vars so there's no performance-related 522 // need to remove them from the dict. 523 /* 524 if(!value[0] && name[0] == '$') 525 { 526 gEvalState.globalVars.remove(this); 527 return; 528 } 529 */ 530 if (value == typeValueEmpty) 531 { 532 if (bufferLen > 0) 533 { 534 dFree(sval); 535 bufferLen = 0; 536 } 537 538 sval = typeValueEmpty; 539 fval = 0.f; 540 ival = 0; 541 type = TypeInternalString; 542 return; 543 } 544 545 U32 stringLen = dStrlen(value); 546 547 // If it's longer than 256 bytes, it's certainly not a number. 548 // 549 // (This decision may come back to haunt you. Shame on you if it 550 // does.) 551 if (stringLen < 256) 552 { 553 fval = dAtof(value); 554 ival = dAtoi(value); 555 } 556 else 557 { 558 fval = 0.f; 559 ival = 0; 560 } 561 562 // may as well pad to the next cache line 563 U32 newLen = ((stringLen + 1) + 15) & ~15; 564 565 if (bufferLen == 0) 566 sval = (char *)dMalloc(newLen); 567 else if (newLen > bufferLen) 568 sval = (char *)dRealloc(sval, newLen); 569 570 type = TypeInternalString; 571 572 bufferLen = newLen; 573 dStrcpy(sval, value, newLen); 574 } 575 else 576 Con::setData(type, dataPtr, 0, 1, &value, enumTable); 577} 578 579 580void ConsoleValue::setStackStringValue(const char *value) 581{ 582 if (value == NULL) value = typeValueEmpty; 583 584 if (type <= ConsoleValue::TypeInternalString) 585 { 586 // sval might still be temporarily present so we need to check and free it 587 if (bufferLen > 0) 588 { 589 dFree(sval); 590 bufferLen = 0; 591 } 592 593 if (value == typeValueEmpty) 594 { 595 sval = typeValueEmpty; 596 fval = 0.f; 597 ival = 0; 598 type = TypeInternalString; 599 return; 600 } 601 602 U32 stringLen = dStrlen(value); 603 if (stringLen < 256) 604 { 605 fval = dAtof(value); 606 ival = dAtoi(value); 607 } 608 else 609 { 610 fval = 0.f; 611 ival = 0; 612 } 613 614 type = TypeInternalStackString; 615 sval = (char*)value; 616 bufferLen = 0; 617 } 618 else 619 Con::setData(type, dataPtr, 0, 1, &value, enumTable); 620} 621 622void ConsoleValue::setStringStackPtrValue(StringStackPtr ptrValue) 623{ 624 if (type <= ConsoleValue::TypeInternalString) 625 { 626 const char *value = StringStackPtrRef(ptrValue).getPtr(&STR); 627 if (bufferLen > 0) 628 { 629 dFree(sval); 630 bufferLen = 0; 631 } 632 633 U32 stringLen = dStrlen(value); 634 if (stringLen < 256) 635 { 636 fval = dAtof(value); 637 ival = dAtoi(value); 638 } 639 else 640 { 641 fval = 0.f; 642 ival = 0; 643 } 644 645 type = TypeInternalStringStackPtr; 646 sval = (char*)(value - STR.mBuffer); 647 bufferLen = 0; 648 } 649 else 650 { 651 const char *value = StringStackPtrRef(ptrValue).getPtr(&STR); 652 Con::setData(type, dataPtr, 0, 1, &value, enumTable); 653 } 654} 655 656S32 Dictionary::getIntVariable(StringTableEntry name, bool *entValid) 657{ 658 Entry *ent = lookup(name); 659 if (ent) 660 { 661 if (entValid) 662 *entValid = true; 663 return ent->getIntValue(); 664 } 665 666 if (entValid) 667 *entValid = false; 668 669 return 0; 670} 671 672F32 Dictionary::getFloatVariable(StringTableEntry name, bool *entValid) 673{ 674 Entry *ent = lookup(name); 675 if (ent) 676 { 677 if (entValid) 678 *entValid = true; 679 return ent->getFloatValue(); 680 } 681 682 if (entValid) 683 *entValid = false; 684 685 return 0; 686} 687 688void Dictionary::setVariable(StringTableEntry name, const char *value) 689{ 690 Entry *ent = add(name); 691 if (!value) 692 value = ""; 693 ent->setStringValue(value); 694} 695 696Dictionary::Entry* Dictionary::addVariable(const char *name, 697 S32 type, 698 void *dataPtr, 699 const char* usage) 700{ 701 AssertFatal(type >= 0, "Dictionary::addVariable - Got bad type!"); 702 703 if (name[0] != '$') 704 { 705 scratchBuffer[0] = '$'; 706 dStrcpy(scratchBuffer + 1, name, 1023); 707 name = scratchBuffer; 708 } 709 710 Entry *ent = add(StringTable->insert(name)); 711 712 if (ent->value.type <= ConsoleValue::TypeInternalString && 713 ent->value.bufferLen > 0) 714 dFree(ent->value.sval); 715 716 ent->value.type = type; 717 ent->value.dataPtr = dataPtr; 718 ent->mUsage = usage; 719 720 // Fetch enum table, if any. 721 722 ConsoleBaseType* conType = ConsoleBaseType::getType(type); 723 AssertFatal(conType, "Dictionary::addVariable - invalid console type"); 724 ent->value.enumTable = conType->getEnumTable(); 725 726 return ent; 727} 728 729bool Dictionary::removeVariable(StringTableEntry name) 730{ 731 if (Entry *ent = lookup(name)) 732 { 733 remove(ent); 734 return true; 735 } 736 return false; 737} 738 739void Dictionary::addVariableNotify(const char *name, const Con::NotifyDelegate &callback) 740{ 741 Entry *ent = lookup(StringTable->insert(name)); 742 if (!ent) 743 return; 744 745 if (!ent->notify) 746 ent->notify = new Entry::NotifySignal(); 747 748 ent->notify->notify(callback); 749} 750 751void Dictionary::removeVariableNotify(const char *name, const Con::NotifyDelegate &callback) 752{ 753 Entry *ent = lookup(StringTable->insert(name)); 754 if (ent && ent->notify) 755 ent->notify->remove(callback); 756} 757 758void Dictionary::validate() 759{ 760 AssertFatal(ownHashTable.owner == this, 761 "Dictionary::validate() - Dictionary not owner of own hashtable!"); 762} 763 764void ExprEvalState::pushFrame(StringTableEntry frameName, Namespace *ns) 765{ 766#ifdef DEBUG_SPEW 767 validate(); 768 769 Platform::outputDebugString("[ConsoleInternal] Pushing new frame for '%s' at %i", 770 frameName, mStackDepth); 771#endif 772 773 if (mStackDepth + 1 > stack.size()) 774 { 775#ifdef DEBUG_SPEW 776 Platform::outputDebugString("[ConsoleInternal] Growing stack by one frame"); 777#endif 778 779 stack.push_back(new Dictionary); 780 } 781 782 Dictionary& newFrame = *(stack[mStackDepth]); 783 newFrame.setState(this); 784 785 newFrame.scopeName = frameName; 786 newFrame.scopeNamespace = ns; 787 788 mStackDepth++; 789 currentVariable = NULL; 790 791 AssertFatal(!newFrame.getCount(), "ExprEvalState::pushFrame - Dictionary not empty!"); 792 793#ifdef DEBUG_SPEW 794 validate(); 795#endif 796} 797 798void ExprEvalState::popFrame() 799{ 800 AssertFatal(mStackDepth > 0, "ExprEvalState::popFrame - Stack Underflow!"); 801 802#ifdef DEBUG_SPEW 803 validate(); 804 805 Platform::outputDebugString("[ConsoleInternal] Popping %sframe at %i", 806 getCurrentFrame().isOwner() ? "" : "shared ", mStackDepth - 1); 807#endif 808 809 mStackDepth--; 810 stack[mStackDepth]->reset(); 811 currentVariable = NULL; 812 813#ifdef DEBUG_SPEW 814 validate(); 815#endif 816} 817 818void ExprEvalState::pushFrameRef(S32 stackIndex) 819{ 820 AssertFatal(stackIndex >= 0 && stackIndex < stack.size(), "You must be asking for a valid frame!"); 821 822#ifdef DEBUG_SPEW 823 validate(); 824 825 Platform::outputDebugString("[ConsoleInternal] Cloning frame from %i to %i", 826 stackIndex, mStackDepth); 827#endif 828 829 if (mStackDepth + 1 > stack.size()) 830 { 831#ifdef DEBUG_SPEW 832 Platform::outputDebugString("[ConsoleInternal] Growing stack by one frame"); 833#endif 834 835 stack.push_back(new Dictionary); 836 } 837 838 Dictionary& newFrame = *(stack[mStackDepth]); 839 newFrame.setState(this, stack[stackIndex]); 840 841 mStackDepth++; 842 currentVariable = NULL; 843 844#ifdef DEBUG_SPEW 845 validate(); 846#endif 847} 848 849ExprEvalState::ExprEvalState() 850{ 851 VECTOR_SET_ASSOCIATION(stack); 852 globalVars.setState(this); 853 thisObject = NULL; 854 traceOn = false; 855 currentVariable = NULL; 856 mStackDepth = 0; 857 stack.reserve(64); 858 mShouldReset = false; 859 mResetLocked = false; 860 copyVariable = NULL; 861} 862 863ExprEvalState::~ExprEvalState() 864{ 865 // Delete callframes. 866 867 while (!stack.empty()) 868 { 869 delete stack.last(); 870 stack.decrement(); 871 } 872} 873 874void ExprEvalState::validate() 875{ 876 AssertFatal(mStackDepth <= stack.size(), 877 "ExprEvalState::validate() - Stack depth pointing beyond last stack frame!"); 878 879 for (U32 i = 0; i < stack.size(); ++i) 880 stack[i]->validate(); 881} 882 883DefineEngineFunction(backtrace, void, (), , 884 "@brief Prints the scripting call stack to the console log.\n\n" 885 "Used to trace functions called from within functions. Can help discover what functions were called " 886 "(and not yet exited) before the current point in scripts.\n\n" 887 "@ingroup Debugging") 888{ 889 U32 totalSize = 1; 890 891 for (U32 i = 0; i < gEvalState.getStackDepth(); i++) 892 { 893 if (gEvalState.stack[i]->scopeNamespace && gEvalState.stack[i]->scopeNamespace->mEntryList->mPackage) 894 totalSize += dStrlen(gEvalState.stack[i]->scopeNamespace->mEntryList->mPackage) + 2; 895 if (gEvalState.stack[i]->scopeName) 896 totalSize += dStrlen(gEvalState.stack[i]->scopeName) + 3; 897 if (gEvalState.stack[i]->scopeNamespace && gEvalState.stack[i]->scopeNamespace->mName) 898 totalSize += dStrlen(gEvalState.stack[i]->scopeNamespace->mName) + 2; 899 } 900 901 char *buf = Con::getReturnBuffer(totalSize); 902 buf[0] = 0; 903 for (U32 i = 0; i < gEvalState.getStackDepth(); i++) 904 { 905 dStrcat(buf, "->", totalSize); 906 907 if (gEvalState.stack[i]->scopeNamespace && gEvalState.stack[i]->scopeNamespace->mEntryList->mPackage) 908 { 909 dStrcat(buf, "[", totalSize); 910 dStrcat(buf, gEvalState.stack[i]->scopeNamespace->mEntryList->mPackage, totalSize); 911 dStrcat(buf, "]", totalSize); 912 } 913 if (gEvalState.stack[i]->scopeNamespace && gEvalState.stack[i]->scopeNamespace->mName) 914 { 915 dStrcat(buf, gEvalState.stack[i]->scopeNamespace->mName, totalSize); 916 dStrcat(buf, "::", totalSize); 917 } 918 if (gEvalState.stack[i]->scopeName) 919 dStrcat(buf, gEvalState.stack[i]->scopeName, totalSize); 920 } 921 922 Con::printf("BackTrace: %s", buf); 923} 924 925Namespace::Entry::Entry() 926{ 927 mCode = NULL; 928 mType = InvalidFunctionType; 929 mUsage = NULL; 930 mHeader = NULL; 931 mNamespace = NULL; 932 cb.mStringCallbackFunc = NULL; 933 mFunctionLineNumber = 0; 934 mFunctionName = StringTable->EmptyString(); 935 mFunctionOffset = 0; 936 mMinArgs = 0; 937 mMaxArgs = 0; 938 mNext = NULL; 939 mPackage = StringTable->EmptyString(); 940 mToolOnly = false; 941} 942 943void Namespace::Entry::clear() 944{ 945 if (mCode) 946 { 947 mCode->decRefCount(); 948 mCode = NULL; 949 } 950 951 // Clean up usage strings generated for script functions. 952 if ((mType == Namespace::Entry::ConsoleFunctionType) && mUsage) 953 { 954 dFree(mUsage); 955 mUsage = NULL; 956 } 957} 958 959Namespace::Namespace() 960{ 961 mPackage = NULL; 962 mUsage = NULL; 963 mCleanUpUsage = false; 964 mName = NULL; 965 mParent = NULL; 966 mNext = NULL; 967 mEntryList = NULL; 968 mHashSize = 0; 969 mHashTable = 0; 970 mHashSequence = 0; 971 mRefCountToParent = 0; 972 mClassRep = 0; 973 lastUsage = NULL; 974} 975 976Namespace::~Namespace() 977{ 978 clearEntries(); 979 if (mUsage && mCleanUpUsage) 980 { 981 dFree(mUsage); 982 mUsage = NULL; 983 mCleanUpUsage = false; 984 } 985} 986 987void Namespace::clearEntries() 988{ 989 for (Entry *walk = mEntryList; walk; walk = walk->mNext) 990 walk->clear(); 991} 992 993Namespace *Namespace::find(StringTableEntry name, StringTableEntry package) 994{ 995 if (name == NULL && package == NULL) 996 return mGlobalNamespace; 997 998 auto pair = std::make_pair(name, package); 999 auto pos = gNamespaceCache.find(pair); 1000 if (pos != gNamespaceCache.end()) 1001 return pos->second; 1002 1003 Namespace *ret = (Namespace *)mAllocator.alloc(sizeof(Namespace)); 1004 constructInPlace(ret); 1005 ret->mPackage = package; 1006 ret->mName = name; 1007 ret->mNext = mNamespaceList; 1008 mNamespaceList = ret; 1009 1010 // insert into namespace cache. 1011 gNamespaceCache[pair] = ret; 1012 1013 return ret; 1014} 1015 1016bool Namespace::unlinkClass(Namespace *parent) 1017{ 1018 AssertFatal(mPackage == NULL, "Namespace::unlinkClass - Must not be called on a namespace coming from a package!"); 1019 1020 // Skip additions to this namespace coming from packages. 1021 1022 Namespace* walk = getPackageRoot(); 1023 1024 // Make sure "parent" is the direct parent namespace. 1025 1026 if (parent != NULL && walk->mParent && walk->mParent != parent) 1027 { 1028 Con::errorf(ConsoleLogEntry::General, "Namespace::unlinkClass - cannot unlink namespace parent linkage for %s for %s.", 1029 walk->mName, walk->mParent->mName); 1030 return false; 1031 } 1032 1033 // Decrease the reference count. Note that we do this on 1034 // the bottom-most namespace, i.e. the one guaranteed not 1035 // to come from a package. 1036 1037 mRefCountToParent--; 1038 AssertFatal(mRefCountToParent >= 0, "Namespace::unlinkClass - reference count to parent is less than 0"); 1039 1040 // Unlink if the count dropped to zero. 1041 1042 if (mRefCountToParent == 0) 1043 { 1044 walk->mParent = NULL; 1045 trashCache(); 1046 } 1047 1048 return true; 1049} 1050 1051 1052bool Namespace::classLinkTo(Namespace *parent) 1053{ 1054 Namespace* walk = getPackageRoot(); 1055 1056 if (walk->mParent && walk->mParent != parent) 1057 { 1058 Con::errorf(ConsoleLogEntry::General, "Error: cannot change namespace parent linkage of %s from %s to %s.", 1059 walk->mName, walk->mParent->mName, parent->mName); 1060 return false; 1061 } 1062 1063 trashCache(); 1064 walk->mParent = parent; 1065 1066 mRefCountToParent++; 1067 1068 return true; 1069} 1070 1071void Namespace::buildHashTable() 1072{ 1073 if (mHashSequence == mCacheSequence) 1074 return; 1075 1076 if (!mEntryList && mParent) 1077 { 1078 mParent->buildHashTable(); 1079 mHashTable = mParent->mHashTable; 1080 mHashSize = mParent->mHashSize; 1081 mHashSequence = mCacheSequence; 1082 return; 1083 } 1084 1085 U32 entryCount = 0; 1086 Namespace * ns; 1087 for (ns = this; ns; ns = ns->mParent) 1088 for (Entry *walk = ns->mEntryList; walk; walk = walk->mNext) 1089 if (lookupRecursive(walk->mFunctionName) == walk) 1090 entryCount++; 1091 1092 mHashSize = entryCount + (entryCount >> 1) + 1; 1093 1094 if (!(mHashSize & 1)) 1095 mHashSize++; 1096 1097 mHashTable = (Entry **)mCacheAllocator.alloc(sizeof(Entry *) * mHashSize); 1098 for (U32 i = 0; i < mHashSize; i++) 1099 mHashTable[i] = NULL; 1100 1101 for (ns = this; ns; ns = ns->mParent) 1102 { 1103 for (Entry *walk = ns->mEntryList; walk; walk = walk->mNext) 1104 { 1105 U32 index = HashPointer(walk->mFunctionName) % mHashSize; 1106 while (mHashTable[index] && mHashTable[index]->mFunctionName != walk->mFunctionName) 1107 { 1108 index++; 1109 if (index >= mHashSize) 1110 index = 0; 1111 } 1112 1113 if (!mHashTable[index]) 1114 mHashTable[index] = walk; 1115 } 1116 } 1117 1118 mHashSequence = mCacheSequence; 1119} 1120 1121void Namespace::getUniqueEntryLists(Namespace *other, VectorPtr<Entry*> *outThisList, VectorPtr<Entry*> *outOtherList) 1122{ 1123 // All namespace entries in the common ACR should be 1124 // ignored when checking for duplicate entry names. 1125 static VectorPtr<Namespace::Entry*> commonEntries; 1126 commonEntries.clear(); 1127 1128 AbstractClassRep *commonACR = mClassRep->getCommonParent(other->mClassRep); 1129 commonACR->getNameSpace()->getEntryList(&commonEntries); 1130 1131 // Make life easier 1132 VectorPtr<Namespace::Entry*> &thisEntries = *outThisList; 1133 VectorPtr<Namespace::Entry*> &compEntries = *outOtherList; 1134 1135 // Clear, just in case they aren't 1136 thisEntries.clear(); 1137 compEntries.clear(); 1138 1139 getEntryList(&thisEntries); 1140 other->getEntryList(&compEntries); 1141 1142 // Run through all of the entries in the common ACR, and remove them from 1143 // the other two entry lists 1144 for (NamespaceEntryListIterator itr = commonEntries.begin(); itr != commonEntries.end(); itr++) 1145 { 1146 // Check this entry list 1147 for (NamespaceEntryListIterator thisItr = thisEntries.begin(); thisItr != thisEntries.end(); thisItr++) 1148 { 1149 if (*thisItr == *itr) 1150 { 1151 thisEntries.erase(thisItr); 1152 break; 1153 } 1154 } 1155 1156 // Same check for component entry list 1157 for (NamespaceEntryListIterator compItr = compEntries.begin(); compItr != compEntries.end(); compItr++) 1158 { 1159 if (*compItr == *itr) 1160 { 1161 compEntries.erase(compItr); 1162 break; 1163 } 1164 } 1165 } 1166} 1167 1168void Namespace::init() 1169{ 1170 // create the global namespace 1171 mGlobalNamespace = (Namespace *)mAllocator.alloc(sizeof(Namespace)); 1172 constructInPlace(mGlobalNamespace); 1173 mGlobalNamespace->mPackage = NULL; 1174 mGlobalNamespace->mName = NULL; 1175 mGlobalNamespace->mNext = NULL; 1176 mNamespaceList = mGlobalNamespace; 1177 1178 // Insert into namespace cache. 1179 gNamespaceCache[std::make_pair(mGlobalNamespace->mName, mGlobalNamespace->mPackage)] = mGlobalNamespace; 1180} 1181 1182Namespace *Namespace::global() 1183{ 1184 return mGlobalNamespace; 1185} 1186 1187void Namespace::shutdown() 1188{ 1189 // The data chunker will release all memory in one go 1190 // without calling destructors, so we do this manually here. 1191 1192 for (Namespace *walk = mNamespaceList; walk; walk = walk->mNext) 1193 walk->~Namespace(); 1194} 1195 1196void Namespace::trashCache() 1197{ 1198 mCacheSequence++; 1199 mCacheAllocator.freeBlocks(); 1200} 1201 1202const char *Namespace::tabComplete(const char *prevText, S32 baseLen, bool fForward) 1203{ 1204 if (mHashSequence != mCacheSequence) 1205 buildHashTable(); 1206 1207 const char *bestMatch = NULL; 1208 for (U32 i = 0; i < mHashSize; i++) 1209 if (mHashTable[i] && canTabComplete(prevText, bestMatch, mHashTable[i]->mFunctionName, baseLen, fForward)) 1210 bestMatch = mHashTable[i]->mFunctionName; 1211 return bestMatch; 1212} 1213 1214Namespace::Entry *Namespace::lookupRecursive(StringTableEntry name) 1215{ 1216 for (Namespace *ns = this; ns; ns = ns->mParent) 1217 for (Entry *walk = ns->mEntryList; walk; walk = walk->mNext) 1218 if (walk->mFunctionName == name) 1219 return walk; 1220 1221 return NULL; 1222} 1223 1224Namespace::Entry *Namespace::lookup(StringTableEntry name) 1225{ 1226 if (mHashSequence != mCacheSequence) 1227 buildHashTable(); 1228 1229 U32 index = HashPointer(name) % mHashSize; 1230 while (mHashTable[index] && mHashTable[index]->mFunctionName != name) 1231 { 1232 index++; 1233 if (index >= mHashSize) 1234 index = 0; 1235 } 1236 return mHashTable[index]; 1237} 1238 1239static S32 QSORT_CALLBACK compareEntries(const void* a, const void* b) 1240{ 1241 const Namespace::Entry* fa = *((Namespace::Entry**)a); 1242 const Namespace::Entry* fb = *((Namespace::Entry**)b); 1243 1244 return dStricmp(fa->mFunctionName, fb->mFunctionName); 1245} 1246 1247void Namespace::getEntryList(VectorPtr<Entry*> *vec) 1248{ 1249 if (mHashSequence != mCacheSequence) 1250 buildHashTable(); 1251 1252 for (U32 i = 0; i < mHashSize; i++) 1253 if (mHashTable[i]) 1254 vec->push_back(mHashTable[i]); 1255 1256 dQsort(vec->address(), vec->size(), sizeof(Namespace::Entry *), compareEntries); 1257} 1258 1259Namespace::Entry *Namespace::createLocalEntry(StringTableEntry name) 1260{ 1261 for (Entry *walk = mEntryList; walk; walk = walk->mNext) 1262 { 1263 if (walk->mFunctionName == name) 1264 { 1265 walk->clear(); 1266 return walk; 1267 } 1268 } 1269 1270 Entry *ent = (Entry *)mAllocator.alloc(sizeof(Entry)); 1271 constructInPlace(ent); 1272 1273 ent->mNamespace = this; 1274 ent->mFunctionName = name; 1275 ent->mNext = mEntryList; 1276 ent->mPackage = mPackage; 1277 ent->mToolOnly = false; 1278 mEntryList = ent; 1279 return ent; 1280} 1281 1282void Namespace::addFunction(StringTableEntry name, CodeBlock *cb, U32 functionOffset, const char* usage, U32 lineNumber) 1283{ 1284 Entry *ent = createLocalEntry(name); 1285 trashCache(); 1286 1287 ent->mUsage = usage; 1288 ent->mCode = cb; 1289 ent->mFunctionOffset = functionOffset; 1290 ent->mCode->incRefCount(); 1291 ent->mType = Entry::ConsoleFunctionType; 1292 ent->mFunctionLineNumber = lineNumber; 1293} 1294 1295void Namespace::addCommand(StringTableEntry name, StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header) 1296{ 1297 Entry *ent = createLocalEntry(name); 1298 trashCache(); 1299 1300 ent->mUsage = usage; 1301 ent->mHeader = header; 1302 ent->mMinArgs = minArgs; 1303 ent->mMaxArgs = maxArgs; 1304 ent->mToolOnly = isToolOnly; 1305 1306 ent->mType = Entry::StringCallbackType; 1307 ent->cb.mStringCallbackFunc = cb; 1308} 1309 1310void Namespace::addCommand(StringTableEntry name, IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header) 1311{ 1312 Entry *ent = createLocalEntry(name); 1313 trashCache(); 1314 1315 ent->mUsage = usage; 1316 ent->mHeader = header; 1317 ent->mMinArgs = minArgs; 1318 ent->mMaxArgs = maxArgs; 1319 ent->mToolOnly = isToolOnly; 1320 1321 ent->mType = Entry::IntCallbackType; 1322 ent->cb.mIntCallbackFunc = cb; 1323} 1324 1325void Namespace::addCommand(StringTableEntry name, VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header) 1326{ 1327 Entry *ent = createLocalEntry(name); 1328 trashCache(); 1329 1330 ent->mUsage = usage; 1331 ent->mHeader = header; 1332 ent->mMinArgs = minArgs; 1333 ent->mMaxArgs = maxArgs; 1334 ent->mToolOnly = isToolOnly; 1335 1336 ent->mType = Entry::VoidCallbackType; 1337 ent->cb.mVoidCallbackFunc = cb; 1338} 1339 1340void Namespace::addCommand(StringTableEntry name, FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header) 1341{ 1342 Entry *ent = createLocalEntry(name); 1343 trashCache(); 1344 1345 ent->mUsage = usage; 1346 ent->mHeader = header; 1347 ent->mMinArgs = minArgs; 1348 ent->mMaxArgs = maxArgs; 1349 ent->mToolOnly = isToolOnly; 1350 1351 ent->mType = Entry::FloatCallbackType; 1352 ent->cb.mFloatCallbackFunc = cb; 1353} 1354 1355void Namespace::addCommand(StringTableEntry name, BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly, ConsoleFunctionHeader* header) 1356{ 1357 Entry *ent = createLocalEntry(name); 1358 trashCache(); 1359 1360 ent->mUsage = usage; 1361 ent->mHeader = header; 1362 ent->mMinArgs = minArgs; 1363 ent->mMaxArgs = maxArgs; 1364 ent->mToolOnly = isToolOnly; 1365 1366 ent->mType = Entry::BoolCallbackType; 1367 ent->cb.mBoolCallbackFunc = cb; 1368} 1369 1370void Namespace::addScriptCallback(const char *funcName, const char *usage, ConsoleFunctionHeader* header) 1371{ 1372 static U32 uid = 0; 1373 char buffer[1024]; 1374 char lilBuffer[32]; 1375 dStrcpy(buffer, funcName, 1024); 1376 dSprintf(lilBuffer, 32, "_%d_cb", uid++); 1377 dStrcat(buffer, lilBuffer, 1024); 1378 1379 Entry *ent = createLocalEntry(StringTable->insert(buffer)); 1380 trashCache(); 1381 1382 ent->mUsage = usage; 1383 ent->mHeader = header; 1384 ent->mMinArgs = -2; 1385 ent->mMaxArgs = -3; 1386 1387 ent->mType = Entry::ScriptCallbackType; 1388 ent->cb.mCallbackName = funcName; 1389} 1390 1391void Namespace::markGroup(const char* name, const char* usage) 1392{ 1393 static U32 uid = 0; 1394 char buffer[1024]; 1395 char lilBuffer[32]; 1396 dStrcpy(buffer, name, 1024); 1397 dSprintf(lilBuffer, 32, "_%d", uid++); 1398 dStrcat(buffer, lilBuffer, 1024); 1399 1400 Entry *ent = createLocalEntry(StringTable->insert(buffer)); 1401 trashCache(); 1402 1403 if (usage != NULL) 1404 lastUsage = (char*)(ent->mUsage = usage); 1405 else 1406 ent->mUsage = lastUsage; 1407 1408 ent->mMinArgs = -1; // Make sure it explodes if somehow we run this entry. 1409 ent->mMaxArgs = -2; 1410 1411 ent->mType = Entry::GroupMarker; 1412 ent->cb.mGroupName = name; 1413} 1414 1415extern S32 executeBlock(StmtNode *block, ExprEvalState *state); 1416 1417ConsoleValueRef Namespace::Entry::execute(S32 argc, ConsoleValueRef *argv, ExprEvalState *state) 1418{ 1419 STR.clearFunctionOffset(); 1420 1421 if (mType == ConsoleFunctionType) 1422 { 1423 if (mFunctionOffset) 1424 { 1425 return mCode->exec(mFunctionOffset, argv[0], mNamespace, argc, argv, false, mPackage); 1426 } 1427 else 1428 { 1429 return ConsoleValueRef(); 1430 } 1431 } 1432 1433#ifndef TORQUE_DEBUG 1434 // [tom, 12/13/2006] This stops tools functions from working in the console, 1435 // which is useful behavior when debugging so I'm ifdefing this out for debug builds. 1436 if (mToolOnly && !Con::isCurrentScriptToolScript()) 1437 { 1438 Con::errorf(ConsoleLogEntry::Script, "%s::%s - attempting to call tools only function from outside of tools", mNamespace->mName, mFunctionName); 1439 return ConsoleValueRef(); 1440 } 1441#endif 1442 1443 if ((mMinArgs && argc < mMinArgs) || (mMaxArgs && argc > mMaxArgs)) 1444 { 1445 Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", mNamespace->mName, mFunctionName); 1446 Con::warnf(ConsoleLogEntry::Script, "usage: %s", mUsage); 1447 return ConsoleValueRef(); 1448 } 1449 1450 switch (mType) 1451 { 1452 case StringCallbackType: 1453 return ConsoleValueRef::fromValue(CSTK.pushStackString(cb.mStringCallbackFunc(state->thisObject, argc, argv))); 1454 case IntCallbackType: 1455 return ConsoleValueRef::fromValue(CSTK.pushUINT((U32)cb.mBoolCallbackFunc(state->thisObject, argc, argv))); 1456 case FloatCallbackType: 1457 return ConsoleValueRef::fromValue(CSTK.pushFLT((U32)cb.mBoolCallbackFunc(state->thisObject, argc, argv))); 1458 case VoidCallbackType: 1459 cb.mVoidCallbackFunc(state->thisObject, argc, argv); 1460 return ConsoleValueRef(); 1461 case BoolCallbackType: 1462 return ConsoleValueRef::fromValue(CSTK.pushUINT((U32)cb.mBoolCallbackFunc(state->thisObject, argc, argv))); 1463 } 1464 1465 return ConsoleValueRef(); 1466} 1467 1468//----------------------------------------------------------------------------- 1469// Doc string code. 1470 1471namespace { 1472 1473 /// Scan the given usage string for an argument list description. With the 1474 /// old console macros, these were usually included as the first part of the 1475 /// usage string. 1476 bool sFindArgumentListSubstring(const char* usage, const char*& start, const char*& end) 1477 { 1478 if (!usage) 1479 return false; 1480 1481 const char* ptr = usage; 1482 while (*ptr && *ptr != '(' && *ptr != '\n') // Only scan first line of usage string. 1483 { 1484 // Stop on the first alphanumeric character as we expect 1485 // argument lists to precede descriptions. 1486 if (dIsalnum(*ptr)) 1487 return false; 1488 1489 ptr++; 1490 } 1491 1492 if (*ptr != '(') 1493 return false; 1494 1495 start = ptr; 1496 ptr++; 1497 1498 bool inString = false; 1499 U32 nestingCount = 0; 1500 1501 while (*ptr && (*ptr != ')' || nestingCount > 0 || inString)) 1502 { 1503 if (*ptr == '(') 1504 nestingCount++; 1505 else if (*ptr == ')') 1506 nestingCount--; 1507 else if (*ptr == '"') 1508 inString = !inString; 1509 else if (*ptr == '\\' && ptr[1] == '"') 1510 ptr++; 1511 ptr++; 1512 } 1513 1514 if (*ptr) 1515 ptr++; 1516 end = ptr; 1517 1518 return true; 1519 } 1520 1521 /// 1522 void sParseList(const char* str, Vector< String>& outList) 1523 { 1524 // Skip the initial '( '. 1525 1526 const char* ptr = str; 1527 while (*ptr && dIsspace(*ptr)) 1528 ptr++; 1529 1530 if (*ptr == '(') 1531 { 1532 ptr++; 1533 while (*ptr && dIsspace(*ptr)) 1534 ptr++; 1535 } 1536 1537 // Parse out list items. 1538 1539 while (*ptr && *ptr != ')') 1540 { 1541 // Find end of element. 1542 1543 const char* start = ptr; 1544 1545 bool inString = false; 1546 U32 nestingCount = 0; 1547 1548 while (*ptr && ((*ptr != ')' && *ptr != ',') || nestingCount > 0 || inString)) 1549 { 1550 if (*ptr == '(') 1551 nestingCount++; 1552 else if (*ptr == ')') 1553 nestingCount--; 1554 else if (*ptr == '"') 1555 inString = !inString; 1556 else if (*ptr == '\\' && ptr[1] == '"') 1557 ptr++; 1558 ptr++; 1559 } 1560 1561 // Backtrack to remove trailing whitespace. 1562 1563 const char* end = ptr; 1564 if (*end == ',' || *end == ')') 1565 end--; 1566 while (end > start && dIsspace(*end)) 1567 end--; 1568 if (*end) 1569 end++; 1570 1571 // Add to list. 1572 1573 if (start != end) 1574 outList.push_back(String(start, end - start)); 1575 1576 // Skip comma and whitespace. 1577 1578 if (*ptr == ',') 1579 ptr++; 1580 while (*ptr && dIsspace(*ptr)) 1581 ptr++; 1582 } 1583 } 1584 1585 /// 1586 void sGetArgNameAndType(const String& str, String& outType, String& outName) 1587 { 1588 if (!str.length()) 1589 { 1590 outType = String::EmptyString; 1591 outName = String::EmptyString; 1592 return; 1593 } 1594 1595 // Find first non-ID character from right. 1596 1597 S32 index = str.length() - 1; 1598 while (index >= 0 && (dIsalnum(str[index]) || str[index] == '_')) 1599 index--; 1600 1601 const U32 nameStartIndex = index + 1; 1602 1603 // Find end of type name by skipping rightmost whitespace inwards. 1604 1605 while (index >= 0 && dIsspace(str[index])) 1606 index--; 1607 1608 // 1609 1610 outName = String(&((const char*)str)[nameStartIndex]); 1611 outType = String(str, index + 1); 1612 } 1613 1614 /// Return the type name to show in documentation for the given C++ type. 1615 const char* sGetDocTypeString(const char* nativeType) 1616 { 1617 if (dStrncmp(nativeType, "const ", 6) == 0) 1618 nativeType += 6; 1619 1620 if (String::compare(nativeType, "char*") == 0 || String::compare(nativeType, "char *") == 0) 1621 return "string"; 1622 else if (String::compare(nativeType, "S32") == 0) 1623 return "int"; 1624 else if (String::compare(nativeType, "U32") == 0) 1625 return "uint"; 1626 else if (String::compare(nativeType, "F32") == 0) 1627 return "float"; 1628 1629 const U32 length = dStrlen(nativeType); 1630 if (nativeType[length - 1] == '&' || nativeType[length - 1] == '*') 1631 return StringTable->insertn(nativeType, length - 1); 1632 1633 return nativeType; 1634 } 1635} 1636 1637String Namespace::Entry::getBriefDescription(String* outRemainingDocText) const 1638{ 1639 String docString = getDocString(); 1640 1641 S32 newline = docString.find('\n'); 1642 if (newline == -1) 1643 { 1644 if (outRemainingDocText) 1645 *outRemainingDocText = String(); 1646 return docString; 1647 } 1648 1649 String brief = docString.substr(0, newline); 1650 if (outRemainingDocText) 1651 *outRemainingDocText = docString.substr(newline + 1); 1652 1653 return brief; 1654} 1655 1656String Namespace::Entry::getDocString() const 1657{ 1658 const char* argListStart; 1659 const char* argListEnd; 1660 1661 if (sFindArgumentListSubstring(mUsage, argListStart, argListEnd)) 1662 { 1663 // Skip the " - " part present in some old doc strings. 1664 1665 const char* ptr = argListEnd; 1666 while (*ptr && dIsspace(*ptr)) 1667 ptr++; 1668 1669 if (*ptr == '-') 1670 { 1671 ptr++; 1672 while (*ptr && dIsspace(*ptr)) 1673 ptr++; 1674 } 1675 1676 return ptr; 1677 } 1678 1679 return mUsage; 1680} 1681 1682String Namespace::Entry::getArgumentsString() const 1683{ 1684 StringBuilder str; 1685 1686 if (mHeader) 1687 { 1688 // Parse out the argument list string supplied with the extended 1689 // function header and add default arguments as we go. 1690 1691 Vector< String> argList; 1692 Vector< String> defaultArgList; 1693 1694 sParseList(mHeader->mArgString, argList); 1695 sParseList(mHeader->mDefaultArgString, defaultArgList); 1696 1697 str.append('('); 1698 1699 const U32 numArgs = argList.size(); 1700 const U32 numDefaultArgs = defaultArgList.size(); 1701 const U32 firstDefaultArgIndex = numArgs - numDefaultArgs; 1702 1703 for (U32 i = 0; i < numArgs; ++i) 1704 { 1705 // Add separator if not first arg. 1706 1707 if (i > 0) 1708 str.append(','); 1709 1710 // Add type and name. 1711 1712 String name; 1713 String type; 1714 1715 sGetArgNameAndType(argList[i], type, name); 1716 1717 str.append(' '); 1718 str.append(sGetDocTypeString(type)); 1719 str.append(' '); 1720 str.append(name); 1721 1722 // Add default value, if any. 1723 1724 if (i >= firstDefaultArgIndex) 1725 { 1726 str.append('='); 1727 str.append(defaultArgList[i - firstDefaultArgIndex]); 1728 } 1729 } 1730 1731 if (numArgs > 0) 1732 str.append(' '); 1733 str.append(')'); 1734 } 1735 else 1736 { 1737 // No extended function header. Try to parse out the argument 1738 // list from the usage string. 1739 1740 const char* argListStart; 1741 const char* argListEnd; 1742 1743 if (sFindArgumentListSubstring(mUsage, argListStart, argListEnd)) 1744 str.append(argListStart, argListEnd - argListStart); 1745 else if (mType == ConsoleFunctionType && mCode) 1746 { 1747 // This isn't correct but the nonsense console stuff is set up such that all 1748 // functions that have no function bodies are keyed to offset 0 to indicate "no code." 1749 // This loses the association with the original function definition so we can't really 1750 // tell here what the actual prototype is except if we searched though the entire opcode 1751 // stream for the corresponding OP_FUNC_DECL (which would require dealing with the 1752 // variable-size instructions). 1753 1754 if (!mFunctionOffset) 1755 return "()"; 1756 1757 String args = mCode->getFunctionArgs(mFunctionOffset); 1758 if (args.isEmpty()) 1759 return "()"; 1760 1761 str.append("( "); 1762 str.append(args); 1763 str.append(" )"); 1764 } 1765 } 1766 1767 return str.end(); 1768} 1769 1770String Namespace::Entry::getPrototypeString() const 1771{ 1772 StringBuilder str; 1773 1774 // Start with return type. 1775 1776 if (mHeader && mHeader->mReturnString) 1777 { 1778 str.append(sGetDocTypeString(mHeader->mReturnString)); 1779 str.append(' '); 1780 } 1781 else 1782 switch (mType) 1783 { 1784 case StringCallbackType: 1785 str.append("string "); 1786 break; 1787 1788 case IntCallbackType: 1789 str.append("int "); 1790 break; 1791 1792 case FloatCallbackType: 1793 str.append("float "); 1794 break; 1795 1796 case VoidCallbackType: 1797 str.append("void "); 1798 break; 1799 1800 case BoolCallbackType: 1801 str.append("bool "); 1802 break; 1803 1804 case ScriptCallbackType: 1805 break; 1806 } 1807 1808 // Add function name and arguments. 1809 1810 if (mType == ScriptCallbackType) 1811 str.append(cb.mCallbackName); 1812 else 1813 str.append(mFunctionName); 1814 1815 str.append(getArgumentsString()); 1816 1817 return str.end(); 1818} 1819 1820//----------------------------------------------------------------------------- 1821 1822StringTableEntry Namespace::mActivePackages[Namespace::MaxActivePackages]; 1823U32 Namespace::mNumActivePackages = 0; 1824U32 Namespace::mOldNumActivePackages = 0; 1825 1826bool Namespace::isPackage(StringTableEntry name) 1827{ 1828 for (Namespace *walk = mNamespaceList; walk; walk = walk->mNext) 1829 if (walk->mPackage == name) 1830 return true; 1831 return false; 1832} 1833 1834U32 Namespace::getActivePackagesCount() 1835{ 1836 return mNumActivePackages; 1837} 1838 1839StringTableEntry Namespace::getActivePackage(U32 index) 1840{ 1841 if (index >= mNumActivePackages) 1842 return StringTable->EmptyString(); 1843 1844 return mActivePackages[index]; 1845} 1846 1847void Namespace::activatePackage(StringTableEntry name) 1848{ 1849 if (mNumActivePackages == MaxActivePackages) 1850 { 1851 Con::printf("ActivatePackage(%s) failed - Max package limit reached: %d", name, MaxActivePackages); 1852 return; 1853 } 1854 if (!name) 1855 return; 1856 1857 // see if this one's already active 1858 for (U32 i = 0; i < mNumActivePackages; i++) 1859 if (mActivePackages[i] == name) 1860 return; 1861 1862 // kill the cache 1863 trashCache(); 1864 1865 // find all the package namespaces... 1866 for (Namespace *walk = mNamespaceList; walk; walk = walk->mNext) 1867 { 1868 if (walk->mPackage == name) 1869 { 1870 Namespace *parent = Namespace::find(walk->mName); 1871 // hook the parent 1872 walk->mParent = parent->mParent; 1873 parent->mParent = walk; 1874 1875 // now swap the entries: 1876 Entry *ew; 1877 for (ew = parent->mEntryList; ew; ew = ew->mNext) 1878 ew->mNamespace = walk; 1879 1880 for (ew = walk->mEntryList; ew; ew = ew->mNext) 1881 ew->mNamespace = parent; 1882 1883 ew = walk->mEntryList; 1884 walk->mEntryList = parent->mEntryList; 1885 parent->mEntryList = ew; 1886 } 1887 } 1888 mActivePackages[mNumActivePackages++] = name; 1889} 1890 1891void Namespace::deactivatePackage(StringTableEntry name) 1892{ 1893 U32 oldNumActivePackages = mNumActivePackages; 1894 1895 // Remove all packages down to the given one 1896 deactivatePackageStack(name); 1897 1898 // Now add back all packages that followed the given one 1899 if (!oldNumActivePackages) 1900 return; 1901 for (U32 i = mNumActivePackages + 1; i < oldNumActivePackages; i++) 1902 activatePackage(mActivePackages[i]); 1903} 1904 1905void Namespace::deactivatePackageStack(StringTableEntry name) 1906{ 1907 S32 i, j; 1908 for (i = 0; i < mNumActivePackages; i++) 1909 if (mActivePackages[i] == name) 1910 break; 1911 if (i == mNumActivePackages) 1912 return; 1913 1914 trashCache(); 1915 1916 // Remove all packages down to the given one 1917 for (j = mNumActivePackages - 1; j >= i; j--) 1918 { 1919 // gotta unlink em in reverse order... 1920 for (Namespace *walk = mNamespaceList; walk; walk = walk->mNext) 1921 { 1922 if (walk->mPackage == mActivePackages[j]) 1923 { 1924 Namespace *parent = Namespace::find(walk->mName); 1925 // hook the parent 1926 parent->mParent = walk->mParent; 1927 walk->mParent = NULL; 1928 1929 // now swap the entries: 1930 Entry *ew; 1931 for (ew = parent->mEntryList; ew; ew = ew->mNext) 1932 ew->mNamespace = walk; 1933 1934 for (ew = walk->mEntryList; ew; ew = ew->mNext) 1935 ew->mNamespace = parent; 1936 1937 ew = walk->mEntryList; 1938 walk->mEntryList = parent->mEntryList; 1939 parent->mEntryList = ew; 1940 } 1941 } 1942 } 1943 mNumActivePackages = i; 1944} 1945 1946void Namespace::unlinkPackages() 1947{ 1948 mOldNumActivePackages = mNumActivePackages; 1949 if (!mNumActivePackages) 1950 return; 1951 deactivatePackageStack(mActivePackages[0]); 1952} 1953 1954void Namespace::relinkPackages() 1955{ 1956 if (!mOldNumActivePackages) 1957 return; 1958 for (U32 i = 0; i < mOldNumActivePackages; i++) 1959 activatePackage(mActivePackages[i]); 1960} 1961 1962 1963DefineEngineFunction(isPackage, bool, (String identifier), , 1964 "@brief Returns true if the identifier is the name of a declared package.\n\n" 1965 "@ingroup Packages\n") 1966{ 1967 StringTableEntry name = StringTable->insert(identifier.c_str()); 1968 return Namespace::isPackage(name); 1969} 1970 1971DefineEngineFunction(activatePackage, void, (String packageName), , 1972 "@brief Activates an existing package.\n\n" 1973 "The activation occurs by updating the namespace linkage of existing functions and methods. " 1974 "If the package is already activated the function does nothing.\n" 1975 "@ingroup Packages\n") 1976{ 1977 StringTableEntry name = StringTable->insert(packageName.c_str()); 1978 Namespace::activatePackage(name); 1979} 1980 1981DefineEngineFunction(deactivatePackage, void, (String packageName), , 1982 "@brief Deactivates a previously activated package.\n\n" 1983 "The package is deactivated by removing its namespace linkages to any function or method. " 1984 "If there are any packages above this one in the stack they are deactivated as well. " 1985 "If the package is not on the stack this function does nothing.\n" 1986 "@ingroup Packages\n") 1987{ 1988 StringTableEntry name = StringTable->insert(packageName.c_str()); 1989 Namespace::deactivatePackage(name); 1990} 1991 1992DefineEngineFunction(getPackageList, const char*, (), , 1993 "@brief Returns a space delimited list of the active packages in stack order.\n\n" 1994 "@ingroup Packages\n") 1995{ 1996 if (Namespace::getActivePackagesCount() == 0) 1997 return ""; 1998 1999 // Determine size of return buffer 2000 dsize_t buffersize = 0; 2001 for (U32 i = 0; i < Namespace::getActivePackagesCount(); ++i) 2002 { 2003 buffersize += dStrlen(Namespace::getActivePackage(i)) + 1; 2004 } 2005 2006 U32 maxBufferSize = buffersize + 1; 2007 char* returnBuffer = Con::getReturnBuffer(maxBufferSize); 2008 U32 returnLen = 0; 2009 for (U32 i = 0; i < Namespace::getActivePackagesCount(); ++i) 2010 { 2011 dSprintf(returnBuffer + returnLen, maxBufferSize - returnLen, "%s ", Namespace::getActivePackage(i)); 2012 returnLen = dStrlen(returnBuffer); 2013 } 2014 2015 // Trim off the last extra space 2016 if (returnLen > 0 && returnBuffer[returnLen - 1] == ' ') 2017 returnBuffer[returnLen - 1] = '\0'; 2018 2019 return returnBuffer; 2020} 2021