From 06909a6a5ecab92e5b025b2bc37e45f055485aa5 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 25 Oct 2024 15:27:02 +0000 Subject: [PATCH] feat: Barest outline of a Dispatcher, providing only typeOf so far (#11) Reviewed-on: https://code.studioinfinity.org/glen/math5/pulls/11 Co-authored-by: Glen Whitney Co-committed-by: Glen Whitney --- src/Complex/type.ts | 4 +- src/core/Dispatcher.ts | 132 ++++++++++++++++++++++++++++++++++++++--- src/index.ts | 11 +++- 3 files changed, 136 insertions(+), 11 deletions(-) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index a97a894..623d20b 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -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['typeOf']}) => - (z: Complex) => joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)), + (z: Complex) => ({ + T: joinTypes(dep.typeOf(z.re), dep.typeOf(z.im)) + }), from: { Complex: (dep: {convert: CommonSignature['convert']}) => (z: Complex): Complex => diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 5ae8e06..73d2aae 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -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 > = {[K in NeedKeys]: ImpType} -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} > - done(info?: ReflectedTypeInfo): Implementations & ReflectedTypeInfo + done(): Implementations } // And a function that actually provides the builder interface: @@ -191,8 +188,8 @@ function impBuilder< Specs & {[K in NewKeys]: DepCheck} > }, - done(info?: ReflectedTypeInfo) { - return { ...sofar, ...info } + done() { + return sofar } } } @@ -202,3 +199,124 @@ export function implementations( ): ImplementationBuilder { 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} +type Reflected = {_reflectedType5: any} +type TypeSpec = { + name: string, + before?: string[], + test: Callable, + from: Record, + infer?: Callable +} +type ImpItem = Implementation | Factory +type ImpGroup = Record + +// 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 {} + +type ImpHolder = { + implementations: Record, // Key is a signature + factories: Record +} + +type DispatcherInstance = { + implementationData: Record, // Key is an operation name + types: TypeSpec[], // Order is order to try types in + behaviors: Record, // 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' +} diff --git a/src/index.ts b/src/index.ts index c575454..2523518 100644 --- a/src/index.ts +++ b/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'))