Introduce BooleanT and boolean functions #17
11 changed files with 151 additions and 61 deletions
|
@ -18,4 +18,7 @@
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
mocha: '^11.1.0',
|
mocha: '^11.1.0',
|
||||||
},
|
},
|
||||||
|
dependencies: {
|
||||||
|
'array-keyed-map': '^2.1.3',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
|
@ -7,6 +7,10 @@ settings:
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
|
dependencies:
|
||||||
|
array-keyed-map:
|
||||||
|
specifier: ^2.1.3
|
||||||
|
version: 2.1.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
mocha:
|
mocha:
|
||||||
specifier: ^11.1.0
|
specifier: ^11.1.0
|
||||||
|
@ -49,6 +53,9 @@ packages:
|
||||||
argparse@2.0.1:
|
argparse@2.0.1:
|
||||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
|
|
||||||
|
array-keyed-map@2.1.3:
|
||||||
|
resolution: {integrity: sha512-JIUwuFakO+jHjxyp4YgSiKXSZeC0U+R1jR94bXWBcVlFRBycqXlb+kH9JHxBGcxnVuSqx5bnn0Qz9xtSeKOjiA==}
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
|
@ -398,6 +405,8 @@ snapshots:
|
||||||
|
|
||||||
argparse@2.0.1: {}
|
argparse@2.0.1: {}
|
||||||
|
|
||||||
|
array-keyed-map@2.1.3: {}
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
binary-extensions@2.3.0: {}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {onType} from './helpers.js'
|
||||||
|
|
||||||
const typeObject = {} // have to make sure there is only one
|
const typeObject = {} // have to make sure there is only one
|
||||||
|
|
||||||
export const types = () => typeObject
|
export const types = () => typeObject
|
||||||
|
@ -5,7 +7,8 @@ export const types = () => typeObject
|
||||||
export class Type {
|
export class Type {
|
||||||
constructor(f, options = {}) {
|
constructor(f, options = {}) {
|
||||||
this.test = f
|
this.test = f
|
||||||
this.from = new Map(options.from ?? [])
|
this.from = options.from ?? onType() // empty Implementations if no ...
|
||||||
|
// ... conversions specified
|
||||||
}
|
}
|
||||||
toString() {
|
toString() {
|
||||||
return this.name || `[Type ${this.test}]`
|
return this.name || `[Type ${this.test}]`
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import {typeOf, Type, bootstrapTypes} from './Type.js'
|
import ArrayKeyedMap from 'array-keyed-map'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Implementations, isPlainFunction, isPlainObject, onType
|
Implementations, isPlainFunction, isPlainObject, onType
|
||||||
} from './helpers.js'
|
} from './helpers.js'
|
||||||
import {
|
import {bootstrapTypes, Returns, typeOf, Type} from './Type.js'
|
||||||
alwaysMatches, matched, needsCollection, Any, Multiple
|
import {matched, needsCollection, Passthru} from './TypePatterns.js'
|
||||||
} from './TypePatterns.js'
|
|
||||||
|
|
||||||
export class TypeDispatcher {
|
export class TypeDispatcher {
|
||||||
constructor(...specs) {
|
constructor(...specs) {
|
||||||
|
@ -37,25 +36,39 @@ export class TypeDispatcher {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (typeof val === 'function') {
|
if (typeof val === 'function') {
|
||||||
val = onType(Multiple(Any), val)
|
val = onType(Passthru, val)
|
||||||
}
|
}
|
||||||
if (val instanceof Implementations) {
|
if (val instanceof Implementations) {
|
||||||
if (!(key in this)) {
|
if (!(key in this)) {
|
||||||
// need to set up the item
|
// Need to "bootstrap" the item:
|
||||||
|
// We initially define it with a temporary getter, only
|
||||||
|
// because we don't know whether ultimately it will produce
|
||||||
|
// a function or a non-callable value, and the "permanent"
|
||||||
|
// getter we want depends on which it turns out to be. This
|
||||||
|
// situation means it's not supported to replace a key
|
||||||
|
// corresponding to a method, after it's been fetched once,
|
||||||
|
// with a definition that produces a non-callable value;
|
||||||
|
// the TypeDispatcher will go on returning a function, and
|
||||||
|
// even if you call that function, it will throw an error
|
||||||
|
// when it tries to call the non-callable value.
|
||||||
|
// Conversely, if you try to replace a key corresponding
|
||||||
|
// to a non-callable value, after it's been fetched once,
|
||||||
|
// with a function, it will work, but it will not be able to
|
||||||
|
// perform type dispatch on that function.
|
||||||
Object.defineProperty(this, key, {
|
Object.defineProperty(this, key, {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get: () => {
|
get: () => {
|
||||||
let tryValue
|
let tryValue
|
||||||
let tryTypes = []
|
|
||||||
try {
|
try {
|
||||||
tryValue = this.resolve(key, [])
|
tryValue = this.resolve(key, [])
|
||||||
} catch {
|
} catch {
|
||||||
// Has no value for the empty type list, so
|
// Has no value for the empty type list, so therefore
|
||||||
// find a type list it will have a value for
|
// it must be a method, as there is no way to supply
|
||||||
tryTypes =
|
// any types for a non-function value. Hence, we can
|
||||||
this._implementations[key][0][0].sampleTypes()
|
// just make tryValue any plain function, since it is
|
||||||
tryValue = this.resolve(key, tryTypes)
|
// never actually used, just its type analyzed.
|
||||||
|
tryValue = () => undefined
|
||||||
}
|
}
|
||||||
// Redefine the property according to what sort of
|
// Redefine the property according to what sort of
|
||||||
// entity it is:
|
// entity it is:
|
||||||
|
@ -74,7 +87,7 @@ export class TypeDispatcher {
|
||||||
})
|
})
|
||||||
return standard
|
return standard
|
||||||
}
|
}
|
||||||
if (tryTypes.length) tryValue = undefined
|
|
||||||
if (typeof tryValue === 'object') {
|
if (typeof tryValue === 'object') {
|
||||||
if (!('resolve' in tryValue)) {
|
if (!('resolve' in tryValue)) {
|
||||||
tryValue.resolve = types => this.resolve(key, types)
|
tryValue.resolve = types => this.resolve(key, types)
|
||||||
|
@ -92,6 +105,7 @@ export class TypeDispatcher {
|
||||||
})
|
})
|
||||||
return get()
|
return get()
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(this, key, {
|
Object.defineProperty(this, key, {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
@ -100,13 +114,16 @@ export class TypeDispatcher {
|
||||||
return tryValue
|
return tryValue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Finally, initialize the other data for this key:
|
||||||
this._implementations[key] = []
|
this._implementations[key] = []
|
||||||
this._behaviors[key] = new Map()
|
this._behaviors[key] = new ArrayKeyedMap()
|
||||||
this._dependencies[key] = new Map()
|
this._dependencies[key] = new ArrayKeyedMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now add all of the patterns of this implementation:
|
// Now add all of the patterns of this implementation:
|
||||||
for (const [pattern, result] of val.patterns) {
|
for (const [pattern, result] of val.patterns) {
|
||||||
if (alwaysMatches(pattern)) {
|
if (pattern === Passthru) {
|
||||||
if (key in this._fallbacks) this._disengageFallback(key)
|
if (key in this._fallbacks) this._disengageFallback(key)
|
||||||
this._fallbacks[key] = result
|
this._fallbacks[key] = result
|
||||||
} else {
|
} else {
|
||||||
|
@ -128,7 +145,7 @@ export class TypeDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
// install value as a catchall value
|
// install value as a catchall value
|
||||||
this.merge({[key]: onType(Multiple(Any), val)})
|
this.merge({[key]: onType(Passthru, val)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +168,10 @@ export class TypeDispatcher {
|
||||||
} else {
|
} else {
|
||||||
const from = state.pos++
|
const from = state.pos++
|
||||||
if ('actual' in elt) { // incorporate conversion
|
if ('actual' in elt) { // incorporate conversion
|
||||||
const convert = elt.matched.from.get(elt.actual)(this)
|
let convert = elt.convertor
|
||||||
|
if (!convert.returns) { // it's a factory that produces convert
|
||||||
|
convert = convert(this, elt.actual)
|
||||||
|
}
|
||||||
extractors.push(args => convert(args[from]))
|
extractors.push(args => convert(args[from]))
|
||||||
} else extractors.push(args => args[from])
|
} else extractors.push(args => args[from])
|
||||||
}
|
}
|
||||||
|
@ -167,14 +187,17 @@ export class TypeDispatcher {
|
||||||
if (this.resolve._genDepsOf?.length) this._addToDeps(key, types)
|
if (this.resolve._genDepsOf?.length) this._addToDeps(key, types)
|
||||||
|
|
||||||
const behave = this._behaviors[key]
|
const behave = this._behaviors[key]
|
||||||
|
// Return the cached resolution if it's there
|
||||||
if (behave.has(types)) return behave.get(types)
|
if (behave.has(types)) return behave.get(types)
|
||||||
|
|
||||||
|
// Otherwise, perform the resolution and cache the result
|
||||||
const imps = this._implementations[key]
|
const imps = this._implementations[key]
|
||||||
let needItem = true
|
let needItem = true
|
||||||
let item
|
let item
|
||||||
let pattern
|
|
||||||
let template
|
let template
|
||||||
if (imps.length) {
|
if (imps.length) {
|
||||||
for (const options of [{}, {convert: true}]) {
|
for (const options of [{}, {convert: true}]) {
|
||||||
|
let pattern
|
||||||
for ([pattern, item] of imps) {
|
for ([pattern, item] of imps) {
|
||||||
let finalIndex
|
let finalIndex
|
||||||
;[finalIndex, template] = pattern.match(types, options)
|
;[finalIndex, template] = pattern.match(types, options)
|
||||||
|
@ -189,50 +212,59 @@ export class TypeDispatcher {
|
||||||
if (needItem && key in this._fallbacks) {
|
if (needItem && key in this._fallbacks) {
|
||||||
needItem = false
|
needItem = false
|
||||||
item = this._fallbacks[key]
|
item = this._fallbacks[key]
|
||||||
template = [types]
|
template = types
|
||||||
}
|
}
|
||||||
if (needItem) {
|
if (needItem) {
|
||||||
throw new TypeError(`no matching definition of '${key}' on '${types}'`)
|
throw new TypeError(`no matching definition of '${key}' on '${types}'`)
|
||||||
}
|
}
|
||||||
if (!isPlainFunction(item) || 'returns' in item) {
|
// If this key is producing a non-function value, we're done
|
||||||
|
if (!isPlainFunction(item)) {
|
||||||
behave.set(types, item)
|
behave.set(types, item)
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
// item is a Factory. We have to use it to build the behavior
|
|
||||||
|
// item is a function, either a direct behavior or
|
||||||
|
// a factory. We have to use it to build the final behavior
|
||||||
// First set up to record dependencies
|
// First set up to record dependencies
|
||||||
if (!('_genDepsOf' in this.resolve)) {
|
if (!('_genDepsOf' in this.resolve)) {
|
||||||
this.resolve._genDepsOf = []
|
this.resolve._genDepsOf = []
|
||||||
}
|
}
|
||||||
this.resolve._genDepsOf.push([key, types])
|
|
||||||
let theBehavior = () => undefined
|
let theBehavior = () => undefined
|
||||||
try {
|
this.resolve._genDepsOf.push([key, types]) // Important: make sure
|
||||||
theBehavior = item(
|
// not to return without popping _genDepsOf
|
||||||
DependencyRecorder(this, [], this), matched(template))
|
if (!('returns' in item)) {
|
||||||
} catch {
|
// looks like a factory
|
||||||
// Oops, didn't work as a factory, so guess we were wrong.
|
|
||||||
// Just make it the direct value for this key on these types:
|
|
||||||
behave.set(types, item)
|
|
||||||
return item
|
|
||||||
} finally {
|
|
||||||
this.resolve._genDepsOf.pop()
|
|
||||||
}
|
|
||||||
if (typeof theBehavior === 'function'
|
|
||||||
&& theBehavior.length
|
|
||||||
&& needsCollection(template)
|
|
||||||
) {
|
|
||||||
// have to wrap the behavior to collect the actual arguments
|
|
||||||
// in the way corresponding to the template. That may generate
|
|
||||||
// more dependencies:
|
|
||||||
this.resolve._genDepsOf.push([key, types])
|
|
||||||
try {
|
try {
|
||||||
const collectFunction = this._generateCollectFunction(template)
|
theBehavior = item(
|
||||||
theBehavior = (...args) => theBehavior(...collectFunction(args))
|
DependencyRecorder(this, [], this), matched(template))
|
||||||
} finally {
|
} catch {
|
||||||
this.resolve._genDepsOf.pop()
|
// Oops, didn't work as a factory, so guess we were wrong.
|
||||||
|
// Just make it the direct value for this key on these types:
|
||||||
|
theBehavior = item
|
||||||
|
}
|
||||||
|
} else theBehavior = item
|
||||||
|
|
||||||
|
let finalBehavior = theBehavior
|
||||||
|
if (typeof theBehavior === 'function') {
|
||||||
|
const returning = theBehavior.returns
|
||||||
|
if (!returning) {
|
||||||
|
throw new TypeError(
|
||||||
|
`No return type specified for ${key} on ${types}`)
|
||||||
|
}
|
||||||
|
if (theBehavior.length && needsCollection(template)) {
|
||||||
|
// have to wrap the behavior to collect the actual arguments
|
||||||
|
// in the way corresponding to the template. Generating that
|
||||||
|
// argument transformer may generate more dependencies.
|
||||||
|
const morph = this._generateCollectFunction(template)
|
||||||
|
finalBehavior =
|
||||||
|
Returns(returning, (...args) => theBehavior(...morph(args)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
behave.set(types, theBehavior)
|
|
||||||
return theBehavior
|
this.resolve._genDepsOf.pop() // OK, now it's safe to return
|
||||||
|
behave.set(types, finalBehavior)
|
||||||
|
return finalBehavior
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method called to invalidate a set of behaviors
|
// Method called to invalidate a set of behaviors
|
||||||
|
|
|
@ -20,8 +20,13 @@ class MatchTypePattern extends TypePattern {
|
||||||
const actual = typeSequence[position]
|
const actual = typeSequence[position]
|
||||||
if (position < typeSequence.length) {
|
if (position < typeSequence.length) {
|
||||||
if (actual === this.type) return [position + 1, actual]
|
if (actual === this.type) return [position + 1, actual]
|
||||||
if (options.convert && this.type.from.has(actual)) {
|
if (options.convert) {
|
||||||
return [position + 1, {actual, matched: this.type}]
|
for (const [pattern, convertor] of this.type.from.patterns) {
|
||||||
|
const [pos] = pattern.match([actual])
|
||||||
|
if (pos === 1) {
|
||||||
|
return [position + 1, {actual, convertor, matched: this.type}]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [-1, Undefined]
|
return [-1, Undefined]
|
||||||
|
@ -131,8 +136,18 @@ class MultiPattern extends TypePattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Multiple = item => new MultiPattern(item)
|
export const Multiple = item => new MultiPattern(item)
|
||||||
export const alwaysMatches =
|
|
||||||
pat => pat instanceof MultiPattern && pat.pattern instanceof AnyPattern
|
// Like Multiple(Any) except leaves the argument list alone; it doesn't
|
||||||
|
// chunk it into a single Array of all arguments
|
||||||
|
class PassthruPattern extends TypePattern {
|
||||||
|
match(typeSequence, options={}) {
|
||||||
|
const position = options.position ?? 0
|
||||||
|
return [typeSequence.length, typeSequence.slice(position)]
|
||||||
|
}
|
||||||
|
sampleTypes() {return []}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Passthru = new PassthruPattern()
|
||||||
|
|
||||||
// returns the template just of matched types, dropping any actual types
|
// returns the template just of matched types, dropping any actual types
|
||||||
export const matched = (template) => {
|
export const matched = (template) => {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import {TypeDispatcher} from '../TypeDispatcher.js'
|
import {TypeDispatcher} from '../TypeDispatcher.js'
|
||||||
import * as numbers from '#number/all.js'
|
import * as booleans from '#boolean/all.js'
|
||||||
import * as generics from '#generic/all.js'
|
import * as generics from '#generic/all.js'
|
||||||
|
import * as numbers from '#number/all.js'
|
||||||
import {onType} from "#core/helpers.js"
|
import {onType} from "#core/helpers.js"
|
||||||
import {Any} from "#core/TypePatterns.js"
|
import {Any} from "#core/TypePatterns.js"
|
||||||
import {Returns} from "#core/Type.js"
|
import {Returns} from "#core/Type.js"
|
||||||
|
@ -47,4 +48,15 @@ describe('TypeDispatcher', () => {
|
||||||
gnmath.merge({multiply: plain((a,b) => Math.floor(a) * b)})
|
gnmath.merge({multiply: plain((a,b) => Math.floor(a) * b)})
|
||||||
assert.strictEqual(gnmath.square(-2.5), 7.5)
|
assert.strictEqual(gnmath.square(-2.5), 7.5)
|
||||||
})
|
})
|
||||||
|
it('detects dependencies on conversion operations', () => {
|
||||||
|
const bgn = new TypeDispatcher(booleans, generics, numbers)
|
||||||
|
const {BooleanT, NumberT} = bgn.types
|
||||||
|
assert(!bgn._behaviors.negate.has([BooleanT]))
|
||||||
|
assert.strictEqual(bgn.negate(true), -1)
|
||||||
|
assert(bgn._behaviors.negate.has([BooleanT]))
|
||||||
|
const deps = bgn._dependencies.negate
|
||||||
|
bgn.merge({number: onType([BooleanT], Returns(NumberT, b => b ? 2 : 0))})
|
||||||
|
assert(!bgn._behaviors.negate.has([BooleanT]))
|
||||||
|
assert.strictEqual(bgn.negate(true), -2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import {pattern, Any, Multiple, Optional} from '../TypePatterns.js'
|
import {
|
||||||
|
pattern, Any, Multiple, Optional, needsCollection
|
||||||
|
} from '../TypePatterns.js'
|
||||||
import {Undefined, TypeOfTypes} from '../Type.js'
|
import {Undefined, TypeOfTypes} from '../Type.js'
|
||||||
|
|
||||||
describe('Type patterns', () => {
|
describe('Type patterns', () => {
|
||||||
|
@ -93,4 +95,10 @@ describe('Type patterns', () => {
|
||||||
whyNot.sampleTypes(), [Undefined, TypeOfTypes])
|
whyNot.sampleTypes(), [Undefined, TypeOfTypes])
|
||||||
assert(whyNot.equal(whyNot))
|
assert(whyNot.equal(whyNot))
|
||||||
})
|
})
|
||||||
|
it('determines whether a template needs a collection function', () => {
|
||||||
|
assert(!needsCollection([Undefined]))
|
||||||
|
assert(needsCollection([Undefined, [Undefined, Undefined]]))
|
||||||
|
assert(needsCollection(
|
||||||
|
[Undefined, {actual: Undefined, matched: TypeOfTypes}]))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,8 +9,8 @@ describe('Core helpers', () => {
|
||||||
it('defines what Implementations are', () => {
|
it('defines what Implementations are', () => {
|
||||||
const imps = onType(Undefined, 7, [TypeOfTypes, Undefined], -3)
|
const imps = onType(Undefined, 7, [TypeOfTypes, Undefined], -3)
|
||||||
assert(imps instanceof Implementations)
|
assert(imps instanceof Implementations)
|
||||||
assert(imps.patterns instanceof Map)
|
assert(imps.patterns instanceof Array)
|
||||||
assert(imps.patterns.keys().every(k => k instanceof TypePattern))
|
assert(imps.patterns.every(([k]) => k instanceof TypePattern))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('detects plain objects', () => {
|
it('detects plain objects', () => {
|
||||||
|
|
|
@ -2,12 +2,12 @@ import {pattern} from './TypePatterns.js'
|
||||||
|
|
||||||
export class Implementations {
|
export class Implementations {
|
||||||
constructor(imps) {
|
constructor(imps) {
|
||||||
this.patterns = new Map()
|
this.patterns = []
|
||||||
this._add(imps)
|
this._add(imps)
|
||||||
}
|
}
|
||||||
_add(imps) {
|
_add(imps) {
|
||||||
for (let i = 0; i < imps.length; ++i) {
|
for (let i = 0; i < imps.length; ++i) {
|
||||||
this.patterns.set(pattern(imps[i]), imps[++i])
|
this.patterns.push([pattern(imps[i]), imps[++i]])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
also(...imps) {
|
also(...imps) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {Type} from '#core/Type.js'
|
import {Type} from '#core/Type.js'
|
||||||
|
import {onType} from '#core/helpers.js'
|
||||||
import {BooleanT} from '#boolean/BooleanT.js'
|
import {BooleanT} from '#boolean/BooleanT.js'
|
||||||
|
|
||||||
export const NumberT = new Type(n => typeof n === 'number', {
|
export const NumberT = new Type(n => typeof n === 'number', {
|
||||||
from: [[BooleanT, math => math.number.resolve([BooleanT])]],
|
from: onType(BooleanT, math => math.number.resolve([BooleanT])),
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,14 @@ describe('NumberT Type', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can convert from BooleanT to NumberT', () => {
|
it('can convert from BooleanT to NumberT', () => {
|
||||||
const cnvBtoN = NumberT.from.get(BooleanT)(math)
|
const convertImps = NumberT.from
|
||||||
|
let cnvBtoN
|
||||||
|
for (const [pattern, convFactory] of convertImps.patterns) {
|
||||||
|
if (pattern.match([BooleanT])) {
|
||||||
|
cnvBtoN = convFactory(math)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
assert.strictEqual(cnvBtoN(true), 1)
|
assert.strictEqual(cnvBtoN(true), 1)
|
||||||
assert.strictEqual(cnvBtoN(false), 0)
|
assert.strictEqual(cnvBtoN(false), 0)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue