test: Add tests for all existing functionality, correcting issues (#9)
All checks were successful
/ test (push) Successful in 11s
All checks were successful
/ test (push) Successful in 11s
Resolves #8. Reviewed-on: #9 Co-authored-by: Glen Whitney <glen@studioinfinity.org> Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
parent
64f99c0f2d
commit
1a6890f458
12 changed files with 298 additions and 24 deletions
|
@ -1,9 +1,4 @@
|
||||||
import {Returns} from './helpers.js'
|
const typeObject = {} // have to make sure there is only one
|
||||||
|
|
||||||
// Order matters here, because these items will merged into a new
|
|
||||||
// dispatcher in the order they are listed
|
|
||||||
|
|
||||||
const typeObject = {}
|
|
||||||
|
|
||||||
export const types = () => typeObject
|
export const types = () => typeObject
|
||||||
|
|
||||||
|
@ -16,11 +11,20 @@ export class Type {
|
||||||
export const Undefined = new Type(t => typeof t === 'undefined')
|
export const Undefined = new Type(t => typeof t === 'undefined')
|
||||||
export const TypeOfTypes = new Type(t => t instanceof Type)
|
export const TypeOfTypes = new Type(t => t instanceof Type)
|
||||||
|
|
||||||
|
export const Returns = (type, f) => (f.returns = type, f)
|
||||||
|
|
||||||
export const typeOf = Returns(TypeOfTypes, item => {
|
export const typeOf = Returns(TypeOfTypes, item => {
|
||||||
for (const type of Object.values(typeObject)) {
|
for (const type of Object.values(typeObject)) {
|
||||||
if (!(type instanceof Type)) continue
|
if (!(type instanceof Type)) continue
|
||||||
if (type.test(item)) return type
|
if (type.test(item)) return type
|
||||||
}
|
}
|
||||||
|
let errorMsg = ''
|
||||||
|
try {
|
||||||
|
errorMsg = `no known type for '${item}'` // fails for Symbols, e.g.
|
||||||
|
} catch {
|
||||||
|
errorMsg = `no known type for '${item.toString()}'`
|
||||||
|
}
|
||||||
|
throw new TypeError(errorMsg)
|
||||||
})
|
})
|
||||||
|
|
||||||
// bootstrapping order matters, but order of exports in a module isn't
|
// bootstrapping order matters, but order of exports in a module isn't
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import {typeOf, Type, bootstrapTypes} from './Type.js'
|
import {typeOf, Type, bootstrapTypes} from './Type.js'
|
||||||
|
|
||||||
import {Implementations, isPlainObject, onType} from './helpers.js'
|
import {
|
||||||
|
Implementations, isPlainFunction, isPlainObject, onType
|
||||||
|
} from './helpers.js'
|
||||||
import {alwaysMatches, Any, Multiple} from './TypePatterns.js'
|
import {alwaysMatches, Any, Multiple} from './TypePatterns.js'
|
||||||
|
|
||||||
// helper that organizes a list into the same chunks as a pattern is
|
// helper that organizes a list into the same chunks as a pattern is
|
||||||
|
@ -64,7 +66,7 @@ export class TypeDispatcher {
|
||||||
}
|
}
|
||||||
// Redefine the property according to what sort of
|
// Redefine the property according to what sort of
|
||||||
// entity it is:
|
// entity it is:
|
||||||
if (typeof tryValue === 'function') {
|
if (isPlainFunction(tryValue)) {
|
||||||
// the usual case: a method of the dispatcher
|
// the usual case: a method of the dispatcher
|
||||||
const standard = (...args) => {
|
const standard = (...args) => {
|
||||||
const types = args.map(typeOf)
|
const types = args.map(typeOf)
|
||||||
|
@ -161,7 +163,7 @@ export class TypeDispatcher {
|
||||||
if (imps.length) {
|
if (imps.length) {
|
||||||
for ([pattern, item] of imps) {
|
for ([pattern, item] of imps) {
|
||||||
let finalIndex
|
let finalIndex
|
||||||
;[finalIndex, template] = pattern.match(types, 0)
|
;[finalIndex, template] = pattern.match(types)
|
||||||
if (finalIndex === types.length) {
|
if (finalIndex === types.length) {
|
||||||
needItem = false
|
needItem = false
|
||||||
break
|
break
|
||||||
|
@ -176,7 +178,7 @@ export class TypeDispatcher {
|
||||||
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 (typeof item !== 'function' || 'returns' in item) {
|
if (!isPlainFunction(item) || 'returns' in item) {
|
||||||
behave.set(types, item)
|
behave.set(types, item)
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
@ -186,8 +188,17 @@ export class TypeDispatcher {
|
||||||
this.resolve._genDepsOf = []
|
this.resolve._genDepsOf = []
|
||||||
}
|
}
|
||||||
this.resolve._genDepsOf.push([key, types])
|
this.resolve._genDepsOf.push([key, types])
|
||||||
let theBehavior = item(DependencyRecorder(this, [], this), pattern)
|
let theBehavior = () => undefined
|
||||||
this.resolve._genDepsOf.pop()
|
try {
|
||||||
|
theBehavior = item(DependencyRecorder(this, [], this), template)
|
||||||
|
} catch {
|
||||||
|
// Didn't work as a factory, so guess we were wrong; just
|
||||||
|
// make this entity the behavior:
|
||||||
|
behave.set(types, item)
|
||||||
|
return item
|
||||||
|
} finally {
|
||||||
|
this.resolve._genDepsOf.pop()
|
||||||
|
}
|
||||||
if (typeof theBehavior === 'function'
|
if (typeof theBehavior === 'function'
|
||||||
&& theBehavior.length
|
&& theBehavior.length
|
||||||
&& template.some(elt => Array.isArray(elt))
|
&& template.some(elt => Array.isArray(elt))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Type, Undefined} from './Type.js'
|
import {Type, Undefined} from './Type.js'
|
||||||
|
|
||||||
class TypePattern {
|
export class TypePattern {
|
||||||
match(typeSequence, position) {
|
match(typeSequence, position = 0) {
|
||||||
throw new Error('Specific TypePatterns must implement match')
|
throw new Error('Specific TypePatterns must implement match')
|
||||||
}
|
}
|
||||||
sampleTypes() {
|
sampleTypes() {
|
||||||
|
@ -15,7 +15,7 @@ class MatchTypePattern extends TypePattern {
|
||||||
super()
|
super()
|
||||||
this.type = typeToMatch
|
this.type = typeToMatch
|
||||||
}
|
}
|
||||||
match(typeSequence, position) {
|
match(typeSequence, position = 0) {
|
||||||
if (position < typeSequence.length
|
if (position < typeSequence.length
|
||||||
&& typeSequence[position] === this.type
|
&& typeSequence[position] === this.type
|
||||||
) {
|
) {
|
||||||
|
@ -32,7 +32,7 @@ class SequencePattern extends TypePattern {
|
||||||
super()
|
super()
|
||||||
this.patterns = itemsToMatch.map(pattern)
|
this.patterns = itemsToMatch.map(pattern)
|
||||||
}
|
}
|
||||||
match(typeSequence, position) {
|
match(typeSequence, position = 0) {
|
||||||
const matches = []
|
const matches = []
|
||||||
for (const pat of this.patterns) {
|
for (const pat of this.patterns) {
|
||||||
const [newPos, newMatch] = pat.match(typeSequence, position)
|
const [newPos, newMatch] = pat.match(typeSequence, position)
|
||||||
|
@ -62,7 +62,7 @@ export const pattern = patternOrSpec => {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnyPattern extends TypePattern {
|
class AnyPattern extends TypePattern {
|
||||||
match(typeSequence, position) {
|
match(typeSequence, position = 0) {
|
||||||
return position < typeSequence.length
|
return position < typeSequence.length
|
||||||
? [position + 1, typeSequence[position]]
|
? [position + 1, typeSequence[position]]
|
||||||
: [-1, Undefined]
|
: [-1, Undefined]
|
||||||
|
@ -74,9 +74,10 @@ export const Any = new AnyPattern()
|
||||||
|
|
||||||
class OptionalPattern extends TypePattern {
|
class OptionalPattern extends TypePattern {
|
||||||
constructor(item) {
|
constructor(item) {
|
||||||
|
super()
|
||||||
this.pattern = pattern(item)
|
this.pattern = pattern(item)
|
||||||
}
|
}
|
||||||
match(typeSequence, position) {
|
match(typeSequence, position = 0) {
|
||||||
const matches = []
|
const matches = []
|
||||||
const [newPos, newMatch] = this.pattern.match(typeSequence, position)
|
const [newPos, newMatch] = this.pattern.match(typeSequence, position)
|
||||||
if (newPos >= 0) {
|
if (newPos >= 0) {
|
||||||
|
@ -98,7 +99,7 @@ class MultiPattern extends TypePattern {
|
||||||
super()
|
super()
|
||||||
this.pattern = pattern(item)
|
this.pattern = pattern(item)
|
||||||
}
|
}
|
||||||
match(typeSequence, position) {
|
match(typeSequence, position = 0) {
|
||||||
const matches = []
|
const matches = []
|
||||||
while (true) {
|
while (true) {
|
||||||
const [newPos, newMatch] = this.pattern.match(typeSequence, position)
|
const [newPos, newMatch] = this.pattern.match(typeSequence, position)
|
||||||
|
|
43
src/core/__test__/Type.spec.js
Normal file
43
src/core/__test__/Type.spec.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import math from '#nanomath'
|
||||||
|
import {Number} from '#number/Number.js'
|
||||||
|
|
||||||
|
import {Returns} from '../Type.js'
|
||||||
|
import {isPlainFunction} from '../helpers.js'
|
||||||
|
|
||||||
|
describe('Core types', () => {
|
||||||
|
it('creates an object with all of the types', () => {
|
||||||
|
assert('Number' in math.types)
|
||||||
|
assert.strictEqual(math.types.Number, Number)
|
||||||
|
assert('Undefined' in math.types)
|
||||||
|
assert(!('Type' in math.types))
|
||||||
|
assert(math.types.Undefined.test(undefined))
|
||||||
|
assert(!math.types.Undefined.test(3))
|
||||||
|
assert(math.types.TypeOfTypes.test(math.types.Undefined))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports a typeOf operator', () => {
|
||||||
|
const tO = math.typeOf
|
||||||
|
assert.strictEqual(tO(7), Number)
|
||||||
|
assert.strictEqual(tO(undefined), math.types.Undefined)
|
||||||
|
assert.strictEqual(tO(Number), math.types.TypeOfTypes)
|
||||||
|
assert.throws(() => tO(Symbol()), TypeError)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('lets you create a new Type', () => {
|
||||||
|
const Foo = new math.Type(
|
||||||
|
item => item && typeof item === 'object' && 'bar' in item)
|
||||||
|
assert(Foo.test({bar: 'baz'}))
|
||||||
|
math.merge({Foo})
|
||||||
|
assert.strictEqual(math.types.Foo, Foo)
|
||||||
|
assert.strictEqual(math.typeOf({gold: 7, bar: 3}), Foo)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('provides a return-value labeling', () => {
|
||||||
|
const labeledF = Returns(math.types.Undefined, () => undefined)
|
||||||
|
assert.strictEqual(typeof labeledF, 'function')
|
||||||
|
assert.strictEqual(labeledF.returns, math.types.Undefined)
|
||||||
|
assert(isPlainFunction(labeledF))
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
41
src/core/__test__/TypeDispatcher.spec.js
Normal file
41
src/core/__test__/TypeDispatcher.spec.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import {TypeDispatcher} from '../TypeDispatcher.js'
|
||||||
|
import {numbers} from '../../numbers.js'
|
||||||
|
import {onType} from "#core/helpers.js"
|
||||||
|
import {Any} from "#core/TypePatterns.js"
|
||||||
|
import {Returns} from "#core/Type.js"
|
||||||
|
|
||||||
|
describe('TypeDispatcher', () => {
|
||||||
|
it('can build in stages', () => {
|
||||||
|
const incremental = new TypeDispatcher()
|
||||||
|
assert.strictEqual(
|
||||||
|
incremental.typeOf(undefined), incremental.types.Undefined)
|
||||||
|
incremental.merge(numbers)
|
||||||
|
const {Number, TypeOfTypes, Undefined} = incremental.types
|
||||||
|
assert(Number.test(7))
|
||||||
|
assert.strictEqual(incremental.add(-1.5, 0.5), -1)
|
||||||
|
// Make Undefined act like zero:
|
||||||
|
incremental.merge({add: onType(
|
||||||
|
[Undefined, Any], (_m, [_U, T]) => Returns(T, (_a, b) => b),
|
||||||
|
[Any, Undefined], (_m, [T]) => Returns(T, a => a)
|
||||||
|
)})
|
||||||
|
assert.strictEqual(incremental.add(7, undefined), 7)
|
||||||
|
assert.strictEqual(
|
||||||
|
incremental.resolve('add', [TypeOfTypes, Undefined]).returns,
|
||||||
|
TypeOfTypes)
|
||||||
|
assert.strictEqual(incremental.add(undefined, -3.25), -3.25)
|
||||||
|
assert.strictEqual(
|
||||||
|
incremental.add.resolve([Undefined, Number]).returns,
|
||||||
|
Number)
|
||||||
|
// Oops, changed my mind, make it work like NaN with numbers:
|
||||||
|
const alwaysNaN = Returns(Number, () => NaN)
|
||||||
|
incremental.merge({add: onType(
|
||||||
|
[Undefined, Number], alwaysNaN,
|
||||||
|
[Number, Undefined], alwaysNaN
|
||||||
|
)})
|
||||||
|
assert(isNaN(incremental.add(undefined, -3.25)))
|
||||||
|
assert.strictEqual(
|
||||||
|
incremental.add.resolve([Undefined, Number]).returns,
|
||||||
|
Number)
|
||||||
|
})
|
||||||
|
})
|
96
src/core/__test__/TypePatterns.spec.js
Normal file
96
src/core/__test__/TypePatterns.spec.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import {pattern, Any, Multiple, Optional} from '../TypePatterns.js'
|
||||||
|
import {Undefined, TypeOfTypes} from '../Type.js'
|
||||||
|
|
||||||
|
describe('Type patterns', () => {
|
||||||
|
it('makes patterns from types and sequences thereof', () => {
|
||||||
|
const undef1 = pattern(Undefined)
|
||||||
|
assert.deepStrictEqual(undef1.match([Undefined]), [1, Undefined])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
undef1.match([Undefined, Undefined]), [1, Undefined])
|
||||||
|
assert.deepStrictEqual(undef1.match([TypeOfTypes]), [-1, Undefined])
|
||||||
|
assert.deepStrictEqual(undef1.match([]), [-1, Undefined])
|
||||||
|
assert.deepStrictEqual(undef1.sampleTypes(), [Undefined])
|
||||||
|
assert(undef1.equal(pattern(Undefined)))
|
||||||
|
assert(!undef1.equal(pattern(TypeOfTypes)))
|
||||||
|
const tu2 = pattern([TypeOfTypes, undef1])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
tu2.match([Undefined, TypeOfTypes]), [-1, Undefined])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
tu2.match([TypeOfTypes, Undefined]), [2, [TypeOfTypes, Undefined]])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows Any as a wildcard', () => {
|
||||||
|
const midWild = pattern([Undefined, Any, TypeOfTypes])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midWild.match([Undefined, Undefined, TypeOfTypes]),
|
||||||
|
[3, [Undefined, Undefined, TypeOfTypes]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midWild.match([Undefined, TypeOfTypes, TypeOfTypes]),
|
||||||
|
[3, [Undefined, TypeOfTypes, TypeOfTypes]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midWild.match([Undefined, TypeOfTypes, Undefined]),
|
||||||
|
[-1, Undefined])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midWild.match([Undefined, TypeOfTypes]),
|
||||||
|
[-1, Undefined])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midWild.sampleTypes(), [Undefined, Undefined, TypeOfTypes])
|
||||||
|
assert(midWild.equal(midWild))
|
||||||
|
assert(!midWild.equal(pattern([Any, Any, TypeOfTypes])))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("supports greedy multiple and optional parameters", () => {
|
||||||
|
const midOpt = pattern([Undefined, Optional(TypeOfTypes), Undefined])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midOpt.match([Undefined, Undefined]), [2, [Undefined, [], Undefined]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midOpt.match([Undefined, TypeOfTypes, Undefined]),
|
||||||
|
[3, [Undefined, [TypeOfTypes], Undefined]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midOpt.match([Undefined, TypeOfTypes, TypeOfTypes]),
|
||||||
|
[-1, Undefined])
|
||||||
|
assert.deepStrictEqual(midOpt.sampleTypes(), [Undefined, Undefined])
|
||||||
|
const midMulti = pattern([Undefined, Multiple(TypeOfTypes), Undefined])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midMulti.match([Undefined, Undefined]),
|
||||||
|
[2, [Undefined, [], Undefined]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midMulti.match([Undefined, TypeOfTypes, Undefined]),
|
||||||
|
[3, [Undefined, [TypeOfTypes], Undefined]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midMulti.match(
|
||||||
|
[Undefined, TypeOfTypes, TypeOfTypes, TypeOfTypes, Undefined]),
|
||||||
|
[5, [Undefined, [TypeOfTypes, TypeOfTypes, TypeOfTypes], Undefined]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
midMulti.match([Undefined, TypeOfTypes, TypeOfTypes]),
|
||||||
|
[-1, Undefined])
|
||||||
|
assert.deepStrictEqual(midOpt.sampleTypes(), [Undefined, Undefined])
|
||||||
|
assert(!midMulti.equal(midOpt))
|
||||||
|
const whyNot = pattern(
|
||||||
|
[Undefined, Multiple([Undefined, TypeOfTypes]), TypeOfTypes])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
whyNot.match([Undefined, TypeOfTypes]),
|
||||||
|
[2, [Undefined, [], TypeOfTypes]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
whyNot.match([Undefined, Undefined, TypeOfTypes, TypeOfTypes]),
|
||||||
|
[4, [Undefined, [[Undefined, TypeOfTypes]], TypeOfTypes]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
whyNot.match([
|
||||||
|
Undefined,
|
||||||
|
Undefined, TypeOfTypes, Undefined, TypeOfTypes,
|
||||||
|
TypeOfTypes
|
||||||
|
]),
|
||||||
|
[6, [
|
||||||
|
Undefined,
|
||||||
|
[[Undefined, TypeOfTypes], [Undefined, TypeOfTypes]],
|
||||||
|
TypeOfTypes
|
||||||
|
]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
whyNot.match([Undefined, TypeOfTypes, Undefined, TypeOfTypes]),
|
||||||
|
[2, [Undefined, [], TypeOfTypes]])
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
whyNot.sampleTypes(), [Undefined, TypeOfTypes])
|
||||||
|
assert(whyNot.equal(whyNot))
|
||||||
|
})
|
||||||
|
})
|
35
src/core/__test__/helpers.spec.js
Normal file
35
src/core/__test__/helpers.spec.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import {
|
||||||
|
Implementations, onType, isPlainObject, isPlainFunction
|
||||||
|
} from '../helpers.js'
|
||||||
|
import {Type, Undefined, TypeOfTypes} from '../Type.js'
|
||||||
|
import {TypePattern} from '../TypePatterns.js'
|
||||||
|
|
||||||
|
describe('Core helpers', () => {
|
||||||
|
it('defines what Implementations are', () => {
|
||||||
|
const imps = onType(Undefined, 7, [TypeOfTypes, Undefined], -3)
|
||||||
|
assert(imps instanceof Implementations)
|
||||||
|
assert(imps.patterns instanceof Map)
|
||||||
|
assert(imps.patterns.keys().every(k => k instanceof TypePattern))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('detects plain objects', () => {
|
||||||
|
assert(!isPlainObject(Undefined))
|
||||||
|
assert(!isPlainObject([2, 3, 4]))
|
||||||
|
assert(!isPlainObject('pat'))
|
||||||
|
assert(isPlainObject({plain: 'object'}))
|
||||||
|
assert(isPlainObject({}))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('detects plain functions (not constructors)', () => {
|
||||||
|
function embedded() {return 1}
|
||||||
|
class Embedded {}
|
||||||
|
assert(isPlainFunction(embedded))
|
||||||
|
assert(isPlainFunction(() => 'wow'))
|
||||||
|
assert(isPlainFunction(it))
|
||||||
|
assert(!isPlainFunction(7))
|
||||||
|
assert(!isPlainFunction(Implementations))
|
||||||
|
assert(!isPlainFunction(Type))
|
||||||
|
assert(!isPlainFunction(Embedded))
|
||||||
|
})
|
||||||
|
})
|
|
@ -12,11 +12,35 @@ export class Implementations {
|
||||||
|
|
||||||
export const onType = (...imps) => new Implementations(imps)
|
export const onType = (...imps) => new Implementations(imps)
|
||||||
|
|
||||||
export const Returns = (type, f) => (f.returns = type, f)
|
export const isPlainObject = obj => {
|
||||||
|
|
||||||
export const isPlainObject = (obj) => {
|
|
||||||
if (typeof obj !== 'object') return false
|
if (typeof obj !== 'object') return false
|
||||||
if (!obj) return false // excludes null
|
if (!obj) return false // excludes null
|
||||||
const proto = Object.getPrototypeOf(obj)
|
const proto = Object.getPrototypeOf(obj)
|
||||||
return !proto || proto === Object.prototype
|
return !proto || proto === Object.prototype
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Class entities have typeof === 'function', but mostly we do not
|
||||||
|
// want to treat them like ordinary functions, hence we need some way
|
||||||
|
// of telling. There doesn't seem to be a definitive way, so we
|
||||||
|
// use a mishmash of techniques from
|
||||||
|
// https://stackoverflow.com/questions/526559
|
||||||
|
const isClass = f => {
|
||||||
|
if (!f.prototype) return false
|
||||||
|
if (f.constructor !== Function) return false
|
||||||
|
const protoCtor = f.prototype.constructor
|
||||||
|
if (protoCtor
|
||||||
|
&& protoCtor.toString
|
||||||
|
&& protoCtor.toString().startsWith('class')
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (Object.getPrototypeOf(f) !== Function.prototype) return true
|
||||||
|
if (Object.getOwnPropertyNames(f).includes('arguments')) return false
|
||||||
|
return Object.getOwnPropertyNames(f.prototype).length > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true for functions that are not class constructors, at
|
||||||
|
// least so far as we can tell:
|
||||||
|
export const isPlainFunction = f => {
|
||||||
|
return typeof f === 'function' && !isClass(f)
|
||||||
|
}
|
||||||
|
|
8
src/number/__test__/type.spec.js
Normal file
8
src/number/__test__/type.spec.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import math from '#nanomath'
|
||||||
|
|
||||||
|
describe('number type operations', () => {
|
||||||
|
it('converts to number', () => {
|
||||||
|
assert.strictEqual(math.number(2.637), 2.637)
|
||||||
|
})
|
||||||
|
})
|
8
src/number/__test__/utils.spec.js
Normal file
8
src/number/__test__/utils.spec.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import math from '#nanomath'
|
||||||
|
|
||||||
|
describe('number utilities', () => {
|
||||||
|
it('clones a number', () => {
|
||||||
|
assert.strictEqual(math.clone(2.637), 2.637)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,5 +1,7 @@
|
||||||
import {Returns, onType} from '#core/helpers.js'
|
|
||||||
import {Number} from './Number.js'
|
import {Number} from './Number.js'
|
||||||
|
|
||||||
|
import {onType} from '#core/helpers.js'
|
||||||
|
import {Returns} from '#core/Type.js'
|
||||||
|
|
||||||
export const plain = f => onType(
|
export const plain = f => onType(
|
||||||
Array(f.length).fill(Number), Returns(Number, f))
|
Array(f.length).fill(Number), Returns(Number, f))
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"imports" : {
|
"imports" : {
|
||||||
"#nanomath": "./nanomath.js",
|
"#nanomath": "./nanomath.js",
|
||||||
"#core/*.js": "./core/*.js"
|
"#core/*.js": "./core/*.js",
|
||||||
|
"#number/*.js": "./number/*.js"
|
||||||
},
|
},
|
||||||
"type" : "module"
|
"type" : "module"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue