diff --git a/core/logic/smn_adt_array.cpp b/core/logic/smn_adt_array.cpp index fd85743fac..b09c92568c 100644 --- a/core/logic/smn_adt_array.cpp +++ b/core/logic/smn_adt_array.cpp @@ -587,27 +587,68 @@ static cell_t FindStringInArray(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); } - // the blocknumber is not guaranteed to always be passed + // The parameters above 2 are optional + size_t blocknumber = 0; if (params[0] >= 3) { blocknumber = (size_t)params[3]; + if (blocknumber >= array->blocksize()) + { + return pContext->ThrowNativeError("Invalid block %d (blocksize: %d)", blocknumber, array->blocksize()); + } } - if (blocknumber >= array->blocksize()) + int startidx = -1; + if (params[0] >= 4) { - return pContext->ThrowNativeError("Invalid block %d (blocksize: %d)", blocknumber, array->blocksize()); + startidx = params[4]; + if (startidx > 0 && startidx > array->size()) + { + return pContext->ThrowNativeError("Invalid start index %d (max: %d)", startidx, array->size()); + } } + bool reverse = false; + if (params[0] >= 5) + { + reverse = params[5]; + } + + typedef int (*STRCOMPARE)(const char *, const char *); + STRCOMPARE comparefn = (params[0] < 6 || params[6]) ? strcmp : strcasecmp; + char *str; pContext->LocalToString(params[2], &str); - for (unsigned int i = 0; i < array->size(); i++) + if (reverse) + { + if (startidx < 0) + { + startidx = array->size(); + } + for (int i = (startidx - 1); i >= 0; i--) + { + const char *array_str = (const char *)&array->base()[i * array->blocksize() + blocknumber]; + if (comparefn(str, array_str) == 0) + { + return (cell_t) i; + } + } + } + else { - const char *array_str = (const char *)&array->base()[i * array->blocksize() + blocknumber]; - if (strcmp(str, array_str) == 0) + if (startidx < -1) + { + startidx = -1; + } + for (unsigned int i = (startidx + 1); i < array->size(); i++) { - return (cell_t) i; + const char *array_str = (const char *)&array->base()[i * array->blocksize() + blocknumber]; + if (comparefn(str, array_str) == 0) + { + return (cell_t) i; + } } } @@ -626,24 +667,62 @@ static cell_t FindValueInArray(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Invalid Handle %x (error: %d)", params[1], err); } - // the blocknumber is not guaranteed to always be passed + // The parameters above 2 are optional + size_t blocknumber = 0; if (params[0] >= 3) { - blocknumber = (size_t) params[3]; + blocknumber = (size_t)params[3]; + if (blocknumber >= array->blocksize()) + { + return pContext->ThrowNativeError("Invalid block %d (blocksize: %d)", blocknumber, array->blocksize()); + } } - if (blocknumber >= array->blocksize()) + int startidx = -1; + if (params[0] >= 4) { - return pContext->ThrowNativeError("Invalid block %d (blocksize: %d)", blocknumber, array->blocksize()); + startidx = params[4]; + if (startidx > 0 && startidx > array->size()) + { + return pContext->ThrowNativeError("Invalid start index %d (max: %d)", startidx, array->size()); + } } - for (unsigned int i = 0; i < array->size(); i++) + bool reverse = false; + if (params[0] >= 5) { - cell_t *blk = array->at(i); - if (params[2] == blk[blocknumber]) + reverse = params[5]; + } + + if (reverse) + { + if (startidx < 0) + { + startidx = array->size(); + } + for (int i = (startidx - 1); i >= 0; i--) + { + cell_t *blk = array->at(i); + if (params[2] == blk[blocknumber]) + { + return (cell_t) i; + } + } + } + else + { + if (startidx < -1) + { + startidx = -1; + } + for (unsigned int i = (startidx + 1); i < array->size(); i++) { - return (cell_t) i; + cell_t *blk = array->at(i); + if (params[2] == blk[blocknumber]) + { + return (cell_t) i; + } } } diff --git a/plugins/include/adt_array.inc b/plugins/include/adt_array.inc index 3ac8d8e3c4..f398dc9e18 100644 --- a/plugins/include/adt_array.inc +++ b/plugins/include/adt_array.inc @@ -207,19 +207,28 @@ methodmap ArrayList < Handle { // Returns the index for the first occurrence of the provided string. If // the string cannot be located, -1 will be returned. // - // @param item String to search for - // @param block Optionally which block to search in + // @param item String to search for. + // @param block Optionally which block to search in. + // @param start Index to start searching from (exclusive), or -1 for direction default. + // Valid numbers are in the interval [-1..Length]. + // @param reverse Whether the search direction should be reversed. + // @param caseSensitive If true (default), comparison is case sensitive. + // If false, comparison is case insensitive. // @return Array index, or -1 on failure - public native int FindString(const char[] item, int block=0); + // @error Invalid block, or invalid start index. + public native int FindString(const char[] item, int block=0, int start=-1, bool reverse=false, bool caseSensitive=true); // Returns the index for the first occurrence of the provided value. If the // value cannot be located, -1 will be returned. // - // @param item Value to search for - // @param block Optionally which block to search in - // @return Array index, or -1 on failure - // @error Invalid block index - public native int FindValue(any item, int block=0); + // @param item Value to search for. + // @param block Optionally which block to search in. + // @param start Index to start searching from (exclusive), or -1 for direction default. + // Valid numbers are in the interval [-1..Length]. + // @param reverse Whether the search direction should be reversed. + // @return Array index, or -1 on failure. + // @error Invalid block, or invalid start index. + public native int FindValue(any item, int block=0, int start=-1, bool reverse=false); // Sort an ADT Array. Specify the type as Integer, Float, or String. // diff --git a/plugins/testsuite/mock/test_arraylist_find.sp b/plugins/testsuite/mock/test_arraylist_find.sp new file mode 100644 index 0000000000..010b27737c --- /dev/null +++ b/plugins/testsuite/mock/test_arraylist_find.sp @@ -0,0 +1,141 @@ +#pragma semicolon 1 +#pragma newdecls required +#include + +enum struct TestStruct +{ + int intval; + char strval[32]; +} + +public void OnPluginStart() +{ + ArrayList list = new ArrayList(sizeof(TestStruct)); + + // -------------------------------------------------------------------------------- + + SetTestContext("EmptyArrayTest"); + + AssertEq("test_forward", list.FindString("index3", TestStruct::strval, -1, false), -1); + AssertEq("test_reverse", list.FindString("index3", TestStruct::strval, -1, true), -1); + AssertEq("test_forward", list.FindValue(3, TestStruct::intval, -1, false), -1); + AssertEq("test_reverse", list.FindValue(3, TestStruct::intval, -1, true), -1); + + // -------------------------------------------------------------------------------- + + // Fill + TestStruct ts; + for (int i = 0; i < 10; i++) + { + ts.intval = i; + Format(ts.strval, sizeof(ts.strval), "index%d", i); + list.PushArray(ts); + } + + // -------------------------------------------------------------------------------- + + SetTestContext("FindString"); + + AssertEq("test_defaults", list.FindString("index3", TestStruct::strval), 3); + + AssertEq("test_forward", list.FindString("index3", TestStruct::strval, -1, false), 3); + AssertEq("test_forward", list.FindString("index3", TestStruct::strval, 0, false), 3); + AssertEq("test_forward", list.FindString("index3", TestStruct::strval, 2, false), 3); + AssertEq("test_forward", list.FindString("index3", TestStruct::strval, 3, false), -1); + AssertEq("test_forward", list.FindString("index3", TestStruct::strval, 10, false), -1); + + AssertEq("test_reverse", list.FindString("index3", TestStruct::strval, -1, true), 3); + AssertEq("test_reverse", list.FindString("index3", TestStruct::strval, 0, true), -1); + AssertEq("test_reverse", list.FindString("index3", TestStruct::strval, 3, true), -1); + AssertEq("test_reverse", list.FindString("index3", TestStruct::strval, 4, true), 3); + AssertEq("test_reverse", list.FindString("index3", TestStruct::strval, 10, true), 3); + + AssertEq("test_bottom", list.FindString("index0", TestStruct::strval, -1, false), 0); + AssertEq("test_bottom", list.FindString("index0", TestStruct::strval, -1, true), 0); + AssertEq("test_bottom", list.FindString("index0", TestStruct::strval, 1, true), 0); + AssertEq("test_bottom", list.FindString("index0", TestStruct::strval, 10, false), -1); + AssertEq("test_bottom", list.FindString("index0", TestStruct::strval, 10, true), 0); + + AssertEq("test_top", list.FindString("index9", TestStruct::strval, -1, false), 9); + AssertEq("test_top", list.FindString("index9", TestStruct::strval, -1, true), 9); + AssertEq("test_top", list.FindString("index9", TestStruct::strval, 8, false), 9); + AssertEq("test_top", list.FindString("index9", TestStruct::strval, 10, false), -1); + AssertEq("test_top", list.FindString("index9", TestStruct::strval, 10, true), 9); + + AssertEq("test_case_sensitive", list.FindString("INDEX0", TestStruct::strval, .caseSensitive = true), -1); + AssertEq("test_case_sensitive", list.FindString("INDEX0", TestStruct::strval, .caseSensitive = false), 0); + + // -------------------------------------------------------------------------------- + + SetTestContext("FindValue"); + + AssertEq("test_defaults", list.FindValue(3, TestStruct::intval), 3); + + AssertEq("test_forward", list.FindValue(3, TestStruct::intval, -1, false), 3); + AssertEq("test_forward", list.FindValue(3, TestStruct::intval, 0, false), 3); + AssertEq("test_forward", list.FindValue(3, TestStruct::intval, 2, false), 3); + AssertEq("test_forward", list.FindValue(3, TestStruct::intval, 3, false), -1); + AssertEq("test_forward", list.FindValue(3, TestStruct::intval, 10, false), -1); + + AssertEq("test_reverse", list.FindValue(3, TestStruct::intval, -1, true), 3); + AssertEq("test_reverse", list.FindValue(3, TestStruct::intval, 0, true), -1); + AssertEq("test_reverse", list.FindValue(3, TestStruct::intval, 3, true), -1); + AssertEq("test_reverse", list.FindValue(3, TestStruct::intval, 4, true), 3); + AssertEq("test_reverse", list.FindValue(3, TestStruct::intval, 10, true), 3); + + AssertEq("test_bottom", list.FindValue(0, TestStruct::intval, -1, false), 0); + AssertEq("test_bottom", list.FindValue(0, TestStruct::intval, -1, true), 0); + AssertEq("test_bottom", list.FindValue(0, TestStruct::intval, 1, true), 0); + AssertEq("test_bottom", list.FindValue(0, TestStruct::intval, 10, false), -1); + AssertEq("test_bottom", list.FindValue(0, TestStruct::intval, 10, true), 0); + + AssertEq("test_top", list.FindValue(9, TestStruct::intval, -1, false), 9); + AssertEq("test_top", list.FindValue(9, TestStruct::intval, -1, true), 9); + AssertEq("test_top", list.FindValue(9, TestStruct::intval, 8, false), 9); + AssertEq("test_top", list.FindValue(9, TestStruct::intval, 10, false), -1); + AssertEq("test_top", list.FindValue(9, TestStruct::intval, 10, true), 9); + + // -------------------------------------------------------------------------------- + + SetTestContext("IterateOverFindString"); + int found, index; + + // Duplicate last entry + list.PushArray(ts); + + found = 0; index = -1; + while ((index = list.FindString("index9", TestStruct::strval, index, false)) != -1) + { + found++; + } + AssertEq("test_find_all_strings_forward", found, 2); + + found = 0; index = -1; + while ((index = list.FindString("index9", TestStruct::strval, index, true)) != -1) + { + found++; + } + AssertEq("test_find_all_strings_reverse", found, 2); + + // -------------------------------------------------------------------------------- + + SetTestContext("IterateOverFindValue"); + + found = 0, index = -1; + while ((index = list.FindValue(9, TestStruct::intval, index, false)) != -1) + { + found++; + } + AssertEq("test_find_all_values_forward", found, 2); + + found = 0; index = -1; + while ((index = list.FindValue(9, TestStruct::intval, index, true)) != -1) + { + found++; + } + AssertEq("test_find_all_values_reverse", found, 2); + + // -------------------------------------------------------------------------------- + + PrintToServer("OK"); +} \ No newline at end of file