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