Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
strlib/strlib.inc
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The code was previously clobbering the parameter count with the destination array size BEFORE modifying it. Now we save it first.
1801 lines (1416 sloc)
41.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #if defined STRLIB_INC | |
| #endinput | |
| #endif | |
| #define STRLIB_INC | |
| #include <a_samp> | |
| #if !defined STRLIB_BUFFER_SIZE | |
| #define STRLIB_BUFFER_SIZE 2048 | |
| #endif | |
| #if !defined STRLIB_RETURN_SIZE | |
| #define STRLIB_RETURN_SIZE 128 | |
| #endif | |
| #if !defined STRLIB_USE_FORMATEX | |
| #if defined __fmt_funcinc | |
| #if !defined FormatSpecifier | |
| #error Please include formatex before strlib. | |
| #endif | |
| #define STRLIB_USE_FORMATEX true | |
| #else | |
| #define STRLIB_USE_FORMATEX false | |
| #endif | |
| #endif | |
| // Used in strtrim (deprecated) | |
| enum trim_edges { | |
| trim_left = 1, | |
| trim_right = 2, | |
| trim_both = trim_left | trim_right | |
| }; | |
| // Used in strtrim and strpad | |
| enum string_edges { | |
| edge_left = 1, | |
| edge_right = 2, | |
| edge_both = edge_left | edge_right | |
| }; | |
| /* | |
| * Returns a formatted string. | |
| * | |
| * Parameters: | |
| * fmat[] - The format string. | |
| * ... - The format variables. | |
| * | |
| * Returns: | |
| * The formatted string. | |
| */ | |
| forward sprintf(const fmat[], {Float, _}:...); | |
| /* | |
| * Get the first character of a string | |
| * | |
| * Parameters: | |
| * string[] - The string. | |
| * | |
| * Returns: | |
| * The first character of the string. | |
| */ | |
| forward strgetfirstc(const string[]); | |
| /* | |
| * Get a character from a specific index in a string. | |
| * | |
| * Parameters: | |
| * string[] - The string. | |
| * index - The position in the string. | |
| * | |
| * Returns: | |
| * The character at that index, or '\0' if out of range. | |
| */ | |
| forward strgetc(const string[], index); | |
| /* | |
| * Get the size of a string. | |
| * | |
| * Parameters: | |
| * string[] - The string. | |
| * | |
| * Returns: | |
| * The size of the string, in bytes. | |
| */ | |
| forward strsize(const string[]); | |
| /* | |
| * Find out if a string is empty. | |
| * | |
| * Parameters: | |
| * string[] - The string. | |
| * | |
| * Returns: | |
| * True if empty, otherwise false. | |
| */ | |
| forward bool:isempty(const string[]); | |
| /* | |
| * Compare two strings. | |
| * | |
| * Parameters: | |
| * str1[] - The first string. | |
| * str2[] - The second string. | |
| * ignorecase - Whether to compare them in a case-insensitive manner. | |
| * | |
| * Returns: | |
| * True if equal, otherwise false. | |
| */ | |
| forward bool:isequal(const str1[], const str2[], bool:ignorecase = false); | |
| /* | |
| * Compare two strings, return Levenshtein distance between the two | |
| * | |
| * Parameters: | |
| * str1[] - The first string. | |
| * str2[] - The second string. | |
| * ignorecase - Whether to compare them in a case-insensitive manner. | |
| * | |
| * Returns: | |
| * Number of changes between the two strings. | |
| * This includes insertions, removals, and added characters (in that order I believe). | |
| */ | |
| forward strdistance(const str1[], const str2[], bool:ignorecase = false); | |
| /* | |
| * Split a string by a given delimiter. | |
| * | |
| * Parameters: | |
| * output[][] - A multi-dimensional array that will be filled with substrings. | |
| * input[] - The input string to split. | |
| * delimiter[] - The delimiter to split by. Defaults to ",". | |
| * limit - The max. no. substrings. | |
| * trim - Whether to trim the substrings from whitespace. Defaults to true. | |
| * ignorecase - Whether the search for "delimiter" should be case-insensitive. | |
| * size1 - The size of the 1st dimension of output (otput[this][]). Defaults to sizeof(output). | |
| * size2 - The size of the 2nd dimension of output (otput[][this]). Defaults to sizeof(output[]). | |
| * | |
| * Returns: | |
| * The number of substrings that were copied into the array. | |
| */ | |
| forward strexplode(output[][], const input[], const delimiter[] = !",", limit = cellmax, bool:trim = true, bool:ignorecase = false, size1 = sizeof(output), size2 = sizeof(output[])); | |
| /* | |
| * Glue together strings into one. | |
| * | |
| * Parameters: | |
| * glue[] - The string that will be between all other strings. | |
| * output[] - The output string. | |
| * maxlength - The size of "output". Defaults to sizeof(output). | |
| * ...[] - Strings to glue together. | |
| * | |
| * Returns: | |
| * Nothing | |
| */ | |
| forward strimplode(const glue[], output[], maxlength = sizeof(output), ...); | |
| /* | |
| * Replace occurrences of the search string with the replacement string. | |
| * | |
| * Parameters: | |
| * string[] - The string to perform the replacing in. | |
| * search[] - The string to look for. | |
| * replacement[] - The string to put instead of "search". | |
| * ignorecase - Whether the search for "search" should be case-insensitive. Defaults to false. | |
| * pos - The position to start at. Defaults to 0 (the beginning). | |
| * limit - Limit the number of replacements. Defaults to -1 (no limit). | |
| * maxlength - The size of "string". Defaults to sizeof(string). | |
| * | |
| * Returns: | |
| * The number of replacements that were made. | |
| */ | |
| forward strreplace(string[], const search[], const replacement[], bool:ignorecase = false, pos = 0, limit = -1, maxlength = sizeof(string)); | |
| /* | |
| * Trim whitespace or a specific group of characters from a string. | |
| * | |
| * Parameters: | |
| * string[] - The string to trim. | |
| * chars[] - A string with characters to trim, or all whitespace if empty. Default is all whitespace. | |
| * edge - The edge(s) to trim (edge_left/edge_right/edge_both). Default is edge_both. | |
| * | |
| * Returns: | |
| * Nothing | |
| */ | |
| forward strtrim(string[], const chars[] = !"", string_edges:edge = edge_both); | |
| /* | |
| * Pad edge(s) of a string with spaces. | |
| * | |
| * Parameters: | |
| * string[] - The string to pad. | |
| * length - The new length of the string. | |
| * substr[] - The substring to pad with. Defaults to a space (" "). | |
| * edge - The edge(s) to pad (edge_left/edge_right/edge_both). Default is edge_both. | |
| * trim_first - Whether to trim the string before padding. | |
| * trim_chars[] - The chars to trim, defaults is all whitespace. | |
| * maxlength - The size of "string". Defaults to sizeof(string). | |
| * input - Used internally. | |
| */ | |
| forward strpad(string[], length, const substr[] = !" ", string_edges:edge = edge_both, bool:trim_first = true, const trim_chars[] = !"", maxlength = sizeof(string), const input[] = !""); | |
| /* | |
| * Wrap a string inside two other strings. | |
| * | |
| * Parameters: | |
| * left[] - The string on the left side. | |
| * string[] - The middle string that will be modified. | |
| * right[] - The string on the right side. | |
| * maxlength - The size of "string". Defaults to sizeof(string). | |
| */ | |
| forward strwrap(const left[], string[], const right[], maxlength = sizeof(string)); | |
| /* | |
| * Count substrings. | |
| * | |
| * Parameters: | |
| * string[] - The string to search inside. | |
| * sub[] - The string to search for. | |
| * ignorecase - Whether the search should be case-insensitive. | |
| * count_overlapped - Whether to count overlapping strings ("abcabc" in "abcabcabc" will count 2 instead of 1). | |
| * | |
| * Returns: | |
| * The number of occurrences of "sub" in "string". | |
| */ | |
| forward strcount(const string[], const sub[], bool:ignorecase = false, bool:count_overlapped = false); | |
| /* | |
| * Read a string from a PAWN string literal. | |
| * | |
| * Parameters: | |
| * output[] - The variable to save into. | |
| * input[] - The string literal. | |
| * pos - The position in "input" to start reading from. Will be modified to the end of the literal. | |
| * maxlength - The size of "output". Defaults to sizeof(output). | |
| * | |
| * Returns: | |
| * true on success, false on error. | |
| */ | |
| forward bool:strfromliteral(output[], const input[], &pos = 0, maxlength = sizeof(output)); | |
| /* | |
| * Build a PAWN string literal from a given string. | |
| * | |
| * Parameters: | |
| * output[] - The variable to save into. | |
| * substrings[] - The string to build from. | |
| * maxlength - The size of "output". Defaults to sizeof(output). | |
| * | |
| * Returns: | |
| * Nothing | |
| */ | |
| forward strtoliteral(output[], const input[], maxlength = sizeof(output), bool:paranoid = true); | |
| /* | |
| * Convert an array to a string. | |
| * | |
| * Example: {0x1122, 0x5566} becomes "0000112200005566". | |
| * | |
| * Parameters: | |
| * output[] - The variable to save into. | |
| * input[] - The array to build from. | |
| * inputlength - The size of "input". Defaults to sizeof(input). | |
| * maxlength - The size of "output". Defaults to sizeof(output). | |
| * | |
| * Returns: | |
| * Nothing | |
| */ | |
| forward strfrombin(output[], const input[], inputlength = sizeof(input), maxlength = sizeof(output)); | |
| /* | |
| * Convert a string to an array. | |
| * | |
| * Example: "0000112200005566" becomes {0x1122, 0x5566}. | |
| * | |
| * Parameters: | |
| * output[] - The variable to save into. | |
| * input[] - The array to build from. | |
| * maxlength - The size of "output". Defaults to sizeof(output). | |
| * | |
| * Returns: | |
| * The length of the output, in cells. | |
| */ | |
| forward strtobin(output[], const input[], maxlength = sizeof(output)); | |
| /* | |
| * Concatenate one string with a part of another. | |
| * | |
| * Parameters: | |
| * dest[] - The variable to concatenate the other part with. | |
| * source[] - The string to extract from. | |
| * start - The start offset, defaults to 0. | |
| * end - The start offset, defaults to end of string. | |
| * maxlength - The size of "dest". Defaults to sizeof(dest). | |
| */ | |
| forward strcatmid(dest[], const source[], start = 0, end = -1, maxlength = sizeof(dest)); | |
| /* | |
| * UTF-8 encode a string. Characters above 127 will be encoded into | |
| * two or more characters. | |
| * | |
| * Parameters: | |
| * dest[] - The output variable. | |
| * source[] - The string to encode. | |
| * maxlength - The size of "dest". Defaults to sizeof(dest). | |
| */ | |
| forward utf8encode(dest[], const source[], maxlength = sizeof(dest)); | |
| /* | |
| * UTF-8 decode a string. UTF-8 characters will be collapsed into single | |
| * characters in the array. | |
| * | |
| * Parameters: | |
| * dest[] - The output variable. | |
| * source[] - The string to encode. | |
| * maxlength - The size of "dest". Defaults to sizeof(dest). | |
| */ | |
| forward utf8decode(dest[], const source[], maxlength = sizeof(dest)); | |
| /* | |
| * Decode an encoded URL. | |
| * | |
| * Parameters: | |
| * output[] - The output variable. | |
| * input[] - The string to decode. | |
| * maxlength - The size of "output". Defaults to sizeof(output). | |
| */ | |
| forward strurldecode(output[], const input[], maxlength = sizeof(output)); | |
| /* | |
| * URL encode a string. | |
| * | |
| * Parameters: | |
| * output[] - The output variable. | |
| * input[] - The string to encode. | |
| * maxlength - The size of "output". Defaults to sizeof(output). | |
| * pack - Whether to pack the output. Defaults to false. | |
| */ | |
| forward strurlencode(output[], const input[], maxlength = sizeof(output), bool:pack = false); | |
| // Same as above, but output is returned | |
| forward ret_strcatmid(const string[], const source[], start = 0, end = -1); | |
| forward ret_strfrombin(const input[], inputlength = sizeof(input)); | |
| forward ret_strimplode(const glue[], ...); | |
| forward ret_strreplace(const string[], const search[], const replacement[], bool:ignorecase = false, pos = 0, limit = -1); | |
| forward ret_strfromliteral(const input[], &pos = 0); | |
| forward ret_strtoliteral(const input[], bool:paranoid = true); | |
| forward ret_strtrim(const string[], const chars[] = !"", string_edges:edge = edge_both); | |
| forward ret_strpad(const string[], length, const substr[] = !" ", string_edges:edge = edge_both, bool:trim_first = true, const trim_chars[] = !""); | |
| forward ret_strwrap(const left[], const string[], const right[]); | |
| forward ret_strurldecode(const input[]); | |
| forward ret_strurlencode(const input[], bool:pack = false); | |
| forward ret_utf8encode(const input[]); | |
| forward ret_utf8decode(const input[]); | |
| // Return from native functions | |
| forward ret_strpack(const source[]); | |
| forward ret_strunpack(const source[]); | |
| forward ret_strcat(const string1[], const string2[]); | |
| forward ret_strmid(const source[], start, end); | |
| forward ret_strins(const string[], const substr[], pos, maxlength = sizeof(string)); | |
| forward ret_strdel(const string[], start, end); | |
| forward ret_valstr(value, bool:pack = false); | |
| forward ret_GetPlayerName(playerid, bool:pack = false); | |
| stock | |
| // Used throughout the library | |
| g_StrlibBuffer[2048] | |
| ; | |
| // Workaround for compiler bug | |
| forward _strlib_funcinc(); | |
| public _strlib_funcinc() { | |
| new temp[1]; | |
| format(!temp, 0, !temp); | |
| strcat(temp, temp); | |
| strpack(temp, temp); | |
| strunpack(temp, temp); | |
| } | |
| // Internal functions | |
| static stock RedirectArgument(arg, ...) { | |
| #emit LOAD.S.pri 0 | |
| #emit ADD.C 12 | |
| #emit LOAD.S.alt arg | |
| #emit SHL.C.alt 2 | |
| #emit ADD | |
| #emit MOVE.alt | |
| #emit LOAD.S.pri 16 | |
| #emit STOR.I | |
| } | |
| static stock CopyArgumentToHeap(arg, bool:pack = false, const argptr[] = "") { | |
| new arg_address, address; | |
| #emit LOAD.S.pri 0 | |
| #emit ADD.C 12 | |
| #emit LOAD.S.alt arg | |
| #emit SHL.C.alt 2 | |
| #emit ADD | |
| #emit LOAD.I | |
| #emit STOR.S.pri arg_address | |
| #emit STOR.S.pri argptr | |
| if (pack) { | |
| new bytes = ((strlen(argptr) + 1 + 3) / 4) * 4; | |
| #emit LCTRL 2 | |
| #emit STOR.S.pri address | |
| #emit LOAD.S.alt bytes | |
| #emit ADD | |
| #emit SCTRL 2 | |
| //strpack(dest[], const source[], maxlength = sizeof dest) | |
| #emit LOAD.S.pri bytes | |
| #emit SHR.C.pri 2 | |
| #emit PUSH.pri | |
| #emit PUSH.S arg_address | |
| #emit PUSH.S address | |
| #emit PUSH.C 12 | |
| #emit SYSREQ.C strpack | |
| #emit STACK 16 | |
| } else { | |
| new bytes = (strlen(argptr) + 1) * 4; | |
| #emit LCTRL 2 | |
| #emit STOR.S.pri address | |
| #emit LOAD.S.alt bytes | |
| #emit ADD | |
| #emit SCTRL 2 | |
| //strunpack(dest[], const source[], maxlength = sizeof dest) | |
| #emit LOAD.S.pri bytes | |
| #emit SHR.C.pri 2 | |
| #emit PUSH.pri | |
| #emit PUSH.S arg_address | |
| #emit PUSH.S address | |
| #emit PUSH.C 12 | |
| #emit SYSREQ.C strunpack | |
| #emit STACK 16 | |
| } | |
| #emit LOAD.S.pri 0 | |
| #emit ADD.C 12 | |
| #emit LOAD.S.alt arg | |
| #emit SHL.C.alt 2 | |
| #emit ADD | |
| #emit MOVE.alt | |
| #emit LOAD.S.pri address | |
| #emit STOR.I | |
| return address; | |
| } | |
| static stock RestoreHeapToAddress(address) { | |
| #emit LOAD.S.pri address | |
| #emit SCTRL 2 | |
| } | |
| static stock IsOverlapping(const str1[], size1 = sizeof(str1), const str2[], size2 = sizeof(str2)) { | |
| new addr1, addr2; | |
| if (size1 == -1) { | |
| size1 = strsize(str1); | |
| } else { | |
| size1 *= 4; | |
| } | |
| if (size2 == -1) { | |
| size2 = strsize(str2); | |
| } else { | |
| size2 *= 4; | |
| } | |
| #emit LOAD.S.pri str1 | |
| #emit STOR.S.pri addr1 | |
| #emit LOAD.S.pri str2 | |
| #emit STOR.S.pri addr2 | |
| return (addr1 < addr2 + size2) && (addr2 < addr1 + size1); | |
| } | |
| // strlib functions | |
| #if !defined ispacked | |
| #define ispacked(%1) \ | |
| ((%1)[0] > 255) | |
| #endif | |
| stock strgetfirstc(const string[]) { | |
| return ispacked(string) ? string{0} : string[0]; | |
| } | |
| stock strgetc(const string[], index) { | |
| if (index < 0) | |
| return '\0'; | |
| new len = strlen(string); | |
| if (index >= len) | |
| return '\0'; | |
| return ispacked(string) ? string{index} : string[index]; | |
| } | |
| stock strsize(const string[]) { | |
| new len = strlen(string); | |
| if (ispacked(string)) | |
| return len + 1; | |
| return (len + 1) * 4; | |
| } | |
| stock bool:isempty(const string[]) { | |
| if (ispacked(string)) | |
| return string{0} == '\0'; | |
| else | |
| return string[0] == '\0'; | |
| } | |
| stock bool:isequal(const str1[], const str2[], bool:ignorecase = false) { | |
| new | |
| c1 = (str1[0] > 255) ? str1{0} : str1[0], | |
| c2 = (str2[0] > 255) ? str2{0} : str2[0] | |
| ; | |
| if (!c1 != !c2) | |
| return false; | |
| return !strcmp(str1, str2, ignorecase); | |
| } | |
| stock strdistance(const str1[], const str2[], bool:ignorecase = false) { | |
| // If they are equal, theres no distance anyways | |
| if(isequal(str1, str2, ignorecase)) | |
| return 0; | |
| static data[128][128]; | |
| new bool:pack1 = ispacked(str1), | |
| bool:pack2 = ispacked(str2); | |
| new size1 = strlen(str1), | |
| size2 = strlen(str2); | |
| // Zero-length strings would return the size of the other string, because it's that many insertions | |
| if (size1 == 0) | |
| return size2; | |
| if (size2 == 0) | |
| return size1; | |
| // Intitalize data array | |
| for (new i; i <= size1; i++) | |
| data[i][0] = i; | |
| for (new j; j <= size2; j++) | |
| data[0][j] = j; | |
| // Loop through both strings, comparing each character to each character in the other string (think matrix) | |
| for (new j = 1; j <= size2; j++) { | |
| for (new i = 1; i <= size1; i++) { | |
| new char1 = pack1 ? str1{i - 1} : str1[i - 1], | |
| char2 = pack2 ? str2{j - 1} : str2[j - 1]; | |
| // If ignorecase, make chars lower case. | |
| if(ignorecase) { | |
| if (65 <= char1 <= 90) | |
| char1 += 32; | |
| if (65 <= char2 <= 90) | |
| char2 += 32; | |
| } | |
| if (char1 == char2) | |
| data[i][j] = data[i - 1][j - 1]; | |
| else { | |
| new l1 = data[i - 1][j] + 1, | |
| l2 = data[i][j - 1] + 1, | |
| l3 = data[i - 1][j - 1] + 1; | |
| l2 = (l1 > l2 ? l2 : l1); | |
| data[i][j] = (l3 > l2 ? l2 : l3); | |
| } | |
| } | |
| } | |
| return data[size1][size2]; | |
| } | |
| stock strimplode(const glue[], output[], maxlength = sizeof(output), ...) { | |
| new args = numargs(); | |
| // Null-out "output" | |
| output[0] = '\0'; | |
| // Loop the variable arguments (the ones after "maxlength"). | |
| for (new arg = 3; arg < args; arg++) { | |
| // If this isn't the first string, append the glue. | |
| if (arg != 3) | |
| strcat(output, glue, maxlength); | |
| // Wrap these in braces or they will be a part of the above if statement (compiler bug) | |
| { | |
| // Get the address of argument no. <arg> | |
| #emit LCTRL 5 | |
| #emit ADD.C 12 | |
| #emit LOAD.S.alt arg | |
| #emit SHL.C.alt 2 | |
| #emit ADD | |
| #emit LOAD.I | |
| // Push the maxlength, arg address, and output address | |
| #emit PUSH.S maxlength | |
| #emit PUSH.pri | |
| #emit PUSH.S output | |
| // Push the argument count | |
| #emit PUSH.C 12 | |
| // call strcat | |
| #emit SYSREQ.C strcat | |
| // Restore the stack | |
| #emit STACK 16 | |
| } | |
| } | |
| } | |
| stock strexplode(output[][], const input[], const delimiter[] = !",", limit = cellmax, bool:trim = true, bool:ignorecase = false, size1 = sizeof(output), size2 = sizeof(output[])) { | |
| if (!size1 || !size2) { | |
| printf("(strexplode) ERROR: size1 = %d, size2 = %d. Can't be 0.", size1, size2); | |
| return 0; | |
| } | |
| if (isempty(delimiter)) { | |
| print(!"(strexplode) ERROR: delimiter is empty."); | |
| return 0; | |
| } | |
| if (trim) { | |
| new i = -1; | |
| if (ispacked(input)) { | |
| while (input{++i}) { | |
| if (input{i} > ' ') { | |
| i = -1; | |
| break; | |
| } | |
| } | |
| } else { | |
| while (input[++i]) { | |
| if (input[i] > ' ') { | |
| i = -1; | |
| break; | |
| } | |
| } | |
| } | |
| if (i != -1) | |
| return 0; | |
| } else if (isempty(input)) { | |
| return 0; | |
| } | |
| if (limit == 0) { | |
| return 0; | |
| } else if (limit == cellmax) { | |
| limit = 0; | |
| } | |
| new | |
| pos = 0, | |
| next, | |
| bool:packed = ispacked(input), | |
| dlen = strlen(delimiter), | |
| count = 0, | |
| end | |
| ; | |
| while (pos != -1) { | |
| ++count; | |
| if (limit > 0 && count >= limit) { | |
| next = -1; | |
| } else { | |
| next = strfind(input, delimiter, ignorecase, pos); | |
| } | |
| end = (next == -1) ? cellmax : next; | |
| if (trim) { | |
| if (end == cellmax) | |
| end = strlen(input); | |
| if (packed) { | |
| while (0 < input{pos} <= ' ') pos++; | |
| while (end > 0 && input{end - 1} <= ' ') end--; | |
| } else { | |
| while (0 < input[pos] <= ' ') pos++; | |
| while (end > 0 && input[end - 1] <= ' ') end--; | |
| } | |
| } | |
| strmid(output[count - 1], input, pos, end, size2); | |
| if (count >= size1 || next == -1 || (limit < 0 && count >= -limit)) | |
| break; | |
| pos = next + dlen; | |
| } | |
| return count; | |
| } | |
| stock strreplace(string[], const search[], const replacement[], bool:ignorecase = false, pos = 0, limit = -1, maxlength = sizeof(string)) { | |
| // No need to do anything if the limit is 0. | |
| if (limit == 0) | |
| return 0; | |
| new | |
| sublen = strlen(search), | |
| replen = strlen(replacement), | |
| bool:packed = ispacked(string), | |
| maxlen = maxlength, | |
| len = strlen(string), | |
| count = 0 | |
| ; | |
| // "maxlen" holds the max string length (not to be confused with "maxlength", which holds the max. array size). | |
| // Since packed strings hold 4 characters per array slot, we multiply "maxlen" by 4. | |
| if (packed) | |
| maxlen *= 4; | |
| // If the length of the substring is 0, we have nothing to look for.. | |
| if (!sublen) | |
| return 0; | |
| // In this line we both assign the return value from "strfind" to "pos" then check if it's -1. | |
| while (-1 != (pos = strfind(string, search, ignorecase, pos))) { | |
| // Delete the string we found | |
| strdel(string, pos, pos + sublen); | |
| len -= sublen; | |
| // If there's anything to put as replacement, insert it. Make sure there's enough room first. | |
| if (replen && len + replen < maxlen) { | |
| strins(string, replacement, pos, maxlength); | |
| pos += replen; | |
| len += replen; | |
| } | |
| // Is there a limit of number of replacements, if so, did we break it? | |
| if (limit != -1 && ++count >= limit) | |
| break; | |
| } | |
| return count; | |
| } | |
| stock strtrim(string[], const chars[] = !"", string_edges:edge = edge_both) { | |
| new bool:packed = ispacked(string); | |
| // If "chars" is empty, trim whitespace | |
| if (!strgetfirstc(chars)) { | |
| // Should the left side be trimmed? | |
| if (edge & edge_left) { | |
| new i = 0; | |
| if (packed) | |
| while (0 < string{i} <= ' ') i++; | |
| else | |
| while (0 < string[i] <= ' ') i++; | |
| if (i) { | |
| strdel(string, 0, i); | |
| } | |
| } | |
| // Should the right side be trimmed? | |
| if (edge & edge_right) { | |
| new i = strlen(string); | |
| if (i) { | |
| if (packed) { | |
| while (--i && 0 < string{i} <= ' ') {} | |
| string{i + 1} = '\0'; | |
| } else { | |
| while (--i && 0 < string[i] <= ' ') {} | |
| string[i + 1] = '\0'; | |
| } | |
| } | |
| } | |
| } else { | |
| // Should the left side be trimmed? | |
| if (edge & edge_left) { | |
| new i = 0, sub[2]; | |
| if (packed) { | |
| while ((sub[0] = string{i})) { | |
| if (strfind(chars, sub) == -1) | |
| break; | |
| i++; | |
| } | |
| if (i) { | |
| strdel(string, 0, i); | |
| } | |
| } else { | |
| while ((sub[0] = string[i])) { | |
| if (strfind(chars, sub) == -1) | |
| break; | |
| i++; | |
| } | |
| if (i) strdel(string, 0, i); | |
| } | |
| } | |
| // Should the right side be trimmed? | |
| if (edge & edge_right) { | |
| new i = strlen(string), sub[2]; | |
| if (i >= 0) { | |
| if (packed) { | |
| while (i--) { | |
| sub[0] = string{i}; | |
| if (strfind(chars, sub) == -1) | |
| break; | |
| } | |
| string{i + 1} = '\0'; | |
| } else { | |
| while (i--) { | |
| sub[0] = string[i]; | |
| if (strfind(chars, sub) == -1) | |
| break; | |
| } | |
| string[i + 1] = '\0'; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| stock strpad(string[], length, const substr[] = !" ", string_edges:edge = edge_both, bool:trim_first = true, const trim_chars[] = !"", maxlength = sizeof(string), const input[] = !"") { | |
| if (trim_first) { | |
| strtrim(string, trim_chars, edge); | |
| } | |
| new | |
| heap, | |
| length_left = 0, | |
| length_right = 0, | |
| len = strlen(string), | |
| sublen = strlen(substr), | |
| bool:packed, | |
| bool:subpacked = ispacked(substr) | |
| ; | |
| if (len > length) | |
| return; | |
| else | |
| length -= len; | |
| // Make "input" a pointer to "string" | |
| #emit LOAD.S.pri string | |
| #emit STOR.S.pri input | |
| // Copy "input" to the heap so it won't be linked to "string" anymore. | |
| heap = CopyArgumentToHeap(7); | |
| string[0] = '\0'; | |
| len = 0; | |
| switch (edge) { | |
| case edge_left: | |
| length_left = length; | |
| case edge_right: | |
| length_right = length; | |
| default: | |
| length_left = length / 2, length_right = length - length_left; | |
| } | |
| if (length_left) { | |
| while (len < length_left) { | |
| if (subpacked) | |
| strcat(string, substr, length_left * 4); | |
| else | |
| strcat(string, substr, length_left + 1); | |
| len += sublen; | |
| } | |
| if (subpacked) | |
| string{length_left} = 0; | |
| } | |
| strcat(string, input, maxlength); | |
| if (length_right) { | |
| len = strlen(string); | |
| length_right += len; | |
| packed = ispacked(string); | |
| while (len < length_right) { | |
| if (packed) | |
| strcat(string, substr, length_right / 4 + 1); | |
| else | |
| strcat(string, substr, length_right + 1); | |
| len += sublen; | |
| } | |
| if (packed) | |
| string{length_right + 1} = 0; | |
| } | |
| RestoreHeapToAddress(heap); | |
| } | |
| stock strwrap(const left[], string[], const right[], maxlength = sizeof(string)) { | |
| strins(string, left, 0, maxlength); | |
| strcat(string, right, maxlength); | |
| } | |
| stock strcount(const string[], const sub[], bool:ignorecase = false, bool:count_overlapped = false) { | |
| new | |
| increment = count_overlapped ? 1 : strlen(sub), | |
| pos = -increment, | |
| count = 0 | |
| ; | |
| while (-1 != (pos = strfind(string, sub, ignorecase, pos + increment))) | |
| count++; | |
| return count; | |
| } | |
| stock bool:strfromliteral(output[], const input[], &pos = 0, maxlength = sizeof(output)) { | |
| new | |
| length = strlen(input), | |
| c, | |
| outlen = 0, | |
| heap = 0 | |
| ; | |
| // No need to do anything else. | |
| if (!length) | |
| return true; | |
| if (IsOverlapping(output, maxlength, input, -1)) | |
| heap = CopyArgumentToHeap(1); | |
| output[0] = '\0'; | |
| if (input[0] == '"') | |
| pos++; | |
| for (;; pos++) { | |
| if (outlen >= maxlength - 1 || pos >= length) | |
| break; | |
| c = input[pos]; | |
| switch (c) { | |
| // String ended | |
| case '"': break; | |
| case '\\': {} | |
| default: { | |
| output[outlen++] = c; | |
| continue; | |
| } | |
| } | |
| // String ends with a backslash - invalid. | |
| if (pos == length - 1) | |
| goto return_false; | |
| // We're after a backslash now, let's see what's there. | |
| c = input[++pos]; | |
| switch (c) { | |
| case '"', | |
| '\'', | |
| '\\', | |
| '%': output[outlen++] = c; | |
| case 'a': output[outlen++] = '\a'; | |
| case 'b': output[outlen++] = '\b'; | |
| case 'e': output[outlen++] = '\e'; | |
| case 'f': output[outlen++] = '\f'; | |
| case 'r': output[outlen++] = '\r'; | |
| case 'n': output[outlen++] = '\n'; | |
| case 't': output[outlen++] = '\t'; | |
| case 'v': output[outlen++] = '\v'; | |
| case 'x': { | |
| new val = 0; | |
| // String ends with "\x" - invalid. | |
| if (c == length - 1) | |
| goto return_false; | |
| while ((c = input[pos + 1])) { | |
| if ('a' <= c <= 'f' || 'A' <= c <= 'F') { | |
| val = (val << 4) + (tolower(c) - 'a' + 10); | |
| } else if ('0' <= c <= '9') { | |
| val = (val << 4) + (c - '0'); | |
| } else { | |
| break; | |
| } | |
| pos++; | |
| } | |
| if (c == ';') | |
| pos++; | |
| output[outlen++] = val; | |
| } | |
| case '0' .. '9': { | |
| new val = 0; | |
| while ((c = input[pos])) { | |
| if ('0' <= c <= '9') { | |
| val = val * 10 + (c - '0'); | |
| } else { | |
| break; | |
| } | |
| pos++; | |
| } | |
| if (c != ';') pos--; | |
| output[outlen++] = val; | |
| } | |
| default: { | |
| goto return_false; | |
| } | |
| } | |
| } | |
| output[outlen] = '\0'; | |
| pos++; | |
| new bool:ret = true; | |
| goto return_true; | |
| return_false: | |
| ret = false; | |
| return_true: | |
| if (heap) | |
| RestoreHeapToAddress(heap); | |
| return ret; | |
| } | |
| stock strtoliteral(output[], const input[], maxlength = sizeof(output), bool:paranoid = true) { | |
| new i, c, outlen, heap = 0; | |
| if (IsOverlapping(output, maxlength, input, -1)) | |
| heap = CopyArgumentToHeap(1); | |
| output[outlen++] = '"'; | |
| for (i = 0; (c = input[i]); i++) { | |
| if (maxlength - outlen <= 3) { | |
| outlen = min(outlen, maxlength - 2); | |
| break; | |
| } | |
| switch (c) { | |
| case ' ', '!', '#' .. '[', ']', '^' .. '~': | |
| output[outlen++] = c; | |
| case '"': strunpack(output[outlen], !"\\\"", 3), outlen += 2; | |
| case '\a': strunpack(output[outlen], !"\\a" , 3), outlen += 2; | |
| case '\b': strunpack(output[outlen], !"\\b" , 3), outlen += 2; | |
| case '\e': strunpack(output[outlen], !"\\e" , 3), outlen += 2; | |
| case '\f': strunpack(output[outlen], !"\\f" , 3), outlen += 2; | |
| case '\r': strunpack(output[outlen], !"\\r" , 3), outlen += 2; | |
| case '\n': strunpack(output[outlen], !"\\n" , 3), outlen += 2; | |
| case '\t': strunpack(output[outlen], !"\\t" , 3), outlen += 2; | |
| case '\v': strunpack(output[outlen], !"\\v" , 3), outlen += 2; | |
| case '\\': strunpack(output[outlen], !"\\\\" , 3), outlen += 2; | |
| default: { | |
| if (!paranoid && 0x80 <= c <= 0xFF) { | |
| output[outlen++] = c; | |
| continue; | |
| } | |
| if (maxlength - outlen <= 8) | |
| break; | |
| format(output[outlen], 7, "\\x%03x;", c); | |
| outlen += 6; | |
| } | |
| } | |
| } | |
| output[outlen++] = '"'; | |
| output[outlen] = '\0'; | |
| if (heap) | |
| RestoreHeapToAddress(heap); | |
| } | |
| stock strfrombin(output[], const input[], inputlength = sizeof(input), maxlength = sizeof(output)) { | |
| static const hex_chars[] = "0123456789ABCDEF"; | |
| new outlen = 0, heap = 0; | |
| if (IsOverlapping(output, maxlength, input, -1)) | |
| heap = CopyArgumentToHeap(1); | |
| for (new i = 0; i < inputlength; i++) { | |
| if (maxlength - outlen <= 7) { | |
| outlen = min(outlen, maxlength - 1); | |
| break; | |
| } | |
| new input_cell = input[i]; | |
| output[outlen++] = hex_chars[(input_cell ) >>> 28]; | |
| output[outlen++] = hex_chars[(input_cell & 0x0F000000) >>> 24]; | |
| output[outlen++] = hex_chars[(input_cell & 0x00F00000) >>> 20]; | |
| output[outlen++] = hex_chars[(input_cell & 0x000F0000) >>> 16]; | |
| output[outlen++] = hex_chars[(input_cell & 0x0000F000) >>> 12]; | |
| output[outlen++] = hex_chars[(input_cell & 0x00000F00) >>> 8]; | |
| output[outlen++] = hex_chars[(input_cell & 0x000000F0) >>> 4]; | |
| output[outlen++] = hex_chars[(input_cell & 0x0000000F) ]; | |
| } | |
| output[outlen] = '\0'; | |
| if (heap) | |
| RestoreHeapToAddress(heap); | |
| } | |
| stock strtobin(output[], const input[], maxlength = sizeof(output)) { | |
| new len = strlen(input), outlen = 0, heap = 0; | |
| if (IsOverlapping(output, maxlength, input, -1)) | |
| heap = CopyArgumentToHeap(1); | |
| for (new i = 0; i < len;) { | |
| if (outlen >= maxlength || i > len - 8) { | |
| break; | |
| } | |
| new c, out = 0; | |
| #define ADD_OUT(%1) \ | |
| c = input[i++]; out |= (('a' <= c <= 'f' || 'A' <= c <= 'F') ? (tolower(c) - 'a' + 10) : (c - '0')) << %1 | |
| ADD_OUT(28); | |
| ADD_OUT(24); | |
| ADD_OUT(20); | |
| ADD_OUT(16); | |
| ADD_OUT(12); | |
| ADD_OUT(8); | |
| ADD_OUT(4); | |
| ADD_OUT(0); | |
| #undef ADD_OUT | |
| output[outlen++] = out; | |
| } | |
| if (heap) | |
| RestoreHeapToAddress(heap); | |
| return outlen; | |
| } | |
| stock strurlencode(output[], const input[], maxlength = sizeof(output), bool:pack = false) { | |
| static const hex_chars[] = "0123456789ABCDEF"; | |
| new | |
| len = strlen(input), | |
| bool:packed = ispacked(input), | |
| outlen = 0, | |
| heap = 0 | |
| ; | |
| if (IsOverlapping(output, maxlength, input, -1)) | |
| heap = CopyArgumentToHeap(1, packed); | |
| if (pack) | |
| maxlength *= 4; | |
| for (new i = 0; i < len; i++) { | |
| if (maxlength - outlen <= 1) | |
| break; | |
| new c = packed ? input{i} : input[i]; | |
| switch (c) { | |
| case 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '_': { | |
| if (pack) | |
| output{outlen++} = c; | |
| else | |
| output[outlen++] = c; | |
| } | |
| case ' ': { | |
| if (pack) | |
| output{outlen++} = '+'; | |
| else | |
| output[outlen++] = '+'; | |
| } | |
| default: { | |
| if (maxlength - outlen <= 3) | |
| break; | |
| if (pack) { | |
| output{outlen++} = '%'; | |
| output{outlen++} = hex_chars[(c & 0xF0) >>> 4]; | |
| output{outlen++} = hex_chars[c & 0x0F]; | |
| } else { | |
| output[outlen++] = '%'; | |
| output[outlen++] = hex_chars[(c & 0xF0) >>> 4]; | |
| output[outlen++] = hex_chars[c & 0x0F]; | |
| } | |
| } | |
| } | |
| } | |
| if (pack) | |
| output{outlen} = '\0'; | |
| else | |
| output[outlen] = '\0'; | |
| if (heap) | |
| RestoreHeapToAddress(heap); | |
| } | |
| stock strurldecode(output[], const input[], maxlength = sizeof(output)) { | |
| new prev_pos = 0, pos = 0, inputlen = strlen(input), len, heap = 0; | |
| if (IsOverlapping(output, maxlength, input, -1)) | |
| heap = CopyArgumentToHeap(1); | |
| output[0] = '\0'; | |
| while (-1 != (pos = strfind(input, "%", _, pos))) { | |
| static str[2]; | |
| new c; | |
| if (prev_pos != pos) { | |
| len = strlen(output); | |
| strcatmid(output, input, prev_pos, pos, maxlength); | |
| strreplace(output, "+", " ", _, len, _, maxlength); | |
| } | |
| if (inputlen < pos + 3) | |
| goto func_end; | |
| str[0] = 0; | |
| c = input[pos + 1]; str[0] |= (('a' <= c <= 'f' || 'A' <= c <= 'F') ? (tolower(c) - 'a' + 10) : (c - '0')) << 4; | |
| c = input[pos + 2]; str[0] |= (('a' <= c <= 'f' || 'A' <= c <= 'F') ? (tolower(c) - 'a' + 10) : (c - '0')); | |
| strcat(output, str, maxlength); | |
| prev_pos = (pos += 3); | |
| } | |
| len = strlen(output); | |
| strcatmid(output, input, prev_pos, _, maxlength); | |
| strreplace(output, "+", " ", _, len, _, maxlength); | |
| func_end: | |
| if (heap) | |
| RestoreHeapToAddress(heap); | |
| } | |
| stock strcatmid(dest[], const source[], start = 0, end = -1, maxlength = sizeof(dest)) { | |
| new heap = 0; | |
| if (IsOverlapping(dest, maxlength, source, -1)) | |
| heap = CopyArgumentToHeap(1); | |
| if (start == 0 && end == -1) { | |
| strcat(dest, source, maxlength); | |
| } else { | |
| if (end == -1) | |
| end = strlen(source); | |
| if (ispacked(dest)) { | |
| new len = strlen(dest); | |
| if (ispacked(source)) { | |
| strunpack(g_StrlibBuffer, source); | |
| strcat(dest, g_StrlibBuffer[start], min(maxlength, (len + end - start) / 4 + 1)); | |
| } else { | |
| strcat(dest, source[start], min(maxlength, (len + end - start) / 4 + 1)); | |
| } | |
| dest{len + end - start} = '\0'; | |
| } else { | |
| if (ispacked(source)) { | |
| strunpack(g_StrlibBuffer, source); | |
| strcat(dest, g_StrlibBuffer[start], min(maxlength, strlen(dest) + end - start + 1)); | |
| } else { | |
| strcat(dest, source[start], min(maxlength, strlen(dest) + end - start + 1)); | |
| } | |
| } | |
| } | |
| if (heap) | |
| RestoreHeapToAddress(heap); | |
| } | |
| stock utf8encode(dest[], const source[], maxlength = sizeof(dest)) { | |
| new heap = 0; | |
| if (IsOverlapping(dest, maxlength, source, -1)) { | |
| heap = CopyArgumentToHeap(1); | |
| } | |
| new len = strlen(source); | |
| new packed = ispacked(source); | |
| dest[0] = '\0'; | |
| new idx = 0; | |
| for (new i = 0; i < len; i++) { | |
| new c = packed ? source{i} : source[i]; | |
| if (c >= 0x80) { | |
| if (c > 0x4000000) { | |
| // 6 byte | |
| dest[idx++] = 0b11111100 | ((c >>> 30) & 0b00000001); | |
| dest[idx++] = 0b10000000 | ((c >>> 24) & 0b00111111); | |
| dest[idx++] = 0b10000000 | ((c >>> 18) & 0b00111111); | |
| dest[idx++] = 0b10000000 | ((c >>> 12) & 0b00111111); | |
| dest[idx++] = 0b10000000 | ((c >>> 6) & 0b00111111); | |
| dest[idx++] = 0b10000000 | (c & 0b00111111); | |
| } else if (c > 0x200000) { | |
| // 5 byte | |
| dest[idx++] = 0b11111000 | ((c >>> 24) & 0b00000011); | |
| dest[idx++] = 0b10000000 | ((c >>> 18) & 0b00111111); | |
| dest[idx++] = 0b10000000 | ((c >>> 12) & 0b00111111); | |
| dest[idx++] = 0b10000000 | ((c >>> 6) & 0b00111111); | |
| dest[idx++] = 0b10000000 | (c & 0b00111111); | |
| } else if (c > 0x10000) { | |
| // 4 byte | |
| dest[idx++] = 0b11110000 | ((c >>> 18) & 0b00000111); | |
| dest[idx++] = 0b10000000 | ((c >>> 12) & 0b00111111); | |
| dest[idx++] = 0b10000000 | ((c >>> 6) & 0b00111111); | |
| dest[idx++] = 0b10000000 | (c & 0b00111111); | |
| } else if (c > 0x800) { | |
| // 3 byte | |
| dest[idx++] = 0b11100000 | ((c >>> 12) & 0b00001111); | |
| dest[idx++] = 0b10000000 | ((c >>> 6) & 0b00111111); | |
| dest[idx++] = 0b10000000 | (c & 0b00111111); | |
| } else { | |
| // 2 byte | |
| dest[idx++] = 0b11000000 | ((c >>> 6) & 0b00011111); | |
| dest[idx++] = 0b10000000 | (c & 0b00111111); | |
| } | |
| } else if (c > 0) { | |
| dest[idx++] = c; | |
| } | |
| } | |
| dest[idx++] = '\0'; | |
| if (heap) { | |
| RestoreHeapToAddress(heap); | |
| } | |
| } | |
| stock utf8decode(dest[], const source[], maxlength = sizeof(dest)) { | |
| new heap = 0; | |
| if (IsOverlapping(dest, maxlength, source, -1)) { | |
| heap = CopyArgumentToHeap(1); | |
| } | |
| new len = strlen(source); | |
| dest[0] = '\0'; | |
| new idx = 0; | |
| for (new i = 0; i < len; i++) { | |
| new c = source[i]; | |
| if (c & 0b10000000) { | |
| if (c & 0b11100000 == 0b11000000) { | |
| // 2 byte | |
| if (i + 1 >= len) continue; | |
| dest[idx++] = (c & 0b00011111) << 6 | (source[++i] & 0b00111111); | |
| } else if (c & 0b11110000 == 0b11100000) { | |
| // 3 byte | |
| if (i + 2 >= len) continue; | |
| dest[idx++] = (c & 0b00001111) << 12 | | |
| (source[++i] & 0b00111111) << 6 | | |
| (source[++i] & 0b00111111); | |
| } else if (c & 0b11111000 == 0b11110000) { | |
| // 4 byte | |
| if (i + 3 >= len) continue; | |
| dest[idx++] = (c & 0b00000111) << 18 | | |
| (source[++i] & 0b00111111) << 12 | | |
| (source[++i] & 0b00111111) << 6 | | |
| (source[++i] & 0b00111111); | |
| } else if (c & 0b11111100 == 0b11111000) { | |
| // 5 byte | |
| if (i + 4 >= len) continue; | |
| dest[idx++] = (c & 0b00000011) << 24 | | |
| (source[++i] & 0b00111111) << 18 | | |
| (source[++i] & 0b00111111) << 12 | | |
| (source[++i] & 0b00111111) << 6 | | |
| (source[++i] & 0b00111111); | |
| } else if (c & 0b11111110 == 0b11111100) { | |
| // 6 byte | |
| if (i + 5 >= len) continue; | |
| dest[idx++] = (c & 0b00000001) << 30 | | |
| (source[++i] & 0b00111111) << 24 | | |
| (source[++i] & 0b00111111) << 18 | | |
| (source[++i] & 0b00111111) << 12 | | |
| (source[++i] & 0b00111111) << 6 | | |
| (source[++i] & 0b00111111); | |
| } | |
| } else { | |
| dest[idx++] = c; | |
| } | |
| } | |
| dest[idx++] = 0; | |
| if (heap) { | |
| RestoreHeapToAddress(heap); | |
| } | |
| } | |
| stock ret_strcatmid(const string[], const source[], start = 0, end = -1) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, string); | |
| strcatmid(output, source, start, end); | |
| return output; | |
| } | |
| stock ret_strfrombin(const input[], inputlength = sizeof(input)) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strfrombin(output, input, inputlength); | |
| return output; | |
| } | |
| stock ret_strimplode(const glue[], ...) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| const maxlength = sizeof(output); | |
| new args = numargs(); | |
| // Loop the variable arguments (the ones after "maxlength"). | |
| for (new arg = 1; arg < args; arg++) { | |
| // If this isn't the first string, append the glue. | |
| if (arg != 1) | |
| strcat(output, glue, maxlength); | |
| // Wrap these in braces or they will be a part of the above if statement (compiler bug) | |
| { | |
| // Get the address of argument no. <arg> | |
| #emit LCTRL 5 | |
| #emit ADD.C 12 | |
| #emit LOAD.S.alt arg | |
| #emit SHL.C.alt 2 | |
| #emit ADD | |
| #emit LOAD.I | |
| // Push the maxlength, arg address, and output address | |
| #emit PUSH.C maxlength | |
| #emit PUSH.pri | |
| #emit PUSH.ADR output | |
| // Push the argument count | |
| #emit PUSH.C 12 | |
| // call strcat | |
| #emit SYSREQ.C strcat | |
| // Restore the stack | |
| #emit STACK 16 | |
| } | |
| } | |
| // Fix compiler bug (returning strings in variadic functions) | |
| #emit LOAD.S.pri 8 | |
| #emit ADD.C 12 | |
| #emit MOVE.alt | |
| #emit LCTRL 5 | |
| #emit ADD | |
| #emit LOAD.I | |
| #emit STOR.S.pri 20 | |
| return output; | |
| } | |
| stock ret_strreplace(const string[], const search[], const replacement[], bool:ignorecase = false, pos = 0, limit = -1) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, string); | |
| strreplace(output, search, replacement, ignorecase, pos, limit); | |
| return output; | |
| } | |
| stock ret_strfromliteral(const input[], &pos = 0) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, input); | |
| strfromliteral(output, input, pos); | |
| return output; | |
| } | |
| stock ret_strtoliteral(const input[], bool:paranoid = true) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, input); | |
| strtoliteral(output, input, paranoid); | |
| return output; | |
| } | |
| stock ret_strtrim(const string[], const chars[] = !"", string_edges:edge = edge_both) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, string); | |
| strtrim(output, chars, edge); | |
| return output; | |
| } | |
| stock ret_strpad(const string[], length, const substr[] = !" ", string_edges:edge = edge_both, bool:trim_first = true, const trim_chars[] = !"") { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, string); | |
| strpad(output, length, substr, edge, trim_first, trim_chars); | |
| return output; | |
| } | |
| stock ret_strwrap(const left[], const string[], const right[]) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, left); | |
| strcat(output, string); | |
| strcat(output, right); | |
| return output; | |
| } | |
| stock ret_strurldecode(const input[]) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, input); | |
| strurldecode(output, input); | |
| return output; | |
| } | |
| stock ret_strurlencode(const input[], bool:pack = false) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, input); | |
| strurlencode(output, input, _, pack); | |
| return output; | |
| } | |
| stock ret_utf8encode(const input[]) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| utf8encode(output, input); | |
| return output; | |
| } | |
| stock ret_utf8decode(const input[]) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| utf8decode(output, input); | |
| return output; | |
| } | |
| stock ret_strpack(const source[]) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strpack(output, source); | |
| return output; | |
| } | |
| stock ret_strunpack(const source[]) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strunpack(output, source); | |
| return output; | |
| } | |
| stock ret_strcat(const string1[], const string2[]) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, string1); | |
| strcat(output, string2); | |
| return output; | |
| } | |
| stock ret_strmid(const source[], start, end) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strmid(output, source, start, end); | |
| return output; | |
| } | |
| stock ret_strins(const string[], const substr[], pos, maxlength = sizeof(string)) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, string); | |
| strins(output, substr, pos); | |
| return output; | |
| } | |
| stock ret_strdel(const string[], start, end) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| strcat(output, string); | |
| strdel(output, start, end); | |
| return output; | |
| } | |
| stock ret_valstr(value, bool:pack = false) { | |
| new output[STRLIB_RETURN_SIZE]; | |
| format(output, sizeof(output), "%d", value); | |
| if (pack) | |
| strpack(output, output); | |
| return output; | |
| } | |
| stock ret_GetPlayerName(playerid, bool:pack = false) { | |
| new output[MAX_PLAYER_NAME]; | |
| GetPlayerName(playerid, output, sizeof(output)); | |
| if (pack) | |
| strpack(output, output); | |
| return output; | |
| } | |
| stock sprintf(const fmat[], {Float, _}:...) { | |
| static output[STRLIB_RETURN_SIZE], frm_header[3], heap; | |
| const output_size = sizeof(output); | |
| if (ispacked(fmat)) { | |
| heap = CopyArgumentToHeap(0); | |
| } else { | |
| heap = 0; | |
| }{} | |
| // Store current frame header | |
| #emit LCTRL 5 | |
| #emit CONST.alt frm_header | |
| #emit MOVS 12 | |
| // Remember the parameter count | |
| #emit LOAD.S.alt 8 | |
| // Change the stack pointer to FRM + 12 | |
| #emit ADD.C 12 // pri is FRM (see above) | |
| #emit SCTRL 4 | |
| // Push sizeof(output) | |
| #emit PUSH.C output_size | |
| // Push output | |
| #emit PUSH.C output | |
| // Push the argument count | |
| #emit MOVE.pri | |
| #emit ADD.C 8 | |
| #emit PUSH.pri | |
| #if !STRLIB_USE_FORMATEX | |
| const formatex = 0; // Dummy used to avoid "unknown symbol" error | |
| goto do_sysreq; | |
| #endif | |
| // Call formatex (unless this was skipped above) | |
| #emit LCTRL 6 | |
| #emit ADD.C 36 | |
| #emit PUSH.pri | |
| #emit CONST.pri formatex | |
| #emit SCTRL 6 | |
| #if !STRLIB_USE_FORMATEX | |
| do_sysreq: | |
| #endif | |
| // Call format (unless formatex was called, in which case this is skipped) | |
| #emit SYSREQ.C format | |
| // Restore the stack pointer to FRM | |
| #emit LCTRL 5 | |
| #emit SCTRL 4 | |
| // Copy back the frame header | |
| #emit MOVE.alt | |
| #emit CONST.pri frm_header | |
| #emit MOVS 12 | |
| // Restore heap if needed | |
| if (heap) { | |
| RestoreHeapToAddress(heap); | |
| }{} | |
| // IMPORTANT: Fix compiler bug (returning strings in variadic functions) | |
| #emit LOAD.S.pri 8 | |
| #emit ADD.C 12 | |
| #emit MOVE.alt | |
| #emit LCTRL 5 | |
| #emit ADD | |
| #emit LOAD.I | |
| #emit STOR.S.pri 20 // 16 + (static_args * 4) | |
| return output; | |
| // It is actually used, just not by its symbol name | |
| #pragma unused fmat | |
| } |