139 lines
4.8 KiB
TypeScript
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
|
|
}
|