sourcemod/core/smn_string.cpp
2008-07-11 08:18:43 +00:00

623 lines
14 KiB
C++

/**
* vim: set ts=4 :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#include <string.h>
#include <stdlib.h>
#include "sm_globals.h"
#include "sm_stringutil.h"
#include <ITextParsers.h>
#include <ctype.h>
inline const char *_strstr(const char *str, const char *substr)
{
#ifdef PLATFORM_WINDOWS
return strstr(str, substr);
#elif defined PLATFORM_LINUX
return (const char *)strstr(str, substr);
#endif
}
/*********************************************
* *
* STRING MANIPULATION NATIVE IMPLEMENTATIONS *
* *
*********************************************/
static cell_t sm_strlen(IPluginContext *pCtx, const cell_t *params)
{
char *str;
pCtx->LocalToString(params[1], &str);
return strlen(str);
}
static cell_t sm_contain(IPluginContext *pCtx, const cell_t *params)
{
typedef const char *(*STRSEARCH)(const char *, const char *);
STRSEARCH func;
char *str, *substr;
pCtx->LocalToString(params[1], &str);
pCtx->LocalToString(params[2], &substr);
func = (params[3]) ? _strstr : stristr;
const char *pos = func(str, substr);
if (pos)
{
return (pos - str);
}
return -1;
}
static cell_t sm_strcmp(IPluginContext *pCtx, const cell_t *params)
{
typedef int (*STRCOMPARE)(const char *, const char *);
STRCOMPARE func;
char *str1, *str2;
pCtx->LocalToString(params[1], &str1);
pCtx->LocalToString(params[2], &str2);
func = (params[3]) ? strcmp : stricmp;
return (func(str1, str2));
}
static cell_t sm_strncmp(IPluginContext *pCtx, const cell_t *params)
{
char *str1, *str2;
pCtx->LocalToString(params[1], &str1);
pCtx->LocalToString(params[2], &str2);
if (params[4])
{
return strncmp(str1, str2, (size_t)params[3]);
} else {
return strncasecmp(str1, str2, (size_t)params[3]);
}
}
static cell_t sm_strcopy(IPluginContext *pCtx, const cell_t *params)
{
char *dest, *src;
pCtx->LocalToString(params[1], &dest);
pCtx->LocalToString(params[3], &src);
return strncopy(dest, src, params[2]);
}
static cell_t sm_strconvint(IPluginContext *pCtx, const cell_t *params)
{
char *str, *dummy;
pCtx->LocalToString(params[1], &str);
return static_cast<cell_t>(strtol(str, &dummy, params[2]));
}
static cell_t StringToIntEx(IPluginContext *pCtx, const cell_t *params)
{
char *str, *dummy = NULL;
cell_t *addr;
pCtx->LocalToString(params[1], &str);
pCtx->LocalToPhysAddr(params[2], &addr);
*addr = static_cast<cell_t>(strtol(str, &dummy, params[3]));
return dummy - str;
}
static cell_t sm_numtostr(IPluginContext *pCtx, const cell_t *params)
{
char *str;
pCtx->LocalToString(params[2], &str);
size_t res = UTIL_Format(str, params[3], "%d", params[1]);
return static_cast<cell_t>(res);
}
static cell_t sm_strtofloat(IPluginContext *pCtx, const cell_t *params)
{
char *str, *dummy;
pCtx->LocalToString(params[1], &str);
float val = (float)strtod(str, &dummy);
return sp_ftoc(val);
}
static cell_t StringToFloatEx(IPluginContext *pCtx, const cell_t *params)
{
char *str, *dummy = NULL;
cell_t *addr;
pCtx->LocalToString(params[1], &str);
pCtx->LocalToPhysAddr(params[2], &addr);
float val = (float)strtod(str, &dummy);
*addr = sp_ftoc(val);
return dummy - str;
}
static cell_t sm_floattostr(IPluginContext *pCtx, const cell_t *params)
{
char *str;
pCtx->LocalToString(params[2], &str);
size_t res = UTIL_Format(str, params[3], "%f", sp_ctof(params[1]));
return static_cast<cell_t>(res);
}
static cell_t sm_formatex(IPluginContext *pCtx, const cell_t *params)
{
char *buf, *fmt;
size_t res;
int arg = 4;
pCtx->LocalToString(params[1], &buf);
pCtx->LocalToString(params[3], &fmt);
res = atcprintf(buf, static_cast<size_t>(params[2]), fmt, pCtx, params, &arg);
return static_cast<cell_t>(res);
}
static char g_formatbuf[2048];
static cell_t sm_format(IPluginContext *pCtx, const cell_t *params)
{
char *buf, *fmt, *destbuf;
cell_t start_addr, end_addr, maxparam;
size_t res, maxlen;
int arg = 4;
bool copy = false;
pCtx->LocalToString(params[1], &destbuf);
pCtx->LocalToString(params[3], &fmt);
maxlen = static_cast<size_t>(params[2]);
start_addr = params[1];
end_addr = params[1] + maxlen;
maxparam = params[0];
for (cell_t i=3; i<=maxparam; i++)
{
if ((params[i] >= start_addr) && (params[i] <= end_addr))
{
copy = true;
break;
}
}
buf = (copy) ? g_formatbuf : destbuf;
res = atcprintf(buf, maxlen, fmt, pCtx, params, &arg);
if (copy)
{
memcpy(destbuf, g_formatbuf, res+1);
}
return static_cast<cell_t>(res);
}
static cell_t sm_vformat(IPluginContext *pContext, const cell_t *params)
{
int vargPos = static_cast<int>(params[4]);
/* Get the parent parameter array */
cell_t *local_params = pContext->GetLocalParams();
cell_t max = local_params[0];
if (vargPos > (int)max + 1)
{
return pContext->ThrowNativeError("Argument index is invalid: %d", vargPos);
}
cell_t addr_start = params[1];
cell_t addr_end = addr_start + params[2];
bool copy = false;
for (int i=vargPos; i<=max; i++)
{
/* Does this clip bounds? */
if ((local_params[i] >= addr_start)
&& (local_params[i] <= addr_end))
{
copy = true;
break;
}
}
/* Get destination info */
char *format, *destination;
size_t maxlen = static_cast<size_t>(params[2]);
if (copy)
{
destination = g_formatbuf;
} else {
pContext->LocalToString(params[1], &destination);
}
pContext->LocalToString(params[3], &format);
size_t total = atcprintf(destination, maxlen, format, pContext, local_params, &vargPos);
/* Perform copy-on-write if we need to */
if (copy)
{
pContext->StringToLocal(params[1], maxlen, g_formatbuf);
}
return total;
}
/* :TODO: make this UTF8 safe */
static cell_t BreakString(IPluginContext *pContext, const cell_t *params)
{
const char *input;
char *out;
size_t outMax;
/* Get parameters */
pContext->LocalToString(params[1], (char **)&input);
pContext->LocalToString(params[2], &out);
outMax = params[3];
const char *inptr = input;
/* Eat up whitespace */
while (*inptr != '\0' && textparsers->IsWhitespace(inptr))
{
inptr++;
}
if (*inptr == '\0')
{
if (outMax)
{
*out = '\0';
}
return -1;
}
const char *start, *end = NULL;
bool quoted = (*inptr == '"');
if (quoted)
{
inptr++;
start = inptr;
/* Read input until we reach a quote. */
while (*inptr != '\0' && *inptr != '"')
{
/* Update the end point, increment the stream. */
end = inptr++;
}
/* Read one more token if we reached an end quote */
if (*inptr == '"')
{
inptr++;
}
} else {
start = inptr;
/* Read input until we reach a space */
while (*inptr != '\0' && !textparsers->IsWhitespace(inptr))
{
/* Update the end point, increment the stream. */
end = inptr++;
}
}
/* Copy the string we found, if necessary */
if (end == NULL)
{
if (outMax)
{
*out = '\0';
}
} else if (outMax) {
char *outptr = out;
outMax--;
for (const char *ptr=start;
(ptr <= end) && ((unsigned)(outptr - out) < (outMax));
ptr++, outptr++)
{
*outptr = *ptr;
}
*outptr = '\0';
}
/* Consume more of the string until we reach non-whitespace */
while (*inptr != '\0' && textparsers->IsWhitespace(inptr))
{
inptr++;
}
if (*inptr == '\0')
{
return -1;
}
return inptr - input;
}
static cell_t GetCharBytes(IPluginContext *pContext, const cell_t *params)
{
char *str;
pContext->LocalToString(params[1], &str);
return _GetUTF8CharBytes(str);
};
static cell_t IsCharAlpha(IPluginContext *pContext, const cell_t *params)
{
char chr = params[1];
if (_GetUTF8CharBytes(&chr) != 1)
{
return 0;
}
return isalpha(chr);
}
static cell_t IsCharNumeric(IPluginContext *pContext, const cell_t *params)
{
char chr = params[1];
if (_GetUTF8CharBytes(&chr) != 1)
{
return 0;
}
return isdigit(chr);
}
static cell_t IsCharSpace(IPluginContext *pContext, const cell_t *params)
{
char chr = params[1];
if (_GetUTF8CharBytes(&chr) != 1)
{
return 0;
}
return isspace(chr);
}
static cell_t IsCharMB(IPluginContext *pContext, const cell_t *params)
{
char chr = params[1];
unsigned int bytes = _GetUTF8CharBytes(&chr);
if (bytes == 1)
{
return 0;
}
return bytes;
}
static cell_t IsCharUpper(IPluginContext *pContext, const cell_t *params)
{
char chr = params[1];
if (_GetUTF8CharBytes(&chr) != 1)
{
return 0;
}
return isupper(chr);
}
static cell_t IsCharLower(IPluginContext *pContext, const cell_t *params)
{
char chr = params[1];
if (_GetUTF8CharBytes(&chr) != 1)
{
return 0;
}
return islower(chr);
}
static cell_t ReplaceString(IPluginContext *pContext, const cell_t *params)
{
char *text, *search, *replace;
size_t maxlength;
pContext->LocalToString(params[1], &text);
pContext->LocalToString(params[3], &search);
pContext->LocalToString(params[4], &replace);
maxlength = (size_t)params[2];
if (search[0] == '\0')
{
return pContext->ThrowNativeError("Cannot replace searches of empty strings");
}
return UTIL_ReplaceAll(text, maxlength, search, replace);
}
static cell_t ReplaceStringEx(IPluginContext *pContext, const cell_t *params)
{
char *text, *search, *replace;
size_t maxlength;
pContext->LocalToString(params[1], &text);
pContext->LocalToString(params[3], &search);
pContext->LocalToString(params[4], &replace);
maxlength = (size_t)params[2];
size_t searchLen = (params[5] == -1) ? strlen(search) : (size_t)params[5];
size_t replaceLen = (params[6] == -1) ? strlen(replace) : (size_t)params[6];
if (searchLen == 0)
{
return pContext->ThrowNativeError("Cannot replace searches of empty strings");
}
char *ptr = UTIL_ReplaceEx(text, maxlength, search, searchLen, replace, replaceLen);
if (ptr == NULL)
{
return -1;
}
return ptr - text;
}
static cell_t TrimString(IPluginContext *pContext, const cell_t *params)
{
char *str;
pContext->LocalToString(params[1], &str);
size_t chars = strlen(str);
if (chars == 0)
{
return 0;
}
char *end = str + chars - 1;
/* Iterate backwards through string until we reach first non-whitespace char */
while (end >= str && textparsers->IsWhitespace(end))
{
end--;
}
/* Replace first whitespace char (at the end) with null terminator */
*(end + 1) = '\0';
/* Iterate forwards through string until first non-whitespace char is reached */
while (textparsers->IsWhitespace(str))
{
str++;
}
size_t bytes;
pContext->StringToLocalUTF8(params[1], chars + 1, str, &bytes);
return bytes;
}
static cell_t SplitString(IPluginContext *pContext, const cell_t *params)
{
char *text, *split;
pContext->LocalToString(params[1], &text);
pContext->LocalToString(params[2], &split);
size_t maxLen = (size_t)params[4];
size_t textLen = strlen(text);
size_t splitLen = strlen(split);
if (splitLen > textLen)
{
return -1;
}
/**
* Note that it's <= ... you could also just add 1,
* but this is a bit nicer
*/
for (size_t i=0; i<=textLen - splitLen; i++)
{
if (strncmp(&text[i], split, splitLen) == 0)
{
/* Split hereeeee */
if (i >= maxLen)
{
pContext->StringToLocalUTF8(params[3], maxLen, text, NULL);
} else {
pContext->StringToLocalUTF8(params[3], i+1, text, NULL);
}
return (cell_t)(i + splitLen);
}
}
return -1;
}
static cell_t StripQuotes(IPluginContext *pContext, const cell_t *params)
{
char *text;
size_t length;
pContext->LocalToString(params[1], &text);
length = strlen(text);
if (text[0] == '"' && text[length-1] == '"')
{
/* Null-terminate */
text[--length] = '\0';
/* Move the remaining bytes (including null terminator) down by one */
memmove(&text[0], &text[1], length);
return 1;
}
return 0;
}
REGISTER_NATIVES(basicStrings)
{
{"BreakString", BreakString},
{"FloatToString", sm_floattostr},
{"Format", sm_format},
{"FormatEx", sm_formatex},
{"GetCharBytes", GetCharBytes},
{"IntToString", sm_numtostr},
{"IsCharAlpha", IsCharAlpha},
{"IsCharLower", IsCharLower},
{"IsCharMB", IsCharMB},
{"IsCharNumeric", IsCharNumeric},
{"IsCharSpace", IsCharSpace},
{"IsCharUpper", IsCharUpper},
{"ReplaceString", ReplaceString},
{"ReplaceStringEx", ReplaceStringEx},
{"SplitString", SplitString},
{"strlen", sm_strlen},
{"StrContains", sm_contain},
{"strcmp", sm_strcmp},
{"strncmp", sm_strncmp},
{"strcopy", sm_strcopy},
{"StringToInt", sm_strconvint},
{"StringToIntEx", StringToIntEx},
{"StringToFloat", sm_strtofloat},
{"StringToFloatEx", StringToFloatEx},
{"StripQuotes", StripQuotes},
{"TrimString", TrimString},
{"VFormat", sm_vformat},
{NULL, NULL},
};