main.js (9864B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 /* eslint-disable no-restricted-syntax, no-invalid-this */ 20 21 'use strict'; 22 23 // MODULES // 24 25 var isCollection = require( '@stdlib/assert/is-collection' ); 26 var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive; 27 var isNonNegativeInteger = require( '@stdlib/assert/is-nonnegative-integer' ).isPrimitive; 28 var setReadOnly = require( './../../define-nonenumerable-read-only-property' ); 29 var setReadOnlyAccessor = require( './../../define-nonenumerable-read-only-accessor' ); 30 var iteratorSymbol = require( '@stdlib/symbol/iterator' ); 31 var MAX_ITERATIONS = require( '@stdlib/constants/float64/max' ); 32 33 34 // MAIN // 35 36 /** 37 * Circular buffer constructor. 38 * 39 * @constructor 40 * @param {(PositiveInteger|Collection)} buffer - buffer size or an array-like object to use as the underlying buffer 41 * @throws {TypeError} must provide either a valid buffer size or an array-like object 42 * @returns {CircularBuffer} circular buffer instance 43 * 44 * @example 45 * var b = new CircularBuffer( 3 ); 46 * 47 * // Fill the buffer... 48 * var v = b.push( 'foo' ); 49 * // returns undefined 50 * 51 * v = b.push( 'bar' ); 52 * // returns undefined 53 * 54 * v = b.push( 'beep' ); 55 * // returns undefined 56 * 57 * // Add another value to the buffer and return the removed element: 58 * v = b.push( 'boop' ); 59 * // returns 'foo' 60 */ 61 function CircularBuffer( buffer ) { 62 var i; 63 if ( !(this instanceof CircularBuffer) ) { 64 return new CircularBuffer( buffer ); 65 } 66 if ( isPositiveInteger( buffer ) ) { 67 this._buffer = []; 68 for ( i = 0; i < buffer; i++ ) { 69 this._buffer.push( 0.0 ); // initialize with zeros, but could be any value (we're just ensuring a contiguous block of memory) 70 } 71 } else if ( isCollection( buffer ) ) { 72 this._buffer = buffer; 73 } else { 74 throw new TypeError( 'invalid argument. Must provide either a valid buffer size (positive integer) or an array-like object which can serve as the underlying buffer. Value: `' + buffer + '`.' ); 75 } 76 this._length = this._buffer.length; 77 this._count = 0; 78 this._i = -1; 79 return this; 80 } 81 82 /** 83 * Clears the buffer. 84 * 85 * @name clear 86 * @memberof CircularBuffer.prototype 87 * @type {Function} 88 * @returns {CircularBuffer} circular buffer instance 89 * 90 * @example 91 * var b = new CircularBuffer( 2 ); 92 * 93 * // Add values to the buffer: 94 * b.push( 'foo' ); 95 * b.push( 'bar' ); 96 * b.push( 'beep' ); 97 * b.push( 'boop' ); 98 * 99 * // Get the number of elements currently in the buffer: 100 * var n = b.count; 101 * // returns 2 102 * 103 * // Clear the buffer: 104 * b.clear(); 105 * 106 * // Get the number of buffer values: 107 * n = b.count; 108 * // returns 0 109 */ 110 setReadOnly( CircularBuffer.prototype, 'clear', function clear() { 111 this._count = 0; 112 this._i = -1; // NOTE: this ensures that we always fill the buffer starting at index `0`. 113 return this; 114 }); 115 116 /** 117 * Number of elements currently in the buffer. 118 * 119 * @name count 120 * @memberof CircularBuffer.prototype 121 * @type {NonNegativeInteger} 122 * 123 * @example 124 * var b = new CircularBuffer( 4 ); 125 * 126 * // Get the value count: 127 * var n = b.count; 128 * // returns 0 129 * 130 * // Add values to the buffer: 131 * b.push( 'foo' ); 132 * b.push( 'bar' ); 133 * 134 * // Get the value count: 135 * n = b.count; 136 * // returns 2 137 */ 138 setReadOnlyAccessor( CircularBuffer.prototype, 'count', function get() { 139 return this._count; 140 }); 141 142 /** 143 * Boolean indicating whether a circular buffer is full. 144 * 145 * @name full 146 * @memberof CircularBuffer.prototype 147 * @type {boolean} 148 * 149 * @example 150 * var b = new CircularBuffer( 3 ); 151 * 152 * // Determine if the buffer is full: 153 * var bool = b.full; 154 * // returns false 155 * 156 * // Add values to the buffer: 157 * b.push( 'foo' ); 158 * b.push( 'bar' ); 159 * b.push( 'beep' ); 160 * b.push( 'boop' ); 161 * 162 * // Determine if the buffer is full: 163 * bool = b.full; 164 * // returns true 165 */ 166 setReadOnlyAccessor( CircularBuffer.prototype, 'full', function get() { 167 return this._count === this._length; 168 }); 169 170 /** 171 * Returns an iterator for iterating over a circular buffer. 172 * 173 * ## Notes 174 * 175 * - An iterator does not iterate over partially full buffers. 176 * 177 * @name iterator 178 * @memberof CircularBuffer.prototype 179 * @type {Function} 180 * @param {NonNegativeInteger} [niters] - number of iterations 181 * @throws {TypeError} must provide a nonnegative integer 182 * @returns {Iterator} iterator 183 * 184 * @example 185 * var b = new CircularBuffer( 3 ); 186 * 187 * // Add values to the buffer: 188 * b.push( 'foo' ); 189 * b.push( 'bar' ); 190 * b.push( 'beep' ); 191 * b.push( 'boop' ); 192 * 193 * // Create an iterator: 194 * var it = b.iterator( b.length ); 195 * 196 * // Iterate over the buffer... 197 * var v = it.next().value; 198 * // returns 'bar' 199 * 200 * v = it.next().value; 201 * // returns 'beep' 202 * 203 * v = it.next().value; 204 * // returns 'boop' 205 * 206 * var bool = it.next().done; 207 * // returns true 208 */ 209 setReadOnly( CircularBuffer.prototype, 'iterator', function iterator( niters ) { 210 var iter; 211 var self; 212 var FLG; 213 var N; 214 var n; 215 var i; 216 217 if ( arguments.length ) { 218 if ( !isNonNegativeInteger( niters ) ) { 219 throw new TypeError( 'invalid argument. Must provide a nonnegative integer. Value: `' + niters + '`.' ); 220 } 221 N = niters; 222 } else { 223 N = MAX_ITERATIONS; 224 } 225 self = this; 226 227 // Initialize the iteration index and counter: 228 i = this._i; 229 n = 0; 230 231 // Create an iterator protocol-compliant object: 232 iter = {}; 233 setReadOnly( iter, 'next', next ); 234 setReadOnly( iter, 'return', end ); 235 if ( iteratorSymbol ) { 236 setReadOnly( iter, iteratorSymbol, factory ); 237 } 238 return iter; 239 240 /** 241 * Returns an iterator protocol-compliant object containing the next iterated value. 242 * 243 * @private 244 * @returns {Object} iterator protocol-compliant object 245 */ 246 function next() { 247 /* eslint-disable no-underscore-dangle */ 248 n += 1; 249 if ( FLG || n > N ) { 250 return { 251 'done': true 252 }; 253 } 254 // If the buffer is only partially full, don't allow iteration over "undefined" elements (this ensures similar behavior with `toArray()`)... 255 if ( self._count !== self._length ) { 256 FLG = true; 257 return { 258 'done': true 259 }; 260 } 261 i = (i+1) % self._length; 262 return { 263 'value': self._buffer[ i ], 264 'done': false 265 }; 266 267 /* eslint-enable no-underscore-dangle */ 268 } 269 270 /** 271 * Finishes an iterator. 272 * 273 * @private 274 * @param {*} [value] - value to return 275 * @returns {Object} iterator protocol-compliant object 276 */ 277 function end( value ) { 278 FLG = true; 279 if ( arguments.length ) { 280 return { 281 'value': value, 282 'done': true 283 }; 284 } 285 return { 286 'done': true 287 }; 288 } 289 290 /** 291 * Returns a new iterator. 292 * 293 * @private 294 * @returns {Iterator} iterator 295 */ 296 function factory() { 297 return self.iterator( N ); 298 } 299 }); 300 301 /** 302 * Circular buffer length (capacity). 303 * 304 * @name length 305 * @memberof CircularBuffer.prototype 306 * @type {NonNegativeInteger} 307 * 308 * @example 309 * var b = new CircularBuffer( 4 ); 310 * 311 * // Get the buffer capacity: 312 * var len = b.length; 313 * // returns 4 314 */ 315 setReadOnlyAccessor( CircularBuffer.prototype, 'length', function get() { 316 return this._length; 317 }); 318 319 /** 320 * Adds a value to the circular buffer. 321 * 322 * @name push 323 * @memberof CircularBuffer.prototype 324 * @type {Function} 325 * @returns {(*|void)} removed element or undefined 326 * 327 * @example 328 * var b = new CircularBuffer( 3 ); 329 * 330 * // Fill the buffer: 331 * var v = b.push( 'foo' ); 332 * // returns undefined 333 * 334 * v = b.push( 'bar' ); 335 * // returns undefined 336 * 337 * v = b.push( 'beep' ); 338 * // returns undefined 339 * 340 * // Add another value to the buffer and return the removed element: 341 * v = b.push( 'boop' ); 342 * // returns 'foo' 343 */ 344 setReadOnly( CircularBuffer.prototype, 'push', function push( value ) { 345 var v; 346 347 // Compute the next buffer index: 348 this._i = (this._i+1) % this._length; 349 350 // Check if we are still filling the buffer... 351 if ( this._count < this._length ) { 352 this._buffer[ this._i ] = value; 353 this._count += 1; 354 return; 355 } 356 // Replace an existing buffer element... 357 v = this._buffer[ this._i ]; 358 this._buffer[ this._i ] = value; 359 return v; 360 }); 361 362 /** 363 * Returns an array of circular buffer values. 364 * 365 * @name toArray 366 * @memberof CircularBuffer.prototype 367 * @type {Function} 368 * @returns {Array} circular buffer values 369 * 370 * @example 371 * var b = new CircularBuffer( 3 ); 372 * 373 * // Add values to the buffer: 374 * b.push( 'foo' ); 375 * b.push( 'bar' ); 376 * b.push( 'beep' ); 377 * b.push( 'boop' ); 378 * 379 * // Get an array of buffer values: 380 * var vals = b.toArray(); 381 * // returns [ 'bar', 'beep', 'boop' ] 382 */ 383 setReadOnly( CircularBuffer.prototype, 'toArray', function toArray() { 384 var out; 385 var k; 386 var i; 387 388 out = []; 389 for ( i = 1; i <= this._count; i++ ) { 390 // Note: in a full buffer, `count == length`; in a partially full buffer, we need to ensure we always start at index `0` 391 k = (this._i+i) % this._count; 392 out.push( this._buffer[ k ] ); 393 } 394 return out; 395 }); 396 397 /** 398 * Serializes a circular buffer as JSON. 399 * 400 * ## Notes 401 * 402 * - `JSON.stringify()` implicitly calls this method when stringifying a `CircularBuffer` instance. 403 * 404 * @name toJSON 405 * @memberof CircularBuffer.prototype 406 * @type {Function} 407 * @returns {Object} serialized circular buffer 408 * 409 * @example 410 * var b = new CircularBuffer( 3 ); 411 * 412 * // Add values to the buffer: 413 * b.push( 'foo' ); 414 * b.push( 'bar' ); 415 * b.push( 'beep' ); 416 * b.push( 'boop' ); 417 * 418 * // Serialize to JSON: 419 * var o = b.toJSON(); 420 * // returns { 'type': 'circular-buffer', 'length': 3, 'data': [ 'bar', 'beep', 'boop' ] } 421 */ 422 setReadOnly( CircularBuffer.prototype, 'toJSON', function toJSON() { 423 var out = {}; 424 out.type = 'circular-buffer'; 425 out.length = this._length; 426 out.data = this.toArray(); 427 return out; 428 }); 429 430 431 // EXPORTS // 432 433 module.exports = CircularBuffer;