typocomath/src/core/Dispatcher.ts

139 lines
4.8 KiB
TypeScript

/* 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<string, Function>
// 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<TypeName, Function>,
infer?: (d: DependenciesType) => (z: unknown) => TypeName
}
type Callable = (...args: any) => any
type SpecObject = Record<string, Callable | TypeSpecification>
type SpecificationsGroup = Record<string, SpecObject>
// Find all of the keys of an object that are functions but not aliases
// to another operation
type BaseOperations<Obj extends SpecObject> =
{[K in keyof Obj]:
Obj[K] extends Callable
? ReturnType<Obj[K]>['aliasOf'] extends (string | undefined)
? never
: K extends string ? K : never
: never
}[keyof Obj]
// Gather all of the operations specified in a SpecificationsGroup
type DispatcherOperations<G extends SpecificationsGroup> =
{[K in keyof G]: BaseOperations<G[K]>}[keyof G]
// Get the type of a given operation in a SpecObject:
type BaseOperationSignature<
Obj extends SpecObject,
Name extends BaseOperations<Obj>
> = ValueIntersectionByKeyUnion<
{[K in keyof Obj]:
Obj[K] extends Callable
? K extends Name
? ReturnType<Obj[K]>
: ReturnType<Obj[K]>['aliasOf'] extends (Name | undefined)
? ReturnType<Obj[K]> : unknown
: unknown
},
keyof Obj>
// Get the type of a given operation in a SpecificationsGroup
type OperationSignature<
G extends SpecificationsGroup,
Name extends DispatcherOperations<G>
> = ValueIntersectionByKeyUnion<
{[K in keyof G]:
Name extends BaseOperations<G[K]>
? BaseOperationSignature<G[K], Name>
: unknown},
keyof G>
// Put it all together into the typing of the dispatcher
type DispatcherInterface<G extends SpecificationsGroup> =
{[Op in DispatcherOperations<G>]: OperationSignature<G, Op>}
export function createDispatcher<G extends SpecificationsGroup>(
collection: G): DispatcherInterface<G>
{
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<G>
}
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
}