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: };