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