feat: Barest outline of a Dispatcher, providing only typeOf so far #11
@ -14,7 +14,9 @@ export const Complex_type = {
|
|||||||
typeof z === 'object' && z != null && 're' in z && 'im' in z
|
typeof z === 'object' && z != null && 're' in z && 'im' in z
|
||||||
&& dep.testT(z.re) && dep.testT(z.im),
|
&& dep.testT(z.re) && dep.testT(z.im),
|
||||||
infer: (dep: {typeOf: CommonSignature<undefined>['typeOf']}) =>
|
infer: (dep: {typeOf: CommonSignature<undefined>['typeOf']}) =>
|
||||||
(z: Complex<unknown>) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)),
|
(z: Complex<unknown>) => ({
|
||||||
|
T: joinTypes(dep.typeOf(z.re), dep.typeOf(z.im))
|
||||||
|
}),
|
||||||
from: {
|
from: {
|
||||||
Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) =>
|
Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) =>
|
||||||
(z: Complex<U>): Complex<T> =>
|
(z: Complex<U>): Complex<T> =>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import {inspect} from 'node:util'
|
||||||
import type {AnyFunc, CommonSignature, GenSigs} from '@/interfaces/type'
|
import type {AnyFunc, CommonSignature, GenSigs} from '@/interfaces/type'
|
||||||
|
|
||||||
// A base type that roughly describes the dependencies of a single factory
|
// A base type that roughly describes the dependencies of a single factory
|
||||||
@ -102,10 +103,6 @@ type Implementations<
|
|||||||
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||||
> = {[K in NeedKeys]: ImpType<Signatures, K, Specs[K]>}
|
> = {[K in NeedKeys]: ImpType<Signatures, K, Specs[K]>}
|
||||||
|
|
||||||
export interface ReflectedTypeInfo {
|
|
||||||
reflectedType5: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// The builder interface that lets us assemble narrowly-typed Implementations:
|
// The builder interface that lets us assemble narrowly-typed Implementations:
|
||||||
interface ImplementationBuilder<
|
interface ImplementationBuilder<
|
||||||
Signatures extends GenSigs,
|
Signatures extends GenSigs,
|
||||||
@ -139,7 +136,7 @@ interface ImplementationBuilder<
|
|||||||
Specs & {[K in NewKeys]: DepCheck<RD, Signatures>}
|
Specs & {[K in NewKeys]: DepCheck<RD, Signatures>}
|
||||||
>
|
>
|
||||||
|
|
||||||
done(info?: ReflectedTypeInfo): Implementations<Signatures, NeedKeys, NeedList, Specs> & ReflectedTypeInfo
|
done(): Implementations<Signatures, NeedKeys, NeedList, Specs>
|
||||||
}
|
}
|
||||||
|
|
||||||
// And a function that actually provides the builder interface:
|
// And a function that actually provides the builder interface:
|
||||||
@ -191,8 +188,8 @@ function impBuilder<
|
|||||||
Specs & {[K in NewKeys]: DepCheck<RD, Signatures, DepKeys>}
|
Specs & {[K in NewKeys]: DepCheck<RD, Signatures, DepKeys>}
|
||||||
>
|
>
|
||||||
},
|
},
|
||||||
done(info?: ReflectedTypeInfo) {
|
done() {
|
||||||
return { ...sofar, ...info }
|
return sofar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,3 +199,124 @@ export function implementations<Signatures extends GenSigs>(
|
|||||||
): ImplementationBuilder<Signatures, never, {}, {}> {
|
): ImplementationBuilder<Signatures, never, {}, {}> {
|
||||||
return impBuilder({})
|
return impBuilder({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now we turn to creating a Dispatcher itself. For this we use loose types,
|
||||||
|
// and rely on the type annotations from our special build for runtime type
|
||||||
|
// identification.
|
||||||
|
|
||||||
|
type Callable = (...args: any) => any
|
||||||
|
type Implementation = {implementation: Callable}
|
||||||
|
type Factory = {factory: Callable, dependencies: Record<string, any>}
|
||||||
|
type Reflected = {_reflectedType5: any}
|
||||||
|
type TypeSpec = {
|
||||||
|
name: string,
|
||||||
|
before?: string[],
|
||||||
|
test: Callable,
|
||||||
|
from: Record<string, Callable>,
|
||||||
|
infer?: Callable
|
||||||
|
}
|
||||||
|
type ImpItem = Implementation | Factory
|
||||||
|
type ImpGroup = Record<string, ImpItem>
|
||||||
|
|
||||||
|
// When this is being compiled, TypeScript can't tell that the
|
||||||
|
// ImpSpecification entities will have been reflected:
|
||||||
|
type ImpSpecification = (ImpGroup | (() => ImpGroup) | TypeSpec) // & Reflected
|
||||||
|
|
||||||
|
interface ImpSpecs extends Record<string, ImpSpecification | ImpSpecs> {}
|
||||||
|
|
||||||
|
type ImpHolder = {
|
||||||
|
implementations: Record<string, Callable>, // Key is a signature
|
||||||
|
factories: Record<string, Factory>
|
||||||
|
}
|
||||||
|
|
||||||
|
type DispatcherInstance = {
|
||||||
|
implementationData: Record<string, ImpHolder>, // Key is an operation name
|
||||||
|
types: TypeSpec[], // Order is order to try types in
|
||||||
|
behaviors: Record<string, Callable>, // Key is opname; actually executable!
|
||||||
|
}
|
||||||
|
|
||||||
|
function newDispatcherInstance(): DispatcherInstance {
|
||||||
|
return {
|
||||||
|
implementationData: {},
|
||||||
|
types: [],
|
||||||
|
behaviors: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTypeSpec(spec: ImpSpecification | ImpSpecs): spec is TypeSpec {
|
||||||
|
if ('name' in spec
|
||||||
|
&& typeof spec.name === 'string'
|
||||||
|
&& 'test' in spec
|
||||||
|
&& 'from' in spec
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The assemble function creates a dispatcher from a mess of specifications
|
||||||
|
export function assemble(specifications: ImpSpecs, into?: DispatcherInstance ) {
|
||||||
|
if (into === undefined) {
|
||||||
|
into = newDispatcherInstance()
|
||||||
|
const show = inspect(specifications, {depth: 18, colors: true})
|
||||||
|
console.log('Specifications are', show)
|
||||||
|
}
|
||||||
|
for (const specName in specifications) {
|
||||||
|
console.log('Processing', specName)
|
||||||
|
const spec = specifications[specName]
|
||||||
|
if ('_reflectedType5' in spec) {
|
||||||
|
if (isTypeSpec(spec)) {
|
||||||
|
registerTypeSpec(spec, into)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// implementations that we need to deal with
|
||||||
|
console.log('Need to incorporate', Object.keys(spec))
|
||||||
|
} else {
|
||||||
|
// Just another layer of specification
|
||||||
|
assemble(spec as ImpSpecs, into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
into.behaviors.typeOf = (a: unknown) => whichType(a, into.types)
|
||||||
|
return into.behaviors
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerTypeSpec(typeSpec: TypeSpec, into: DispatcherInstance) {
|
||||||
|
let position = into.types.length
|
||||||
|
if ('before' in typeSpec) {
|
||||||
|
for (const typeName of typeSpec.before) {
|
||||||
|
const typeIndex = into.types.findIndex(t => t.name = typeName)
|
||||||
|
if (typeIndex >= 0 && typeIndex < position) position = typeIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
into.types.splice(position, 0, typeSpec)
|
||||||
|
// likely there will be more to do in the long run
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string name of the type of _a_ per the type specifications
|
||||||
|
// in _types_, or 'unknown' if no type matches
|
||||||
|
function whichType(a: unknown, types: TypeSpec[]) {
|
||||||
|
for (const typeSpec of types) {
|
||||||
|
const typeName = typeSpec.name
|
||||||
|
// First check if this is a ground type or a generic:
|
||||||
|
const typeSpecType = (typeSpec as TypeSpec & Reflected)._reflectedType5
|
||||||
|
if (!('_typeParameters' in typeSpecType.test)) {
|
||||||
|
// ground type, just test it
|
||||||
|
if (typeSpec.test(a)) return typeName
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Generic type. In this case, the test will be a factory, dependent
|
||||||
|
// on a test for each of the type parameters. So assemble those
|
||||||
|
// dependencies:
|
||||||
|
const typePars = typeSpecType.test._typeParameters
|
||||||
|
const permissiveTests = Object.fromEntries(typePars.map(k =>
|
||||||
|
[`test${k}`, x => true]))
|
||||||
|
const testAllUnknown = typeSpec.test(permissiveTests)
|
||||||
|
if (!testAllUnknown(a)) continue
|
||||||
|
// Here, a seems to be in some instantiation of this generic type.
|
||||||
|
// Need to infer which instantiation
|
||||||
|
const thisInfer = typeSpec.infer({typeOf: x => whichType(x, types)})
|
||||||
|
const typeArguments = thisInfer(a)
|
||||||
|
return `${typeName}<${typePars.map(k => typeArguments[k]).join(',')}>`
|
||||||
|
}
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
11
src/index.ts
11
src/index.ts
@ -1,5 +1,10 @@
|
|||||||
import {inspect} from 'node:util'
|
import {assemble} from '@/core/Dispatcher'
|
||||||
|
|
||||||
import * as specifications from './all'
|
import * as specifications from './all'
|
||||||
|
|
||||||
console.log(inspect(specifications, {depth: 18, colors: true}))
|
const math = assemble(specifications)
|
||||||
|
console.log('PRODUCED', math)
|
||||||
|
|
||||||
|
console.log(math.typeOf(17))
|
||||||
|
console.log(math.typeOf({re: 3.4, im: -0.1}))
|
||||||
|
console.log(math.typeOf({re: {re: 1, im: 0}, im: {re: 0, im: -1}}))
|
||||||
|
console.log(math.typeOf('no string type yet'))
|
||||||
|
Loading…
Reference in New Issue
Block a user