/
index.js
116 lines (97 loc) · 2.96 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// generate a random hash delimiter to avoid malicious hash collisions
function MixedTupleMap() {
this.clear();
}
MixedTupleMap.prototype = {
toString: function() {
return '[object MixedTupleMap]';
},
// The difference between MixedTupleMap and WeakTupleMap is that this one has
// a hash method that sorts key parts by putting non-primitive parts first.
// This enables optimal garbage collection of values in the map.
_hash: function( tuple ) {
// Speed up hash generation for the folowing pattern:
// if ( !cache.has(t) ) { cache.set( t, slowFn(t) ); }
if ( tuple === this._lastTuple ) {
return this._lastHash;
}
let l = tuple.length;
let prim = [];
let primOrder = [];
let nonPrimOrder = [];
for( let i = 0; i < l; i++) {
let arg = tuple[i];
let argType = typeof arg;
if ( argType !== null && ( argType === 'object' || argType === 'function' ) ) {
nonPrimOrder.push( i );
} else {
prim.push( argType === 'string' ? '"' + arg + '"' : '' + arg );
primOrder.push( i );
}
}
if ( nonPrimOrder.length === 0 ) {
throw new Error('Tuple must have at least one non-primitive part');
}
prim.push('[' + nonPrimOrder.concat( primOrder ).join() + ']');
this._lastTuple = tuple;
this._lastHash = {
nonPrimOrder,
// concatenate serialized arguments using a complex separator
// (to avoid key collisions)
primHash: prim.join('/<[MI_SEP]>/')
};
return this._lastHash;
},
has: function( tuple ) {
let curr = this._cache;
const hash = this._hash( tuple );
const l = hash.nonPrimOrder.length;
for( let i = 0; i < l; i++) {
const arg = tuple[hash.nonPrimOrder[i]]
if ( curr.has && curr.has(arg) ) {
curr = curr.get(arg);
} else {
return false;
}
}
return ( curr.has || false ) && curr.has( hash.primHash );
},
set: function( tuple, value ) {
let curr = this._cache;
const hash = this._hash( tuple );
const l = hash.nonPrimOrder.length;
let mustCreate = false;
for( let i = 0; i < l; i++) {
const arg = tuple[hash.nonPrimOrder[i]]
if ( !mustCreate && curr.has(arg) ) {
curr = curr.get(arg);
} else {
mustCreate = true;
curr.set( arg, ( curr = ( i < l - 1 ) ? new WeakMap() : new Map() ) );
}
}
curr.set(hash.primHash, value);
return this;
},
get: function( tuple ) {
let curr = this._cache;
const hash = this._hash( tuple );
const l = hash.nonPrimOrder.length;
for( let i = 0; i < l; i++) {
const arg = tuple[hash.nonPrimOrder[i]]
const ret = curr.get && curr.get(arg);
if ( ret === undefined ) {
return ret;
} else {
curr = ret;
}
}
return curr.get && curr.get( hash.primHash );
},
clear: function() {
this._cache = new WeakMap();
delete this._lastTuple;
delete this._lastHash;
},
};
export default MixedTupleMap;