/* A Dispatcher is a collection of operations that do run-time * dispatch on the types of their arguments. Thus, every individual * method is like a typed-function (from the library by that name), * but they can depend on one another and on ona another's implementations * for specific types (including their own). */ import {parseReflectedType, ImplementationDef} from './parseReflectedType.js' import type {ValueIntersectionByKeyUnion} from '../interfaces/type.js' // First helper types and functions for the Dispatcher type TypeName = string type Parameter = TypeName type Signature = Parameter[] type DependenciesType = Record // A "canned" dependency for a builtin function: export type typeOfDependency = {typeOf: (x: unknown) => TypeName} // Utility needed in type definitions //dummy implementation for now export function joinTypes(a: TypeName, b: TypeName) { if (a === b) return a return 'any' } // Now types used in the Dispatcher class itself type TypeSpecification = { name: string, // just until we get reflection, then we can remove this property before?: TypeName[], test: ((x: unknown) => boolean) | ((d: DependenciesType) => (x: unknown) => boolean), from: Record, infer?: (d: DependenciesType) => (z: unknown) => TypeName } type Callable = (...args: any) => any type SpecObject = Record type SpecificationsGroup = Record // Find all of the keys of an object that are functions but not aliases // to another operation type BaseOperations = {[K in keyof Obj]: Obj[K] extends Callable ? ReturnType['aliasOf'] extends (string | undefined) ? never : K extends string ? K : never : never }[keyof Obj] // Gather all of the operations specified in a SpecificationsGroup type DispatcherOperations = {[K in keyof G]: BaseOperations}[keyof G] // Get the type of a given operation in a SpecObject: type BaseOperationSignature< Obj extends SpecObject, Name extends BaseOperations > = ValueIntersectionByKeyUnion< {[K in keyof Obj]: Obj[K] extends Callable ? K extends Name ? ReturnType : ReturnType['aliasOf'] extends (Name | undefined) ? ReturnType : unknown : unknown }, keyof Obj> // Get the type of a given operation in a SpecificationsGroup type OperationSignature< G extends SpecificationsGroup, Name extends DispatcherOperations > = ValueIntersectionByKeyUnion< {[K in keyof G]: Name extends BaseOperations ? BaseOperationSignature : unknown}, keyof G> // Put it all together into the typing of the dispatcher type DispatcherInterface = {[Op in DispatcherOperations]: OperationSignature} export function createDispatcher( collection: G): DispatcherInterface { const implementations = [] const types = {} // who knows what the type of this will be const behaviors = {} // ditto for (const key in collection) { console.log('Working on', key) for (const identifier in collection[key]) { const item = collection[key][identifier] if (typeof item === 'function') { implementations.push([key, identifier, item]) } else { console.log('Handling type', key, ':', identifier) installType( // In this design, installType would modify // types by side effect; maybe Jos prefers some other // factoring of this? types, item.name, collection[key][identifier] as TypeSpecification) } } } for (const trio of implementations) { const [k, id, imp] = trio console.log('Handling implementation', id, 'from', k) installSpecification( behaviors, id, parseReflectedType(id, imp.reflectedType), imp) } return {} as DispatcherInterface } function installType( types: Object, name: TypeName, typespec: TypeSpecification) { console.log('Pretending to install type', name, typespec) //TODO: implement me } function installSpecification( behaviors: Object, // Same issue as mentioned above with side effects... name: string, defn: ImplementationDef, specification: Function // possible todo: constrain this type based // on the signature, return type, and dependencies. Not sure if // that's really possible, though. ) { console.log('Pretending to install', name, 'with signatures') for (const signature of defn.fn.signatures) { console.log(' ', signature.args, '=>', signature.returns) } if (defn.fn.aliasOf) { console.log(' As an alias of', defn.fn.aliasOf) } //TODO: implement me }