buffer: implement `iterable` interface
authorVladimir Kurchatkin <vladimir.kurchatkin@gmail.com>
Tue, 23 Dec 2014 18:03:54 +0000 (21:03 +0300)
committerVladimir Kurchatkin <vladimir.kurchatkin@gmail.com>
Wed, 28 Jan 2015 13:40:15 +0000 (16:40 +0300)
This makes possible to use `for..of` loop with
buffers. Also related `keys`, `values` and `entries`
methods are added for feature parity with `Uint8Array`.

PR-URL: https://github.com/iojs/io.js/pull/525
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
benchmark/buffers/buffer-iterate.js [new file with mode: 0644]
doc/api/buffer.markdown
lib/buffer.js
test/parallel/test-buffer-iterator.js [new file with mode: 0644]

diff --git a/benchmark/buffers/buffer-iterate.js b/benchmark/buffers/buffer-iterate.js
new file mode 100644 (file)
index 0000000..77a0b59
--- /dev/null
@@ -0,0 +1,63 @@
+var SlowBuffer = require('buffer').SlowBuffer;
+var common = require('../common.js');
+var assert = require('assert');
+
+var bench = common.createBenchmark(main, {
+  size: [16, 512, 1024, 4096, 16386],
+  type: ['fast', 'slow'],
+  method: ['for', 'forOf', 'iterator'],
+  n: [1e3]
+});
+
+var methods = {
+  'for': benchFor,
+  'forOf': benchForOf,
+  'iterator': benchIterator
+};
+
+function main(conf) {
+  var len = +conf.size;
+  var clazz = conf.type === 'fast' ? Buffer : SlowBuffer;
+  var buffer = new clazz(len);
+  buffer.fill(0);
+
+  methods[conf.method](buffer, conf.n);
+}
+
+
+function benchFor(buffer, n) {
+  bench.start();
+
+  for (var k = 0; k < n; k++)
+    for (var i = 0; i < buffer.length; i++)
+      assert(buffer[i] === 0);
+
+  bench.end(n);
+}
+
+function benchForOf(buffer, n) {
+  bench.start();
+
+  for (var k = 0; k < n; k++)
+    for (var b of buffer)
+      assert(b === 0);
+
+  bench.end(n);
+}
+
+function benchIterator(buffer, n) {
+  bench.start();
+
+  for (var k = 0; k < n; k++) {
+    var iter = buffer[Symbol.iterator]();
+    var cur = iter.next();
+
+    while (!cur.done) {
+      assert(cur.value === 0);
+      cur = iter.next();
+    }
+
+  }
+
+  bench.end(n);
+}
index 2269345..f683618 100644 (file)
@@ -797,6 +797,19 @@ buffer.
     var b = new Buffer(50);
     b.fill("h");
 
+### buffer.values()
+
+Creates iterator for buffer values (bytes). This function is called automatically
+when `buffer` is used in a `for..of` statement.
+
+### buffer.keys()
+
+Creates iterator for buffer keys (indices).
+
+### buffer.entries()
+
+Creates iterator for `[index, byte]` arrays.
+
 ## buffer.INSPECT_MAX_BYTES
 
 * Number, Default: 50
@@ -807,6 +820,22 @@ be overridden by user modules.
 Note that this is a property on the buffer module returned by
 `require('buffer')`, not on the Buffer global, or a buffer instance.
 
+## ES6 iteration
+
+Buffers can be iterated over using `for..of` syntax:
+
+    var buf = new Buffer([1, 2, 3]);
+
+    for (var b of buf)
+      console.log(b)
+
+    // 1
+    // 2
+    // 3
+
+Additionally, `buffer.values()`, `buffer.keys()` and `buffer.entries()`
+methods can be used to create iterators.
+
 ## Class: SlowBuffer
 
 Returns an un-pooled `Buffer`.
index dbc5d5f..4c87726 100644 (file)
@@ -930,3 +930,80 @@ Buffer.prototype.writeDoubleBE = function writeDoubleBE(val, offset, noAssert) {
   internal.writeDoubleBE(this, val, offset);
   return offset + 8;
 };
+
+// ES6 iterator
+
+var ITERATOR_KIND_KEYS = 1;
+var ITERATOR_KIND_ENTRIES = 3;
+
+function BufferIteratorResult(value, done) {
+  this.value = value;
+  this.done = done;
+}
+
+var resultCache = new Array(256);
+
+for (var i = 0; i < 256; i++)
+  resultCache[i] = Object.freeze(new BufferIteratorResult(i, false));
+
+var finalResult = Object.freeze(new BufferIteratorResult(undefined, true));
+
+function BufferIterator(buffer, kind) {
+  this._buffer = buffer;
+  this._kind = kind;
+  this._index = 0;
+}
+
+BufferIterator.prototype.next = function() {
+  var buffer = this._buffer;
+  var kind = this._kind;
+  var index = this._index;
+
+  if (index >= buffer.length)
+    return finalResult;
+
+  this._index++;
+
+  if (kind === ITERATOR_KIND_ENTRIES)
+    return new BufferIteratorResult([index, buffer[index]], false);
+
+  return new BufferIteratorResult(index, false);
+};
+
+function BufferValueIterator(buffer) {
+  BufferIterator.call(this, buffer, null);
+}
+
+BufferValueIterator.prototype.next = function() {
+  var buffer = this._buffer;
+  var index = this._index;
+
+  if (index >= buffer.length)
+    return finalResult;
+
+  this._index++;
+
+  return resultCache[buffer[index]];
+};
+
+
+BufferIterator.prototype[Symbol.iterator] = function() {
+  return this;
+};
+
+BufferValueIterator.prototype[Symbol.iterator] =
+    BufferIterator.prototype[Symbol.iterator];
+
+Buffer.prototype.keys = function() {
+  return new BufferIterator(this, ITERATOR_KIND_KEYS);
+};
+
+Buffer.prototype.entries = function() {
+  return new BufferIterator(this, ITERATOR_KIND_ENTRIES);
+};
+
+Buffer.prototype.values = function() {
+  return new BufferValueIterator(this);
+};
+
+Buffer.prototype[Symbol.iterator] = Buffer.prototype.values;
diff --git a/test/parallel/test-buffer-iterator.js b/test/parallel/test-buffer-iterator.js
new file mode 100644 (file)
index 0000000..ab02934
--- /dev/null
@@ -0,0 +1,61 @@
+var common = require('../common');
+var assert = require('assert');
+
+var buffer = new Buffer([1, 2, 3, 4, 5]);
+var arr;
+var b;
+
+// buffers should be iterable
+
+arr = [];
+
+for (b of buffer)
+  arr.push(b);
+
+assert.deepEqual(arr, [1, 2, 3, 4, 5]);
+
+
+// buffer iterators should be iterable
+
+arr = [];
+
+for (b of buffer[Symbol.iterator]())
+  arr.push(b);
+
+assert.deepEqual(arr, [1, 2, 3, 4, 5]);
+
+
+// buffer#values() should return iterator for values
+
+arr = [];
+
+for (b of buffer.values())
+  arr.push(b);
+
+assert.deepEqual(arr, [1, 2, 3, 4, 5]);
+
+
+// buffer#keys() should return iterator for keys
+
+arr = [];
+
+for (b of buffer.keys())
+  arr.push(b);
+
+assert.deepEqual(arr, [0, 1, 2, 3, 4]);
+
+
+// buffer#entries() should return iterator for entries
+
+arr = [];
+
+for (var b of buffer.entries())
+  arr.push(b);
+
+assert.deepEqual(arr, [
+  [0, 1],
+  [1, 2],
+  [2, 3],
+  [3, 4],
+  [4, 5]
+]);