solarite 0.5.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/Solarite-debug.js +4847 -3878
- package/dist/Solarite.js +4583 -3626
- package/dist/Solarite.min.js +2 -4
- package/package.json +19 -6
- package/readme.md +58 -11
- package/src/Globals.js +54 -72
- package/src/HtmlParser.js +90 -90
- package/src/MultiValueMap.js +57 -105
- package/src/NodeGroup.js +624 -470
- package/src/Path.js +224 -211
- package/src/PathToAttribValue.js +401 -261
- package/src/PathToAttribs.js +113 -80
- package/src/PathToComment.js +7 -7
- package/src/PathToComponent.js +183 -188
- package/src/PathToEvent.js +76 -64
- package/src/PathToKey.js +19 -0
- package/src/PathToNodes.js +1053 -566
- package/src/RootNodeGroup.js +120 -8
- package/src/Shell.js +570 -348
- package/src/Solarite.d.ts +134 -113
- package/src/Solarite.js +243 -286
- package/src/Template.js +195 -274
- package/src/Util.js +353 -351
- package/src/assert.js +10 -10
- package/src/assignAttributes.js +63 -0
- package/src/delve.js +55 -43
- package/src/h.js +220 -138
- package/src/jsx-dev-runtime.d.ts +1 -0
- package/src/jsx-dev-runtime.js +5 -0
- package/src/jsx-runtime.d.ts +21 -0
- package/src/jsx-runtime.js +84 -0
- package/src/jsx.js +194 -0
- package/src/toEl.js +77 -82
- package/dist/udomdiff-license.txt +0 -18
- package/src/getArg.js +0 -137
- package/src/hash.js +0 -89
- package/src/udomdiff.js +0 -176
- package/src/unused/FastLookupArray.js +0 -54
- package/src/unused/Hashes.js +0 -339
- package/src/unused/InUse.test.js +0 -92
- package/src/unused/InUseMap.js +0 -98
- package/src/unused/LinkedList.js +0 -117
- package/src/unused/LinkedList.test.js +0 -115
- package/src/unused/Misc.js +0 -13
- package/src/unused/Perf.js +0 -47
- package/src/unused/TrackedArray.js +0 -54
- package/src/unused/WeakArray.js +0 -33
- package/src/watch.js +0 -543
package/src/udomdiff.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ISC License
|
|
3
|
-
*
|
|
4
|
-
* Copyright (c) 2020, Andrea Giammarchi, @WebReflection
|
|
5
|
-
*
|
|
6
|
-
* Permission to use, copy, modify, and/or distribute this software for any
|
|
7
|
-
* purpose with or without fee is hereby granted, provided that the above
|
|
8
|
-
* copyright notice and this permission notice appear in all copies.
|
|
9
|
-
*
|
|
10
|
-
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
11
|
-
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
12
|
-
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
13
|
-
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
14
|
-
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
15
|
-
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
16
|
-
* PERFORMANCE OF THIS SOFTWARE.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @param {Node} parentNode The container where children live
|
|
21
|
-
* @param {Node[]} a The list of current/live children
|
|
22
|
-
* @param {Node[]} b The list of future children
|
|
23
|
-
* @param {(entry: Node, action: number) => Node} get
|
|
24
|
-
* The callback invoked per each entry related DOM operation.
|
|
25
|
-
* @param {Node} [before] The optional node used as anchor to insert before.
|
|
26
|
-
* @returns {Node[]} The same list of future children.
|
|
27
|
-
*/
|
|
28
|
-
const udomdiff = (parentNode, a, b, before) => {
|
|
29
|
-
const bLength = b.length;
|
|
30
|
-
let aEnd = a.length;
|
|
31
|
-
let bEnd = bLength;
|
|
32
|
-
let aStart = 0;
|
|
33
|
-
let bStart = 0;
|
|
34
|
-
let map = null;
|
|
35
|
-
while (aStart < aEnd || bStart < bEnd) {
|
|
36
|
-
// append head, tail, or nodes in between: fast path
|
|
37
|
-
if (aEnd === aStart) {
|
|
38
|
-
// we could be in a situation where the rest of nodes that
|
|
39
|
-
// need to be added are not at the end, and in such case
|
|
40
|
-
// the node to `insertBefore`, if the index is more than 0
|
|
41
|
-
// must be retrieved, otherwise it's gonna be the first item.
|
|
42
|
-
const node = bEnd < bLength
|
|
43
|
-
? (bStart
|
|
44
|
-
? (b[bStart - 1].nextSibling)
|
|
45
|
-
: b[bEnd - bStart])
|
|
46
|
-
: before;
|
|
47
|
-
while (bStart < bEnd) {
|
|
48
|
-
let bNode = b[bStart++];
|
|
49
|
-
parentNode.insertBefore(bNode, node);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// remove head or tail: fast path
|
|
53
|
-
else if (bEnd === bStart) {
|
|
54
|
-
while (aStart < aEnd) {
|
|
55
|
-
// remove the node only if it's unknown or not live
|
|
56
|
-
let aNode = a[aStart];
|
|
57
|
-
if (!map || !map.has(aNode)) {
|
|
58
|
-
parentNode.removeChild(aNode);
|
|
59
|
-
}
|
|
60
|
-
aStart++;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
// same node: fast path
|
|
64
|
-
else if (a[aStart] === b[bStart]) {
|
|
65
|
-
aStart++;
|
|
66
|
-
bStart++;
|
|
67
|
-
}
|
|
68
|
-
// same tail: fast path
|
|
69
|
-
else if (a[aEnd - 1] === b[bEnd - 1]) {
|
|
70
|
-
aEnd--;
|
|
71
|
-
bEnd--;
|
|
72
|
-
}
|
|
73
|
-
// The once here single last swap "fast path" has been removed in v1.1.0
|
|
74
|
-
// https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
|
|
75
|
-
// reverse swap: also fast path
|
|
76
|
-
else if (
|
|
77
|
-
a[aStart] === b[bEnd - 1] &&
|
|
78
|
-
b[bStart] === a[aEnd - 1]
|
|
79
|
-
) {
|
|
80
|
-
// this is a "shrink" operation that could happen in these cases:
|
|
81
|
-
// [1, 2, 3, 4, 5]
|
|
82
|
-
// [1, 4, 3, 2, 5]
|
|
83
|
-
// or asymmetric too
|
|
84
|
-
// [1, 2, 3, 4, 5]
|
|
85
|
-
// [1, 2, 3, 5, 6, 4]
|
|
86
|
-
const node = a[--aEnd].nextSibling;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let a2 = b[bStart++];
|
|
90
|
-
let b2 = a[aStart++];
|
|
91
|
-
parentNode.insertBefore(
|
|
92
|
-
a2,
|
|
93
|
-
b2.nextSibling
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
let bNode = b[--bEnd];
|
|
97
|
-
parentNode.insertBefore(bNode, node);
|
|
98
|
-
|
|
99
|
-
// mark the future index as identical (yeah, it's dirty, but cheap 👍)
|
|
100
|
-
// The main reason to do this, is that when a[aEnd] will be reached,
|
|
101
|
-
// the loop will likely be on the fast path, as identical to b[bEnd].
|
|
102
|
-
// In the best case scenario, the next loop will skip the tail,
|
|
103
|
-
// but in the worst one, this node will be considered as already
|
|
104
|
-
// processed, bailing out pretty quickly from the map index check
|
|
105
|
-
a[aEnd] = b[bEnd];
|
|
106
|
-
}
|
|
107
|
-
// map based fallback, "slow" path
|
|
108
|
-
else {
|
|
109
|
-
// the map requires an O(bEnd - bStart) operation once
|
|
110
|
-
// to store all future nodes indexes for later purposes.
|
|
111
|
-
// In the worst case scenario, this is a full O(N) cost,
|
|
112
|
-
// and such scenario happens at least when all nodes are different,
|
|
113
|
-
// but also if both first and last items of the lists are different
|
|
114
|
-
if (!map) {
|
|
115
|
-
map = new Map;
|
|
116
|
-
let i = bStart;
|
|
117
|
-
while (i < bEnd)
|
|
118
|
-
map.set(b[i], i++);
|
|
119
|
-
}
|
|
120
|
-
// if it's a future node, hence it needs some handling
|
|
121
|
-
if (map.has(a[aStart])) {
|
|
122
|
-
// grab the index of such node, 'cause it might have been processed
|
|
123
|
-
const index = map.get(a[aStart]);
|
|
124
|
-
// if it's not already processed, look on demand for the next LCS
|
|
125
|
-
if (bStart < index && index < bEnd) {
|
|
126
|
-
let i = aStart;
|
|
127
|
-
// counts the amount of nodes that are the same in the future
|
|
128
|
-
let sequence = 1;
|
|
129
|
-
while (++i < aEnd && i < bEnd && map.get(a[i]) === (index + sequence))
|
|
130
|
-
sequence++;
|
|
131
|
-
// effort decision here: if the sequence is longer than replaces
|
|
132
|
-
// needed to reach such sequence, which would brings again this loop
|
|
133
|
-
// to the fast path, prepend the difference before a sequence,
|
|
134
|
-
// and move only the future list index forward, so that aStart
|
|
135
|
-
// and bStart will be aligned again, hence on the fast path.
|
|
136
|
-
// An example considering aStart and bStart are both 0:
|
|
137
|
-
// a: [1, 2, 3, 4]
|
|
138
|
-
// b: [7, 1, 2, 3, 6]
|
|
139
|
-
// this would place 7 before 1 and, from that time on, 1, 2, and 3
|
|
140
|
-
// will be processed at zero cost
|
|
141
|
-
if (sequence > (index - bStart)) {
|
|
142
|
-
const node = a[aStart];
|
|
143
|
-
while (bStart < index) {
|
|
144
|
-
let bNode = b[bStart++];
|
|
145
|
-
parentNode.insertBefore(bNode, node);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// if the effort wasn't good enough, fallback to a replace,
|
|
149
|
-
// moving both source and target indexes forward, hoping that some
|
|
150
|
-
// similar node will be found later on, to go back to the fast path
|
|
151
|
-
else {
|
|
152
|
-
let aNode = a[aStart++]
|
|
153
|
-
let bNode = b[bStart++]
|
|
154
|
-
parentNode.replaceChild(
|
|
155
|
-
bNode,
|
|
156
|
-
aNode
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// otherwise move the source forward, 'cause there's nothing to do
|
|
161
|
-
else
|
|
162
|
-
aStart++;
|
|
163
|
-
}
|
|
164
|
-
// this node has no meaning in the future list, so it's more than safe
|
|
165
|
-
// to remove it, and check the next live node out instead, meaning
|
|
166
|
-
// that only the live list index should be forwarded
|
|
167
|
-
else {
|
|
168
|
-
let aNode = a[aStart++];
|
|
169
|
-
parentNode.removeChild(aNode);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return b;
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
export default udomdiff;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* partialUpdate test is 20% slower when using this, even before I override any methods!*/
|
|
3
|
-
export default class FastLookupArray extends Array {
|
|
4
|
-
constructor() {
|
|
5
|
-
super(...arguments)
|
|
6
|
-
this.indexMap = new Map(); // You can replace with WeakMap() but ensure only objects are added.
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
push(item) { // doesn't support pushing multiple
|
|
11
|
-
if (!this.indexMap.has(item)) {
|
|
12
|
-
this[this.length] = item;
|
|
13
|
-
this.indexMap.set(item, this.length - 1);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
indexOf(item) {
|
|
18
|
-
return this.indexMap.get(item) || -1;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
remove(item) {
|
|
22
|
-
const index = this.indexOf(item);
|
|
23
|
-
if (index !== -1) {
|
|
24
|
-
this.splice(index, 1);
|
|
25
|
-
this.indexMap.delete(item);
|
|
26
|
-
|
|
27
|
-
// Adjust the map indices for the items after the removed one
|
|
28
|
-
for (let i = index; i < this.length; i++)
|
|
29
|
-
this.indexMap.set(this[i], i);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
insertBefore(referenceItem, newItem) {
|
|
34
|
-
const refIndex = this.indexOf(referenceItem);
|
|
35
|
-
if (refIndex !== -1) {
|
|
36
|
-
this.splice(refIndex, 0, newItem);
|
|
37
|
-
this.indexMap.set(newItem, refIndex);
|
|
38
|
-
|
|
39
|
-
// Adjust the map indices for the items after the inserted one
|
|
40
|
-
for (let i = refIndex + 1; i < this.length; i++)
|
|
41
|
-
this.indexMap.set(this[i], i);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
set(index, item) {
|
|
46
|
-
const currentItem = this[index];
|
|
47
|
-
if (currentItem !== undefined)
|
|
48
|
-
this.indexMap.delete(currentItem);
|
|
49
|
-
this[index] = item;
|
|
50
|
-
this.indexMap.set(item, index);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// TODO: Override splice?
|
|
54
|
-
}
|
package/src/unused/Hashes.js
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
import {getObjectId} from "../hash.js";
|
|
2
|
-
import Template from "../Template.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Convert obj to a non-cyclic form that can be given to JSON.serialize.
|
|
6
|
-
* This is useful for creating a string that represents a hash of all the deep values of an object.
|
|
7
|
-
*
|
|
8
|
-
* One instance of a node always has the same hash, even if its attributes/children have changed.
|
|
9
|
-
*
|
|
10
|
-
* TODO: Could memoize be faster if given an old version of the same object, and it only finds the differences?
|
|
11
|
-
* Or if we inline getObjectId()
|
|
12
|
-
*
|
|
13
|
-
* @param obj {Object|Array|function|Node}
|
|
14
|
-
* @param seenMap Used internally.
|
|
15
|
-
* @param idCounter Used internally.
|
|
16
|
-
* @returns {{}|*|string} */
|
|
17
|
-
export function memoize(obj, seenMap = new Map(), idCounter = {value: 0}) {
|
|
18
|
-
|
|
19
|
-
// If obj is a Node, Element, or Window, we treat it specially
|
|
20
|
-
if (obj?.nodeType) { // Benchmarking shows this is faster than "instanceof Node"
|
|
21
|
-
let nodeId = getObjectId(obj);
|
|
22
|
-
return `&N:${nodeId}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// TODO: Should I instead use id's to keep track of functions?
|
|
26
|
-
let type = typeof obj
|
|
27
|
-
|
|
28
|
-
if (obj && type === 'object') {
|
|
29
|
-
if (seenMap.has(obj))
|
|
30
|
-
return `&O:${seenMap.get(obj)}`;
|
|
31
|
-
|
|
32
|
-
else {
|
|
33
|
-
seenMap.set(obj, idCounter.value++);
|
|
34
|
-
if (typeof obj.memoize === 'function')
|
|
35
|
-
return obj.memoize();
|
|
36
|
-
|
|
37
|
-
// We iterate through the keys of an object because an object may have changed since the last time we saw it.
|
|
38
|
-
const serializedObj = {};
|
|
39
|
-
for (let key in obj)
|
|
40
|
-
serializedObj[key] = memoize(obj[key], seenMap, idCounter);
|
|
41
|
-
return serializedObj;
|
|
42
|
-
}
|
|
43
|
-
} else if (type === 'function')
|
|
44
|
-
//return `&F:${obj}` // String version of function. This version fails the loop.eventBindings test.
|
|
45
|
-
return `&F:${getObjectId(obj)}`; // Memory reference to function. this makes it twice as slow!
|
|
46
|
-
|
|
47
|
-
else
|
|
48
|
-
return obj;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
function fnv1a(...data) {
|
|
53
|
-
let hash = 0x811c9dc5; // FNV offset basis
|
|
54
|
-
for (let i = 0; i < data.length; i++) {
|
|
55
|
-
hash ^= data[i]; // XOR
|
|
56
|
-
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24); // FNV prime multiplication
|
|
57
|
-
}
|
|
58
|
-
return hash >>> 0; // Convert to 32-bit unsigned integer
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const FNV_PRIME_64 = BigInt("1099511628211");
|
|
62
|
-
const OFFSET_BASIS_64 = BigInt("14695981039346656037");
|
|
63
|
-
function fnv1a_64bit(...data) {
|
|
64
|
-
let hash = OFFSET_BASIS_64;
|
|
65
|
-
for (let i = 0; i < data.length; i++) {
|
|
66
|
-
hash ^= data[i]; // XOR
|
|
67
|
-
// hash = BigInt.asUintN(64, hash*FNV_PRIME_64);
|
|
68
|
-
}
|
|
69
|
-
return hash;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Faster than memoize
|
|
74
|
-
export function hashObject(obj) {
|
|
75
|
-
let combinedData = (0);
|
|
76
|
-
if (obj === null)
|
|
77
|
-
return combinedData;
|
|
78
|
-
|
|
79
|
-
if (Array.isArray(obj)) {
|
|
80
|
-
for (let i=(0); i<obj.length; i++) {
|
|
81
|
-
let v = hashObject(obj[i])
|
|
82
|
-
combinedData = fnv1a(combinedData, i, v);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
else if (typeof obj === 'object') {
|
|
87
|
-
let i = (0);
|
|
88
|
-
for (const key in obj) {
|
|
89
|
-
let k = getObjectId3(key)
|
|
90
|
-
let v = hashObject(obj[key])
|
|
91
|
-
combinedData = fnv1a(combinedData, i, k, v);
|
|
92
|
-
i++
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
else if (typeof obj === 'number')
|
|
96
|
-
return (obj)
|
|
97
|
-
else if (typeof obj === 'string')
|
|
98
|
-
return getObjectId3(obj)
|
|
99
|
-
else
|
|
100
|
-
return getObjectId2(obj);
|
|
101
|
-
|
|
102
|
-
return combinedData;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// TODO: Use this number instead as the high bits for getObjectId2?
|
|
106
|
-
let lastObjectId2 = (1_010_101_101); // Big enough to not easily not collide with the space of unhashed numbers.
|
|
107
|
-
let objectIds2 = new Map();
|
|
108
|
-
|
|
109
|
-
export function getObjectId2(obj) {
|
|
110
|
-
let result = objectIds2.get(obj);
|
|
111
|
-
if (!result) {
|
|
112
|
-
result = (lastObjectId2++); // convert to string, store in result, then add 1 to lastObjectId.
|
|
113
|
-
objectIds2.set(obj, result)
|
|
114
|
-
}
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let objectIds3 = {};
|
|
120
|
-
|
|
121
|
-
export function getObjectId3(obj) {
|
|
122
|
-
// if (typeof obj === 'string') {
|
|
123
|
-
// let l = obj.length;
|
|
124
|
-
// if (l < 512) { // Faster than getObjectId3. 512 was picked randomly and has not been benchmarked.
|
|
125
|
-
// let result = 1;
|
|
126
|
-
// for (let i=0; i < l; i++)
|
|
127
|
-
// result ^= obj.charCodeAt(i) << (i%16);
|
|
128
|
-
// return result
|
|
129
|
-
// }
|
|
130
|
-
// }
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
let result = objectIds3[obj];
|
|
134
|
-
if (!result) {
|
|
135
|
-
result = (lastObjectId2++); // convert to string, store in result, then add 1 to lastObjectId.
|
|
136
|
-
objectIds3[obj] = result
|
|
137
|
-
}
|
|
138
|
-
return result;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
let hash64Cache = new WeakMap();
|
|
143
|
-
export {hash64Cache};
|
|
144
|
-
|
|
145
|
-
// 25% slower than 32-bit version but still faster than memoize+JSON.stringify()
|
|
146
|
-
// TODO: hardcoded hash values for true and false?
|
|
147
|
-
export function hashObject64(obj) {
|
|
148
|
-
let resultLow = 0;
|
|
149
|
-
let resultHigh = 0;
|
|
150
|
-
|
|
151
|
-
if (obj instanceof Template)
|
|
152
|
-
obj = [obj.htmlId, ...obj.exprs]
|
|
153
|
-
|
|
154
|
-
if (obj === null)
|
|
155
|
-
return [0, 0];
|
|
156
|
-
|
|
157
|
-
else if (Array.isArray(obj)) {
|
|
158
|
-
let result
|
|
159
|
-
// = hash64Cache.get(obj);
|
|
160
|
-
// if (!result) {
|
|
161
|
-
for (let i = (0); i < obj.length; i++) {
|
|
162
|
-
let [vLow, vHigh] = hashObject64(obj[i])
|
|
163
|
-
resultLow = fnv1a(resultLow, i, vLow)
|
|
164
|
-
resultHigh = fnv1a(resultHigh, vHigh)
|
|
165
|
-
}
|
|
166
|
-
result = [resultLow, resultHigh]
|
|
167
|
-
// hash64Cache.set(obj, result)
|
|
168
|
-
//}
|
|
169
|
-
return result;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
else if (typeof obj === 'object') {
|
|
173
|
-
let result
|
|
174
|
-
// = hash64Cache.get(obj);
|
|
175
|
-
// if (!result) {
|
|
176
|
-
let i = (0);
|
|
177
|
-
for (const key in obj) {
|
|
178
|
-
let k = getObjectId3(key)
|
|
179
|
-
let [vLow, vHigh] = hashObject64(obj[key])
|
|
180
|
-
resultLow = fnv1a(resultLow, i, vLow)
|
|
181
|
-
resultHigh = fnv1a(resultHigh, k, vHigh)
|
|
182
|
-
i++
|
|
183
|
-
}
|
|
184
|
-
result = [resultLow, resultHigh]
|
|
185
|
-
// hash64Cache.set(obj, result)
|
|
186
|
-
//}
|
|
187
|
-
|
|
188
|
-
return result;
|
|
189
|
-
}
|
|
190
|
-
else if (typeof obj === 'number')
|
|
191
|
-
return [0, obj]
|
|
192
|
-
else if (typeof obj === 'string')
|
|
193
|
-
return [0, getObjectId3(obj)]
|
|
194
|
-
else
|
|
195
|
-
return [0, getObjectId2(obj)];
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// Perforamnce varies greatly.
|
|
202
|
-
export function memoize2(obj) {
|
|
203
|
-
|
|
204
|
-
if (obj === null)
|
|
205
|
-
return 'null';
|
|
206
|
-
|
|
207
|
-
switch (obj?.constructor) {
|
|
208
|
-
case Function:
|
|
209
|
-
case Node:
|
|
210
|
-
return getObjectId(obj);
|
|
211
|
-
case Array:
|
|
212
|
-
return '[' + obj.map(item => memoize2(item)) + ']'
|
|
213
|
-
case Object:
|
|
214
|
-
let result = '{';
|
|
215
|
-
for (let key in obj) {
|
|
216
|
-
//key = key.match(/[^A-Z0-9_]/i) ? JSON.stringify(key) : key;
|
|
217
|
-
key = (key.includes(':')) ? JSON.stringify(key) : key;
|
|
218
|
-
result += `${key}:${memoize2(obj[key])}`
|
|
219
|
-
}
|
|
220
|
-
return result +'}';
|
|
221
|
-
default:
|
|
222
|
-
return JSON.stringify(obj);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// as fast as memoize2, or faster.
|
|
227
|
-
export function memoize3(obj) {
|
|
228
|
-
|
|
229
|
-
if (obj === null)
|
|
230
|
-
return 'null';
|
|
231
|
-
|
|
232
|
-
let type = typeof obj;
|
|
233
|
-
if (Array.isArray(obj))
|
|
234
|
-
return '[' + obj.map(item => memoize2(item)) + ']'
|
|
235
|
-
if (obj instanceof Node || type === 'function')
|
|
236
|
-
return getObjectId(obj);
|
|
237
|
-
if (type=== 'object') {
|
|
238
|
-
let result = '{';
|
|
239
|
-
for (let key in obj) {
|
|
240
|
-
//key = key.match(/[^A-Z0-9_]/i) ? JSON.stringify(key) : key;
|
|
241
|
-
key = (key.includes(':')) ? JSON.stringify(key) : key;
|
|
242
|
-
result += `${key}:${memoize2(obj[key])}`
|
|
243
|
-
}
|
|
244
|
-
return result +'}';
|
|
245
|
-
}
|
|
246
|
-
return JSON.stringify(obj);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// as fast as memoize2, or faster.
|
|
253
|
-
export function memoize4(obj, seenMap=new Map(), idCounter={value: 0}) {
|
|
254
|
-
let result;
|
|
255
|
-
if (obj instanceof Template) {
|
|
256
|
-
if (obj.exprs.length === 1)
|
|
257
|
-
return obj.htmlId + '\f' + memoize4(obj.exprs[0]) // \f is the "Form feed" control character, unlikely to be usedin regular text.
|
|
258
|
-
|
|
259
|
-
let exprLength = obj.exprs.length;
|
|
260
|
-
result = new Array(exprLength + 1);
|
|
261
|
-
result[0] = obj.htmlId;
|
|
262
|
-
let exprs = obj.exprs;
|
|
263
|
-
for (let i = 0; i < exprLength; i++)
|
|
264
|
-
result[i + 1] = memoize4(exprs[i])
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
else if (obj === null)
|
|
268
|
-
result = 'null';
|
|
269
|
-
|
|
270
|
-
else if (Array.isArray(obj)) {
|
|
271
|
-
if (seenMap.has(obj))
|
|
272
|
-
result = `&A${seenMap.get(obj)}`;
|
|
273
|
-
else {
|
|
274
|
-
seenMap.set(obj, idCounter.value++);
|
|
275
|
-
result = '[' + obj.map(item => memoize2(item)) + ']'
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
else if (obj instanceof Node || typeof obj === 'function')
|
|
279
|
-
result = getObjectId(obj);
|
|
280
|
-
|
|
281
|
-
else if (typeof obj=== 'object') {
|
|
282
|
-
if (seenMap.has(obj))
|
|
283
|
-
result = `&O${seenMap.get(obj)}`;
|
|
284
|
-
|
|
285
|
-
else {
|
|
286
|
-
seenMap.set(obj, idCounter.value++);
|
|
287
|
-
|
|
288
|
-
// // String contact approach.
|
|
289
|
-
// result = '{';
|
|
290
|
-
// for (let key in obj) {
|
|
291
|
-
// //key = key.match(/[^A-Z0-9_]/i) ? JSON.stringify(key) : key;
|
|
292
|
-
// key = (key.includes(':')) ? JSON.stringify(key) : key;
|
|
293
|
-
// result += `${key}:${memoize2(obj[key])}`
|
|
294
|
-
// }
|
|
295
|
-
// result = result + '}';
|
|
296
|
-
|
|
297
|
-
// Buffer approach. Seems slightly faster.
|
|
298
|
-
let keys = Object.keys(obj);
|
|
299
|
-
let buffer = new Array(keys.length) + 2;
|
|
300
|
-
buffer[0] = '{'
|
|
301
|
-
buffer[buffer.length-1] = '}'
|
|
302
|
-
let idx = 1;
|
|
303
|
-
for (let key of keys) {
|
|
304
|
-
//key = key.match(/[^A-Z0-9_]/i) ? JSON.stringify(key) : key;
|
|
305
|
-
key = (key.includes(':')) ? JSON.stringify(key) : key;
|
|
306
|
-
buffer[idx] = `${key}:${memoize2(obj[key])},`
|
|
307
|
-
idx++;
|
|
308
|
-
}
|
|
309
|
-
result = buffer.join('');
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
else
|
|
313
|
-
result = JSON.stringify(obj);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return result;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
// Takes half as long as just calling JSON.serialize!
|
|
324
|
-
function containsFunction(obj) {
|
|
325
|
-
// Base case: if the object itself is a function
|
|
326
|
-
let type = typeof obj;
|
|
327
|
-
if (type === 'function') {
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// If the object is an object or array, recursively search its properties
|
|
332
|
-
if (type === 'object' && obj !== null)
|
|
333
|
-
for (let key in obj)
|
|
334
|
-
if (containsFunction(obj[key]))
|
|
335
|
-
return true;
|
|
336
|
-
|
|
337
|
-
// If none of the properties are functions, return false
|
|
338
|
-
return false;
|
|
339
|
-
}
|
package/src/unused/InUse.test.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import {InUseArray, InUseMap} from "./InUseMap.js";
|
|
2
|
-
import Testimony, {assert} from "../../tests/Testimony.js";
|
|
3
|
-
|
|
4
|
-
Testimony.test('InUseArray.constructor', () => {
|
|
5
|
-
const arr = new InUseArray();
|
|
6
|
-
assert.eq(arr.count, 0, 'Initial count should be 0');
|
|
7
|
-
assert.eq(arr.length, 0, 'Initial length should be 0');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
Testimony.test('InUseArray.use - with no value', () => {
|
|
11
|
-
const arr = new InUseArray(1, 2, 3);
|
|
12
|
-
const val = arr.use();
|
|
13
|
-
assert.eq(val, 1, 'Should return the first value');
|
|
14
|
-
assert.eq(arr.count, 1, 'Count should be incremented');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
Testimony.test('InUseArray.use - with specific value', () => {
|
|
18
|
-
const arr = new InUseArray(1, 2, 3);
|
|
19
|
-
arr.count = 1; // Mark the first element as in-use
|
|
20
|
-
const val = arr.use(3);
|
|
21
|
-
assert.eq(val, 3, 'Should return the specified value');
|
|
22
|
-
assert.eq(arr.count, 2, 'Count should be incremented');
|
|
23
|
-
assert.eq(arr[1], 3, 'Value should be moved to the in-use section');
|
|
24
|
-
assert.eq(arr[2], 2, 'Remaining value should be in the available section');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
Testimony.test('InUseArray.use', () => {
|
|
28
|
-
const arr = new InUseArray(1, 2, 3);
|
|
29
|
-
let val = arr.use(4);
|
|
30
|
-
assert.eq(val, undefined,'Should return undefined');
|
|
31
|
-
assert.eq(arr.use(), 1);
|
|
32
|
-
assert.eq(arr.use(3), 3);
|
|
33
|
-
assert.eq(arr.use(), 2);
|
|
34
|
-
assert.eq(arr.use(), undefined);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
Testimony.test('InUseArray.use - all in use', () => {
|
|
38
|
-
const arr = new InUseArray(1, 2, 3);
|
|
39
|
-
arr.count = 3;
|
|
40
|
-
const val = arr.use();
|
|
41
|
-
assert.eq(val, undefined, 'Should return undefined');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
Testimony.test('InUseArray.free', () => {
|
|
45
|
-
const arr = new InUseArray(1, 2, 3);
|
|
46
|
-
arr.count = 3;
|
|
47
|
-
arr.free();
|
|
48
|
-
assert.eq(arr.count, 0, 'Count should be reset to 0');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
Testimony.test('InUseArray.reset', () => {
|
|
52
|
-
const arr = new InUseArray(1, 2, 3);
|
|
53
|
-
arr.reset();
|
|
54
|
-
assert.eq(arr.length, 0, 'Array should be empty');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Testimony.test('InUseMap.add', () => {
|
|
61
|
-
const map = new InUseMap();
|
|
62
|
-
map.add('key', 'value');
|
|
63
|
-
assert.eq(map.data['key'][0], 'value', 'Value should be added to the map');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
Testimony.test('InUseMap.getAll', () => {
|
|
67
|
-
const map = new InUseMap();
|
|
68
|
-
map.add('key', 'value');
|
|
69
|
-
const values = map.getAll('key');
|
|
70
|
-
assert.eq(values.length, 1, 'Should return an array with one value');
|
|
71
|
-
assert.eq(values[0], 'value', 'Should return the correct value');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
Testimony.test('InUseMap.use', () => {
|
|
75
|
-
const map = new InUseMap();
|
|
76
|
-
map.add('key', 'value');
|
|
77
|
-
let value = map.use('key');
|
|
78
|
-
assert.eq(value, 'value', 'Should return and mark the value as in-use');
|
|
79
|
-
value = map.use('key');
|
|
80
|
-
assert.eq(value, undefined, 'Should return and mark the value as in-use');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
Testimony.test('InUseMap.hasValue', () => {
|
|
84
|
-
const map = new InUseMap();
|
|
85
|
-
map.add('key1', 'value1');
|
|
86
|
-
map.add('key2', 'value2');
|
|
87
|
-
const keys = map.hasValue('value1');
|
|
88
|
-
assert.eq(keys.length, 1, 'Should return an array with one key');
|
|
89
|
-
assert.eq(keys[0], 'key1', 'Should return the correct key');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
|