UNPKG

@surface/core

Version:

Provides core functionality of many @surfaces modules.

131 lines (130 loc) 4.1 kB
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); } } }