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