dosycrux
Version:
dosy crux - the crux anasta of JavaScript tool belts
137 lines (125 loc) • 4.38 kB
JavaScript
;
{
const SHALLOW_EQUALITY_SUFFICES = new Set([
Undefined,
Null,
String,
Number,
Boolean
]);
const crux = {
T, ts,
eq,
Null, Undefined
};
try { module.exports = crux; } catch(e) { Object.assign( self, { crux } ); }
//test_eq();
// true if javascript strict equals
// either the primitive is the same value or the object is the same memory location
function same( a, b ) {
return a === b;
}
// true if semantically equal and same type
function eq2( a, b ) {
return eq(a,b) && (ts(a) == ts(b));
}
// true if semantically equal
// false otherwise
function eq( a, b, {
strict_symbols: strict_symbols = false,
decircularize : decircularize = new Set(),
ignore_circular : ignore_circular = true
} = {}) {
if ( decircularize.has( a ) || decircularize.has( b ) ) {
throw new TypeError( `Equality of objects with circular references cannot be determined with this version of eq. Please develop a version that can.` );
} else {
decircularize.add( a );
decircularize.add( b );
}
const ta = T(a);
if ( SHALLOW_EQUALITY_SUFFICES.has( ta ) ) {
return a == b;
}
// else check deep
const snapshot_a = get_snapshot( a );
const snapshot_b = get_snapshot( b );
const slotnames_a = Object.getOwnPropertyNames( snapshot_a );
const slotnames_b = Object.getOwnPropertyNames( snapshot_b );
if ( slotnames_a.length != slotnames_b.length ) {
return false;
}
slotnames_a.sort();
slotnames_b.sort();
const slotnamestring_a = slotnames_a.join(',');
const slotnamestring_b = slotnames_b.join(',');
if ( slotnamestring_a != slotnamestring_b ) {
return false;
}
const symbols_a = Object.getOwnPropertySymbols( snapshot_a );
const symbols_b = Object.getOwnPropertySymbols( snapshot_b );
if ( symbols_a.length != symbols_b.length ) {
return false;
}
// Symbols are equal if their descriptions are equal
let sym_match = true;
if ( strict_symbols ) {
const sym_string_a = symbols_a.map( s => s.toString() ).sort().join(',');
const sym_string_b = symbols_b.map( s => s.toString() ).sort().join(',');
sym_match = sym_match && ( sym_string_a == sym_string_b );
} else {
const sym_set_b = new Set( symbols_b );
sym_match = sym_match && symbols_a.every( (s,i) => sym_set_b.has( s ) );
}
if ( ! sym_match ) {
return false;
}
const names = slotnames_a.concat( symbols_a );
let equal = true;
for( const name of names ) {
const a_val = snapshot_a[name];
const b_val = snapshot_b[name];
if ( decircularize.has( a_val ) || decircularize.has( b_val ) ) {
if ( ignore_circular ) {
console.warn( `Warning: circular reference for name ${name}, at least one of the following has been visited before`, a_val, b_val );
continue;
} else {
throw new TypeError( `Equality of objects with circular references cannot be determined with this version of eq. Please develop a version that can.` );
}
}
const val_equal = eq( a_val, b_val, { decircularize } );
equal = equal && val_equal;
if ( ! equal ) break;
}
return equal;
}
function get_snapshot( o ) {
const oshot = Object.create( null );
const names = Object.getOwnPropertyNames( o ).concat( Object.getOwnPropertySymbols( o ) );
for( const name of names ) {
oshot[name] = o[name];
}
return oshot;
}
// placeholders for Empty types
function Null(){ throw new TypeError(`Null cannot be created.`);}
function Undefined(){ throw new TypeError(`Undefined cannot be created.`);}
// the Type as a constructor
function T( a ) {
if ( a === null ) return Null;
if ( a === undefined ) return Undefined;
const c = a.constructor;
const pc = a.__proto__.constructor;
const sc = a.name ? a : null;
return c || pc || sc || Object;
}
// the Type as a String
function ts( a ) {
const cname = a.constructor ? a.constructor.name : null;
const tname = typeNameMatcher.exec( Object.prototype.toString.call( a ) )[1];
if ( tname !== cname && !! cname ) return cname;
return tname;
}
function test_eq() {
console.log( eq( { d:"CAT",a:1, b:2,c:3 }, {d:"CAT",a:1,b:2,c:3} ) );
}
}