Browse Source

[rewrite] Using ES6 classes

master
Muthu Kumar 7 years ago
parent
commit
ecdc95acce
  1. 25
      example.js
  2. 254
      index.js
  3. 182
      utils/cycle.js
  4. 19
      utils/index.js

25
example.js

@ -1,25 +0,0 @@
const infiniteList = require('.');
const increment = i => i + 2;
const infinite = infiniteList.create(0, increment, 10);
console.log(infinite.top())
console.log(infinite.last())
console.log(infinite.get(5))
console.log(infinite.get(5))
console.log(infinite.get(6))
console.log(infinite.get(14).next())
console.log(infinite.toString())
console.log(infinite.top().next().next().toString())
console.log(infinite.take(5))
for(let i of infinite) {
if(i.index > 100) break;
// console.log(infinite.get(5).value);
console.log(i.value.toString());
}
console.log(infinite.clearCache());

254
index.js

@ -1,114 +1,206 @@
const always = x => _ => x;
/**
*
* Infinity: Create infinitely generating lists in JavaScript.
* @version 0.2.0
* @author Muthu Kumar (MKRhere)
*/
// Utils
const { always, isNonZeroFalsy, stringify, areNumbers } = require('./utils');
/**
* An item of the InfiniteList class. Created when calling .get(n) on an InfiniteList.
* Exposed for instanceof utility sake. Not to be called directly.
* @class InfiniteListItem
*/
class InfiniteListItem {
constructor (list, value, index) {
this.value = value,
this.index = index,
this.next = () => list.get(index + 1),
this.previous = () => list.get(index - 1),
this[Symbol.iterator] = () => ({
next: () => ({
done: false,
value: list.get(index + 1)
})
}),
this.toString = () => {
const val = list.get(i).value;
return ( 'InfiniteListItem [ ..., '
+ ((typeof val === 'object' && val !== null)
? JSON.stringify(val, null, 2)
: val.toString())
+ ', ... ]' )
/**
* Creates an instance of InfiniteListItem.
* @param {any} list Parent list, instance of InfiniteList
* @param {any} value Current value
* @param {any} index Current index
* @memberof InfiniteListItem
*/
constructor(list, value, index) {
this.value = value;
this.index = index;
this.next = z => (!z ? list.get(index + 1) : list.get(index + z));
this.previous = z => (!z ? list.get(index - 1) : list.get(index - z));
// Check if Symbol exists
if (typeof Symbol !== 'undefined' && Symbol.iterator) {
/**
* ES6 Symbol.iterator
* @returns {Iterable.<*>}
*/
this[Symbol.iterator] = () => ({
next: () => ({
done: false,
value: list.get(index + 1)
})
});
}
}
/**
* toString method for pretty printing InfiniteListItem instance.
* @returns {String} Decycled and beautified string
*/
toString() {
return ('InfiniteListItem [ .. ' +
stringify(this.value) +
' .. ]')
};
}
const infiniteList = {
class InfiniteList {
/**
* InfiniteList Constructor. Iterates infinitely until index value is found.
*
* @param {any} start
* @param {any} next
* @returns InfiniteList instance
* InfiniteList Constructor.
* Iterates infinitely until index value is found.
* Stores cache in closure so when the same index is requested again,
* it's returned immediately.
* @param {*} start Starting value
* @param {Function} next Function to find next item
* Accepts current value and optionally previous value
* @constructs InfiniteList
*/
create (start, next) {
constructor(start, next) {
// Closure magic!
let cache = [];
let j = 0;
// Get list item of index i
function get(i) {
/**
* Get InfiniteListItem at index.
* @param {Number} index A non-negative integer representing index
* @returns {InfiniteListItem}
*/
this.get = function (index) {
// Validation
if(
// i is a falsy value except 0
(!i && i !== 0)
// is not a number
|| (typeof i !== 'number')
// is... not a number
|| Number.isNaN(i)
if (
// i is a non-zero falsy value, or is negative
(isNonZeroFalsy(index) || index < 0)
|| !areNumbers(index)
) return;
//TODO: Cache limiting. (Removed after unexpected behaviour)
//TODO: Cache limiting. (Removed for unexpected behaviour)
// Initializing first item if it doesn't exist
if(!cache[0]) cache[0] = start;
if (!cache[0]) cache[0] = start;
// If index were to be infinity, value and index are infinity
if(i === Infinity) return new InfiniteListItem(this, Infinity, Infinity)
if (index === Infinity) return new InfiniteListItem(this, Infinity, Infinity)
// If index exists in cache, return the value
if(i in cache) return new InfiniteListItem(this, cache[i], i);
if (index in cache) return new InfiniteListItem(this, cache[index], index);
// If i doesn't exist in cache
if(!(i in cache)) {
if(cache.length <= i && (cache.length - 1) in cache)
while (cache.length <= i)
cache[cache.length] = next(cache[cache.length - 1]);
if (!(index in cache)) {
if (cache.length <= index && (cache.length - 1) in cache)
while (cache.length <= index)
cache[cache.length] = next(cache[cache.length - 1], cache[cache.length - 2]);
}
return new InfiniteListItem(this, cache[i], i);
return new InfiniteListItem(this, cache[index], index);
}
const take = (from, to) => {
const arr = [];
let source, target;
// "from" number of elements
if(!to) { source = 0; target = from; }
// "target" is the end index!
else { source = from; target = to + 1 };
for(let i = source; i < target; i ++) {
arr.push(get(i));
/**
* Clear cache manually.
* Forces destroy reference to cache, and creates a new cache.
* Old cache will be GC'd.
* @returns {undefined}
*/
this.clearCache = () => (cache = [], undefined);
// Check if Symbol exists
if (typeof Symbol !== 'undefined' && Symbol.iterator) {
/**
* ES6 Symbol.iterator
* @returns {Iterable.<*>}
*/
this[Symbol.iterator] = function () {
return {
next: () => ({
done: false,
value: this.get(j++)
})
}
};
return arr;
};
// Clear cache manually.
const clearCache = () => (cache = [], undefined);
const top = function () { return this.get(0) };
const end = function () { return this.get(Infinity) };
const returns = {
get,
take,
top,
first: top,
end,
last: end,
clearCache,
[Symbol.iterator]: () => ({
next: () => ({
done: false,
value: get(j++)
})
}),
toString: () => 'InfiniteList [ '
+ take(0, 10).map(x => (' ' + x.value.toString()))
+ ' ... ]'
};
return returns;
}
}
}
/**
* Takes a given number of elements from the InfiniteList.
* @param {Number} from Number of elements or starting index
* @param {Number} to Optional ending index
* @returns {Array<InfiniteListItem>} An array of InfiniteListItems
*/
InfiniteList.prototype.take = function (from, to) {
const arr = [];
if(
isNonZeroFalsy(from)
|| (from === 0 && isNonZeroFalsy(to)) // Take 0 elements?
|| (!areNumbers(from) && isNonZeroFalsy(to))
) return arr;
let source, target;
// "from" number of elements
if (isNonZeroFalsy(to)) {
source = 0;
target = from;
}
// "target" is the end index!
else {
source = from;
target = to + 1
};
for (let i = source; i < target; i++) {
arr.push(this.get(i));
};
return arr;
};
/**
* Returns first element of InfiniteList.
* @returns {InfiniteListItem} Instance of InfiniteListItem
*/
InfiniteList.prototype.top = function () {
return this.get(0)
};
/**
* Returns last element of InfiniteList (Infinity).
* @returns {InfiniteListItem} Instance of InfiniteListItem
*/
InfiniteList.prototype.end = function () {
return this.get(Infinity)
};
/**
* toString method for pretty printing InfiniteList instance.
* Snips at 2 elements for arrays and objects, or 5 elements otherwise.
* @returns {String} Pretty printed InfiniteList
*/
InfiniteList.prototype.toString = function () {
const length = typeof this.first() === 'object' ? 2 : 5;
return [
'InfiniteList [',
this
.take(length)
.map(x => (' ' + stringify(x.value))) +
',',
'... ]'
]
.join(' ');
}
module.exports = infiniteList;
/* Convenience methods */
InfiniteList.prototype.first = InfiniteList.prototype.top;
InfiniteList.prototype.last = InfiniteList.prototype.end;
// Exports
module.exports = InfiniteList;
module.exports.InfiniteListItem = InfiniteListItem;

182
utils/cycle.js

@ -0,0 +1,182 @@
/*
cycle.js
2018-05-15
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
*/
// The file uses the WeakMap feature of ES6.
/*jslint eval */
/*property
$ref, decycle, forEach, get, indexOf, isArray, keys, length, push,
retrocycle, set, stringify, test
*/
if (typeof JSON.decycle !== "function") {
JSON.decycle = function decycle(object, replacer) {
"use strict";
// Make a deep copy of an object or array, assuring that there is at most
// one instance of each object or array in the resulting structure. The
// duplicate references (which might be forming cycles) are replaced with
// an object of the form
// {"$ref": PATH}
// where the PATH is a JSONPath string that locates the first occurance.
// So,
// var a = [];
// a[0] = a;
// return JSON.stringify(JSON.decycle(a));
// produces the string '[{"$ref":"$"}]'.
// If a replacer function is provided, then it will be called for each value.
// A replacer function receives a value and returns a replacement value.
// JSONPath is used to locate the unique object. $ indicates the top level of
// the object or array. [NUMBER] or [STRING] indicates a child element or
// property.
var objects = new WeakMap(); // object to path mappings
return (function derez(value, path) {
// The derez function recurses through the object, producing the deep copy.
var old_path; // The path of an earlier occurance of value
var nu; // The new object or array
// If a replacer function was provided, then call it to get a replacement value.
if (replacer !== undefined) {
value = replacer(value);
}
// typeof null === "object", so go on if this value is really an object but not
// one of the weird builtin objects.
if (
typeof value === "object" &&
value !== null &&
!(value instanceof Boolean) &&
!(value instanceof Date) &&
!(value instanceof Number) &&
!(value instanceof RegExp) &&
!(value instanceof String)
) {
// If the value is an object or array, look to see if we have already
// encountered it. If so, return a {"$ref":PATH} object. This uses an
// ES6 WeakMap.
old_path = objects.get(value);
if (old_path !== undefined) {
return {
$ref: old_path
};
}
// Otherwise, accumulate the unique value and its path.
objects.set(value, path);
// If it is an array, replicate the array.
if (Array.isArray(value)) {
nu = [];
value.forEach(function (element, i) {
nu[i] = derez(element, path + "[" + i + "]");
});
} else {
// If it is an object, replicate the object.
nu = {};
Object.keys(value).forEach(function (name) {
nu[name] = derez(
value[name],
path + "[" + JSON.stringify(name) + "]"
);
});
}
return nu;
}
return value;
}(object, "$"));
};
}
if (typeof JSON.retrocycle !== "function") {
JSON.retrocycle = function retrocycle($) {
"use strict";
// Restore an object that was reduced by decycle. Members whose values are
// objects of the form
// {$ref: PATH}
// are replaced with references to the value found by the PATH. This will
// restore cycles. The object will be mutated.
// The eval function is used to locate the values described by a PATH. The
// root object is kept in a $ variable. A regular expression is used to
// assure that the PATH is extremely well formed. The regexp contains nested
// * quantifiers. That has been known to have extremely bad performance
// problems on some browsers for very long strings. A PATH is expected to be
// reasonably short. A PATH is allowed to belong to a very restricted subset of
// Goessner's JSONPath.
// So,
// var s = '[{"$ref":"$"}]';
// return JSON.retrocycle(JSON.parse(s));
// produces an array containing a single element which is the array itself.
var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/;
(function rez(value) {
// The rez function walks recursively through the object looking for $ref
// properties. When it finds one that has a value that is a path, then it
// replaces the $ref object with a reference to the value that is found by
// the path.
if (value && typeof value === "object") {
if (Array.isArray(value)) {
value.forEach(function (element, i) {
if (typeof element === "object" && element !== null) {
var path = element.$ref;
if (typeof path === "string" && px.test(path)) {
value[i] = eval(path);
} else {
rez(element);
}
}
});
} else {
Object.keys(value).forEach(function (name) {
var item = value[name];
if (typeof item === "object" && item !== null) {
var path = item.$ref;
if (typeof path === "string" && px.test(path)) {
value[name] = eval(path);
} else {
rez(item);
}
}
});
}
}
}($));
return $;
};
}
module.exports = JSON;

19
utils/index.js

@ -0,0 +1,19 @@
const JSON = require('./cycle');
const always = x => _ => x;
const isNonZeroFalsy = _ => (
(Boolean(_) === false) && _ !== 0
);
const stringify = _ => {
return ((typeof _ === 'object' && _ !== null) ?
JSON.stringify(JSON.decycle(_), null, 2) :
_.toString())
};
const areNumbers = (...items) => items.every(x => (typeof x === 'number' && x !== NaN));
module.exports = {
always,
isNonZeroFalsy,
stringify,
areNumbers
}
Loading…
Cancel
Save