mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-15 00:41:57 +00:00
401 lines
9.5 KiB
C++
401 lines
9.5 KiB
C++
|
/*
|
||
|
** stringtable.cpp
|
||
|
** Implements the FStringTable class
|
||
|
**
|
||
|
**---------------------------------------------------------------------------
|
||
|
** Copyright 1998-2005 Randy Heit
|
||
|
** All rights reserved.
|
||
|
**
|
||
|
** Redistribution and use in source and binary forms, with or without
|
||
|
** modification, are permitted provided that the following conditions
|
||
|
** are met:
|
||
|
**
|
||
|
** 1. Redistributions of source code must retain the above copyright
|
||
|
** notice, this list of conditions and the following disclaimer.
|
||
|
** 2. Redistributions in binary form must reproduce the above copyright
|
||
|
** notice, this list of conditions and the following disclaimer in the
|
||
|
** documentation and/or other materials provided with the distribution.
|
||
|
** 3. The name of the author may not be used to endorse or promote products
|
||
|
** derived from this software without specific prior written permission.
|
||
|
**
|
||
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
**---------------------------------------------------------------------------
|
||
|
**
|
||
|
*/
|
||
|
|
||
|
#include <string.h>
|
||
|
#include <stddef.h>
|
||
|
|
||
|
#include "stringtable.h"
|
||
|
#include "cmdlib.h"
|
||
|
#include "m_swap.h"
|
||
|
#include "w_wad.h"
|
||
|
#include "i_system.h"
|
||
|
#include "sc_man.h"
|
||
|
#include "zstring.h"
|
||
|
#include "c_dispatch.h"
|
||
|
|
||
|
// PassNum identifies which language pass this string is from.
|
||
|
// PassNum 0 is for DeHacked.
|
||
|
// PassNum 1 is for * strings.
|
||
|
// PassNum 2+ are for specific locales.
|
||
|
|
||
|
struct FStringTable::StringEntry
|
||
|
{
|
||
|
StringEntry *Next;
|
||
|
char *Name;
|
||
|
BYTE PassNum;
|
||
|
char String[2];
|
||
|
};
|
||
|
|
||
|
FStringTable::FStringTable ()
|
||
|
{
|
||
|
for (int i = 0; i < HASH_SIZE; ++i)
|
||
|
{
|
||
|
Buckets[i] = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FStringTable::~FStringTable ()
|
||
|
{
|
||
|
FreeData ();
|
||
|
}
|
||
|
|
||
|
void FStringTable::FreeData ()
|
||
|
{
|
||
|
for (int i = 0; i < HASH_SIZE; ++i)
|
||
|
{
|
||
|
StringEntry *entry = Buckets[i], *next;
|
||
|
Buckets[i] = NULL;
|
||
|
while (entry != NULL)
|
||
|
{
|
||
|
next = entry->Next;
|
||
|
free (entry);
|
||
|
entry = next;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FStringTable::FreeNonDehackedStrings ()
|
||
|
{
|
||
|
for (int i = 0; i < HASH_SIZE; ++i)
|
||
|
{
|
||
|
StringEntry *entry, *next, **pentry;
|
||
|
|
||
|
for (pentry = &Buckets[i], entry = *pentry; entry != NULL; )
|
||
|
{
|
||
|
next = entry->Next;
|
||
|
if (entry->PassNum != 0)
|
||
|
{
|
||
|
*pentry = next;
|
||
|
free (entry);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pentry = &entry->Next;
|
||
|
}
|
||
|
entry = next;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#include "doomerrors.h"
|
||
|
void FStringTable::LoadStrings (bool enuOnly)
|
||
|
{
|
||
|
int lastlump, lump;
|
||
|
int i, j;
|
||
|
|
||
|
FreeNonDehackedStrings ();
|
||
|
|
||
|
lastlump = 0;
|
||
|
|
||
|
while ((lump = Wads.FindLump ("LANGUAGE", &lastlump)) != -1)
|
||
|
{
|
||
|
j = 0;
|
||
|
if (!enuOnly)
|
||
|
{
|
||
|
LoadLanguage (lump, MAKE_ID('*',0,0,0), true, ++j);
|
||
|
for (i = 0; i < 4; ++i)
|
||
|
{
|
||
|
LoadLanguage (lump, LanguageIDs[i], true, ++j);
|
||
|
LoadLanguage (lump, LanguageIDs[i] & MAKE_ID(0xff,0xff,0,0), true, ++j);
|
||
|
LoadLanguage (lump, LanguageIDs[i], false, ++j);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Fill in any missing strings with the default language
|
||
|
LoadLanguage (lump, MAKE_ID('*','*',0,0), true, ++j);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FStringTable::LoadLanguage (int lumpnum, DWORD code, bool exactMatch, int passnum)
|
||
|
{
|
||
|
const DWORD orMask = exactMatch ? 0 : MAKE_ID(0,0,0xff,0);
|
||
|
DWORD inCode = 0;
|
||
|
StringEntry *entry, **pentry;
|
||
|
DWORD bucket;
|
||
|
int cmpval;
|
||
|
bool skip = true;
|
||
|
|
||
|
code |= orMask;
|
||
|
|
||
|
SC_OpenLumpNum (lumpnum, "LANGUAGE");
|
||
|
SC_SetCMode (true);
|
||
|
while (SC_GetString ())
|
||
|
{
|
||
|
if (SC_Compare ("["))
|
||
|
{ // Process language identifiers
|
||
|
bool donot = false;
|
||
|
bool forceskip = false;
|
||
|
skip = true;
|
||
|
SC_MustGetString ();
|
||
|
do
|
||
|
{
|
||
|
size_t len = strlen (sc_String);
|
||
|
if (len != 2 && len != 3)
|
||
|
{
|
||
|
if (len == 1 && sc_String[0] == '~')
|
||
|
{
|
||
|
donot = true;
|
||
|
SC_MustGetString ();
|
||
|
continue;
|
||
|
}
|
||
|
if (len == 1 && sc_String[0] == '*')
|
||
|
{
|
||
|
inCode = MAKE_ID('*',0,0,0);
|
||
|
}
|
||
|
else if (len == 7 && stricmp (sc_String, "default") == 0)
|
||
|
{
|
||
|
inCode = MAKE_ID('*','*',0,0);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SC_ScriptError ("The language code must be 2 or 3 characters long.\n'%s' is %lu characters long.",
|
||
|
sc_String, len);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
inCode = MAKE_ID(tolower(sc_String[0]), tolower(sc_String[1]), tolower(sc_String[2]), 0);
|
||
|
}
|
||
|
if ((inCode | orMask) == code)
|
||
|
{
|
||
|
if (donot)
|
||
|
{
|
||
|
forceskip = true;
|
||
|
donot = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
skip = false;
|
||
|
}
|
||
|
}
|
||
|
SC_MustGetString ();
|
||
|
} while (!SC_Compare ("]"));
|
||
|
if (donot)
|
||
|
{
|
||
|
SC_ScriptError ("You must specify a language after ~");
|
||
|
}
|
||
|
skip |= forceskip;
|
||
|
}
|
||
|
else
|
||
|
{ // Process string definitions.
|
||
|
if (inCode == 0)
|
||
|
{
|
||
|
SC_ScriptError ("Found a string without a language specified.");
|
||
|
}
|
||
|
|
||
|
if (skip)
|
||
|
{ // We're not interested in this language, so skip the string.
|
||
|
SC_MustGetStringName ("=");
|
||
|
SC_MustGetString ();
|
||
|
do
|
||
|
{
|
||
|
SC_MustGetString ();
|
||
|
} while (!SC_Compare (";"));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
string strName (sc_String);
|
||
|
SC_MustGetStringName ("=");
|
||
|
SC_MustGetString ();
|
||
|
string strText (sc_String, ProcessEscapes (sc_String));
|
||
|
SC_MustGetString ();
|
||
|
while (!SC_Compare (";"))
|
||
|
{
|
||
|
ProcessEscapes (sc_String);
|
||
|
strText += sc_String;
|
||
|
SC_MustGetString ();
|
||
|
}
|
||
|
|
||
|
// Does this string exist? If so, should we overwrite it?
|
||
|
bucket = MakeKey (strName.GetChars()) & (HASH_SIZE-1);
|
||
|
pentry = &Buckets[bucket];
|
||
|
entry = *pentry;
|
||
|
cmpval = 1;
|
||
|
while (entry != NULL)
|
||
|
{
|
||
|
cmpval = stricmp (entry->Name, strName.GetChars());
|
||
|
if (cmpval >= 0)
|
||
|
break;
|
||
|
pentry = &entry->Next;
|
||
|
entry = *pentry;
|
||
|
}
|
||
|
if (cmpval == 0 && entry->PassNum >= passnum)
|
||
|
{
|
||
|
*pentry = entry->Next;
|
||
|
free (entry);
|
||
|
entry = NULL;
|
||
|
}
|
||
|
if (entry == NULL || cmpval > 0)
|
||
|
{
|
||
|
entry = (StringEntry *)Malloc (sizeof(*entry) + strText.Len() + strName.Len());
|
||
|
entry->Next = *pentry;
|
||
|
*pentry = entry;
|
||
|
strcpy (entry->String, strText.GetChars());
|
||
|
strcpy (entry->Name = entry->String + strText.Len() + 1, strName.GetChars());
|
||
|
entry->PassNum = passnum;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Replace \ escape sequences in a string with the escaped characters.
|
||
|
size_t FStringTable::ProcessEscapes (char *iptr)
|
||
|
{
|
||
|
char *sptr = iptr, *optr = iptr, c;
|
||
|
|
||
|
while ((c = *iptr++) != '\0')
|
||
|
{
|
||
|
if (c == '\\')
|
||
|
{
|
||
|
c = *iptr++;
|
||
|
if (c == 'n')
|
||
|
c = '\n';
|
||
|
else if (c == 'c')
|
||
|
c = -127 /*0x81*/;
|
||
|
else if (c == 'r')
|
||
|
c = '\r';
|
||
|
else if (c == 't')
|
||
|
c = '\t';
|
||
|
else if (c == '\n')
|
||
|
continue;
|
||
|
}
|
||
|
*optr++ = c;
|
||
|
}
|
||
|
*optr = '\0';
|
||
|
return optr - sptr;
|
||
|
}
|
||
|
|
||
|
// Finds a string by name and returns its value
|
||
|
const char *FStringTable::operator[] (const char *name) const
|
||
|
{
|
||
|
DWORD bucket = MakeKey (name) & (HASH_SIZE - 1);
|
||
|
StringEntry *entry = Buckets[bucket];
|
||
|
|
||
|
while (entry != NULL)
|
||
|
{
|
||
|
int cmpval = stricmp (entry->Name, name);
|
||
|
if (cmpval == 0)
|
||
|
{
|
||
|
return entry->String;
|
||
|
}
|
||
|
if (cmpval == 1)
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
entry = entry->Next;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// Finds a string by name and returns its value. If the string does
|
||
|
// not exist, returns the passed name instead.
|
||
|
const char *FStringTable::operator() (const char *name) const
|
||
|
{
|
||
|
const char *str = operator[] (name);
|
||
|
return str ? str : name;
|
||
|
}
|
||
|
|
||
|
// Find a string by name. pentry1 is a pointer to a pointer to it, and entry1 is a
|
||
|
// pointer to it. Return NULL for entry1 if it wasn't found.
|
||
|
void FStringTable::FindString (const char *name, StringEntry **&pentry1, StringEntry *&entry1)
|
||
|
{
|
||
|
DWORD bucket = MakeKey (name) & (HASH_SIZE - 1);
|
||
|
StringEntry **pentry = &Buckets[bucket], *entry = *pentry;
|
||
|
|
||
|
while (entry != NULL)
|
||
|
{
|
||
|
int cmpval = stricmp (entry->Name, name);
|
||
|
if (cmpval == 0)
|
||
|
{
|
||
|
pentry1 = pentry;
|
||
|
entry1 = entry;
|
||
|
return;
|
||
|
}
|
||
|
if (cmpval == 1)
|
||
|
{
|
||
|
pentry1 = pentry;
|
||
|
entry1 = NULL;
|
||
|
return;
|
||
|
}
|
||
|
pentry = &entry->Next;
|
||
|
entry = *pentry;
|
||
|
}
|
||
|
pentry1 = pentry;
|
||
|
entry1 = entry;
|
||
|
}
|
||
|
|
||
|
// Find a string with the same exact text. Returns its name.
|
||
|
const char *FStringTable::MatchString (const char *string) const
|
||
|
{
|
||
|
for (int i = 0; i < HASH_SIZE; ++i)
|
||
|
{
|
||
|
for (StringEntry *entry = Buckets[i]; entry != NULL; entry = entry->Next)
|
||
|
{
|
||
|
if (strcmp (entry->String, string) == 0)
|
||
|
{
|
||
|
return entry->Name;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void FStringTable::SetString (const char *name, const char *newString)
|
||
|
{
|
||
|
StringEntry **pentry, *oentry;
|
||
|
FindString (name, pentry, oentry);
|
||
|
|
||
|
size_t newlen = strlen (newString);
|
||
|
size_t namelen = strlen (name);
|
||
|
|
||
|
// Create a new string entry
|
||
|
StringEntry *entry = (StringEntry *)Malloc (sizeof(*entry) + newlen + namelen);
|
||
|
strcpy (entry->String, newString);
|
||
|
strcpy (entry->Name = entry->String + newlen + 1, name);
|
||
|
entry->PassNum = 0;
|
||
|
|
||
|
// If this is a new string, insert it. Otherwise, replace the old one.
|
||
|
if (oentry == NULL)
|
||
|
{
|
||
|
entry->Next = *pentry;
|
||
|
*pentry = entry;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*pentry = entry;
|
||
|
entry->Next = oentry->Next;
|
||
|
free (oentry);
|
||
|
}
|
||
|
}
|