2007-01-02 22:44:14 +00:00
|
|
|
|
/*! \file eval.cpp
|
2007-01-03 03:11:53 +00:00
|
|
|
|
* \brief Expression and function evaluation.
|
2007-01-02 22:44:14 +00:00
|
|
|
|
*
|
|
|
|
|
|
* The functions here crack expressions into function calls and arguments,
|
|
|
|
|
|
* perform %-substitutions, locate matching close-parens and so forth.
|
|
|
|
|
|
* This is one of the three parsers in the server. The other two parsers
|
|
|
|
|
|
* are for commands (see command.cpp) and locks (boolexp.cpp).
|
|
|
|
|
|
*
|
|
|
|
|
|
* This file also contains routines to manage the global r-registers.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
#include "copyright.h"
|
|
|
|
|
|
#include "autoconf.h"
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
#include "externs.h"
|
2022-03-19 15:26:57 -06:00
|
|
|
|
using namespace std;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
// parse_to: Split a line at a character, obeying nesting. The line is
|
|
|
|
|
|
// destructively modified (a null is inserted where the delimiter was found)
|
|
|
|
|
|
// dstr is modified to point to the char after the delimiter, and the function
|
|
|
|
|
|
// return value points to the found string (space compressed if specified). If
|
|
|
|
|
|
// we ran off the end of the string without finding the delimiter, dstr is
|
2018-10-03 17:54:51 +00:00
|
|
|
|
// returned as nullptr.
|
2006-09-01 22:59:23 +00:00
|
|
|
|
//
|
2007-03-14 00:55:08 +00:00
|
|
|
|
static UTF8 *parse_to_cleanup( int eval, int first, UTF8 *cstr, UTF8 *rstr,
|
|
|
|
|
|
UTF8 *zstr, UTF8 *strFirewall)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
if ( ( mudconf.space_compress
|
|
|
|
|
|
|| (eval & EV_STRIP_TS))
|
|
|
|
|
|
&& !(eval & EV_NO_COMPRESS)
|
|
|
|
|
|
&& !first
|
|
|
|
|
|
&& strFirewall < cstr
|
|
|
|
|
|
&& cstr[-1] == ' ')
|
|
|
|
|
|
{
|
|
|
|
|
|
zstr--;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( (eval & EV_STRIP_AROUND)
|
|
|
|
|
|
&& *rstr == '{'
|
|
|
|
|
|
&& strFirewall < zstr
|
|
|
|
|
|
&& zstr[-1] == '}')
|
|
|
|
|
|
{
|
|
|
|
|
|
rstr++;
|
|
|
|
|
|
if ( ( mudconf.space_compress
|
|
|
|
|
|
&& !(eval & EV_NO_COMPRESS))
|
|
|
|
|
|
|| (eval & EV_STRIP_LS))
|
|
|
|
|
|
{
|
|
|
|
|
|
while (mux_isspace(*rstr))
|
|
|
|
|
|
{
|
|
|
|
|
|
rstr++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
rstr[-1] = '\0';
|
|
|
|
|
|
zstr--;
|
|
|
|
|
|
if ( ( mudconf.space_compress
|
|
|
|
|
|
&& !(eval & EV_NO_COMPRESS))
|
|
|
|
|
|
|| (eval & EV_STRIP_TS))
|
|
|
|
|
|
{
|
|
|
|
|
|
while ( strFirewall < zstr
|
|
|
|
|
|
&& mux_isspace(zstr[-1]))
|
|
|
|
|
|
{
|
|
|
|
|
|
zstr--;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
*zstr = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
*zstr = '\0';
|
|
|
|
|
|
return rstr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*! \brief Accesses the isSpecial_ tables with proper typecast.
|
|
|
|
|
|
*
|
|
|
|
|
|
* \param table indicates which table: \c L1, \c L2, \c L3, or \c L4.
|
|
|
|
|
|
* \param c character being looked up.
|
|
|
|
|
|
* \return lvalue of table entry.
|
|
|
|
|
|
*/
|
2026-03-03 18:39:35 -07:00
|
|
|
|
#define isSpecial(table, c) isSpecial_##table[static_cast<unsigned char>(c)]
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
// During parsing, this table may be modified for a particular terminating delimeter.
|
|
|
|
|
|
// The table is always restored it's original state.
|
|
|
|
|
|
//
|
|
|
|
|
|
// 0 means mundane character.
|
2026-03-07 20:05:50 -07:00
|
|
|
|
// 1 is 0x20 ' ' delim overridable (only done by parse_to)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
// 2 is 0x5B '[' delim overridable
|
|
|
|
|
|
// 3 is 0x28 '(' delim overridable
|
2007-03-25 20:18:11 +00:00
|
|
|
|
// 4 is 0x25 '%' or 0x5C '\\' not overridable.
|
2006-09-01 22:59:23 +00:00
|
|
|
|
// 5 is 0x29 ')' or 0x5D ']' not overridable.
|
|
|
|
|
|
// 6 is 0x7B '{' not overridable.
|
|
|
|
|
|
// 7 is 0x00 '\0' not overridable.
|
|
|
|
|
|
// 8 is the client-specific terminator.
|
|
|
|
|
|
//
|
|
|
|
|
|
// A code 4 or above means that the client-specified delim cannot override it.
|
|
|
|
|
|
// A code 8 is temporary.
|
|
|
|
|
|
//
|
|
|
|
|
|
static int isSpecial_L3[256] =
|
|
|
|
|
|
{
|
|
|
|
|
|
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00-0x0F
|
2007-03-25 20:18:11 +00:00
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10-0x1F
|
2006-09-01 22:59:23 +00:00
|
|
|
|
0, 0, 0, 0, 0, 4, 0, 0, 3, 5, 0, 0, 0, 0, 0, 0, // 0x20-0x2F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30-0x3F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40-0x4F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 5, 0, 0, // 0x50-0x5F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60-0x6F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, // 0x70-0x7F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80-0x8F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90-0x9F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0-0xAF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0-0xBF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0-0xCF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0-0xDF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0-0xEF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0-0xFF
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static const char isSpecial_L4[256] =
|
|
|
|
|
|
{
|
|
|
|
|
|
4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00-0x0F
|
2007-03-25 20:18:11 +00:00
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10-0x1F
|
2006-09-01 22:59:23 +00:00
|
|
|
|
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20-0x2F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30-0x3F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40-0x4F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 0x50-0x5F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60-0x6F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 3, 0, 0, // 0x70-0x7F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80-0x8F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90-0x9F
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0-0xAF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0-0xBF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0-0xCF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0-0xDF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0-0xEF
|
|
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0-0xFF
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Characters that are valid q-registers, and their offsets in the register
|
|
|
|
|
|
// array. -1 for invalid registers.
|
|
|
|
|
|
//
|
|
|
|
|
|
const signed char mux_RegisterSet[256] =
|
|
|
|
|
|
{
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0x00-0x0F
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0x10-0x1F
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0x20-0x2F
|
|
|
|
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, // 0x30-0x3F
|
|
|
|
|
|
-1,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24, // 0x40-0x4F
|
|
|
|
|
|
25,26,27,28,29,30,31,32, 33,34,35,-1,-1,-1,-1,-1, // 0x50-0x5F
|
|
|
|
|
|
-1,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24, // 0x60-0x6F
|
|
|
|
|
|
25,26,27,28,29,30,31,32, 33,34,35,-1,-1,-1,-1,-1, // 0x70-0x7F
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0x80-0x8F
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0x90-0x9F
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0xA0-0xAF
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0xB0-0xBF
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0xC0-0xCF
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0xD0-0xDF
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, // 0xE0-0xEF
|
|
|
|
|
|
-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1 // 0xF0-0xFF
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2007-03-25 20:18:11 +00:00
|
|
|
|
// Perhaps some compilers don't handle aliased pointers well. For these
|
2006-09-01 22:59:23 +00:00
|
|
|
|
// compilers, we can't change this to just '*zstr++ = *cstr++'. However all
|
|
|
|
|
|
// up-to-date compilers that I know about handle this correctly.
|
|
|
|
|
|
//
|
|
|
|
|
|
#if 1
|
|
|
|
|
|
#define NEXTCHAR *zstr++ = *cstr++;
|
|
|
|
|
|
#else
|
|
|
|
|
|
#define NEXTCHAR \
|
|
|
|
|
|
if (cstr == zstr) \
|
|
|
|
|
|
{ \
|
|
|
|
|
|
cstr++; \
|
|
|
|
|
|
zstr++; \
|
|
|
|
|
|
} \
|
|
|
|
|
|
else \
|
|
|
|
|
|
{ \
|
|
|
|
|
|
*zstr++ = *cstr++; \
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *parse_to(UTF8 **dstr, UTF8 delim, int eval)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
#define stacklim 32
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 stack[stacklim];
|
|
|
|
|
|
UTF8 *rstr, *cstr, *zstr, *strFirewall;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
int sp, tp, bracketlev;
|
|
|
|
|
|
|
2018-10-03 17:54:51 +00:00
|
|
|
|
if ( dstr == nullptr
|
|
|
|
|
|
|| *dstr == nullptr)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
|
return nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (**dstr == '\0')
|
|
|
|
|
|
{
|
|
|
|
|
|
rstr = *dstr;
|
2018-10-03 17:54:51 +00:00
|
|
|
|
*dstr = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return rstr;
|
|
|
|
|
|
}
|
|
|
|
|
|
sp = 0;
|
|
|
|
|
|
bool first = true;
|
|
|
|
|
|
strFirewall = rstr = *dstr;
|
|
|
|
|
|
if ( ( mudconf.space_compress
|
|
|
|
|
|
|| (eval & EV_STRIP_LS))
|
|
|
|
|
|
&& !(eval & EV_NO_COMPRESS))
|
|
|
|
|
|
{
|
|
|
|
|
|
while (mux_isspace(*rstr))
|
|
|
|
|
|
{
|
|
|
|
|
|
rstr++;
|
|
|
|
|
|
}
|
|
|
|
|
|
*dstr = rstr;
|
|
|
|
|
|
}
|
|
|
|
|
|
zstr = cstr = rstr;
|
|
|
|
|
|
int iOriginalCode = isSpecial(L3, delim);
|
|
|
|
|
|
isSpecial(L3, ' ') = 1; // Spaces are special.
|
|
|
|
|
|
if (iOriginalCode <= 3)
|
|
|
|
|
|
{
|
|
|
|
|
|
// We can override this code.
|
|
|
|
|
|
//
|
|
|
|
|
|
isSpecial(L3, delim) = 8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
|
|
{
|
|
|
|
|
|
int iCode = isSpecial(L3, *cstr);
|
|
|
|
|
|
|
|
|
|
|
|
TryAgain:
|
|
|
|
|
|
if (iCode == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Mundane characters and not the delimiter we are looking for.
|
|
|
|
|
|
//
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
iCode = isSpecial(L3, *cstr);
|
|
|
|
|
|
} while (iCode == 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (iCode <= 4)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1 is 0x20 ' ' delim overridable
|
|
|
|
|
|
// 2 is 0x5B '[' delim overridable
|
|
|
|
|
|
// 3 is 0x28 '(' delim overridable
|
2007-03-25 20:18:11 +00:00
|
|
|
|
// 4 is 0x25 '%' or 0x5C '\\' not overridable.
|
2006-09-01 22:59:23 +00:00
|
|
|
|
//
|
|
|
|
|
|
if (iCode <= 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1 is 0x20 ' ' delim overridable
|
|
|
|
|
|
// 2 is 0x5B '[' delim overridable
|
|
|
|
|
|
//
|
|
|
|
|
|
if (iCode == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
// space
|
|
|
|
|
|
//
|
|
|
|
|
|
if ( mudconf.space_compress
|
|
|
|
|
|
&& !(eval & EV_NO_COMPRESS))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (first)
|
|
|
|
|
|
{
|
|
|
|
|
|
rstr++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if ( strFirewall < cstr
|
|
|
|
|
|
&& cstr[-1] == ' ')
|
|
|
|
|
|
{
|
|
|
|
|
|
zstr--;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// '['
|
|
|
|
|
|
//
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
if (sp < stacklim)
|
|
|
|
|
|
{
|
|
|
|
|
|
stack[sp++] = ']';
|
|
|
|
|
|
}
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 3 is 0x28 '(' delim overridable
|
2007-03-25 20:18:11 +00:00
|
|
|
|
// 4 is 0x25 '%' or 0x5C '\\' not overridable.
|
2006-09-01 22:59:23 +00:00
|
|
|
|
//
|
|
|
|
|
|
if (iCode == 3)
|
|
|
|
|
|
{
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
if (sp < stacklim)
|
|
|
|
|
|
{
|
|
|
|
|
|
stack[sp++] = ')';
|
|
|
|
|
|
}
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2007-03-25 20:18:11 +00:00
|
|
|
|
// % and \ escapes.
|
2006-09-01 22:59:23 +00:00
|
|
|
|
//
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
if (*cstr)
|
|
|
|
|
|
{
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 5 is 0x29 ')' or 0x5D ']' not overridable.
|
|
|
|
|
|
// 6 is 0x7B '{' not overridable.
|
|
|
|
|
|
// 7 is 0x00 '\0' not overridable.
|
|
|
|
|
|
// 8 is the client-specific terminator.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (iCode <= 6)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 5 is 0x29 ')' or 0x5D ']' not overridable.
|
|
|
|
|
|
// 6 is 0x7B '{' not overridable.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (iCode == 5)
|
|
|
|
|
|
{
|
|
|
|
|
|
// ) and ]
|
|
|
|
|
|
//
|
|
|
|
|
|
for (tp = sp - 1; tp >= 0 && stack[tp] != *cstr; tp--)
|
|
|
|
|
|
{
|
|
|
|
|
|
; // Nothing.
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we hit something on the stack, unwind to it. Otherwise (it's
|
|
|
|
|
|
// not on stack), if it's our delim we are done, and we convert the
|
|
|
|
|
|
// delim to a null and return a ptr to the char after the null. If
|
|
|
|
|
|
// it's not our delimiter, skip over it normally.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (tp >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
sp = tp;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (*cstr == delim)
|
|
|
|
|
|
{
|
|
|
|
|
|
rstr = parse_to_cleanup(eval, first, cstr, rstr, zstr, strFirewall);
|
|
|
|
|
|
*dstr = ++cstr;
|
|
|
|
|
|
isSpecial(L3, delim) = iOriginalCode;
|
|
|
|
|
|
isSpecial(L3, ' ') = 0; // Spaces no longer special
|
|
|
|
|
|
return rstr;
|
|
|
|
|
|
}
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// {
|
|
|
|
|
|
//
|
|
|
|
|
|
bracketlev = 1;
|
|
|
|
|
|
if (eval & EV_STRIP_CURLY)
|
|
|
|
|
|
{
|
|
|
|
|
|
cstr++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
NEXTCHAR;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (;;)
|
|
|
|
|
|
{
|
|
|
|
|
|
int iCodeL4 = isSpecial(L4, *cstr);
|
|
|
|
|
|
if (iCodeL4 == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Mudane Characters
|
|
|
|
|
|
//
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
iCodeL4 = isSpecial(L4, *cstr);
|
|
|
|
|
|
} while (iCodeL4 == 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (iCodeL4 == 1)
|
|
|
|
|
|
{
|
2007-03-25 20:18:11 +00:00
|
|
|
|
// % and \ escapes.
|
2006-09-01 22:59:23 +00:00
|
|
|
|
//
|
|
|
|
|
|
if (cstr[1])
|
|
|
|
|
|
{
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (iCodeL4 == 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
// '{'
|
|
|
|
|
|
//
|
|
|
|
|
|
bracketlev++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (iCodeL4 == 3)
|
|
|
|
|
|
{
|
|
|
|
|
|
// '}'
|
|
|
|
|
|
//
|
|
|
|
|
|
bracketlev--;
|
|
|
|
|
|
if (bracketlev <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// '\0'
|
|
|
|
|
|
//
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (bracketlev == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (eval & EV_STRIP_CURLY)
|
|
|
|
|
|
{
|
|
|
|
|
|
cstr++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
NEXTCHAR
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 7 is 0x00 '\0' not overridable.
|
|
|
|
|
|
// 8 is the client-specific terminator.
|
|
|
|
|
|
//
|
|
|
|
|
|
if (iCode == 7)
|
|
|
|
|
|
{
|
|
|
|
|
|
// '\0' - End of string.
|
|
|
|
|
|
//
|
|
|
|
|
|
isSpecial(L3, delim) = iOriginalCode;
|
|
|
|
|
|
isSpecial(L3, ' ') = 0; // Spaces no longer special
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Client-Specific terminator
|
|
|
|
|
|
//
|
|
|
|
|
|
if (sp == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
rstr = parse_to_cleanup(eval, first, cstr, rstr, zstr, strFirewall);
|
|
|
|
|
|
*dstr = ++cstr;
|
|
|
|
|
|
isSpecial(L3, delim) = iOriginalCode;
|
|
|
|
|
|
isSpecial(L3, ' ') = 0; // Spaces no longer special
|
|
|
|
|
|
return rstr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// At this point, we need to process the iOriginalCode.
|
|
|
|
|
|
//
|
|
|
|
|
|
iCode = iOriginalCode;
|
|
|
|
|
|
goto TryAgain;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
rstr = parse_to_cleanup(eval, first, cstr, rstr, zstr, strFirewall);
|
2018-10-03 17:54:51 +00:00
|
|
|
|
*dstr = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return rstr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
// parse_arglist: Parse a line into an argument list contained in lbufs. A
|
|
|
|
|
|
// pointer is returned to whatever follows the final delimiter. If the arglist
|
2018-10-03 17:54:51 +00:00
|
|
|
|
// is unterminated, a nullptr is returned. The original arglist is destructively
|
2006-09-01 22:59:23 +00:00
|
|
|
|
// modified.
|
|
|
|
|
|
//
|
2026-08-06 15:00:57 -06:00
|
|
|
|
// `UTF8 *fargs[]` here is CORRECT, not a #2136 straggler: this is the
|
|
|
|
|
|
// PRODUCER side of the contract. It allocates the lbufs and writes the
|
|
|
|
|
|
// pointers into the caller's array, so it owns that memory and must not
|
|
|
|
|
|
// take it const. The `const UTF8 * const fargs[]` spelling is for
|
|
|
|
|
|
// callees; a grep sweep for the old spelling should skip this one.
|
|
|
|
|
|
//
|
2007-04-09 08:36:13 +00:00
|
|
|
|
void parse_arglist( dbref executor, dbref caller, dbref enactor, UTF8 *dstr,
|
|
|
|
|
|
int eval, UTF8 *fargs[], int nfargs,
|
2007-04-09 23:40:23 +00:00
|
|
|
|
const UTF8 *cargs[], int ncargs, int *nArgsParsed )
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
|
if (dstr == nullptr)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
*nArgsParsed = 0;
|
2007-04-09 08:36:13 +00:00
|
|
|
|
return;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2007-04-09 08:36:13 +00:00
|
|
|
|
int iArg = 0;
|
|
|
|
|
|
UTF8 *tstr, *bp;
|
|
|
|
|
|
int peval = (eval & ~EV_EVAL);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2007-04-09 08:36:13 +00:00
|
|
|
|
while ( iArg < nfargs
|
|
|
|
|
|
&& dstr)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2007-04-09 08:36:13 +00:00
|
|
|
|
if (iArg < nfargs - 1)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2007-04-09 08:36:13 +00:00
|
|
|
|
tstr = parse_to(&dstr, ',', peval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2007-04-09 08:36:13 +00:00
|
|
|
|
tstr = parse_to(&dstr, '\0', peval);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2007-04-09 08:36:13 +00:00
|
|
|
|
bp = fargs[iArg] = alloc_lbuf("parse_arglist");
|
2006-09-01 22:59:23 +00:00
|
|
|
|
if (eval & EV_EVAL)
|
|
|
|
|
|
{
|
2026-03-07 19:57:24 -07:00
|
|
|
|
mux_exec(tstr, LBUF_SIZE-1, fargs[iArg], &bp, executor, caller, enactor,
|
2007-01-28 02:08:08 +00:00
|
|
|
|
eval | EV_FCHECK, cargs, ncargs);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
*bp = '\0';
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2007-04-09 08:36:13 +00:00
|
|
|
|
mux_strncpy(fargs[iArg], tstr, LBUF_SIZE-1);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
2007-04-09 08:36:13 +00:00
|
|
|
|
iArg++;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
2007-04-09 08:36:13 +00:00
|
|
|
|
*nArgsParsed = iArg;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
2026-03-07 19:55:23 -07:00
|
|
|
|
// Pronoun support for %-substitutions.
|
2006-09-01 22:59:23 +00:00
|
|
|
|
//
|
2026-03-04 15:20:40 -07:00
|
|
|
|
static PRONOUN_SET builtin_pronoun_sets[4] =
|
|
|
|
|
|
{
|
|
|
|
|
|
{ "it", "it", "its", "its", false }, // [0] neuter
|
|
|
|
|
|
{ "she", "her", "her", "hers", false }, // [1] feminine
|
|
|
|
|
|
{ "he", "him", "his", "his", false }, // [2] masculine
|
|
|
|
|
|
{ "they", "them", "their", "theirs", true }, // [3] plural
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const PRONOUN_SET *get_pronoun_set(dbref player)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2026-03-04 15:20:40 -07:00
|
|
|
|
// Fast path: check for @pronoun attribute (no parent chain).
|
|
|
|
|
|
//
|
|
|
|
|
|
const UTF8 *raw = atr_get_raw(player, A_PRONOUN);
|
|
|
|
|
|
if (raw)
|
|
|
|
|
|
{
|
|
|
|
|
|
size_t nCased;
|
|
|
|
|
|
const UTF8 *pCased = mux_strupr(raw, nCased);
|
|
|
|
|
|
std::vector<UTF8> vKey(pCased, pCased + nCased);
|
|
|
|
|
|
auto it = mudstate.pronoun_groups.find(vKey);
|
|
|
|
|
|
if (it != mudstate.pronoun_groups.end())
|
|
|
|
|
|
{
|
|
|
|
|
|
return it->second;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fall back to @sex first-character matching.
|
|
|
|
|
|
//
|
2006-09-01 22:59:23 +00:00
|
|
|
|
dbref aowner;
|
|
|
|
|
|
int aflags;
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *atr_gotten = atr_pget(player, A_SEX, &aowner, &aflags);
|
|
|
|
|
|
UTF8 first = atr_gotten[0];
|
2006-09-01 22:59:23 +00:00
|
|
|
|
free_lbuf(atr_gotten);
|
2007-03-23 17:32:22 +00:00
|
|
|
|
switch (first)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
case 'p':
|
2007-03-23 17:32:22 +00:00
|
|
|
|
case 'P':
|
2026-03-04 15:20:40 -07:00
|
|
|
|
return &builtin_pronoun_sets[3];
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
case 'm':
|
2007-03-23 17:32:22 +00:00
|
|
|
|
case 'M':
|
2026-03-04 15:20:40 -07:00
|
|
|
|
return &builtin_pronoun_sets[2];
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
case 'f':
|
2007-03-23 17:32:22 +00:00
|
|
|
|
case 'F':
|
2006-09-01 22:59:23 +00:00
|
|
|
|
case 'w':
|
2007-03-23 17:32:22 +00:00
|
|
|
|
case 'W':
|
2026-03-04 15:20:40 -07:00
|
|
|
|
return &builtin_pronoun_sets[1];
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
2026-03-04 15:20:40 -07:00
|
|
|
|
return &builtin_pronoun_sets[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int get_gender(dbref player)
|
|
|
|
|
|
{
|
|
|
|
|
|
const PRONOUN_SET *ps = get_pronoun_set(player);
|
|
|
|
|
|
if (ps == &builtin_pronoun_sets[3]) return 4;
|
|
|
|
|
|
if (ps == &builtin_pronoun_sets[2]) return 3;
|
|
|
|
|
|
if (ps == &builtin_pronoun_sets[1]) return 2;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
// Trace cache routines.
|
|
|
|
|
|
//
|
|
|
|
|
|
typedef struct tcache_ent TCENT;
|
|
|
|
|
|
static struct tcache_ent
|
|
|
|
|
|
{
|
|
|
|
|
|
dbref player;
|
2007-03-14 00:55:08 +00:00
|
|
|
|
UTF8 *orig;
|
|
|
|
|
|
UTF8 *result;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
struct tcache_ent *next;
|
|
|
|
|
|
} *tcache_head;
|
|
|
|
|
|
|
|
|
|
|
|
static bool tcache_top;
|
|
|
|
|
|
static int tcache_count;
|
|
|
|
|
|
|
|
|
|
|
|
void tcache_init(void)
|
|
|
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
|
tcache_head = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
tcache_top = true;
|
|
|
|
|
|
tcache_count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(eval): restore softcode TRACE output (#1023)
The TRACE flag (and per-attribute trace) produced no output: the emitter
tcache_add/tcache_finish was carried into the AST/JIT evaluator but its caller
was never reconnected, so setting TRACE silently did nothing. Confirmed
regression -- it works in origin/release/2.13.
2.13 drove the trace inline from the tree-walking exec() recursion: compute
is_trace, capture the input substring on entry, tcache_add(input, result) on
exit, tcache_finish() at the top. That exact shape does not exist here because
evaluation moved to an AST interpreter plus a JIT/DBT compile path. This
reconnects the emitter against the surviving interpreter rather than porting
2.13's code:
* mux_exec computes is_trace = (Trace(executor) || (eval & EV_TRACE)) &&
!(eval & EV_NOTRACE). When set, it forces the AST interpreter (the JIT has
no per-node frames for the hook to observe) and brackets the outermost
evaluation so accumulated lines flush to the owner.
* ast_eval_node records (source -> result) for the call boundaries --
AST_FUNCCALL and AST_EVALBRACKET -- using ast_raw_text() for the source and
the span it just wrote for the result. The change-filter in tcache_add
drops no-op nodes. Off-path cost is a single bit test, since EV_TRACE is
only set while tracing.
Both the object TRACE flag and the per-attribute trace flag (AttrTrace ->
EV_TRACE) drive it, and EV_TRACE propagates through argument evaluation so
nested calls trace. The tcache emitter (owns orig, applies the change-filter,
respects trace_limit) is reused unchanged; it is exposed from eval.cpp via
externs.h.
Both config knobs restored to 2.13 parity:
* trace_topdown (default true): accumulate and flush outermost-first at the
top; false flushes each line as its subexpression completes (innermost
first).
* trace_output_limit (trace_limit, default 200): caps stored lines top-down;
a "N lines of trace output discarded." notice reports the overflow.
Verified (fresh DB per case): no flag -> result only, zero trace lines, JIT
still used; object TRACE and per-attribute trace each -> nested
'expr -> result' lines; top-down outermost-first, bottom-up innermost-first;
trace_output_limit 2 -> two lines plus "3 lines of trace output discarded."
Regression: smoke 1319/1319, JIT q-register oracle AST=JIT agree (the
trace-forced interpreter path matches the JIT), stress 8/8, netaddr 57/57,
ganl 14/14.
Not included: bare %-substitution granularity (2.13 also traced e.g. '%0'->'7'
as its own line). Call-boundary tracing -- the function nesting a builder
actually debugs -- is restored; per-substitution lines can be added later if
wanted.
Closes #1023.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 18:27:14 -06:00
|
|
|
|
bool tcache_empty(void)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (tcache_top)
|
|
|
|
|
|
{
|
|
|
|
|
|
tcache_top = false;
|
|
|
|
|
|
tcache_count = 0;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(eval): restore softcode TRACE output (#1023)
The TRACE flag (and per-attribute trace) produced no output: the emitter
tcache_add/tcache_finish was carried into the AST/JIT evaluator but its caller
was never reconnected, so setting TRACE silently did nothing. Confirmed
regression -- it works in origin/release/2.13.
2.13 drove the trace inline from the tree-walking exec() recursion: compute
is_trace, capture the input substring on entry, tcache_add(input, result) on
exit, tcache_finish() at the top. That exact shape does not exist here because
evaluation moved to an AST interpreter plus a JIT/DBT compile path. This
reconnects the emitter against the surviving interpreter rather than porting
2.13's code:
* mux_exec computes is_trace = (Trace(executor) || (eval & EV_TRACE)) &&
!(eval & EV_NOTRACE). When set, it forces the AST interpreter (the JIT has
no per-node frames for the hook to observe) and brackets the outermost
evaluation so accumulated lines flush to the owner.
* ast_eval_node records (source -> result) for the call boundaries --
AST_FUNCCALL and AST_EVALBRACKET -- using ast_raw_text() for the source and
the span it just wrote for the result. The change-filter in tcache_add
drops no-op nodes. Off-path cost is a single bit test, since EV_TRACE is
only set while tracing.
Both the object TRACE flag and the per-attribute trace flag (AttrTrace ->
EV_TRACE) drive it, and EV_TRACE propagates through argument evaluation so
nested calls trace. The tcache emitter (owns orig, applies the change-filter,
respects trace_limit) is reused unchanged; it is exposed from eval.cpp via
externs.h.
Both config knobs restored to 2.13 parity:
* trace_topdown (default true): accumulate and flush outermost-first at the
top; false flushes each line as its subexpression completes (innermost
first).
* trace_output_limit (trace_limit, default 200): caps stored lines top-down;
a "N lines of trace output discarded." notice reports the overflow.
Verified (fresh DB per case): no flag -> result only, zero trace lines, JIT
still used; object TRACE and per-attribute trace each -> nested
'expr -> result' lines; top-down outermost-first, bottom-up innermost-first;
trace_output_limit 2 -> two lines plus "3 lines of trace output discarded."
Regression: smoke 1319/1319, JIT q-register oracle AST=JIT agree (the
trace-forced interpreter path matches the JIT), stress 8/8, netaddr 57/57,
ganl 14/14.
Not included: bare %-substitution granularity (2.13 also traced e.g. '%0'->'7'
as its own line). Call-boundary tracing -- the function nesting a builder
actually debugs -- is restored; per-substitution lines can be added later if
wanted.
Closes #1023.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 18:27:14 -06:00
|
|
|
|
void tcache_add(dbref player, UTF8 *orig, const UTF8 *result)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2026-03-03 18:39:35 -07:00
|
|
|
|
if ( strcmp(reinterpret_cast<const char *>(orig), reinterpret_cast<const char *>(result))
|
2007-03-24 04:47:18 +00:00
|
|
|
|
&& (++tcache_count) <= mudconf.trace_limit)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2026-03-03 18:39:35 -07:00
|
|
|
|
TCENT *xp = reinterpret_cast<TCENT *>(alloc_sbuf("tcache_add.sbuf"));
|
2007-03-24 04:47:18 +00:00
|
|
|
|
UTF8 *tp = alloc_lbuf("tcache_add.lbuf");
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2007-03-24 23:28:02 +00:00
|
|
|
|
TruncateToBuffer(result, tp, LBUF_SIZE-1);
|
2007-03-24 04:47:18 +00:00
|
|
|
|
xp->result = tp;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2007-03-24 04:47:18 +00:00
|
|
|
|
xp->player = player;
|
|
|
|
|
|
xp->orig = orig;
|
|
|
|
|
|
xp->next = tcache_head;
|
|
|
|
|
|
tcache_head = xp;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
free_lbuf(orig);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix(eval): restore softcode TRACE output (#1023)
The TRACE flag (and per-attribute trace) produced no output: the emitter
tcache_add/tcache_finish was carried into the AST/JIT evaluator but its caller
was never reconnected, so setting TRACE silently did nothing. Confirmed
regression -- it works in origin/release/2.13.
2.13 drove the trace inline from the tree-walking exec() recursion: compute
is_trace, capture the input substring on entry, tcache_add(input, result) on
exit, tcache_finish() at the top. That exact shape does not exist here because
evaluation moved to an AST interpreter plus a JIT/DBT compile path. This
reconnects the emitter against the surviving interpreter rather than porting
2.13's code:
* mux_exec computes is_trace = (Trace(executor) || (eval & EV_TRACE)) &&
!(eval & EV_NOTRACE). When set, it forces the AST interpreter (the JIT has
no per-node frames for the hook to observe) and brackets the outermost
evaluation so accumulated lines flush to the owner.
* ast_eval_node records (source -> result) for the call boundaries --
AST_FUNCCALL and AST_EVALBRACKET -- using ast_raw_text() for the source and
the span it just wrote for the result. The change-filter in tcache_add
drops no-op nodes. Off-path cost is a single bit test, since EV_TRACE is
only set while tracing.
Both the object TRACE flag and the per-attribute trace flag (AttrTrace ->
EV_TRACE) drive it, and EV_TRACE propagates through argument evaluation so
nested calls trace. The tcache emitter (owns orig, applies the change-filter,
respects trace_limit) is reused unchanged; it is exposed from eval.cpp via
externs.h.
Both config knobs restored to 2.13 parity:
* trace_topdown (default true): accumulate and flush outermost-first at the
top; false flushes each line as its subexpression completes (innermost
first).
* trace_output_limit (trace_limit, default 200): caps stored lines top-down;
a "N lines of trace output discarded." notice reports the overflow.
Verified (fresh DB per case): no flag -> result only, zero trace lines, JIT
still used; object TRACE and per-attribute trace each -> nested
'expr -> result' lines; top-down outermost-first, bottom-up innermost-first;
trace_output_limit 2 -> two lines plus "3 lines of trace output discarded."
Regression: smoke 1319/1319, JIT q-register oracle AST=JIT agree (the
trace-forced interpreter path matches the JIT), stress 8/8, netaddr 57/57,
ganl 14/14.
Not included: bare %-substitution granularity (2.13 also traced e.g. '%0'->'7'
as its own line). Call-boundary tracing -- the function nesting a builder
actually debugs -- is restored; per-substitution lines can be added later if
wanted.
Closes #1023.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 18:27:14 -06:00
|
|
|
|
// Lines dropped past trace_limit (top-down only): tcache_add counts every
|
|
|
|
|
|
// changed subexpression in tcache_count but stops storing past the cap, so
|
|
|
|
|
|
// the overflow is the difference. Read before tcache_finish resets it.
|
|
|
|
|
|
//
|
|
|
|
|
|
int tcache_dropped_count(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (tcache_count > mudconf.trace_limit)
|
|
|
|
|
|
? (tcache_count - mudconf.trace_limit)
|
|
|
|
|
|
: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void tcache_finish(void)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
|
while (tcache_head != nullptr)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
TCENT *xp = tcache_head;
|
|
|
|
|
|
tcache_head = xp->next;
|
2026-07-26 21:52:48 -06:00
|
|
|
|
notify(Owner(xp->player), tprintf(T("%s(#%d)} ‘%s’ -> ‘%s’"), Name(xp->player),
|
2006-09-01 22:59:23 +00:00
|
|
|
|
xp->player, xp->orig, xp->result));
|
|
|
|
|
|
free_lbuf(xp->orig);
|
|
|
|
|
|
free_lbuf(xp->result);
|
|
|
|
|
|
free_sbuf(xp);
|
|
|
|
|
|
}
|
|
|
|
|
|
tcache_top = true;
|
|
|
|
|
|
tcache_count = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 19:40:40 -06:00
|
|
|
|
// ColorTable moved to stringutil.cpp (libmux.so).
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2006-11-02 09:10:58 +00:00
|
|
|
|
#define REFS_PER_FRAME ((LBUF_SIZE - sizeof(void *) - sizeof(int))/sizeof(void *))
|
|
|
|
|
|
typedef struct tag_refsframe
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
int nrefs;
|
|
|
|
|
|
reg_ref *refs[REFS_PER_FRAME];
|
|
|
|
|
|
struct tag_refsframe *next;
|
|
|
|
|
|
} RefsFrame;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2018-10-03 17:54:51 +00:00
|
|
|
|
static RefsFrame *pRefsFrame = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2006-11-02 09:10:58 +00:00
|
|
|
|
reg_ref **PushRegisters(int nNeeded)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
if ( !pRefsFrame
|
|
|
|
|
|
|| pRefsFrame->nrefs < nNeeded)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2026-03-03 18:39:35 -07:00
|
|
|
|
RefsFrame *p = reinterpret_cast<RefsFrame *>(alloc_lbuf("PushRegisters"));
|
2006-11-02 09:10:58 +00:00
|
|
|
|
p->next = pRefsFrame;
|
|
|
|
|
|
p->nrefs = REFS_PER_FRAME;
|
|
|
|
|
|
pRefsFrame = p;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
2006-11-02 09:10:58 +00:00
|
|
|
|
pRefsFrame->nrefs -= nNeeded;
|
|
|
|
|
|
return pRefsFrame->refs + pRefsFrame->nrefs;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2006-11-02 09:10:58 +00:00
|
|
|
|
void PopRegisters(reg_ref **p, int nNeeded)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
UNUSED_PARAMETER(p);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
2006-11-02 09:10:58 +00:00
|
|
|
|
if (pRefsFrame->nrefs == REFS_PER_FRAME)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
RefsFrame *q = pRefsFrame->next;
|
2026-03-03 18:39:35 -07:00
|
|
|
|
free_lbuf(reinterpret_cast<UTF8 *>(pRefsFrame));
|
2006-11-02 09:10:58 +00:00
|
|
|
|
pRefsFrame = q;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
2006-11-02 09:10:58 +00:00
|
|
|
|
//mux_assert(p == pRefsFrame->refs + pRefsFrame->nrefs);
|
|
|
|
|
|
pRefsFrame->nrefs += nNeeded;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 19:40:40 -06:00
|
|
|
|
// parse_rgb moved to stringutil.cpp (libmux.so).
|
2010-08-07 00:01:18 -07:00
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
|
|
|
|
* save_global_regs, restore_global_regs: Save and restore the global
|
|
|
|
|
|
* registers to protect them from various sorts of munging.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
Implement named global registers (#351)
Add TinyMUSH/RhostMUSH-style named registers alongside the existing
36 single-char registers (0-9, a-z). Named registers use alphanumeric
and underscore names up to 32 chars, case-insensitive.
- setq()/setr() accept multi-char names and multi-pair arguments:
setq(myvar,value), setq(a,v1,myvar,v2)
- r() reads named registers: r(myvar)
- %q<name> substitution in the evaluator: %q<myvar>
- regmatch() can assign captures to named registers
- Named registers are stored in a heap-allocated NamedRegsMap (nullptr
when empty) to avoid constructor/destructor issues with pool_alloc'd
BQUE structs
- Save/restore uses a parallel static stack so all 44+ call sites are
unchanged
- Full queue integration: setup_que, wait_que, Task_RunQueueEntry,
all free_qentry and program_data cleanup sites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:03:14 -07:00
|
|
|
|
static std::vector<NamedRegsMap*> named_regs_stack;
|
|
|
|
|
|
|
2006-09-01 22:59:23 +00:00
|
|
|
|
void save_global_regs
|
|
|
|
|
|
(
|
2006-11-02 09:10:58 +00:00
|
|
|
|
reg_ref *preserve[]
|
2006-09-01 22:59:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (mudstate.global_regs[i])
|
|
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
RegAddRef(mudstate.global_regs[i]);
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
2006-11-02 09:10:58 +00:00
|
|
|
|
preserve[i] = mudstate.global_regs[i];
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
Implement named global registers (#351)
Add TinyMUSH/RhostMUSH-style named registers alongside the existing
36 single-char registers (0-9, a-z). Named registers use alphanumeric
and underscore names up to 32 chars, case-insensitive.
- setq()/setr() accept multi-char names and multi-pair arguments:
setq(myvar,value), setq(a,v1,myvar,v2)
- r() reads named registers: r(myvar)
- %q<name> substitution in the evaluator: %q<myvar>
- regmatch() can assign captures to named registers
- Named registers are stored in a heap-allocated NamedRegsMap (nullptr
when empty) to avoid constructor/destructor issues with pool_alloc'd
BQUE structs
- Save/restore uses a parallel static stack so all 44+ call sites are
unchanged
- Full queue integration: setup_que, wait_que, Task_RunQueueEntry,
all free_qentry and program_data cleanup sites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:03:14 -07:00
|
|
|
|
named_regs_stack.push_back(NamedRegsCopy(mudstate.named_regs));
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void save_and_clear_global_regs
|
|
|
|
|
|
(
|
2006-11-02 09:10:58 +00:00
|
|
|
|
reg_ref *preserve[]
|
2006-09-01 22:59:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
|
|
|
|
|
preserve[i] = mudstate.global_regs[i];
|
2018-10-03 17:54:51 +00:00
|
|
|
|
mudstate.global_regs[i] = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
Implement named global registers (#351)
Add TinyMUSH/RhostMUSH-style named registers alongside the existing
36 single-char registers (0-9, a-z). Named registers use alphanumeric
and underscore names up to 32 chars, case-insensitive.
- setq()/setr() accept multi-char names and multi-pair arguments:
setq(myvar,value), setq(a,v1,myvar,v2)
- r() reads named registers: r(myvar)
- %q<name> substitution in the evaluator: %q<myvar>
- regmatch() can assign captures to named registers
- Named registers are stored in a heap-allocated NamedRegsMap (nullptr
when empty) to avoid constructor/destructor issues with pool_alloc'd
BQUE structs
- Save/restore uses a parallel static stack so all 44+ call sites are
unchanged
- Full queue integration: setup_que, wait_que, Task_RunQueueEntry,
all free_qentry and program_data cleanup sites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:03:14 -07:00
|
|
|
|
named_regs_stack.push_back(mudstate.named_regs);
|
|
|
|
|
|
mudstate.named_regs = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void restore_global_regs
|
|
|
|
|
|
(
|
2006-11-02 09:10:58 +00:00
|
|
|
|
reg_ref *preserve[]
|
2006-09-01 22:59:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
if (mudstate.global_regs[i])
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
RegRelease(mudstate.global_regs[i]);
|
2018-10-03 17:54:51 +00:00
|
|
|
|
mudstate.global_regs[i] = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
2006-11-02 09:10:58 +00:00
|
|
|
|
|
|
|
|
|
|
if (preserve[i])
|
2006-09-01 22:59:23 +00:00
|
|
|
|
{
|
2006-11-02 09:10:58 +00:00
|
|
|
|
mudstate.global_regs[i] = preserve[i];
|
2018-10-03 17:54:51 +00:00
|
|
|
|
preserve[i] = nullptr;
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
Implement named global registers (#351)
Add TinyMUSH/RhostMUSH-style named registers alongside the existing
36 single-char registers (0-9, a-z). Named registers use alphanumeric
and underscore names up to 32 chars, case-insensitive.
- setq()/setr() accept multi-char names and multi-pair arguments:
setq(myvar,value), setq(a,v1,myvar,v2)
- r() reads named registers: r(myvar)
- %q<name> substitution in the evaluator: %q<myvar>
- regmatch() can assign captures to named registers
- Named registers are stored in a heap-allocated NamedRegsMap (nullptr
when empty) to avoid constructor/destructor issues with pool_alloc'd
BQUE structs
- Save/restore uses a parallel static stack so all 44+ call sites are
unchanged
- Full queue integration: setup_que, wait_que, Task_RunQueueEntry,
all free_qentry and program_data cleanup sites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:03:14 -07:00
|
|
|
|
NamedRegsClear(mudstate.named_regs);
|
|
|
|
|
|
if (!named_regs_stack.empty())
|
|
|
|
|
|
{
|
|
|
|
|
|
mudstate.named_regs = named_regs_stack.back();
|
|
|
|
|
|
named_regs_stack.pop_back();
|
|
|
|
|
|
}
|
2006-09-01 22:59:23 +00:00
|
|
|
|
}
|
2006-11-02 09:10:58 +00:00
|
|
|
|
|
2026-03-19 09:27:48 -06:00
|
|
|
|
static std::shared_ptr<RegBuffer> last_regbuf;
|
2006-11-02 15:56:08 +00:00
|
|
|
|
|
2007-03-14 00:55:08 +00:00
|
|
|
|
void RegAssign(reg_ref **regref, size_t nLength, const UTF8 *ptr)
|
2006-11-02 09:10:58 +00:00
|
|
|
|
{
|
2018-10-03 17:54:51 +00:00
|
|
|
|
if ( nullptr == regref
|
|
|
|
|
|
|| nullptr == ptr
|
2006-11-28 01:42:19 +00:00
|
|
|
|
|| LBUF_SIZE <= nLength)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2006-11-25 08:07:09 +00:00
|
|
|
|
|
2006-11-02 15:56:08 +00:00
|
|
|
|
// Put any previous register value out of the way.
|
|
|
|
|
|
//
|
2018-10-03 17:54:51 +00:00
|
|
|
|
if (nullptr != *regref)
|
2006-11-02 09:10:58 +00:00
|
|
|
|
{
|
|
|
|
|
|
RegRelease(*regref);
|
2018-10-03 17:54:51 +00:00
|
|
|
|
*regref = nullptr;
|
2006-11-02 09:10:58 +00:00
|
|
|
|
}
|
2006-11-02 15:56:08 +00:00
|
|
|
|
|
2026-03-19 09:27:48 -06:00
|
|
|
|
// Let go of the last buffer if we can't use it.
|
2006-11-02 15:56:08 +00:00
|
|
|
|
//
|
2006-11-25 08:07:09 +00:00
|
|
|
|
size_t nSize = nLength + 1;
|
2026-03-19 09:27:48 -06:00
|
|
|
|
if (last_regbuf && last_regbuf->used + nSize > LBUF_SIZE)
|
2006-11-02 15:56:08 +00:00
|
|
|
|
{
|
2026-03-19 09:27:48 -06:00
|
|
|
|
last_regbuf.reset();
|
2006-11-02 15:56:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 09:27:48 -06:00
|
|
|
|
// Grab a new, fresh buffer if we don't have one.
|
2006-11-02 15:56:08 +00:00
|
|
|
|
//
|
2026-03-19 09:27:48 -06:00
|
|
|
|
if (!last_regbuf)
|
2006-11-02 15:56:08 +00:00
|
|
|
|
{
|
2026-03-19 09:27:48 -06:00
|
|
|
|
last_regbuf = std::make_shared<RegBuffer>();
|
2006-11-02 15:56:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 09:27:48 -06:00
|
|
|
|
// Pack value into the buffer.
|
2006-11-25 08:07:09 +00:00
|
|
|
|
//
|
2026-03-19 09:27:48 -06:00
|
|
|
|
UTF8 *p = last_regbuf->data + last_regbuf->used;
|
|
|
|
|
|
memcpy(p, ptr, nLength);
|
|
|
|
|
|
p[nLength] = '\0';
|
|
|
|
|
|
last_regbuf->used += nSize;
|
2006-11-25 08:07:09 +00:00
|
|
|
|
|
|
|
|
|
|
// Fill in new regref.
|
2006-11-02 15:56:08 +00:00
|
|
|
|
//
|
2026-03-19 09:27:48 -06:00
|
|
|
|
*regref = new reg_ref();
|
2006-11-02 09:10:58 +00:00
|
|
|
|
(*regref)->refcount = 1;
|
2026-03-19 09:27:48 -06:00
|
|
|
|
(*regref)->buf = last_regbuf;
|
2006-11-25 08:07:09 +00:00
|
|
|
|
(*regref)->reg_len = nLength;
|
|
|
|
|
|
(*regref)->reg_ptr = p;
|
2006-11-02 09:24:28 +00:00
|
|
|
|
}
|
Implement named global registers (#351)
Add TinyMUSH/RhostMUSH-style named registers alongside the existing
36 single-char registers (0-9, a-z). Named registers use alphanumeric
and underscore names up to 32 chars, case-insensitive.
- setq()/setr() accept multi-char names and multi-pair arguments:
setq(myvar,value), setq(a,v1,myvar,v2)
- r() reads named registers: r(myvar)
- %q<name> substitution in the evaluator: %q<myvar>
- regmatch() can assign captures to named registers
- Named registers are stored in a heap-allocated NamedRegsMap (nullptr
when empty) to avoid constructor/destructor issues with pool_alloc'd
BQUE structs
- Save/restore uses a parallel static stack so all 44+ call sites are
unchanged
- Full queue integration: setup_que, wait_que, Task_RunQueueEntry,
all free_qentry and program_data cleanup sites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:03:14 -07:00
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Named global register helpers.
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
constexpr size_t MAX_NAMED_REG_LEN = 32;
|
|
|
|
|
|
|
|
|
|
|
|
static std::vector<UTF8> MakeRegKey(const UTF8 *name, size_t len)
|
|
|
|
|
|
{
|
|
|
|
|
|
std::vector<UTF8> key(len);
|
|
|
|
|
|
for (size_t i = 0; i < len; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
key[i] = mux_tolower_ascii(name[i]);
|
|
|
|
|
|
}
|
|
|
|
|
|
return key;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool IsSingleCharReg(const UTF8 *name, int ®num)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( nullptr != name
|
|
|
|
|
|
&& name[0] != '\0'
|
|
|
|
|
|
&& name[1] == '\0')
|
|
|
|
|
|
{
|
|
|
|
|
|
int r = mux_RegisterSet[static_cast<unsigned char>(name[0])];
|
|
|
|
|
|
if ( 0 <= r
|
|
|
|
|
|
&& r < MAX_GLOBAL_REGS)
|
|
|
|
|
|
{
|
|
|
|
|
|
regnum = r;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool IsValidNamedReg(const UTF8 *name, size_t len)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( nullptr == name
|
|
|
|
|
|
|| 0 == len
|
|
|
|
|
|
|| MAX_NAMED_REG_LEN < len)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (size_t i = 0; i < len; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
UTF8 ch = name[i];
|
|
|
|
|
|
if ( !mux_isalnum(ch)
|
|
|
|
|
|
&& ch != '_')
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NamedRegAssign(NamedRegsMap *&map, const UTF8 *name, size_t nNameLen, size_t nValueLen, const UTF8 *value)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( nullptr == name
|
|
|
|
|
|
|| 0 == nNameLen
|
|
|
|
|
|
|| !IsValidNamedReg(name, nNameLen))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (nullptr == map)
|
|
|
|
|
|
{
|
|
|
|
|
|
map = new NamedRegsMap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<UTF8> key = MakeRegKey(name, nNameLen);
|
|
|
|
|
|
auto it = map->find(key);
|
|
|
|
|
|
if (it != map->end())
|
|
|
|
|
|
{
|
|
|
|
|
|
RegRelease(it->second);
|
|
|
|
|
|
it->second = nullptr;
|
|
|
|
|
|
map->erase(it);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( nullptr != value
|
|
|
|
|
|
&& nValueLen < LBUF_SIZE)
|
|
|
|
|
|
{
|
|
|
|
|
|
reg_ref *rr = nullptr;
|
|
|
|
|
|
RegAssign(&rr, nValueLen, value);
|
|
|
|
|
|
(*map)[key] = rr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
reg_ref *NamedRegRead(const NamedRegsMap *map, const UTF8 *name, size_t nNameLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
if ( nullptr == map
|
|
|
|
|
|
|| nullptr == name
|
|
|
|
|
|
|| 0 == nNameLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<UTF8> key = MakeRegKey(name, nNameLen);
|
|
|
|
|
|
auto it = map->find(key);
|
|
|
|
|
|
if (it != map->end())
|
|
|
|
|
|
{
|
|
|
|
|
|
return it->second;
|
|
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NamedRegsClear(NamedRegsMap *&map)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == map)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (auto &kv : *map)
|
|
|
|
|
|
{
|
|
|
|
|
|
RegRelease(kv.second);
|
|
|
|
|
|
}
|
|
|
|
|
|
delete map;
|
|
|
|
|
|
map = nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NamedRegsMap *NamedRegsCopy(const NamedRegsMap *src)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nullptr == src || src->empty())
|
|
|
|
|
|
{
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
NamedRegsMap *dst = new NamedRegsMap;
|
|
|
|
|
|
for (const auto &kv : *src)
|
|
|
|
|
|
{
|
|
|
|
|
|
RegAddRef(kv.second);
|
|
|
|
|
|
(*dst)[kv.first] = kv.second;
|
|
|
|
|
|
}
|
|
|
|
|
|
return dst;
|
|
|
|
|
|
}
|
|
|
|
|
|
|