From 4dedc0929472976e8730fd317b05a4eabf522f86 Mon Sep 17 00:00:00 2001 From: Sam Rijs Date: Sun, 14 Dec 2014 13:27:53 +1100 Subject: [PATCH 1/2] buffer: add indexOf method Adds a `.indexOf` method to `Buffer`, which borrows semantics from both `Array.prototype.indexOf` and `String.prototype.indexOf`. `Buffer.prototype.indexOf` can be invoked with a Buffer, a string or a number as the needle. If the needle a Buffer or string, it will find the first occurrence of this sequence of bytes. If the needle is a number, it will find the first occurrence of this byte. Reviewed-by: Sam Rijs Fixes: https://github.com/iojs/io.js/issues/95 PR-URL: https://github.com/iojs/io.js/pull/160 --- doc/api/buffer.markdown | 7 ++++++ lib/buffer.js | 10 ++++++++ src/node_buffer.cc | 50 ++++++++++++++++++++++++++++++++++++++ test/simple/test-buffer.js | 10 ++++++++ 4 files changed, 77 insertions(+) diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 5d9cd9b9b7a1bb..19790b44fd9e9d 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -764,6 +764,13 @@ buffer. var b = new Buffer(50); b.fill("h"); +### buf.indexOf(value[, fromIndex]) + +* `value` Buffer or String or Number +* `fromIndex` Number, Optional, Default: 0 + +Finds the index within the buffer of the first occurrence of the specified value, starting the search at fromIndex. Returns -1 if the value is not found. + ## buffer.INSPECT_MAX_BYTES * Number, Default: 50 diff --git a/lib/buffer.js b/lib/buffer.js index f14783d62650cf..6d0e7c8203c49d 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -332,6 +332,16 @@ Buffer.prototype.fill = function fill(val, start, end) { return this; }; +Buffer.prototype.indexOf = function indexOf(needle, pos) { + if (typeof needle === 'number') { + needle = new Buffer([needle]); + } else if (typeof needle === 'string') { + needle = new Buffer(needle); + } else if (!(needle instanceof Buffer)) { + throw new TypeError('Argument must be a Buffer, number or string'); + } + return internal.indexOf(this, needle, pos); +}; // XXX remove in v0.13 Buffer.prototype.get = util.deprecate(function get(offset) { diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 4c1caeb6eccabe..e3fdc10f4dd7b9 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -34,6 +34,7 @@ #include #define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) #define CHECK_NOT_OOB(r) \ do { \ @@ -585,6 +586,54 @@ void Compare(const FunctionCallbackInfo &args) { args.GetReturnValue().Set(val); } +void IndexOf(const FunctionCallbackInfo &args) { + Local obj = args[0]->ToObject(); + char* obj_data = + static_cast(obj->GetIndexedPropertiesExternalArrayData()); + int32_t obj_length = obj->GetIndexedPropertiesExternalArrayDataLength(); + + Local search = args[1]->ToObject(); + char* search_data = + static_cast(search->GetIndexedPropertiesExternalArrayData()); + int32_t search_length = search->GetIndexedPropertiesExternalArrayDataLength(); + + int32_t pos = args[2]->Int32Value(); + int32_t start = MIN(MAX(pos, 0), obj_length); + + if (search_length == 0) { + return args.GetReturnValue().Set(start); + } + + while (search_length <= obj_length - start) { + // Search for the first byte of the needle. + char *chr = reinterpret_cast( + memchr(&obj_data[start], search_data[0], obj_length - start)); + int32_t chrpos = (intptr_t)chr - (intptr_t)obj_data; + if (chr == NULL) { + // First byte not found, short circuit. + return args.GetReturnValue().Set(-1); + } + if (search_length == 1) { + // Nothing more to compare, we found it. + return args.GetReturnValue().Set(chrpos); + } + if (search_length > obj_length - chrpos) { + // Needle is longer than the rest of the haystack, + // no way it is contained in there. + return args.GetReturnValue().Set(-1); + } + int cmp = memcmp(&chr[1], &search_data[1], search_length - 1); + if (cmp == 0) { + // All bytes are equal, we found it. + return args.GetReturnValue().Set(chrpos); + } + // Advance start position for next iteration. + start = chrpos + 1; + } + + return args.GetReturnValue().Set(-1); +} + // pass Buffer object to load prototype methods void SetupBufferJS(const FunctionCallbackInfo& args) { @@ -629,6 +678,7 @@ void SetupBufferJS(const FunctionCallbackInfo& args) { env->SetMethod(internal, "byteLength", ByteLength); env->SetMethod(internal, "compare", Compare); env->SetMethod(internal, "fill", Fill); + env->SetMethod(internal, "indexOf", IndexOf); env->SetMethod(internal, "readDoubleBE", ReadDoubleBE); env->SetMethod(internal, "readDoubleLE", ReadDoubleLE); diff --git a/test/simple/test-buffer.js b/test/simple/test-buffer.js index bf742f93480934..e56e34773364f2 100644 --- a/test/simple/test-buffer.js +++ b/test/simple/test-buffer.js @@ -1184,3 +1184,13 @@ assert.throws(function() { var b = new Buffer(1); b.equals('abc'); }); + +// IndexOf Tests +assert.equal(Buffer('abc').indexOf(''), 0) +assert.equal(Buffer('abc').indexOf('bd'), -1) +assert.equal(Buffer('abc').indexOf('bc'), 1) +assert.equal(Buffer('abc').indexOf(0x62), 1) +assert.equal(Buffer('abc').indexOf(Buffer('bc')), 1) +assert.equal(Buffer('abc').indexOf(Buffer([0x62,0x63])), 1) +assert.equal(Buffer('abc').indexOf('bc', 1), 1) +assert.equal(Buffer('abc').indexOf('bc', 2), -1) From b36c6f1531fcfa6a5cba267a697fbc605756ccef Mon Sep 17 00:00:00 2001 From: Martin Algesten Date: Sun, 14 Dec 2014 12:22:27 +0530 Subject: [PATCH 2/2] buffer: add lastIndexOf method Expanding and reusing code by Sam Rijs in commit 4dedc09 to also add a `.lastIndexOf` method to `Buffer`. `Buffer.prototype.lastIndexOf` takes the same arguments as its predecessor and uses a divide-and-conquer method to find the last occurrence of the sequence of byte. Reviewed-by: Martin Algesten Related-To: https://github.com/iojs/io.js/issues/95 Original PR-URL: https://github.com/iojs/io.js/pull/160 PR-URL: https://github.com/iojs/io.js/pull/161 --- doc/api/buffer.markdown | 13 +++++- lib/buffer.js | 11 +++++ src/node_buffer.cc | 90 ++++++++++++++++++++++++++++---------- test/simple/test-buffer.js | 10 +++++ 4 files changed, 100 insertions(+), 24 deletions(-) diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 19790b44fd9e9d..175840a4a2309c 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -769,7 +769,18 @@ buffer. * `value` Buffer or String or Number * `fromIndex` Number, Optional, Default: 0 -Finds the index within the buffer of the first occurrence of the specified value, starting the search at fromIndex. Returns -1 if the value is not found. +Finds the index within the buffer of the first occurrence of the +specified value, starting the search at fromIndex. Returns -1 if the +value is not found. + +### buf.lastIndexOf(value[, fromIndex]) + +* `value` Buffer or String or Number +* `fromIndex` Number, Optional, Default: 0 + +Finds the index within the buffer of the *last* occurrence of the +specified value, starting the search at fromIndex. Returns -1 if the +value is not found. ## buffer.INSPECT_MAX_BYTES diff --git a/lib/buffer.js b/lib/buffer.js index 6d0e7c8203c49d..e9e677ed9e5dd9 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -343,6 +343,17 @@ Buffer.prototype.indexOf = function indexOf(needle, pos) { return internal.indexOf(this, needle, pos); }; +Buffer.prototype.lastIndexOf = function indexOf(needle, pos) { + if (typeof needle === 'number') { + needle = new Buffer([needle]); + } else if (typeof needle === 'string') { + needle = new Buffer(needle); + } else if (!(needle instanceof Buffer)) { + throw new TypeError('Argument must be a Buffer, number or string'); + } + return internal.lastIndexOf(this, needle, pos); +}; + // XXX remove in v0.13 Buffer.prototype.get = util.deprecate(function get(offset) { offset = ~~offset; diff --git a/src/node_buffer.cc b/src/node_buffer.cc index e3fdc10f4dd7b9..5b1b016bb7102c 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -586,24 +586,9 @@ void Compare(const FunctionCallbackInfo &args) { args.GetReturnValue().Set(val); } -void IndexOf(const FunctionCallbackInfo &args) { - Local obj = args[0]->ToObject(); - char* obj_data = - static_cast(obj->GetIndexedPropertiesExternalArrayData()); - int32_t obj_length = obj->GetIndexedPropertiesExternalArrayDataLength(); - - Local search = args[1]->ToObject(); - char* search_data = - static_cast(search->GetIndexedPropertiesExternalArrayData()); - int32_t search_length = search->GetIndexedPropertiesExternalArrayDataLength(); - - int32_t pos = args[2]->Int32Value(); - int32_t start = MIN(MAX(pos, 0), obj_length); - - if (search_length == 0) { - return args.GetReturnValue().Set(start); - } - +int32_t IndexOf_DoSearch(char* obj_data, int32_t obj_length, + char* search_data, int32_t search_length, + int32_t start) { while (search_length <= obj_length - start) { // Search for the first byte of the needle. char *chr = reinterpret_cast( @@ -611,29 +596,87 @@ void IndexOf(const FunctionCallbackInfo &args) { int32_t chrpos = (intptr_t)chr - (intptr_t)obj_data; if (chr == NULL) { // First byte not found, short circuit. - return args.GetReturnValue().Set(-1); + return -1; } if (search_length == 1) { // Nothing more to compare, we found it. - return args.GetReturnValue().Set(chrpos); + return chrpos; } if (search_length > obj_length - chrpos) { // Needle is longer than the rest of the haystack, // no way it is contained in there. - return args.GetReturnValue().Set(-1); + return -1; } int cmp = memcmp(&chr[1], &search_data[1], search_length - 1); if (cmp == 0) { // All bytes are equal, we found it. - return args.GetReturnValue().Set(chrpos); + return chrpos; } // Advance start position for next iteration. start = chrpos + 1; } - return args.GetReturnValue().Set(-1); + return -1; +} + +void IndexOf_Do(const FunctionCallbackInfo &args, bool isLast) { + Local obj = args[0]->ToObject(); + char* obj_data = + static_cast(obj->GetIndexedPropertiesExternalArrayData()); + int32_t obj_length = obj->GetIndexedPropertiesExternalArrayDataLength(); + + Local search = args[1]->ToObject(); + char* search_data = + static_cast(search->GetIndexedPropertiesExternalArrayData()); + int32_t search_length = search->GetIndexedPropertiesExternalArrayDataLength(); + + int32_t pos = args[2]->Int32Value(); + int32_t start = MIN(MAX(pos, 0), obj_length); + int32_t end = obj_length; + + if (isLast) { + if (search_length == 0) { + return args.GetReturnValue().Set(end); + } + int32_t best = -1; + while (search_length <= end - start) { + int32_t t = IndexOf_DoSearch(obj_data, obj_length, + search_data, search_length, + start); + if (t < 0) { + if (best < 0) { + break; + } else { + end = start; + start = best + 1; + if (start >= end) { + break; + } + } + } else { + best = t; + start = t + 1 + ((int32_t)(end - t) / 2); + } + } + args.GetReturnValue().Set(best); + } else { + if (search_length == 0) { + return args.GetReturnValue().Set(start); + } + int32_t ret = IndexOf_DoSearch(obj_data, obj_length, + search_data, search_length, + start); + args.GetReturnValue().Set(ret); + } +} + +void IndexOf(const FunctionCallbackInfo &args) { + IndexOf_Do(args, false); } +void LastIndexOf(const FunctionCallbackInfo &args) { + IndexOf_Do(args, true); +} // pass Buffer object to load prototype methods void SetupBufferJS(const FunctionCallbackInfo& args) { @@ -679,6 +722,7 @@ void SetupBufferJS(const FunctionCallbackInfo& args) { env->SetMethod(internal, "compare", Compare); env->SetMethod(internal, "fill", Fill); env->SetMethod(internal, "indexOf", IndexOf); + env->SetMethod(internal, "lastIndexOf", LastIndexOf); env->SetMethod(internal, "readDoubleBE", ReadDoubleBE); env->SetMethod(internal, "readDoubleLE", ReadDoubleLE); diff --git a/test/simple/test-buffer.js b/test/simple/test-buffer.js index e56e34773364f2..c08139e7901e2c 100644 --- a/test/simple/test-buffer.js +++ b/test/simple/test-buffer.js @@ -1194,3 +1194,13 @@ assert.equal(Buffer('abc').indexOf(Buffer('bc')), 1) assert.equal(Buffer('abc').indexOf(Buffer([0x62,0x63])), 1) assert.equal(Buffer('abc').indexOf('bc', 1), 1) assert.equal(Buffer('abc').indexOf('bc', 2), -1) + +// LastIndexOf Tests +assert.equal(Buffer('abcabc').lastIndexOf(''), 6) +assert.equal(Buffer('abcabc').lastIndexOf('bd'), -1) +assert.equal(Buffer('abcabc').lastIndexOf('bc'), 4) +assert.equal(Buffer('abcabc').lastIndexOf(0x62), 4) +assert.equal(Buffer('abcabc').lastIndexOf(Buffer('bc')), 4) +assert.equal(Buffer('abcabc').lastIndexOf(Buffer([0x62,0x63])), 4) +assert.equal(Buffer('abcabc').lastIndexOf('bc', 1), 4) +assert.equal(Buffer('abcabc').lastIndexOf('bc', 5), -1)