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
|
||||
&& dep.testT(z.re) && dep.testT(z.im),
|
||||
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: {
|
||||
Complex: <U,T>(dep: {convert: CommonSignature<U,T>['convert']}) =>
|
||||
(z: Complex<U>): Complex<T> =>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import {inspect} from 'node:util'
|
||||
import type {AnyFunc, CommonSignature, GenSigs} from '@/interfaces/type'
|
||||
|
||||
// A base type that roughly describes the dependencies of a single factory
|
||||
@ -102,10 +103,6 @@ type Implementations<
|
||||
Specs extends Specifications<Signatures, NeedKeys, NeedList>
|
||||
> = {[K in NeedKeys]: ImpType<Signatures, K, Specs[K]>}
|
||||
|
||||
export interface ReflectedTypeInfo {
|
||||
reflectedType5: string
|
||||
}
|
||||
|
||||
// The builder interface that lets us assemble narrowly-typed Implementations:
|
||||
interface ImplementationBuilder<
|
||||
Signatures extends GenSigs,
|
||||
@ -139,7 +136,7 @@ interface ImplementationBuilder<
|
||||
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:
|
||||
@ -191,8 +188,8 @@ function impBuilder<
|
||||
Specs & {[K in NewKeys]: DepCheck<RD, Signatures, DepKeys>}
|
||||
>
|
||||
},
|
||||
done(info?: ReflectedTypeInfo) {
|
||||
return { ...sofar, ...info }
|
||||
done() {
|
||||
return sofar
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,3 +199,124 @@ export function implementations<Signatures extends GenSigs>(
|
||||
): ImplementationBuilder<Signatures, never, {}, {}> {
|
||||
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'
|
||||
|
||||
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