Torque3D Documentation / _generateds / consoleInternal.cpp

consoleInternal.cpp

Engine/source/console/consoleInternal.cpp

More...

Classes:

Namespaces:

namespace

Public Defines

define

Public Variables

std::unordered_map< std::pair< StringTableEntry, StringTableEntry >, Namespace * >
char

Public Functions

bool
canTabComplete(const char * prevText, const char * bestMatch, const char * newText, S32 baseLen, bool fForward)
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>" )

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