tinymux/mux/modules/engine/eval.cpp
Stephen Dennis e2b1f974ab docs(#2136): mark parse_arglist as the producer side of the fargs contract
Comment-only.  Kagura's post-gate sweep for the old spelling correctly
identified this as the one deliberate survivor — it allocates the lbufs
and writes the pointers, so it must not take them const — and suggested
the note so a future grep sweep doesn't "fix" it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 15:00:57 -06:00

951 lines
27 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*! \file eval.cpp
* \brief Expression and function evaluation.
*
* 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.
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
using namespace std;
//-----------------------------------------------------------------------------
// 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
// returned as nullptr.
//
static UTF8 *parse_to_cleanup( int eval, int first, UTF8 *cstr, UTF8 *rstr,
UTF8 *zstr, UTF8 *strFirewall)
{
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.
*/
#define isSpecial(table, c) isSpecial_##table[static_cast<unsigned char>(c)]
// 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.
// 1 is 0x20 ' ' delim overridable (only done by parse_to)
// 2 is 0x5B '[' delim overridable
// 3 is 0x28 '(' delim overridable
// 4 is 0x25 '%' or 0x5C '\\' not overridable.
// 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
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10-0x1F
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
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10-0x1F
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
};
// Perhaps some compilers don't handle aliased pointers well. For these
// 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
UTF8 *parse_to(UTF8 **dstr, UTF8 delim, int eval)
{
#define stacklim 32
UTF8 stack[stacklim];
UTF8 *rstr, *cstr, *zstr, *strFirewall;
int sp, tp, bracketlev;
if ( dstr == nullptr
|| *dstr == nullptr)
{
return nullptr;
}
if (**dstr == '\0')
{
rstr = *dstr;
*dstr = nullptr;
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
// 4 is 0x25 '%' or 0x5C '\\' not overridable.
//
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
// 4 is 0x25 '%' or 0x5C '\\' not overridable.
//
if (iCode == 3)
{
first = false;
if (sp < stacklim)
{
stack[sp++] = ')';
}
NEXTCHAR
}
else
{
// % and \ escapes.
//
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)
{
// % and \ escapes.
//
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);
*dstr = nullptr;
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
// is unterminated, a nullptr is returned. The original arglist is destructively
// modified.
//
// `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.
//
void parse_arglist( dbref executor, dbref caller, dbref enactor, UTF8 *dstr,
int eval, UTF8 *fargs[], int nfargs,
const UTF8 *cargs[], int ncargs, int *nArgsParsed )
{
if (dstr == nullptr)
{
*nArgsParsed = 0;
return;
}
int iArg = 0;
UTF8 *tstr, *bp;
int peval = (eval & ~EV_EVAL);
while ( iArg < nfargs
&& dstr)
{
if (iArg < nfargs - 1)
{
tstr = parse_to(&dstr, ',', peval);
}
else
{
tstr = parse_to(&dstr, '\0', peval);
}
bp = fargs[iArg] = alloc_lbuf("parse_arglist");
if (eval & EV_EVAL)
{
mux_exec(tstr, LBUF_SIZE-1, fargs[iArg], &bp, executor, caller, enactor,
eval | EV_FCHECK, cargs, ncargs);
*bp = '\0';
}
else
{
mux_strncpy(fargs[iArg], tstr, LBUF_SIZE-1);
}
iArg++;
}
*nArgsParsed = iArg;
}
//-----------------------------------------------------------------------------
// Pronoun support for %-substitutions.
//
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)
{
// 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.
//
dbref aowner;
int aflags;
UTF8 *atr_gotten = atr_pget(player, A_SEX, &aowner, &aflags);
UTF8 first = atr_gotten[0];
free_lbuf(atr_gotten);
switch (first)
{
case 'p':
case 'P':
return &builtin_pronoun_sets[3];
case 'm':
case 'M':
return &builtin_pronoun_sets[2];
case 'f':
case 'F':
case 'w':
case 'W':
return &builtin_pronoun_sets[1];
}
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;
return 1;
}
//---------------------------------------------------------------------------
// Trace cache routines.
//
typedef struct tcache_ent TCENT;
static struct tcache_ent
{
dbref player;
UTF8 *orig;
UTF8 *result;
struct tcache_ent *next;
} *tcache_head;
static bool tcache_top;
static int tcache_count;
void tcache_init(void)
{
tcache_head = nullptr;
tcache_top = true;
tcache_count = 0;
}
bool tcache_empty(void)
{
if (tcache_top)
{
tcache_top = false;
tcache_count = 0;
return true;
}
return false;
}
void tcache_add(dbref player, UTF8 *orig, const UTF8 *result)
{
if ( strcmp(reinterpret_cast<const char *>(orig), reinterpret_cast<const char *>(result))
&& (++tcache_count) <= mudconf.trace_limit)
{
TCENT *xp = reinterpret_cast<TCENT *>(alloc_sbuf("tcache_add.sbuf"));
UTF8 *tp = alloc_lbuf("tcache_add.lbuf");
TruncateToBuffer(result, tp, LBUF_SIZE-1);
xp->result = tp;
xp->player = player;
xp->orig = orig;
xp->next = tcache_head;
tcache_head = xp;
}
else
{
free_lbuf(orig);
}
}
// 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)
{
while (tcache_head != nullptr)
{
TCENT *xp = tcache_head;
tcache_head = xp->next;
notify(Owner(xp->player), tprintf(T("%s(#%d)} %s -> %s"), Name(xp->player),
xp->player, xp->orig, xp->result));
free_lbuf(xp->orig);
free_lbuf(xp->result);
free_sbuf(xp);
}
tcache_top = true;
tcache_count = 0;
}
// ColorTable moved to stringutil.cpp (libmux.so).
#define REFS_PER_FRAME ((LBUF_SIZE - sizeof(void *) - sizeof(int))/sizeof(void *))
typedef struct tag_refsframe
{
int nrefs;
reg_ref *refs[REFS_PER_FRAME];
struct tag_refsframe *next;
} RefsFrame;
static RefsFrame *pRefsFrame = nullptr;
reg_ref **PushRegisters(int nNeeded)
{
if ( !pRefsFrame
|| pRefsFrame->nrefs < nNeeded)
{
RefsFrame *p = reinterpret_cast<RefsFrame *>(alloc_lbuf("PushRegisters"));
p->next = pRefsFrame;
p->nrefs = REFS_PER_FRAME;
pRefsFrame = p;
}
pRefsFrame->nrefs -= nNeeded;
return pRefsFrame->refs + pRefsFrame->nrefs;
}
void PopRegisters(reg_ref **p, int nNeeded)
{
UNUSED_PARAMETER(p);
if (pRefsFrame->nrefs == REFS_PER_FRAME)
{
RefsFrame *q = pRefsFrame->next;
free_lbuf(reinterpret_cast<UTF8 *>(pRefsFrame));
pRefsFrame = q;
}
//mux_assert(p == pRefsFrame->refs + pRefsFrame->nrefs);
pRefsFrame->nrefs += nNeeded;
}
// parse_rgb moved to stringutil.cpp (libmux.so).
/* ---------------------------------------------------------------------------
* save_global_regs, restore_global_regs: Save and restore the global
* registers to protect them from various sorts of munging.
*/
static std::vector<NamedRegsMap*> named_regs_stack;
void save_global_regs
(
reg_ref *preserve[]
)
{
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
{
if (mudstate.global_regs[i])
{
RegAddRef(mudstate.global_regs[i]);
}
preserve[i] = mudstate.global_regs[i];
}
named_regs_stack.push_back(NamedRegsCopy(mudstate.named_regs));
}
void save_and_clear_global_regs
(
reg_ref *preserve[]
)
{
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
{
preserve[i] = mudstate.global_regs[i];
mudstate.global_regs[i] = nullptr;
}
named_regs_stack.push_back(mudstate.named_regs);
mudstate.named_regs = nullptr;
}
void restore_global_regs
(
reg_ref *preserve[]
)
{
for (int i = 0; i < MAX_GLOBAL_REGS; i++)
{
if (mudstate.global_regs[i])
{
RegRelease(mudstate.global_regs[i]);
mudstate.global_regs[i] = nullptr;
}
if (preserve[i])
{
mudstate.global_regs[i] = preserve[i];
preserve[i] = nullptr;
}
}
NamedRegsClear(mudstate.named_regs);
if (!named_regs_stack.empty())
{
mudstate.named_regs = named_regs_stack.back();
named_regs_stack.pop_back();
}
}
static std::shared_ptr<RegBuffer> last_regbuf;
void RegAssign(reg_ref **regref, size_t nLength, const UTF8 *ptr)
{
if ( nullptr == regref
|| nullptr == ptr
|| LBUF_SIZE <= nLength)
{
return;
}
// Put any previous register value out of the way.
//
if (nullptr != *regref)
{
RegRelease(*regref);
*regref = nullptr;
}
// Let go of the last buffer if we can't use it.
//
size_t nSize = nLength + 1;
if (last_regbuf && last_regbuf->used + nSize > LBUF_SIZE)
{
last_regbuf.reset();
}
// Grab a new, fresh buffer if we don't have one.
//
if (!last_regbuf)
{
last_regbuf = std::make_shared<RegBuffer>();
}
// Pack value into the buffer.
//
UTF8 *p = last_regbuf->data + last_regbuf->used;
memcpy(p, ptr, nLength);
p[nLength] = '\0';
last_regbuf->used += nSize;
// Fill in new regref.
//
*regref = new reg_ref();
(*regref)->refcount = 1;
(*regref)->buf = last_regbuf;
(*regref)->reg_len = nLength;
(*regref)->reg_ptr = p;
}
// ---------------------------------------------------------------------------
// 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 &regnum)
{
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;
}