core/smn_string.cpp
author David Anderson <dvander@alliedmods.net>
Fri May 14 19:28:10 2010 -0700 (2010-05-14)
changeset 3002 409a6de13f09
parent 2637 ed7b03e3bf16
child 3005 2db965f8db3f
permissions -rw-r--r--
Move some string functions from core to logic (bug 4406 part 4, r=fyren).
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include <string.h>
    33 #include <stdlib.h>
    34 #include "sm_globals.h"
    35 #include "sm_stringutil.h"
    36 #include <ITextParsers.h>
    37 #include <ctype.h>
    38 #include "logic_bridge.h"
    39 
    40 inline const char *_strstr(const char *str, const char *substr)
    41 {
    42 #ifdef PLATFORM_WINDOWS
    43 	return strstr(str, substr);
    44 #elif defined PLATFORM_LINUX || defined PLATFORM_APPLE
    45 	return (const char *)strstr(str, substr);
    46 #endif
    47 }
    48 
    49 /*********************************************
    50 *                                            *
    51 * STRING MANIPULATION NATIVE IMPLEMENTATIONS *
    52 *                                            *
    53 *********************************************/
    54 
    55 static cell_t sm_strlen(IPluginContext *pCtx, const cell_t *params)
    56 {
    57 	char *str;
    58 	pCtx->LocalToString(params[1], &str);
    59 
    60 	return strlen(str);
    61 }
    62 
    63 static cell_t sm_contain(IPluginContext *pCtx, const cell_t *params)
    64 {
    65 	typedef const char *(*STRSEARCH)(const char *, const char *);
    66 	STRSEARCH func;
    67 	char *str, *substr;
    68 
    69 	pCtx->LocalToString(params[1], &str);
    70 	pCtx->LocalToString(params[2], &substr);
    71 
    72 	func = (params[3]) ? _strstr : logicore.stristr;
    73 	const char *pos = func(str, substr);
    74 	if (pos)
    75 	{
    76 		return (pos - str);
    77 	}
    78 
    79 	return -1;
    80 }
    81 
    82 static cell_t sm_strcmp(IPluginContext *pCtx, const cell_t *params)
    83 {
    84 	typedef int (*STRCOMPARE)(const char *, const char *);
    85 	STRCOMPARE func;
    86 	char *str1, *str2;
    87 
    88 	pCtx->LocalToString(params[1], &str1);
    89 	pCtx->LocalToString(params[2], &str2);
    90 
    91 	func = (params[3]) ? strcmp : stricmp;
    92 
    93 	return (func(str1, str2));
    94 }
    95 
    96 static cell_t sm_strncmp(IPluginContext *pCtx, const cell_t *params)
    97 {
    98 	char *str1, *str2;
    99 
   100 	pCtx->LocalToString(params[1], &str1);
   101 	pCtx->LocalToString(params[2], &str2);
   102 	
   103 	if  (params[4])
   104 	{
   105 		return strncmp(str1, str2, (size_t)params[3]);
   106 	} else {
   107 		return strncasecmp(str1, str2, (size_t)params[3]);
   108 	}
   109 }
   110 
   111 static cell_t sm_strcopy(IPluginContext *pCtx, const cell_t *params)
   112 {
   113 	char *dest, *src;
   114 
   115 	pCtx->LocalToString(params[1], &dest);
   116 	pCtx->LocalToString(params[3], &src);
   117 
   118 	return strncopy(dest, src, params[2]);
   119 }
   120 
   121 static cell_t sm_strconvint(IPluginContext *pCtx, const cell_t *params)
   122 {
   123 	char *str, *dummy;
   124 	pCtx->LocalToString(params[1], &str);
   125 
   126 	return static_cast<cell_t>(strtol(str, &dummy, params[2]));
   127 }
   128 
   129 static cell_t StringToIntEx(IPluginContext *pCtx, const cell_t *params)
   130 {
   131 	char *str, *dummy = NULL;
   132 	cell_t *addr;
   133 	pCtx->LocalToString(params[1], &str);
   134 	pCtx->LocalToPhysAddr(params[2], &addr);
   135 
   136 	*addr = static_cast<cell_t>(strtol(str, &dummy, params[3]));
   137 
   138 	return dummy - str;
   139 }
   140 
   141 static cell_t sm_numtostr(IPluginContext *pCtx, const cell_t *params)
   142 {
   143 	char *str;
   144 	pCtx->LocalToString(params[2], &str);
   145 	size_t res = UTIL_Format(str, params[3], "%d", params[1]);
   146 
   147 	return static_cast<cell_t>(res);
   148 }
   149 
   150 static cell_t sm_strtofloat(IPluginContext *pCtx, const cell_t *params)
   151 {
   152 	char *str, *dummy;
   153 	pCtx->LocalToString(params[1], &str);
   154 
   155 	float val = (float)strtod(str, &dummy);
   156 
   157 	return sp_ftoc(val);
   158 }
   159 
   160 static cell_t StringToFloatEx(IPluginContext *pCtx, const cell_t *params)
   161 {
   162 	char *str, *dummy = NULL;
   163 	cell_t *addr;
   164 	pCtx->LocalToString(params[1], &str);
   165 	pCtx->LocalToPhysAddr(params[2], &addr);
   166 
   167 	float val = (float)strtod(str, &dummy);
   168 
   169 	*addr = sp_ftoc(val);
   170 
   171 	return dummy - str;
   172 }
   173 
   174 static cell_t sm_floattostr(IPluginContext *pCtx, const cell_t *params)
   175 {
   176 	char *str;
   177 	pCtx->LocalToString(params[2], &str);
   178 	size_t res = UTIL_Format(str, params[3], "%f", sp_ctof(params[1]));
   179 
   180 	return static_cast<cell_t>(res);
   181 }
   182 
   183 static cell_t sm_formatex(IPluginContext *pCtx, const cell_t *params)
   184 {
   185 	char *buf, *fmt;
   186 	size_t res;
   187 	int arg = 4;
   188 
   189 	pCtx->LocalToString(params[1], &buf);
   190 	pCtx->LocalToString(params[3], &fmt);
   191 	res = atcprintf(buf, static_cast<size_t>(params[2]), fmt, pCtx, params, &arg);
   192 
   193 	return static_cast<cell_t>(res);
   194 }
   195 
   196 class StaticCharBuf
   197 {
   198     char *buffer;
   199     size_t max_size;
   200 public:
   201     StaticCharBuf() : buffer(NULL), max_size(0)
   202     {
   203     }
   204     ~StaticCharBuf()
   205     {
   206         free(buffer);
   207     }
   208     char* GetWithSize(size_t len)
   209     {
   210         if (len > max_size)
   211         {
   212             buffer = (char *)realloc(buffer, len);
   213             max_size = len;
   214         }
   215         return buffer;
   216     }
   217 };
   218 
   219 static char g_formatbuf[2048];
   220 static StaticCharBuf g_extrabuf;
   221 static cell_t sm_format(IPluginContext *pCtx, const cell_t *params)
   222 {
   223 	char *buf, *fmt, *destbuf;
   224 	cell_t start_addr, end_addr, maxparam;
   225 	size_t res, maxlen;
   226 	int arg = 4;
   227 	bool copy = false;
   228 	char *__copy_buf;
   229 
   230 	pCtx->LocalToString(params[1], &destbuf);
   231 	pCtx->LocalToString(params[3], &fmt);
   232 
   233 	maxlen = static_cast<size_t>(params[2]);
   234 	start_addr = params[1];
   235 	end_addr = params[1] + maxlen;
   236 	maxparam = params[0];
   237 
   238 	for (cell_t i=3; i<=maxparam; i++)
   239 	{
   240 		if ((params[i] >= start_addr) && (params[i] <= end_addr))
   241 		{
   242 			copy = true;
   243 			break;
   244 		}
   245 	}
   246 
   247 	if (copy)
   248 	{
   249 		if (maxlen > sizeof(g_formatbuf))
   250 		{
   251 			__copy_buf = g_extrabuf.GetWithSize(maxlen);
   252 		}
   253 		else
   254 		{
   255 			__copy_buf = g_formatbuf;
   256 		}
   257 	}
   258 
   259 	buf = (copy) ? __copy_buf : destbuf;
   260 	res = atcprintf(buf, maxlen, fmt, pCtx, params, &arg);
   261 
   262 	if (copy)
   263 	{
   264 		memcpy(destbuf, __copy_buf, res+1);
   265 	}
   266 
   267 	return static_cast<cell_t>(res);
   268 }
   269 
   270 static cell_t sm_vformat(IPluginContext *pContext, const cell_t *params)
   271 {
   272 	int vargPos = static_cast<int>(params[4]);
   273 
   274 	/* Get the parent parameter array */
   275 	cell_t *local_params = pContext->GetLocalParams();
   276 
   277 	cell_t max = local_params[0];
   278 	if (vargPos > (int)max + 1)
   279 	{
   280 		return pContext->ThrowNativeError("Argument index is invalid: %d", vargPos);
   281 	}
   282 
   283 	cell_t addr_start = params[1];
   284 	cell_t addr_end = addr_start + params[2];
   285 	bool copy = false;
   286 	for (int i=vargPos; i<=max; i++)
   287 	{
   288 		/* Does this clip bounds? */
   289 		if ((local_params[i] >= addr_start)
   290 			&& (local_params[i] <= addr_end))
   291 		{
   292 			copy = true;
   293 			break;
   294 		}
   295 	}
   296 
   297 	/* Get destination info */
   298 	char *format, *destination;
   299 	size_t maxlen = static_cast<size_t>(params[2]);
   300 
   301 	if (copy)
   302 	{
   303 		destination = g_formatbuf;
   304 	} else {
   305 		pContext->LocalToString(params[1], &destination);
   306 	}
   307 
   308 	pContext->LocalToString(params[3], &format);
   309 
   310 	size_t total = atcprintf(destination, maxlen, format, pContext, local_params, &vargPos);
   311 
   312 	/* Perform copy-on-write if we need to */
   313 	if (copy)
   314 	{
   315 		pContext->StringToLocal(params[1], maxlen, g_formatbuf);
   316 	}
   317 
   318 	return total;
   319 }
   320 
   321 /* :TODO: make this UTF8 safe */
   322 static cell_t BreakString(IPluginContext *pContext, const cell_t *params)
   323 {
   324 	const char *input;
   325 	char *out;
   326 	size_t outMax;
   327 
   328 	/* Get parameters */
   329 	pContext->LocalToString(params[1], (char **)&input);
   330 	pContext->LocalToString(params[2], &out);
   331 	outMax = params[3];
   332 
   333 	const char *inptr = input;
   334 	/* Eat up whitespace */
   335 	while (*inptr != '\0' && textparsers->IsWhitespace(inptr))
   336 	{
   337 		inptr++;
   338 	}
   339 
   340 	if (*inptr == '\0')
   341 	{
   342 		if (outMax)
   343 		{
   344 			*out = '\0';
   345 		}
   346 		return -1;
   347 	}
   348 
   349 	const char *start, *end = NULL;
   350 
   351 	bool quoted = (*inptr == '"');
   352 	if (quoted)
   353 	{
   354 		inptr++;
   355 		start = inptr;
   356 		/* Read input until we reach a quote. */
   357 		while (*inptr != '\0' && *inptr != '"')
   358 		{
   359 			/* Update the end point, increment the stream. */
   360 			end = inptr++;
   361 		}
   362 		/* Read one more token if we reached an end quote */
   363 		if (*inptr == '"')
   364 		{
   365 			inptr++;
   366 		}
   367 	} else {
   368 		start = inptr;
   369 		/* Read input until we reach a space */
   370 		while (*inptr != '\0' && !textparsers->IsWhitespace(inptr))
   371 		{
   372 			/* Update the end point, increment the stream. */
   373 			end = inptr++;
   374 		}
   375 	}
   376 
   377 	/* Copy the string we found, if necessary */
   378 	if (end == NULL)
   379 	{
   380 		if (outMax)
   381 		{
   382 			*out = '\0';
   383 		}
   384 	} else if (outMax) {
   385 		char *outptr = out;
   386 		outMax--;
   387 		for (const char *ptr=start;
   388 			(ptr <= end) && ((unsigned)(outptr - out) < (outMax));
   389 			ptr++, outptr++)
   390 			{
   391 			*outptr = *ptr;
   392 		}
   393 		*outptr = '\0';
   394 	}
   395 
   396 	/* Consume more of the string until we reach non-whitespace */
   397 	while (*inptr != '\0' && textparsers->IsWhitespace(inptr))
   398 	{
   399 		inptr++;
   400 	}
   401 
   402 	if (*inptr == '\0')
   403 	{
   404 		return -1;
   405 	}
   406 
   407 	return inptr - input;
   408 }
   409 
   410 static cell_t GetCharBytes(IPluginContext *pContext, const cell_t *params)
   411 {
   412 	char *str;
   413 	pContext->LocalToString(params[1], &str);
   414 
   415 	return _GetUTF8CharBytes(str);
   416 };
   417 
   418 static cell_t IsCharAlpha(IPluginContext *pContext, const cell_t *params)
   419 {
   420 	char chr = params[1];
   421 
   422 	if (_GetUTF8CharBytes(&chr) != 1)
   423 	{
   424 		return 0;
   425 	}
   426 
   427 	return isalpha(chr);
   428 }
   429 
   430 static cell_t IsCharNumeric(IPluginContext *pContext, const cell_t *params)
   431 {
   432 	char chr = params[1];
   433 
   434 	if (_GetUTF8CharBytes(&chr) != 1)
   435 	{
   436 		return 0;
   437 	}
   438 
   439 	return isdigit(chr);
   440 }
   441 
   442 static cell_t IsCharSpace(IPluginContext *pContext, const cell_t *params)
   443 {
   444 	char chr = params[1];
   445 
   446 	if (_GetUTF8CharBytes(&chr) != 1)
   447 	{
   448 		return 0;
   449 	}
   450 
   451 	return isspace(chr);
   452 }
   453 
   454 static cell_t IsCharMB(IPluginContext *pContext, const cell_t *params)
   455 {
   456 	char chr = params[1];
   457 
   458 	unsigned int bytes = _GetUTF8CharBytes(&chr);
   459 	if (bytes == 1)
   460 	{
   461 		return 0;
   462 	}
   463 
   464 	return bytes;
   465 }
   466 
   467 static cell_t IsCharUpper(IPluginContext *pContext, const cell_t *params)
   468 {
   469 	char chr = params[1];
   470 
   471 	if (_GetUTF8CharBytes(&chr) != 1)
   472 	{
   473 		return 0;
   474 	}
   475 
   476 	return isupper(chr);
   477 }
   478 
   479 static cell_t IsCharLower(IPluginContext *pContext, const cell_t *params)
   480 {
   481 	char chr = params[1];
   482 
   483 	if (_GetUTF8CharBytes(&chr) != 1)
   484 	{
   485 		return 0;
   486 	}
   487 
   488 	return islower(chr);
   489 }
   490 
   491 static cell_t ReplaceString(IPluginContext *pContext, const cell_t *params)
   492 {
   493 	char *text, *search, *replace;
   494 	size_t maxlength;
   495 	bool caseSensitive;
   496 	
   497 	pContext->LocalToString(params[1], &text);
   498 	pContext->LocalToString(params[3], &search);
   499 	pContext->LocalToString(params[4], &replace);
   500 	maxlength = (size_t)params[2];
   501 
   502 	/* Account for old binary plugins that won't pass the last parameter */
   503 	if (params[0] == 5) 
   504 	{
   505 		caseSensitive = (bool)params[5];
   506 	}
   507 	else
   508 	{
   509 		caseSensitive = true;
   510 	}
   511 
   512 	if (search[0] == '\0')
   513 	{
   514 		return pContext->ThrowNativeError("Cannot replace searches of empty strings");
   515 	}
   516 
   517 	return UTIL_ReplaceAll(text, maxlength, search, replace, caseSensitive);
   518 }
   519 
   520 static cell_t ReplaceStringEx(IPluginContext *pContext, const cell_t *params)
   521 {
   522 	char *text, *search, *replace;
   523 	size_t maxlength;
   524 	bool caseSensitive;
   525 
   526 	pContext->LocalToString(params[1], &text);
   527 	pContext->LocalToString(params[3], &search);
   528 	pContext->LocalToString(params[4], &replace);
   529 	maxlength = (size_t)params[2];
   530 
   531 	size_t searchLen = (params[5] == -1) ? strlen(search) : (size_t)params[5];
   532 	size_t replaceLen = (params[6] == -1) ? strlen(replace) : (size_t)params[6];
   533 
   534 	/* Account for old binary plugins that won't pass the last parameter */
   535 	if (params[0] == 7)
   536 	{
   537 		caseSensitive = (bool)params[7];
   538 	}
   539 	else
   540 	{
   541 		caseSensitive = true;
   542 	}
   543 
   544 	if (searchLen == 0)
   545 	{
   546 		return pContext->ThrowNativeError("Cannot replace searches of empty strings");
   547 	}
   548 
   549 	char *ptr = UTIL_ReplaceEx(text, maxlength, search, searchLen, replace, replaceLen, caseSensitive);
   550 
   551 	if (ptr == NULL)
   552 	{
   553 		return -1;
   554 	}
   555 
   556 	return ptr - text;
   557 }
   558 
   559 static cell_t TrimString(IPluginContext *pContext, const cell_t *params)
   560 {
   561 	char *str;
   562 	pContext->LocalToString(params[1], &str);
   563 
   564 	size_t chars = strlen(str);
   565 
   566 	if (chars == 0)
   567 	{
   568 		return 0;
   569 	}
   570 
   571 	char *end = str + chars - 1;
   572 
   573 	/* Iterate backwards through string until we reach first non-whitespace char */
   574 	while (end >= str && textparsers->IsWhitespace(end))
   575 	{
   576 		end--;
   577 	}
   578 
   579 	/* Replace first whitespace char (at the end) with null terminator */
   580 	*(end + 1) = '\0';
   581 
   582 	/* Iterate forwards through string until first non-whitespace char is reached */
   583 	while (textparsers->IsWhitespace(str))
   584 	{
   585 		str++;
   586 	}
   587 
   588 	size_t bytes;
   589 	pContext->StringToLocalUTF8(params[1], chars + 1, str, &bytes);
   590 
   591 	return bytes;
   592 }
   593 
   594 static cell_t SplitString(IPluginContext *pContext, const cell_t *params)
   595 {
   596 	char *text, *split;
   597 	
   598 	pContext->LocalToString(params[1], &text);
   599 	pContext->LocalToString(params[2], &split);
   600 
   601 	size_t maxLen = (size_t)params[4];
   602 	size_t textLen = strlen(text);
   603 	size_t splitLen = strlen(split);
   604 
   605 	if (splitLen > textLen)
   606 	{
   607 		return -1;
   608 	}
   609 
   610 	/**
   611 	 * Note that it's <= ... you could also just add 1,
   612 	 * but this is a bit nicer
   613 	 */
   614 	for (size_t i=0; i<=textLen - splitLen; i++)
   615 	{
   616 		if (strncmp(&text[i], split, splitLen) == 0)
   617 		{
   618 			/* Split hereeeee */
   619 			if (i >= maxLen)
   620 			{
   621 				pContext->StringToLocalUTF8(params[3], maxLen, text, NULL);
   622 			} else {
   623 				pContext->StringToLocalUTF8(params[3], i+1, text, NULL);
   624 			}
   625 			return (cell_t)(i + splitLen);
   626 		}
   627 	}
   628 
   629 	return -1;
   630 }
   631 
   632 static cell_t StripQuotes(IPluginContext *pContext, const cell_t *params)
   633 {
   634 	char *text;
   635 	size_t length;
   636 
   637 	pContext->LocalToString(params[1], &text);
   638 	length = strlen(text);
   639 
   640 	if (text[0] == '"' && text[length-1] == '"')
   641 	{
   642 		/* Null-terminate */
   643 		text[--length] = '\0';
   644 		/* Move the remaining bytes (including null terminator) down by one */
   645 		memmove(&text[0], &text[1], length);
   646 
   647 		return 1;
   648 	}
   649 
   650 	return 0;	
   651 }
   652 
   653 REGISTER_NATIVES(basicStrings)
   654 {
   655 	{"BreakString",			BreakString},
   656 	{"FloatToString",		sm_floattostr},
   657 	{"Format",				sm_format},
   658 	{"FormatEx",			sm_formatex},
   659 	{"GetCharBytes",		GetCharBytes},
   660 	{"IntToString",			sm_numtostr},
   661 	{"IsCharAlpha",			IsCharAlpha},
   662 	{"IsCharLower",			IsCharLower},
   663 	{"IsCharMB",			IsCharMB},
   664 	{"IsCharNumeric",		IsCharNumeric},
   665 	{"IsCharSpace",			IsCharSpace},
   666 	{"IsCharUpper",			IsCharUpper},
   667 	{"ReplaceString",		ReplaceString},
   668 	{"ReplaceStringEx",		ReplaceStringEx},
   669 	{"SplitString",			SplitString},
   670 	{"strlen",				sm_strlen},
   671 	{"StrContains",			sm_contain},
   672 	{"strcmp",				sm_strcmp},
   673 	{"strncmp",				sm_strncmp},
   674 	{"strcopy",				sm_strcopy},
   675 	{"StringToInt",			sm_strconvint},
   676 	{"StringToIntEx",		StringToIntEx},
   677 	{"StringToFloat",		sm_strtofloat},
   678 	{"StringToFloatEx",		StringToFloatEx},
   679 	{"StripQuotes",			StripQuotes},
   680 	{"TrimString",			TrimString},
   681 	{"VFormat",				sm_vformat},
   682 	{NULL,					NULL},
   683 };