@bokeh/bokehjs
Version:
Interactive, novel data visualization
227 lines • 7.17 kB
JavaScript
// Based on Underscore.js 1.8.3 (http://underscorejs.org)
import { isObject } from "./types";
const { hasOwnProperty } = Object.prototype;
export const equals = Symbol("equals");
function is_Equatable(obj) {
return isObject(obj) && equals in obj;
}
export const wildcard = Symbol("wildcard");
const toString = Object.prototype.toString;
export class EqNotImplemented extends Error {
static __name__ = "EqNotImplemented";
}
export class Comparator {
static __name__ = "Comparator";
a_stack = [];
b_stack = [];
structural;
constructor(options) {
this.structural = options?.structural ?? false;
}
eq(a, b) {
if (a === b || Object.is(a, b)) {
return true;
}
if (a === wildcard || b === wildcard) {
return true;
}
if (a == null || b == null) {
return a === b;
}
const class_name = toString.call(a);
if (class_name != toString.call(b)) {
return false;
}
switch (class_name) {
case "[object Number]":
return this.numbers(a, b);
case "[object Symbol]":
return a === b;
case "[object RegExp]":
case "[object String]":
return `${a}` == `${b}`;
case "[object Date]":
case "[object Boolean]":
return +a === +b;
}
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
// Initializing stack of traversed objects.
// It's done here since we only need them for objects and arrays comparison.
const { a_stack, b_stack } = this;
let length = a_stack.length;
while (length-- > 0) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (a_stack[length] === a) {
return b_stack[length] === b;
}
}
a_stack.push(a);
b_stack.push(b);
const result = (() => {
if (is_Equatable(a) && is_Equatable(b)) {
return a[equals](b, this);
}
switch (class_name) {
case "[object Array]":
case "[object Uint8Array]":
case "[object Int8Array]":
case "[object Uint16Array]":
case "[object Int16Array]":
case "[object Uint32Array]":
case "[object Int32Array]":
case "[object Float32Array]":
case "[object Float64Array]": {
return this.arrays(a, b);
}
case "[object ArrayBuffer]":
case "[object SharedArrayBuffer]": {
return this.array_buffers(a, b);
}
case "[object Map]": {
return this.maps(a, b);
}
case "[object Set]": {
return this.sets(a, b);
}
case "[object Object]": {
if (a.constructor == b.constructor && (a.constructor == null || a.constructor === Object)) {
return this.objects(a, b);
}
}
case "[object Function]": {
if (a.constructor == b.constructor && a.constructor === Function) {
return this.eq(`${a}`, `${b}`);
}
}
}
if (typeof Node !== "undefined" && a instanceof Node) {
return this.nodes(a, b);
}
throw new EqNotImplemented(`can't compare objects of type ${class_name}`);
})();
a_stack.pop();
b_stack.pop();
return result;
}
numbers(a, b) {
return a === b || Object.is(a, b);
}
arrays(a, b) {
const { length } = a;
if (length != b.length) {
return false;
}
for (let i = 0; i < length; i++) {
if (!this.eq(a[i], b[i])) {
return false;
}
}
return true;
}
array_buffers(a, b) {
// compare array buffers byte-wise; this doesn't allocate any memory
return this.arrays(new Uint8Array(a), new Uint8Array(b));
}
iterables(a, b) {
const ai = a[Symbol.iterator]();
const bi = b[Symbol.iterator]();
while (true) {
const an = ai.next();
const bn = bi.next();
const an_done = an.done ?? false;
const bn_done = bn.done ?? false;
if (an_done && bn_done) {
return true;
}
if (an_done || bn_done) {
return false;
}
if (!this.eq(an.value, bn.value)) {
return false;
}
}
}
maps(a, b) {
if (a.size != b.size) {
return false;
}
if (this.structural) {
return this.iterables(a.entries(), b.entries());
}
else {
for (const [key, val] of a) {
if (!b.has(key) || !this.eq(val, b.get(key))) {
return false;
}
}
return true;
}
}
sets(a, b) {
if (a.size != b.size) {
return false;
}
if (this.structural) {
return this.iterables(a.entries(), b.entries());
}
else {
for (const key of a) {
if (!b.has(key)) {
return false;
}
}
return true;
}
}
objects(a, b) {
const keys = Object.keys(a);
if (keys.length != Object.keys(b).length) {
return false;
}
for (const key of keys) {
if (!hasOwnProperty.call(b, key) || !this.eq(a[key], b[key])) {
return false;
}
}
return true;
}
nodes(a, b) {
if (a.nodeType != b.nodeType) {
return false;
}
if (a.textContent != b.textContent) {
return false;
}
if (!this.iterables(a.childNodes, b.childNodes)) {
return false;
}
return true;
}
}
const { abs } = Math;
export class SimilarComparator extends Comparator {
tolerance;
static __name__ = "SimilarComparator";
constructor(tolerance = 1e-4) {
super();
this.tolerance = tolerance;
}
numbers(a, b) {
return super.numbers(a, b) || abs(a - b) < this.tolerance;
}
}
export function is_equal(a, b) {
const comparator = new Comparator();
return comparator.eq(a, b);
}
export function is_structurally_equal(a, b) {
const comparator = new Comparator({ structural: true });
return comparator.eq(a, b);
}
export function is_similar(a, b, tolerance) {
const comparator = new SimilarComparator(tolerance);
return comparator.eq(a, b);
}
//# sourceMappingURL=eq.js.map