diff --git a/amxmodx/string.cpp b/amxmodx/string.cpp index 5f597512..3f3f3962 100755 --- a/amxmodx/string.cpp +++ b/amxmodx/string.cpp @@ -926,12 +926,55 @@ static cell AMX_NATIVE_CALL amx_strtok2(AMX *amx, cell *params) return pos; } -//added by BAILOPAN +// native argparse(const text[], pos, buffer, maxlen); +static cell AMX_NATIVE_CALL argparse(AMX *amx, cell *params) +{ + int temp; + const char *input = get_amxstring(amx, params[1], 0, temp); + size_t input_len = size_t(temp); + size_t start_pos = size_t(params[2]); + + cell *buffer = get_amxaddr(amx, params[3]); + size_t buflen = size_t(params[4]); + + // Strip all left-hand whitespace. + size_t i = start_pos; + while (i < input_len && isspace(input[i])) + i++; + + if (i >= input_len) { + *buffer = '\0'; + return -1; + } + + cell *bufpos = buffer; + + bool in_quote = false; + for (; i < input_len; i++) { + // Ignore quotes, except as an indicator as to whether to stop + // at a space. + if (input[i] == '"') { + in_quote = !in_quote; + continue; + } + + // If not in quotes, and we see a space, stop. + if (isspace(input[i]) && !in_quote) + break; + + if (size_t(bufpos - buffer) < buflen) + *bufpos++ = input[i]; + } + + *bufpos = '\0'; + return i; +} + +//added by BAILOPAN :( //Takes a string and breaks it into a 1st param and rest params //strbreak(String[], First[], FirstLen, Rest[], RestLen) static cell AMX_NATIVE_CALL strbreak(AMX *amx, cell *params) /* 5 param */ { - int _len; bool in_quote = false; bool had_quotes = false; @@ -946,9 +989,9 @@ static cell AMX_NATIVE_CALL strbreak(AMX *amx, cell *params) /* 5 param */ size_t len = (size_t)_len; - while (isspace(string[i]) && i (size_t)LeftMax) ? (size_t)LeftMax : pos - _end; - size_t to_go = end-beg; - if (end && to_go) - { - while (to_go--) - *left++ = (cell)*start++; - } - *left = '\0'; + + // If there is anything to copy, make sure we copy min(maxlen, slicelen). + size_t copylen = end >= beg + ? ((end - beg > size_t(LeftMax)) + ? size_t(LeftMax) + : end - beg + ) + : 0; + set_amxstring(amx, params[2], start, copylen); + end = (len-i+1 > (size_t)RightMax) ? (size_t)RightMax : len-i+1; - if (end) + if (end) { start = &(string[i]); while (end--) @@ -1224,6 +1270,7 @@ AMX_NATIVE_INFO string_Natives[] = {"replace", replace}, {"setc", setc}, {"strbreak", strbreak}, + {"argparse", argparse}, {"strtolower", strtolower}, {"strtoupper", strtoupper}, {"str_to_num", strtonum}, diff --git a/plugins/include/string.inc b/plugins/include/string.inc index 57a702a6..e4762781 100755 --- a/plugins/include/string.inc +++ b/plugins/include/string.inc @@ -223,16 +223,6 @@ native strtok(const text[], Left[], leftLen, Right[], rightLen, token=' ', trimS */ native strtok2(const text[], left[], const llen, right[], const rlen, const token = ' ', const trim = 0); -/* Gets parameters from text one at a time - It breaks a string into the first parameter and the rest of the parameters - (A left side and right side of the string) - Example: to split text: "^"This is^" the best year", - strbreak(text, arg1, len1, arg2, len2) - arg1="This is", arg2=the best year - This is more useful than parse() because you can keep breaking - any number of arguments */ -native strbreak(const text[], Left[], leftLen, Right[], rightLen); - /* Strips spaces from the beginning and end of a string. */ native trim(text[]); @@ -277,6 +267,58 @@ stock bool:is_str_num(const sString[]) return sString[i] == 0 && i != 0; } +// Warning: this function is deprecated as it does not work properly. Use +// argparse() or argbreak(). +native strbreak(const text[], Left[], leftLen, Right[], rightLen); + +/** + * Parses an argument string to find the first argument. You can use this to + * replace strbreak(). + * + * You can use argparse() to break a string into all of its arguments: + * new arg[N], pos; + * while (true) { + * pos = argparse(string, pos, arg, sizeof(arg) - 1); + * if (pos == -1) + * break; + * } + * + * All initial whitespace is removed. Remaining characters are read until an + * argument separator is encountered. A separator is any whitespace not inside + * a double-quotation pair (i.e. "x b" is one argument). If only one quotation + * mark appears, argparse() acts as if one existed at the end of the string. + * Quotation marks are never written back, and do not act as separators. For + * example, "a""b""c" will return "abc". An empty quote pair ("") will count + * as an argument containing no characters. + * + * argparse() will write an empty string to argbuffer if no argument is found. + * + * @param text String to tokenize. + * @param pos Position to start parsing from. + * @param argbuffer Buffer to store first argument. + * @param maxlen Size of the buffer. + * @return If no argument was found, -1 is returned. Otherwise, + * the index to the next position to parse from is + * returned. This might be the very end of the string. + */ +native argparse(const text[], pos, argbuffer[], maxlen); + +/* Emulates strbreak() using argparse(). */ +stock argbreak(const text[], left[], leftlen, right[], rightlen) +{ + new pos = argparse(text, 0, left, leftlen); + + if (pos == -1) + return -1; + + new textlen = strlen(text); + while (pos < textlen && isspace(text[pos])) + pos++; + + copy(right, rightlen, text[pos]); + return pos; +} + /* It is basically strbreak but you have a delimiter that is more than one character in length. You pass the Input string, the Left output, the max length of the left output, the right output , the max right length, and then the delimiter string. diff --git a/plugins/testsuite/strbreak.sma b/plugins/testsuite/strbreak.sma new file mode 100644 index 00000000..98724093 --- /dev/null +++ b/plugins/testsuite/strbreak.sma @@ -0,0 +1,339 @@ +// vim: set ts=4 sw=4 tw=99 sts=4 noet ft=c: +#include +#include + +#pragma ctrlchar '\' + +public plugin_init() +{ + register_plugin("strbreak tests", "1.0", "BAILOPAN"); + register_concmd("test_strbreak", "test_strbreak", 0, "admin"); + register_concmd("test_argparse", "test_argparse", 0, "admin"); +} + +new TestCount = 0; +new FailCount = 0; + +test_reset() +{ + TestCount = 0; + FailCount = 0; +} + +test_numequal(a, b) +{ + TestCount++; + if (a == b) { + server_print("[%d] PASS /%d/ == /%d/", TestCount, a, b); + } else { + server_print("[%d] FAIL /%d/ == /%d/", TestCount, a, b); + FailCount++; + } +} + +test_equal(a[], b[]) +{ + TestCount++; + if (equal(a, b)) { + server_print("[%d] PASS /%s/ == /%s/", TestCount, a, b); + } else { + server_print("[%d] FAIL /%s/ == /%s/", TestCount, a, b); + FailCount++; + } +} + +public test_strbreak(id) +{ + test_reset(); + + new left[8], right[8]; + + // Test sort of normal behavior for strbreak(). + strbreak("a b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "b c"); + + strbreak("a", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, ""); + + strbreak("a\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a\""); + test_equal(right, ""); + + strbreak("\"a\" yy", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "yy"); + + strbreak("\"a x", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "\"a x"); + test_equal(right, ""); + + strbreak("a b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "b c"); + + strbreak("\"a\" b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "b c"); + + strbreak("a \"b c\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "\"b c\""); + + strbreak("a q\"b c\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "q\"b c\""); + + strbreak("q \"b c\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "q"); + test_equal(right, "\"b c\""); + + strbreak("q \"b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "q"); + test_equal(right, "\"b c"); + + // strbreak() functionality starts degrading here, but we test this to + // preserve bug-for-bug compatibility. + strbreak("q\"b c\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "\"b c"); + test_equal(right, "\""); + + strbreak("\"a \"a \"b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a \""); + test_equal(right, "\"b c"); + + strbreak("\"a \"a b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a \""); + test_equal(right, "b c"); + + strbreak("\"a\"a b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a\""); + test_equal(right, "b c"); + + strbreak("\"a\" x", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a\""); + test_equal(right, "x"); + + strbreak("\"a\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "\""); + + strbreak("\"a\"b", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "\"a\"b"); + test_equal(right, ""); + + strbreak("q\" b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "q\" b c"); + test_equal(right, ""); + + // Test truncation. + strbreak("123456789A 123456789ABCDE", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234567"); + + strbreak("12345 123456789ABCDE", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "12345"); + test_equal(right, "1234567"); + + strbreak("\"12345\" 123456789ABCDE", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "12345"); + test_equal(right, "1234567"); + + strbreak("\"123456789A\" 123456789ABCDE", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234567"); + + strbreak("\"123456789A\" 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234"); + + strbreak("\"123456789A\" 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234"); + + strbreak("123456789A 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234"); + + strbreak("123456789A 123456778923", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234567"); + + // Bonkers - left-hand makes no sense. Does whitespace count toward the buffer?! + strbreak(" 123456789A 123456778923", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "12345"); + test_equal(right, "1234567"); + + strbreak(" \"123456789A\" 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, ""); + test_equal(right, "1234"); + + strbreak(" \"123456789A\" 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "12"); + test_equal(right, "1234"); + + new text_cmd[7], cheater[64]; + strbreak(" a ", text_cmd, 1, cheater, 31); + test_equal(text_cmd, ""); + test_equal(cheater, ""); + + server_print("%d/%d passed", TestCount - FailCount, TestCount); +} + +public test_argparse(id) +{ + test_reset(); + + new left[8], right[8]; + + // Tests for behavior that we expect to work. + argbreak("a b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "b c"); + + argbreak("a", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, ""); + + argbreak("a\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, ""); + + argbreak("\"a\" yy", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "yy"); + + argbreak("\"a x", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a x"); + test_equal(right, ""); + + argbreak("a b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "b c"); + + argbreak("\"a\" b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "b c"); + + argbreak("a \"b c\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "\"b c\""); + + argbreak("a q\"b c\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "q\"b c\""); + + argbreak("q \"b c\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "q"); + test_equal(right, "\"b c\""); + + argbreak("q \"b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "q"); + test_equal(right, "\"b c"); + + argbreak("q\"b c\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "qb c"); + test_equal(right, ""); + + argbreak("\"a \"a \"b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a a"); + test_equal(right, "\"b c"); + + argbreak("\"a \"a b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a a"); + test_equal(right, "b c"); + + argbreak("\"a\"a b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "aa"); + test_equal(right, "b c"); + + argbreak("\"a\" x", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, "x"); + + argbreak("\"a\"", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "a"); + test_equal(right, ""); + + argbreak("\"a\"b", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "ab"); + test_equal(right, ""); + + argbreak("q\" b c", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "q b c"); + test_equal(right, ""); + + // Test truncation. + argbreak("123456789A 123456789ABCDE", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234567"); + + argbreak("12345 123456789ABCDE", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "12345"); + test_equal(right, "1234567"); + + argbreak("\"12345\" 123456789ABCDE", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "12345"); + test_equal(right, "1234567"); + + argbreak("\"123456789A\" 123456789ABCDE", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234567"); + + argbreak("\"123456789A\" 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234"); + + argbreak("\"123456789A\" 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234"); + + argbreak("123456789A 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234"); + + argbreak("123456789A 123456778923", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234567"); + + argbreak(" 123456789A 123456778923", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234567"); + + argbreak(" \"123456789A\" 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234"); + + argbreak(" \"123456789A\" 1234", left, sizeof(left) - 1, right, sizeof(right) - 1); + test_equal(left, "1234567"); + test_equal(right, "1234"); + + new text_cmd[7], cheater[64]; + argbreak(" a ", text_cmd, 1, cheater, 31); + test_equal(text_cmd, "a"); + test_equal(cheater, ""); + + // Test no-finds. + new pos = argparse(" \t\n \t", 0, left, sizeof(left)); + test_numequal(pos, -1); + test_equal(left, ""); + + // Loop for good measure. + new argv[4][10], argc = 0; + new text[] = "a \"b\" cd\"e\" \t f\" "; + pos = 0; + while (argc < 4) { + pos = argparse(text, pos, argv[argc++], 10 - 1); + if (pos == -1) + break; + } + test_numequal(argc, 4); + test_equal(argv[0], "a"); + test_equal(argv[1], "b"); + test_equal(argv[2], "cde"); + test_equal(argv[3], "f "); + + server_print("%d/%d passed", TestCount - FailCount, TestCount); +} + diff --git a/support/PackageScript b/support/PackageScript index 2e47ea33..2057e933 100644 --- a/support/PackageScript +++ b/support/PackageScript @@ -66,6 +66,11 @@ def split_all(path): parts.insert(0, tail) return parts +def copy_binary(source, dest): + builder.AddCopy(source.binary, dest) + if builder.options.debug == '1': + builder.AddCopy(source.debug, dest) + # Create the distribution folder hierarchy. folder_map = {} for folder in folder_list: @@ -74,7 +79,7 @@ for folder in folder_list: # Copy core dlls. for dll in AMXX.binaries: - builder.AddCopy(dll.binary, folder_map['base/addons/amxmodx/dlls']) + copy_binary(dll, folder_map['base/addons/amxmodx/dlls']) # Copy modules. for module in AMXX.modules: @@ -83,7 +88,7 @@ for module in AMXX.modules: package = ModPackages[parts[1]] else: package = 'base' - builder.AddCopy(module.binary, folder_map[package + '/addons/amxmodx/modules']) + copy_binary(module, folder_map[package + '/addons/amxmodx/modules']) # Copy the compiler. builder.AddCopy(AMXX.amxxpc.binary, folder_map['base/addons/amxmodx/scripting']) @@ -228,6 +233,7 @@ scripting_files = [ 'testsuite/native_test.sma', 'testsuite/nvault_test.sma', 'testsuite/sorttest.sma', + 'testsuite/strbreak.sma', 'testsuite/sqlxtest.sma', 'testsuite/sqlxtest.sq3', 'testsuite/sqlxtest.sql',