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
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
|
|
|