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.

293 lines
5.7 KiB

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