mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module interfaces take `const UTF8 * const fargs[]`. Double-const is load-bearing: C++ qualification conversion needs const at both pointer levels, so builder-side `UTF8 *[]` arrays convert implicitly — the evaluator, the JIT marshaller, and every owner site need zero casts, and slot reassignment inside bodies becomes a compile error for free. The conversions: the flip landed first so the compiler enumerated every violation; this commit is that inventory worked to zero — ~250 sites across funceval, funceval2, functions, funmath, help, mail, session, powers, levels, predicates, conf, walkdb, stringutil, timeutil/ date_scan (regenerated, one-line diff), exp3, and mux_main, each classified per docs/campaign-2136-const-fargs.md's four recipes. New idioms (functions.h): trim_space_sep_n() — non-destructive trim for (pointer, length) consumers, so trim-then-scan sites need no copy at all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers. countwords() and DecodeListOfIntegers() rewritten non-destructive. The flip deleted more than it added: #2157's fun_munge list1 copy, the engine_com help-topic copy, fun_index's in-place NUL write, and five const_casts (process_sex x4, sha1_helper). const_cast budget: zero added. Trap recorded in the brief: an old-signature definition doesn't fail the build — it becomes a C++ overload, and the new-signature symbol stays undefined until dlopen(RTLD_NOW). delim_check, the conn_bridge bridges, the dbt_spike stub, and exp3::Call were all silently shadowed; muxscript was the only host that noticed, because netmux's own net.cpp resolved the flat-namespace lookup. After any signature flip, grep the old spelling. Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and make test-scenario, including the new tests/scenario/sidefx_fargs.py that live-probes the class-3 wrappers smoke never touches (pemit/ trigger/link/tel/wipe/destroy). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
478 lines
12 KiB
C++
478 lines
12 KiB
C++
/*! \file help.cpp
|
||
* \brief In-game help system.
|
||
*
|
||
*/
|
||
|
||
#include "copyright.h"
|
||
#include "autoconf.h"
|
||
#include "config.h"
|
||
#include "externs.h"
|
||
|
||
// Pointers to this struct is what gets stored in the help_htab's.
|
||
//
|
||
struct help_entry
|
||
{
|
||
size_t pos; // Position in file.
|
||
UTF8 *key; // The key this is stored under. nullptr if this is an
|
||
// automatically generated initial substring alias.
|
||
};
|
||
|
||
void helpindex_clean(int iHelpfile)
|
||
{
|
||
StringPtrMap *htab = mudstate.aHelpDesc[iHelpfile].ht;
|
||
if (nullptr == htab)
|
||
{
|
||
return;
|
||
}
|
||
|
||
for (auto &[key, val] : *htab)
|
||
{
|
||
struct help_entry *htab_entry = static_cast<struct help_entry *>(val);
|
||
if (htab_entry->key)
|
||
{
|
||
MEMFREE(htab_entry->key);
|
||
htab_entry->key = nullptr;
|
||
}
|
||
delete htab_entry;
|
||
}
|
||
delete mudstate.aHelpDesc[iHelpfile].ht;
|
||
mudstate.aHelpDesc[iHelpfile].ht = nullptr;
|
||
}
|
||
|
||
static int lineno;
|
||
static int ntopics;
|
||
static FILE *rfp;
|
||
|
||
thread_local UTF8 Line[LBUF_SIZE];
|
||
|
||
static void HelpIndex_Start(FILE *fp)
|
||
{
|
||
lineno = 0;
|
||
ntopics = 0;
|
||
rfp = fp;
|
||
}
|
||
|
||
static bool HelpIndex_Read(size_t *pPos, size_t *nTopic, UTF8 pTopic[TOPIC_NAME_LEN+1])
|
||
{
|
||
size_t nLine = 0;
|
||
|
||
while ( 0 == nLine
|
||
|| '&' != Line[0])
|
||
{
|
||
if (fgets(reinterpret_cast<char *>(Line), LBUF_SIZE-2, rfp) == nullptr)
|
||
{
|
||
*pPos = 0L;
|
||
*nTopic = 0;
|
||
return false;
|
||
}
|
||
++lineno;
|
||
|
||
nLine = strlen(reinterpret_cast<char *>(Line));
|
||
*pPos += nLine;
|
||
if ( 0 < nLine
|
||
&& '\n' != Line[nLine - 1])
|
||
{
|
||
Log.tinyprintf(T("HelpIndex_Read, line %d: line too long" ENDLINE), lineno);
|
||
}
|
||
}
|
||
|
||
++ntopics;
|
||
UTF8 *topic = Line + 1;
|
||
while ( ' ' == *topic
|
||
|| '\t' == *topic
|
||
|| '\r' == *topic)
|
||
{
|
||
topic++;
|
||
}
|
||
|
||
UTF8 *s = topic;
|
||
size_t i = 0;
|
||
while ( '\n' != *s
|
||
&& '\r' != *s
|
||
&& '\0' != *s
|
||
&& i < TOPIC_NAME_LEN)
|
||
{
|
||
if ( ' ' != *s
|
||
|| ( 0 < i
|
||
&& ' ' != pTopic[i-1]))
|
||
{
|
||
pTopic[i++] = *s;
|
||
}
|
||
s++;
|
||
}
|
||
*nTopic = i;
|
||
pTopic[i] = '\0';
|
||
return true;
|
||
}
|
||
|
||
static void HelpIndex_End(void)
|
||
{
|
||
lineno = 0;
|
||
ntopics = 0;
|
||
rfp = nullptr;
|
||
}
|
||
|
||
static void helpindex_read(int iHelpfile)
|
||
{
|
||
helpindex_clean(iHelpfile);
|
||
|
||
mudstate.aHelpDesc[iHelpfile].ht = new StringPtrMap;
|
||
StringPtrMap *htab = mudstate.aHelpDesc[iHelpfile].ht;
|
||
|
||
UTF8 szTextFilename[SBUF_SIZE+8];
|
||
mux_sprintf(szTextFilename, sizeof(szTextFilename), T("%s.txt"),
|
||
mudstate.aHelpDesc[iHelpfile].pBaseFilename);
|
||
|
||
FILE *fp;
|
||
if (!mux_fopen(&fp, szTextFilename, T("rb")))
|
||
{
|
||
STARTLOG(LOG_PROBLEMS, "HLP", "RINDX");
|
||
LBuf p = LBuf_Src("helpindex_read.LOG");
|
||
mux_sprintf(p.get(), LBUF_SIZE, M_("Can’t open %s for reading."), szTextFilename);
|
||
log_text(p);
|
||
ENDLOG;
|
||
return;
|
||
}
|
||
size_t pos = 0;
|
||
size_t nTopicOriginal = 0;
|
||
UTF8 topic[TOPIC_NAME_LEN+1];
|
||
|
||
HelpIndex_Start(fp);
|
||
while (HelpIndex_Read(&pos, &nTopicOriginal, topic))
|
||
{
|
||
// Convert the entry to all lowercase letters and add all leftmost
|
||
// substrings.
|
||
//
|
||
// Topic names which appear earlier in the help file have priority
|
||
// over topics names which appear later in the help file. That is,
|
||
// we do not associate prefixes with this topic if they have already
|
||
// been used on a previous topic.
|
||
//
|
||
size_t nCased;
|
||
UTF8 *pCased = mux_strlwr(topic, nCased);
|
||
|
||
bool bOriginal = true; // First is the longest.
|
||
|
||
for (size_t nTopic = nCased; 0 < nTopic; nTopic--)
|
||
{
|
||
// Avoid adding any entries with a trailing space.
|
||
//
|
||
if (mux_isspace(pCased[nTopic-1]))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
std::vector<UTF8> vkey(pCased, pCased + nTopic);
|
||
auto it = htab->find(vkey);
|
||
struct help_entry *htab_entry = (it != htab->end())
|
||
? static_cast<struct help_entry *>(it->second) : nullptr;
|
||
|
||
if (htab_entry)
|
||
{
|
||
if (!bOriginal)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
htab->erase(it);
|
||
|
||
if (htab_entry->key)
|
||
{
|
||
MEMFREE(htab_entry->key);
|
||
htab_entry->key = nullptr;
|
||
Log.tinyprintf(T("helpindex_read: duplicate %s entries for %s" ENDLINE),
|
||
szTextFilename, pCased);
|
||
}
|
||
delete htab_entry;
|
||
htab_entry = nullptr;
|
||
}
|
||
|
||
try
|
||
{
|
||
htab_entry = new struct help_entry;
|
||
}
|
||
catch (...)
|
||
{
|
||
; // Nothing.
|
||
}
|
||
|
||
if (htab_entry)
|
||
{
|
||
htab_entry->pos = pos;
|
||
htab_entry->key = bOriginal ? StringCloneLen(pCased, nTopic) : nullptr;
|
||
bOriginal = false;
|
||
|
||
htab->emplace(vkey, htab_entry);
|
||
}
|
||
}
|
||
}
|
||
HelpIndex_End();
|
||
|
||
mux_fclose(fp);
|
||
}
|
||
|
||
void helpindex_load(dbref player)
|
||
{
|
||
for (int i = 0; i < mudstate.nHelpDesc; i++)
|
||
{
|
||
helpindex_read(i);
|
||
}
|
||
if ( player != NOTHING
|
||
&& !Quiet(player))
|
||
{
|
||
notify(player, M_("Cache for help indexes refreshed."));
|
||
}
|
||
}
|
||
|
||
void helpindex_init(void)
|
||
{
|
||
helpindex_load(NOTHING);
|
||
}
|
||
|
||
static const UTF8 *MakeCanonicalTopicName(const UTF8 *topic_arg, size_t &nTopic)
|
||
{
|
||
thread_local UTF8 Buffer[LBUF_SIZE];
|
||
|
||
const UTF8 *topic;
|
||
if (topic_arg[0] == '\0')
|
||
{
|
||
topic = T("help");
|
||
nTopic = 4;
|
||
}
|
||
else
|
||
{
|
||
topic = mux_strlwr(topic_arg, nTopic);
|
||
memcpy(Buffer, topic, nTopic+1);
|
||
topic = Buffer;
|
||
}
|
||
return topic;
|
||
}
|
||
|
||
static void ReportMatchedTopics(dbref executor, const UTF8 *topic, StringPtrMap *htab)
|
||
{
|
||
bool matched = false;
|
||
LBuf topic_list = LBuf_Src("help_write");
|
||
UTF8 *buffp = topic_list.get();
|
||
for (auto &[key, val] : *htab)
|
||
{
|
||
struct help_entry *htab_entry = static_cast<struct help_entry *>(val);
|
||
mudstate.wild_invk_ctr = 0;
|
||
if ( htab_entry->key
|
||
&& quick_wild(topic, htab_entry->key))
|
||
{
|
||
matched = true;
|
||
safe_str(htab_entry->key, topic_list, &buffp);
|
||
safe_chr(' ', topic_list, &buffp);
|
||
safe_chr(' ', topic_list, &buffp);
|
||
}
|
||
}
|
||
|
||
if (!matched)
|
||
{
|
||
notify(executor, tprintf(M_("No entry for ‘%s’."), topic));
|
||
}
|
||
else
|
||
{
|
||
notify(executor, tprintf(M_("Here are the entries which match ‘%s’:"), topic));
|
||
*buffp = '\0';
|
||
notify(executor, topic_list);
|
||
}
|
||
}
|
||
|
||
static bool ReportTopic(dbref executor, struct help_entry *htab_entry, int iHelpfile,
|
||
UTF8 *result)
|
||
{
|
||
UTF8 szTextFilename[SBUF_SIZE+8];
|
||
mux_sprintf(szTextFilename, sizeof(szTextFilename), T("%s.txt"),
|
||
mudstate.aHelpDesc[iHelpfile].pBaseFilename);
|
||
|
||
size_t offset = htab_entry->pos;
|
||
FILE *fp;
|
||
if (!mux_fopen(&fp, szTextFilename, T("rb")))
|
||
{
|
||
STARTLOG(LOG_PROBLEMS, "HLP", "OPEN");
|
||
LBuf line = LBuf_Src("ReportTopic.open");
|
||
mux_sprintf(line.get(), LBUF_SIZE, M_("Can’t open %s for reading."), szTextFilename);
|
||
log_text(line);
|
||
ENDLOG;
|
||
return false;
|
||
}
|
||
if (fseek(fp, static_cast<long>(offset), 0) < 0L)
|
||
{
|
||
STARTLOG(LOG_PROBLEMS, "HLP", "SEEK");
|
||
LBuf line = LBuf_Src("ReportTopic.seek");
|
||
mux_sprintf(line.get(), LBUF_SIZE, M_("Seek error in file %s."), szTextFilename);
|
||
log_text(line);
|
||
ENDLOG;
|
||
mux_fclose(fp);
|
||
return false;
|
||
}
|
||
LBuf line = LBuf_Src("ReportTopic");
|
||
UTF8 *bp = result;
|
||
bool bInTopicAliases = true;
|
||
for (;;)
|
||
{
|
||
if ( fgets(reinterpret_cast<char *>(line.get()), LBUF_SIZE - 2, fp) == nullptr
|
||
|| '\0' == line[0])
|
||
{
|
||
break;
|
||
}
|
||
|
||
if ('&' == line[0])
|
||
{
|
||
if (bInTopicAliases)
|
||
{
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
bInTopicAliases = false;
|
||
|
||
// Transform LF into CRLF to be telnet-friendly.
|
||
//
|
||
size_t len = strlen(reinterpret_cast<char *>(line.get()));
|
||
if ( 0 < len
|
||
&& '\n' == line[len-1]
|
||
&& ( 1 == len
|
||
|| '\r' != line[len-2]))
|
||
{
|
||
line[len-1] = '\r';
|
||
line[len ] = '\n';
|
||
line[++len] = '\0';
|
||
}
|
||
|
||
bool bEval = mudstate.aHelpDesc[iHelpfile].bEval;
|
||
if (bEval)
|
||
{
|
||
dbref executor_for_help = executor;
|
||
if (Good_obj(mudconf.help_executor))
|
||
{
|
||
executor_for_help = mudconf.help_executor;
|
||
}
|
||
mux_exec(line, len, result, &bp, executor_for_help, executor, executor,
|
||
EV_NO_COMPRESS | EV_FIGNORE | EV_EVAL, nullptr, 0);
|
||
}
|
||
else
|
||
{
|
||
safe_str(line, result, &bp);
|
||
}
|
||
}
|
||
|
||
// Zap trailing CRLF if present.
|
||
//
|
||
if ( result < bp - 1
|
||
&& '\r' == bp[-2]
|
||
&& '\n' == bp[-1])
|
||
{
|
||
bp -= 2;
|
||
}
|
||
*bp = '\0';
|
||
|
||
mux_fclose(fp);
|
||
return true;
|
||
}
|
||
|
||
static void help_write(dbref executor, UTF8 *topic_arg, int iHelpfile)
|
||
{
|
||
size_t nTopic;
|
||
const UTF8 *topic = MakeCanonicalTopicName(topic_arg, nTopic);
|
||
|
||
StringPtrMap *htab = mudstate.aHelpDesc[iHelpfile].ht;
|
||
auto it = htab->find(std::vector<UTF8>(topic, topic + nTopic));
|
||
struct help_entry *htab_entry = (it != htab->end())
|
||
? static_cast<struct help_entry *>(it->second) : nullptr;
|
||
|
||
if (htab_entry)
|
||
{
|
||
LBuf result = LBuf_Src("help_write");
|
||
if (ReportTopic(executor, htab_entry, iHelpfile, result))
|
||
{
|
||
notify(executor, result);
|
||
}
|
||
else
|
||
{
|
||
notify(executor, M_("Sorry, that function is temporarily unavailable."));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ReportMatchedTopics(executor, topic, htab);
|
||
return;
|
||
}
|
||
}
|
||
|
||
static bool ValidateHelpFileIndex(int iHelpfile)
|
||
{
|
||
if ( iHelpfile < 0
|
||
|| mudstate.mHelpDesc <= iHelpfile)
|
||
{
|
||
UTF8 *buf = alloc_mbuf("do_help.LOG");
|
||
STARTLOG(LOG_BUGS, "BUG", "HELP");
|
||
mux_sprintf(buf, MBUF_SIZE, M_("Unknown help file number: %d"), iHelpfile);
|
||
log_text(buf);
|
||
ENDLOG;
|
||
free_mbuf(buf);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* ---------------------------------------------------------------------------
|
||
* * do_help: display information from new-format news and help files
|
||
*/
|
||
|
||
void do_help(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *message, const UTF8 *cargs[], int ncargs)
|
||
{
|
||
UNUSED_PARAMETER(caller);
|
||
UNUSED_PARAMETER(enactor);
|
||
UNUSED_PARAMETER(eval);
|
||
UNUSED_PARAMETER(cargs);
|
||
UNUSED_PARAMETER(ncargs);
|
||
|
||
int iHelpfile = key;
|
||
|
||
if (!ValidateHelpFileIndex(iHelpfile))
|
||
{
|
||
notify(executor, M_("No such indexed file found."));
|
||
return;
|
||
}
|
||
help_write(executor, message, iHelpfile);
|
||
}
|
||
|
||
void help_helper(dbref executor, int iHelpfile, const UTF8 *topic_arg,
|
||
UTF8 *buff, UTF8 **bufc)
|
||
{
|
||
if (!ValidateHelpFileIndex(iHelpfile))
|
||
{
|
||
return;
|
||
}
|
||
|
||
size_t nTopic;
|
||
const UTF8 *topic = MakeCanonicalTopicName(topic_arg, nTopic);
|
||
|
||
StringPtrMap *htab = mudstate.aHelpDesc[iHelpfile].ht;
|
||
auto it = htab->find(std::vector<UTF8>(topic, topic + nTopic));
|
||
struct help_entry *htab_entry = (it != htab->end())
|
||
? static_cast<struct help_entry *>(it->second) : nullptr;
|
||
|
||
if (htab_entry)
|
||
{
|
||
LBuf result = LBuf_Src("help_helper");
|
||
if (ReportTopic(executor, htab_entry, iHelpfile, result))
|
||
{
|
||
safe_str(result, buff, bufc);
|
||
}
|
||
else
|
||
{
|
||
safe_str(S_("#-1 ERROR"), buff, bufc);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
safe_str(S_("#-1 TOPIC DOES NOT EXIST"), buff, bufc);
|
||
}
|
||
}
|