You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
294 lines
5.7 KiB
294 lines
5.7 KiB
5 years ago
|
var collation = require('./collation')
|
||
|
|
||
|
//
|
||
|
// base type system
|
||
|
//
|
||
|
var base = {}
|
||
|
|
||
|
//
|
||
|
// helper utilities
|
||
|
//
|
||
|
|
||
|
function _valueOf(instance) {
|
||
|
return instance == null ? instance : instance.valueOf()
|
||
|
}
|
||
|
|
||
|
var _toString = Object.prototype.toString
|
||
|
|
||
|
function _isObject(instance) {
|
||
|
return instance && _toString.call(instance) === '[object Object]'
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// base typewise compare implementation
|
||
|
//
|
||
|
base.compare = function (a, b) {
|
||
|
//
|
||
|
// test for invalid values
|
||
|
//
|
||
|
if (base.invalid(a, b))
|
||
|
return NaN
|
||
|
|
||
|
//
|
||
|
// short circuit for identical objects
|
||
|
//
|
||
|
if (a === b)
|
||
|
return 0
|
||
|
|
||
|
//
|
||
|
// short circuit for base bound types
|
||
|
//
|
||
|
var result = base.bound.compare(a, b)
|
||
|
if (result !== undefined)
|
||
|
return result
|
||
|
|
||
|
//
|
||
|
// cache typeof and valueOf for both values
|
||
|
//
|
||
|
var aTypeOf = typeof a
|
||
|
var bTypeOf = typeof b
|
||
|
var aValueOf = _valueOf(a)
|
||
|
var bValueOf = _valueOf(b)
|
||
|
|
||
|
//
|
||
|
// loop over type tags and attempt compare
|
||
|
//
|
||
|
var order = base.order
|
||
|
var sorts = base.sorts
|
||
|
var sort
|
||
|
for (var i = 0, length = order.length; i < length; ++i) {
|
||
|
sort = sorts[order[i]]
|
||
|
|
||
|
//
|
||
|
// if first arg is a member of this sort we have an answer
|
||
|
//
|
||
|
if (sort.is(a, aTypeOf))
|
||
|
//
|
||
|
// if b is the same as a then defer to sort's comparator, else a comes first
|
||
|
//
|
||
|
return sort.is(b, bTypeOf) ? sort.compare(aValueOf, bValueOf) : -1
|
||
|
|
||
|
//
|
||
|
// if b is this type but not a then b comes first
|
||
|
//
|
||
|
if (sort.is(b, bTypeOf))
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// values are incomparable as they didn't match against any registered types
|
||
|
//
|
||
|
return NaN
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// sort equality test
|
||
|
//
|
||
|
base.equal = function(a, b) {
|
||
|
return base.compare(a, b) === 0
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// test for top-level incomparability using invalid sort definitions
|
||
|
//
|
||
|
base.invalid = function (a, b) {
|
||
|
var types = base.invalid
|
||
|
for (var key in types) {
|
||
|
var type = types[key]
|
||
|
if (type && type.is && (type.is(a) || type.is(b)))
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// definitions for explicitly invalid/incomparable types
|
||
|
//
|
||
|
|
||
|
base.invalid.NAN = {
|
||
|
is: function (instance) {
|
||
|
var valueOf = _valueOf(instance)
|
||
|
return valueOf !== valueOf
|
||
|
}
|
||
|
}
|
||
|
|
||
|
base.invalid.ERROR = {
|
||
|
is: function (instance) {
|
||
|
return instance && instance instanceof Error
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// definitions for boundary types, unserializable as values
|
||
|
//
|
||
|
|
||
|
function BoundedKey(bound, upper, prefix) {
|
||
|
this.bound = bound
|
||
|
this.upper = !!upper
|
||
|
this.prefix = prefix
|
||
|
}
|
||
|
|
||
|
function Boundary(sort) {
|
||
|
this.sort = sort
|
||
|
}
|
||
|
|
||
|
Boundary.prototype.lower = function (prefix) {
|
||
|
return new BoundedKey(this, false, prefix)
|
||
|
}
|
||
|
|
||
|
Boundary.prototype.upper = function (prefix) {
|
||
|
return new BoundedKey(this, true, prefix)
|
||
|
}
|
||
|
|
||
|
Boundary.prototype.is = function (source) {
|
||
|
return source instanceof BoundedKey && source.sort === this.sort
|
||
|
}
|
||
|
|
||
|
Boundary.add = function (sort) {
|
||
|
sort.bound = new Boundary(sort)
|
||
|
}
|
||
|
|
||
|
Boundary.add(base)
|
||
|
|
||
|
base.bound.getBoundary = function (source) {
|
||
|
return source instanceof BoundedKey && source.bound
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// compare a values against top level bounds (assumes first arg is an instance)
|
||
|
//
|
||
|
base.bound.compare = function (a, b) {
|
||
|
var aBound = base.bound.is(a)
|
||
|
var bBound = base.bound.is(b)
|
||
|
if (aBound) {
|
||
|
if (bBound && !a.upper === !b.upper)
|
||
|
return 0
|
||
|
return a.upper ? 1 : -1
|
||
|
}
|
||
|
|
||
|
if (bBound)
|
||
|
return -base.bound.compare(b, a)
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// helper to register fixed (nullary) types
|
||
|
//
|
||
|
function fixed(value) {
|
||
|
return {
|
||
|
is: function (instance) {
|
||
|
return instance === value
|
||
|
},
|
||
|
value: value
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// value types defined as ordered map of "sorts"
|
||
|
//
|
||
|
var sorts = base.sorts = {}
|
||
|
|
||
|
sorts.void = fixed(void 0)
|
||
|
sorts.void.compare = collation.inequality
|
||
|
|
||
|
sorts.null = fixed(null)
|
||
|
sorts.null.compare = collation.inequality
|
||
|
|
||
|
var BOOLEAN = sorts.boolean = {}
|
||
|
BOOLEAN.compare = collation.inequality
|
||
|
BOOLEAN.is = function (instance, typeOf) {
|
||
|
return (typeOf || typeof instance) === 'boolean'
|
||
|
}
|
||
|
|
||
|
BOOLEAN.sorts = {}
|
||
|
BOOLEAN.sorts.true = fixed(true)
|
||
|
BOOLEAN.sorts.false = fixed(false)
|
||
|
|
||
|
Boundary.add(BOOLEAN)
|
||
|
|
||
|
|
||
|
var NUMBER = sorts.number = {}
|
||
|
NUMBER.compare = collation.difference
|
||
|
NUMBER.is = function (instance, typeOf) {
|
||
|
return (typeOf || typeof instance) === 'number'
|
||
|
}
|
||
|
|
||
|
NUMBER.sorts = {}
|
||
|
NUMBER.sorts.max = fixed(Number.POSITIVE_INFINITY)
|
||
|
NUMBER.sorts.min = fixed(Number.NEGATIVE_INFINITY)
|
||
|
|
||
|
NUMBER.sorts.positive = {}
|
||
|
NUMBER.sorts.positive.is = function (instance) {
|
||
|
return instance >= 0
|
||
|
}
|
||
|
|
||
|
NUMBER.sorts.negative = {}
|
||
|
NUMBER.sorts.negative.is = function (instance) {
|
||
|
return instance < 0
|
||
|
}
|
||
|
|
||
|
Boundary.add(NUMBER)
|
||
|
|
||
|
|
||
|
var DATE = sorts.date = {}
|
||
|
DATE.compare = collation.difference
|
||
|
DATE.is = function (instance) {
|
||
|
return instance instanceof Date && instance.valueOf() === instance.valueOf()
|
||
|
}
|
||
|
|
||
|
DATE.sorts = {}
|
||
|
DATE.sorts.positive = {}
|
||
|
DATE.sorts.positive.is = function (instance) {
|
||
|
return instance.valueOf() >= 0
|
||
|
}
|
||
|
|
||
|
DATE.sorts.negative = {}
|
||
|
DATE.sorts.negative.is = function (instance) {
|
||
|
return instance.valueOf() < 0
|
||
|
}
|
||
|
|
||
|
Boundary.add(DATE)
|
||
|
|
||
|
|
||
|
var BINARY = sorts.binary = {}
|
||
|
BINARY.empty = new Buffer([])
|
||
|
BINARY.compare = collation.bitwise
|
||
|
BINARY.is = Buffer.isBuffer
|
||
|
|
||
|
Boundary.add(BINARY)
|
||
|
|
||
|
|
||
|
var STRING = sorts.string = {}
|
||
|
STRING.empty = ''
|
||
|
STRING.compare = collation.inequality
|
||
|
STRING.is = function (instance, typeOf) {
|
||
|
return (typeOf || typeof instance) === 'string'
|
||
|
}
|
||
|
|
||
|
Boundary.add(STRING)
|
||
|
|
||
|
|
||
|
var ARRAY = sorts.array = {}
|
||
|
ARRAY.empty = []
|
||
|
ARRAY.compare = collation.recursive.elementwise(base.compare)
|
||
|
ARRAY.is = Array.isArray
|
||
|
|
||
|
Boundary.add(ARRAY)
|
||
|
|
||
|
|
||
|
// var OBJECT = sorts.object = {}
|
||
|
// OBJECT.empty = {}
|
||
|
// OBJECT.compare = collation.recursive.fieldwise(base.compare)
|
||
|
// OBJECT.is = _isObject
|
||
|
|
||
|
// Boundary.add(OBJECT)
|
||
|
|
||
|
//
|
||
|
// default order for instance checking in compare operations
|
||
|
//
|
||
|
base.order = []
|
||
|
for (var key in sorts) {
|
||
|
base.order.push(key)
|
||
|
}
|
||
|
|
||
|
module.exports = base
|