From d08d2179f760f2a6c5c74658127e98a158f8a924 Mon Sep 17 00:00:00 2001 From: Mark Reed Date: Tue, 14 May 2024 14:42:30 -0700 Subject: [PATCH] Initial nodejs string wrap --- javascript/lib.c | 170 +++++++++++++++++++++++++++++++++++++- javascript/stringzilla.js | 8 +- scripts/test.js | 106 +++++++++++++++++++++++- 3 files changed, 278 insertions(+), 6 deletions(-) diff --git a/javascript/lib.c b/javascript/lib.c index c468c8f8..fbc357db 100644 --- a/javascript/lib.c +++ b/javascript/lib.c @@ -9,11 +9,19 @@ */ #include // `printf` for debug builds #include // `malloc` to export strings into UTF-8 +#include // strncmp #include // `napi_*` functions #include // `sz_*` functions +typedef struct { + napi_env env_; + napi_ref wrapper_; + sz_cptr_t start; + sz_size_t length; +} sz_wrapper_t; + napi_value indexOfAPI(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value args[2]; @@ -107,18 +115,174 @@ napi_value countAPI(napi_env env, napi_callback_info info) { return js_count; } +napi_value str_startswith(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + napi_value jsthis; + sz_wrapper_t *this; + sz_wrapper_t *arg; + + napi_get_cb_info(env, info, &argc, args, &jsthis, NULL); + napi_unwrap(env, jsthis, (void **)&this); + napi_unwrap(env, args[0], (void **)&arg); + + napi_value ret; + if (this->length < arg->length) { napi_get_boolean(env, false, &ret); } + else if (strncmp(this->start, arg->start, arg->length) == 0) { napi_get_boolean(env, true, &ret); } + else { napi_get_boolean(env, false, &ret); } + return ret; +} +napi_value str_endswith(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + napi_value jsthis; + sz_wrapper_t *this; + sz_wrapper_t *arg; + + napi_get_cb_info(env, info, &argc, args, &jsthis, NULL); + napi_unwrap(env, jsthis, (void **)&this); + napi_unwrap(env, args[0], (void **)&arg); + + napi_value ret; + if (this->length < arg->length) { napi_get_boolean(env, false, &ret); } + else if (strncmp(this->start + (this->length - arg->length), arg->start, arg->length) == 0) { + napi_get_boolean(env, true, &ret); + } + else { napi_get_boolean(env, false, &ret); } + return ret; +} + + +napi_value str_count(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + napi_value jsthis; + sz_wrapper_t *this; + sz_wrapper_t *arg; + + napi_get_cb_info(env, info, &argc, args, &jsthis, NULL); + napi_unwrap(env, jsthis, (void **)&this); + napi_unwrap(env, args[0], (void **)&arg); + + sz_string_view_t haystack = {this->start, this->length}; + sz_string_view_t needle = {arg->start, arg->length}; + + bool overlap = false; + if (argc > 1) { napi_get_value_bool(env, args[1], &overlap); } + + size_t count = 0; + if (needle.length == 0 || haystack.length == 0 || haystack.length < needle.length) { count = 0; } + else if (overlap) { + while (haystack.length) { + sz_cptr_t ptr = sz_find(haystack.start, haystack.length, needle.start, needle.length); + sz_bool_t found = ptr != NULL; + sz_size_t offset = found ? (sz_size_t)(ptr - haystack.start) : haystack.length; + count += found; + haystack.start += offset + found; + haystack.length -= offset + found; + } + } + else { + while (haystack.length) { + sz_cptr_t ptr = sz_find(haystack.start, haystack.length, needle.start, needle.length); + sz_bool_t found = ptr != NULL; + sz_size_t offset = found ? (sz_size_t)(ptr - haystack.start) : haystack.length; + count += found; + haystack.start += offset + needle.length; + haystack.length -= offset + needle.length * found; + } + } + + napi_value js_count; + napi_create_bigint_uint64(env, count, &js_count); + return js_count; +} +napi_value _str_find(sz_find_t finder, napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + napi_value jsthis; + sz_wrapper_t *this; + sz_wrapper_t *needle; + + napi_get_cb_info(env, info, &argc, args, &jsthis, NULL); + napi_unwrap(env, jsthis, (void **)&this); + napi_unwrap(env, args[0], (void **)&needle); + + + napi_value js_result; + if (needle->length == 0) { napi_create_bigint_int64(env, 0, &js_result); } + else { + sz_cptr_t result = finder(this->start, this->length, needle->start, needle->length); + + // In JavaScript, if `indexOf` is unable to indexOf the specified value, then it should return -1 + if (result == NULL) { napi_create_bigint_int64(env, -1, &js_result); } + else { napi_create_bigint_uint64(env, result - this->start, &js_result); } + } + + return js_result; +} + +napi_value str_find(napi_env env, napi_callback_info info) { + return _str_find(sz_find, env, info); +} +napi_value str_rfind(napi_env env, napi_callback_info info) { + return _str_find(sz_rfind, env, info); +} + +static void destroy(napi_env _unused_env, void *obj, void *_unused_hint) { + sz_wrapper_t *sz = (sz_wrapper_t *)obj; + napi_delete_reference(sz->env_, sz->wrapper_); + free((void *)sz->start); + free(sz); +} + +static napi_value create_instance(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_value jsthis; + sz_wrapper_t *obj = malloc(sizeof(sz_wrapper_t)); + napi_get_cb_info(env, info, &argc, args, &jsthis, NULL); + // if (type_check_data_buffer(env, args, argc) != ADDON_OK) { + // return NULL; + //} + // napi_get_buffer_info(env, args[0], &data, &buf_len); + // TODO type check + napi_get_value_string_utf8(env, args[0], NULL, 0, (size_t *)&obj->length); + obj->start = malloc(obj->length + 1); + napi_get_value_string_utf8(env, args[0], (char *)obj->start, obj->length + 1, (size_t *)&obj->length); + + obj->env_ = env; + napi_wrap(env, jsthis, (void *)obj, destroy, NULL, &obj->wrapper_); + return jsthis; +} napi_value Init(napi_env env, napi_value exports) { // Define an array of property descriptors - napi_property_descriptor findDesc = {"indexOf", 0, indexOfAPI, 0, 0, 0, napi_default, 0}; - napi_property_descriptor countDesc = {"count", 0, countAPI, 0, 0, 0, napi_default, 0}; - napi_property_descriptor properties[] = {findDesc, countDesc}; + napi_property_descriptor properties[] = { + {"indexOf", NULL, indexOfAPI, NULL, NULL, NULL, napi_default, NULL}, + {"find", NULL, indexOfAPI, NULL, NULL, NULL, napi_default, NULL}, + {"count", NULL, countAPI, NULL, NULL, NULL, napi_default, NULL} + }; // Define the properties on the `exports` object size_t propertyCount = sizeof(properties) / sizeof(properties[0]); napi_define_properties(env, exports, propertyCount, properties); + napi_property_descriptor obj_properties[] = { + {"indexOf", NULL, str_find, NULL, NULL, NULL, napi_default, NULL}, + {"find", NULL, str_find, NULL, NULL, NULL, napi_default, NULL}, + {"rfind", NULL, str_rfind, NULL, NULL, NULL, napi_default, NULL}, + {"startswith", NULL, str_startswith, NULL, NULL, NULL, napi_default, NULL}, + {"endswith", NULL, str_endswith, NULL, NULL, NULL, napi_default, NULL}, + {"count", NULL, str_count, NULL, NULL, NULL, napi_default, NULL} + }; + napi_value cons; + + napi_define_class(env, "Str", NAPI_AUTO_LENGTH, create_instance, NULL, + sizeof(obj_properties) / sizeof(*obj_properties), obj_properties, &cons); + napi_set_named_property(env, exports, "Str", cons); + return exports; } diff --git a/javascript/stringzilla.js b/javascript/stringzilla.js index 24b78e24..97662de8 100644 --- a/javascript/stringzilla.js +++ b/javascript/stringzilla.js @@ -8,7 +8,8 @@ module.exports = { * @param {string} needle * @returns {bigint} */ - find: compiled.find, + indexOf: compiled.indexOf, + indexOfB: compiled.indexOfB, /** * Searches for a substring in a larger string. @@ -18,5 +19,8 @@ module.exports = { * @param {boolean} overlap * @returns {bigint} */ - count: compiled.count + count: compiled.count, + + Str: compiled.Str }; + diff --git a/scripts/test.js b/scripts/test.js index 5c1d356e..2a163472 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -2,7 +2,7 @@ import test from 'node:test'; import bindings from 'bindings'; import assert from 'node:assert'; -const stringzilla = bindings('stringzilla'); +const stringzilla = bindings('../../build/Release/stringzilla'); test('Find Word in Text - Positive Case', () => { const result = stringzilla.indexOf('hello world, hello john', 'hello'); @@ -68,3 +68,107 @@ test('Count Words - Empty String Inputs', () => { const result_3 = stringzilla.count('', ''); assert.strictEqual(result_3, 0n); }); + +test('Str Count - Empty String Inputs', () => { + const a = new stringzilla.Str('hello world'); + const b = new stringzilla.Str('hi'); + const empty = new stringzilla.Str(''); + + assert.strictEqual(a.count(empty), 0n); + assert.strictEqual(empty.count(b), 0n); + assert.strictEqual(empty.count(empty), 0n); +}); +test('Str.count - No Occurence', () => { + const a = new stringzilla.Str('hello world'); + const b = new stringzilla.Str('hi'); + + const result_1 = a.count(b); + assert.strictEqual(result_1, 0n); +}); +test('Str.count - Multiple Occurrences with Overlap Test', () => { + const a = new stringzilla.Str('abababab'); + const b = new stringzilla.Str('aba'); + + const result_2 = a.count(b, true); + assert.strictEqual(result_2, 3n); + const result_1 = a.count(b); + assert.strictEqual(result_1, 2n); + +}); + +test('Str.count - Multiple Occurrences', () => { + const a = new stringzilla.Str('abigababzzzzzzzzzzzzzzzzzzzbigzzzzzzzzzzzzfdsafbig'); + const b = new stringzilla.Str('big'); + + const res = a.count(b, true); + assert.strictEqual(res, 3n); + +}); +test('Str.count - Single Occurrence', () => { + const a = new stringzilla.Str('azigababzzzzzzzzzzzzzzzzzzzzigzzzzzzzzzzzzfdsafbig'); + const b = new stringzilla.Str('big'); + + const res = a.count(b, true); + assert.strictEqual(res, 1n); + +}); + + +test('Str.find - Positive Case', () => { + const a = new stringzilla.Str('Can you ifnd me here with find'); + const b = new stringzilla.Str('find'); + assert.strictEqual(a.find(b), 26n); +}); + +test('Str.find - Negative Case (Word Not Found)', () => { + const a = new stringzilla.Str('Can you ifnd me here with find'); + const b = new stringzilla.Str('z'); + assert.strictEqual(a.find(b), -1n); +}); + +test('Str.find - Negative Case (Empty String Inputs)', () => { + const a = new stringzilla.Str('hello world'); + const b = new stringzilla.Str('hi'); + const empty = new stringzilla.Str(''); + + assert.strictEqual(a.find(empty), 0n); + assert.strictEqual(empty.find(b), -1n); + assert.strictEqual(empty.find(empty), 0n); +}); +test('Str.rfind', () => { + const a = new stringzilla.Str('Can you ifnd me here with find'); + const b = new stringzilla.Str('n'); + const can = new stringzilla.Str('Can'); + const z = new stringzilla.Str('z'); + const empty = new stringzilla.Str(''); + assert.strictEqual(a.rfind(b), 28n); + assert.strictEqual(a.rfind(z), -1n); + assert.strictEqual(a.rfind(can), 0n); + assert.strictEqual(a.rfind(empty), 0n); + assert.strictEqual(empty.rfind(z), -1n); + assert.strictEqual(empty.rfind(empty), 0n); +}); +test('Str.startswith', () => { + const a = new stringzilla.Str('Can you ifnd me here with find'); + const b = new stringzilla.Str('n'); + const can = new stringzilla.Str('Can'); + const empty = new stringzilla.Str(''); + assert.strictEqual(a.startswith(b), false); + assert.strictEqual(a.startswith(can), true); + assert.strictEqual(a.startswith(empty), true); + assert.strictEqual(empty.startswith(a), false); + assert.strictEqual(empty.startswith(empty), true); +}); +test('Str.endswith', () => { + const a = new stringzilla.Str('Can you ifnd me here with find'); + const b = new stringzilla.Str('n'); + const can = new stringzilla.Str('find'); + const empty = new stringzilla.Str(''); + assert.strictEqual(a.endswith(b), false); + assert.strictEqual(a.endswith(can), true); + assert.strictEqual(a.endswith(empty), true); + assert.strictEqual(empty.endswith(a), false); + assert.strictEqual(empty.endswith(empty), true); +}); + +