@surface/core
Version:
Provides core functionality of many @surfaces modules.
131 lines (130 loc) • 4.1 kB
JavaScript
import { isIterable } from "./common/generic.js";
import { enumerateKeys } from "./common/object.js";
export default class Hashcode {
cache = new Map();
references = new Set();
stack = ["$root"];
static encode(source) {
return new Hashcode().encode(source);
}
buildPath() {
const iterator = this.stack[Symbol.iterator]();
let path = "";
let next = iterator.next();
if (!next.done) {
do {
path += typeof next.value == "string" ? next.value : `[${String(next.value)}]`;
next = iterator.next();
if (!next.done) {
path += typeof next.value == "string" ? "." : "";
}
} while (!next.done);
}
return path;
}
encode(source) {
let hash = 0;
for (const token of this.getTokens(source)) {
for (const char of token) {
hash = (hash << 5) - hash + char.charCodeAt(0) | 0;
}
}
return 0x7FFFFFFF ^ Math.abs(hash);
}
*enumerateArrayTokens(source) {
const cache = this.cache.get(source);
if (cache) {
yield "{";
yield "\"$ref\"";
yield ":";
yield `"${cache}"`;
yield "}";
return;
}
this.cache.set(source, this.buildPath());
yield "[";
const iterator = source[Symbol.iterator]();
let next = iterator.next();
if (!next.done) {
let index = 0;
do {
this.stack.push(index);
for (const token of this.getTokens(next.value)) {
yield token;
}
this.stack.pop();
next = iterator.next();
if (!next.done) {
index++;
yield ",";
}
} while (!next.done);
}
yield "]";
}
*enumerateObjectTokens(source) {
const cache = this.cache.get(source);
if (cache) {
yield "{";
yield "\"$ref\"";
yield ":";
yield `"${cache}"`;
yield "}";
return;
}
this.cache.set(source, this.buildPath());
yield "{";
yield "\"$type\"";
yield ":";
yield `"${source.constructor.name}"`;
const iterator = enumerateKeys(source);
let next = iterator.next();
if (!next.done) {
yield ",";
do {
yield `"${String(next.value)}"`;
yield ":";
this.stack.push(next.value);
for (const token of this.getTokens(source[next.value])) {
yield token;
}
this.stack.pop();
next = iterator.next();
if (!next.done) {
yield ",";
}
} while (!next.done);
}
yield "}";
}
*getTokens(source) {
if (typeof source == "object" && source) {
if (this.references.has(source)) {
yield "{";
yield "\"$ref\"";
yield ":";
yield `"${this.cache.get(source)}"`;
yield "}";
return;
}
this.references.add(source);
if (isIterable(source)) {
for (const token of this.enumerateArrayTokens(source)) {
yield token;
}
}
else {
for (const token of this.enumerateObjectTokens(source)) {
yield token;
}
}
this.references.delete(source);
}
else if (typeof source == "string" || typeof source == "function") {
yield `"${source}"`;
}
else {
yield String(source);
}
}
}