feat: Runtime type reflection #17
@ -47,13 +47,19 @@ export class Dispatcher {
|
||||
// that's really possible, though.
|
||||
) {
|
||||
console.log('Pretending to install', name, signature, '=>', returns)
|
||||
// @ts-ignore
|
||||
if (behavior.reflectedType) {
|
||||
// @ts-ignore
|
||||
console.log(' Reflected type:', behavior.reflectedType)
|
||||
// TODO: parse the reflected type
|
||||
}
|
||||
//TODO: implement me
|
||||
}
|
||||
installType(name: TypeName, typespec: TypeSpecification) {
|
||||
console.log('Pretending to install type', name, typespec)
|
||||
//TODO: implement me
|
||||
}
|
||||
constructor(collection: SpecificationsGroup) {
|
||||
constructor(collection: SpecificationsGroup) {
|
||||
const implementations = []
|
||||
for (const key in collection) {
|
||||
console.log('Working on', key)
|
||||
|
232
src/core/parseReflectedType.ts
Normal file
232
src/core/parseReflectedType.ts
Normal file
@ -0,0 +1,232 @@
|
||||
export interface FunctionDef {
|
||||
name: string,
|
||||
signatures: Array<{
|
||||
args: Array<{ name: string, type: string }>
|
||||
// FIXME: remove undefined in the end
|
||||
returns: string | undefined
|
||||
}>
|
||||
}
|
||||
|
||||
export interface DependencyDef extends FunctionDef {
|
||||
aliasOf: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a reflected type coming out of TypeScript into a structured object, for example:
|
||||
*
|
||||
* '<T>(dep: configDependency & { complex: ((re: number) => Complex<number>) | ((re: number, im: number) => Complex<number>); }) => (a: number) => number | Complex<number>'
|
||||
*/
|
||||
export function parseReflectedType(name: string, reflectedType: string) : { fn: FunctionDef, dependencies: Record<string, DependencyDef> } {
|
||||
const [factoryArgs, fnArgsBlock, returns] = split(reflectedType, '=>').map(trim)
|
||||
const args = parseArgs(fnArgsBlock)
|
||||
|
||||
const factoryArgsInner = findBlockContents(factoryArgs, '(', ')')
|
||||
const depArg = split(factoryArgsInner.innerText, ':').map(trim)[1]
|
||||
const depArgBlocks = split(depArg, '&').map(trim)
|
||||
|
||||
const deps = depArgBlocks
|
||||
.filter(depArgBlock => {
|
||||
if (depArgBlock.startsWith('{')) {
|
||||
return true
|
||||
} else {
|
||||
console.error(`ERROR: Cannot parse dependency "${depArgBlock}"`)
|
||||
}
|
||||
})
|
||||
.flatMap(parseDependencies)
|
||||
|
||||
const dependencies: Record<string, DependencyDef> = groupBy(deps, 'name')
|
||||
|
||||
return {
|
||||
fn: { name, signatures: [{args, returns}] },
|
||||
dependencies
|
||||
}
|
||||
|
||||
function parseDependencies(deps: string) {
|
||||
const inner = findBlockContents(deps, '{', '}').innerText
|
||||
return split(inner, ';')
|
||||
.map(trim)
|
||||
.filter(notEmpty)
|
||||
.map(parseDependency)
|
||||
}
|
||||
|
||||
// parse a dependency like "complex: ((re: number) => Complex<number>) | ((re: number, im: number) => Complex<number>)"
|
||||
function parseDependency(dep: string) {
|
||||
const [name, def] = split(dep, ':').map(trim)
|
||||
|
||||
const { aliasOf, innerSignature } = parseAliasOf(def)
|
||||
|
||||
const signatures = split(innerSignature, '|')
|
||||
.map(trim)
|
||||
.map(stripParenthesis)
|
||||
.map(signature => {
|
||||
const [argsBlock, returns] = split(signature, '=>').map(trim)
|
||||
|
||||
if (!returns) {
|
||||
console.warn(`ERROR: failed to find return type in '${signature}'`)
|
||||
}
|
||||
|
||||
return {
|
||||
args: parseArgs(argsBlock),
|
||||
returns
|
||||
}
|
||||
})
|
||||
|
||||
return { name, signatures, aliasOf }
|
||||
}
|
||||
|
||||
// parse args like "(re: number, im: number)"
|
||||
function parseArgs(argsBlock: string) : Array<{name: string, type: string}> {
|
||||
const args = findBlockContents(argsBlock, '(', ')').innerText
|
||||
|
||||
return split(args, ',')
|
||||
.map(trim)
|
||||
.map(arg => {
|
||||
const [name, type] = split(arg, ':').map(trim)
|
||||
return { name, type}
|
||||
})
|
||||
}
|
||||
|
||||
// parse "AliasOf<"divide", (a: Complex<T>, b: RealType<Complex<T>>) => Complex<T>>"
|
||||
function parseAliasOf(signature: string) : { innerSignature: string, aliasOf: string | undefined } {
|
||||
if (!signature.startsWith('AliasOf')) {
|
||||
return {
|
||||
innerSignature: signature,
|
||||
aliasOf: undefined
|
||||
}
|
||||
}
|
||||
|
||||
const inner = findBlockContents(signature, '<', '>').innerText.trim()
|
||||
const [aliasOfWithQuotes, innerSignature] = split(inner, ',').map(trim)
|
||||
return {
|
||||
innerSignature,
|
||||
aliasOf: aliasOfWithQuotes.substring(1, aliasOfWithQuotes.length - 1) // remove double quotes
|
||||
}
|
||||
}
|
||||
|
||||
// remove the outer parenthesis, for example "((re: number) => Complex<number>)" returns "(re: number) => Complex<number>"
|
||||
function stripParenthesis(text: string) : string {
|
||||
return text.startsWith('(') && text.endsWith(')')
|
||||
? text.substring(1, text.length - 1)
|
||||
: text
|
||||
}
|
||||
}
|
||||
|
||||
function findBlockContents(text: string, blockStart: string, blockEnd: string, startIndex = 0) : { start: number, end: number, innerText: string } | undefined {
|
||||
let i = startIndex
|
||||
|
||||
while (!matchSubString(text, blockStart, i) && i < text.length) {
|
||||
i++
|
||||
}
|
||||
|
||||
if (i >= text.length) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
i++
|
||||
const start = i
|
||||
|
||||
while (!matchSubString(text, blockEnd, i) || matchSubString(text, '=>', i - 1)) {
|
||||
i = skipBrackets(text, i)
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
if (i >= text.length) {
|
||||
return undefined
|
||||
}
|
||||
const end = i
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
innerText: text.substring(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a string by a delimiter, but ignore all occurrences of the delimiter
|
||||
* that are inside bracket pairs <> () [] {}
|
||||
*/
|
||||
export function split(text: string, delimiter: string) : string[] {
|
||||
const parts: string[] = []
|
||||
|
||||
let i = 0
|
||||
let start = 0
|
||||
while (i < text.length) {
|
||||
i = skipBrackets(text, i)
|
||||
|
||||
if (matchSubString(text, delimiter, i)) {
|
||||
parts.push(text.substring(start, i))
|
||||
i += delimiter.length
|
||||
start = i
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
parts.push(text.substring(start))
|
||||
|
||||
return parts
|
||||
}
|
||||
|
||||
function skipBrackets(text: string, startIndex: number) : number {
|
||||
let level = 0
|
||||
let i = startIndex
|
||||
|
||||
do {
|
||||
if (isBracketOpen(text, i)) {
|
||||
level++
|
||||
}
|
||||
|
||||
if (isBracketClose(text, i) && level > 0) {
|
||||
level--
|
||||
}
|
||||
|
||||
if (level === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
i++
|
||||
} while(i < text.length)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
function isBracketOpen(text: string, index: number) {
|
||||
const char = text[index]
|
||||
return char === '(' || char === '<' || char === '[' || char === '{'
|
||||
}
|
||||
|
||||
function isBracketClose(text: string, index: number) {
|
||||
const char = text[index]
|
||||
// we need to take care of not matching the ">" of the operator "=>"
|
||||
return char === ')' || (char === '>' && text[index - 1] !== '=') || char === ']' || char === '}'
|
||||
}
|
||||
|
||||
function matchSubString(text: string, search: string, index: number) : boolean {
|
||||
for (let i = 0; i < search.length; i++) {
|
||||
if (text[i + index] !== search[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function trim(text: string) : string {
|
||||
return text.trim()
|
||||
}
|
||||
|
||||
function notEmpty(text: string) : boolean {
|
||||
return text.length > 0
|
||||
}
|
||||
|
||||
function groupBy<T>(items: T[], key: string) : Record<string, T> {
|
||||
const obj: Record<string, T> = {}
|
||||
|
||||
items.forEach((item) => {
|
||||
obj[item[key]] = item
|
||||
})
|
||||
|
||||
return obj
|
||||
}
|
26
src/index.ts
26
src/index.ts
@ -1,3 +1,4 @@
|
||||
import { inspect} from 'node:util'
|
||||
import {Dispatcher} from './core/Dispatcher.js'
|
||||
import * as Specifications from './all.js'
|
||||
|
||||
@ -5,6 +6,7 @@ export default new Dispatcher(Specifications)
|
||||
|
||||
import {Complex} from './Complex/type.js'
|
||||
import {absquare as absquare_complex} from './Complex/arithmetic.js'
|
||||
import { parseReflectedType, split } from './core/parseReflectedType.js'
|
||||
|
||||
const mockRealAdd = (a: number, b: number) => a+b
|
||||
const mockComplexAbsquare = (z: Complex<number>) => z.re*z.re + z.im*z.im
|
||||
@ -31,7 +33,23 @@ const sqrt = Specifications.numbers.sqrt({
|
||||
console.log('Result of sqrt(16)=', sqrt(16))
|
||||
console.log('Result of sqrt(-4)=', sqrt(-4))
|
||||
|
||||
// Check type of the generic square implementation
|
||||
console.log('Type of sqrt (number) is', Specifications.numbers.sqrt.reflectedType)
|
||||
console.log('Type of square is', Specifications.generic.square.reflectedType)
|
||||
console.log('Type of complex square root is', Specifications.complex.sqrt.reflectedType)
|
||||
console.log()
|
||||
console.log('1) NUMBER SQRT')
|
||||
console.log('1.1) REFLECTED TYPE:', Specifications.numbers.sqrt.reflectedType)
|
||||
console.log('1.2) PARSED TYPE:', inspect(parseReflectedType('sqrt', Specifications.numbers.sqrt.reflectedType), { depth: null, colors: true }))
|
||||
|
||||
console.log()
|
||||
console.log('2) GENERIC SQUARE')
|
||||
console.log('2.1) REFLECTED TYPE:', Specifications.generic.square.reflectedType)
|
||||
console.log('2.2) PARSED TYPE:', inspect(parseReflectedType('square', Specifications.generic.square.reflectedType), { depth: null, colors: true }))
|
||||
|
||||
console.log()
|
||||
console.log('3) COMPLEX SQRT')
|
||||
console.log('3.1) REFLECTED TYPE:', Specifications.complex.sqrt.reflectedType)
|
||||
console.log('3.2) PARSED TYPE:', inspect(parseReflectedType('sqrt', Specifications.complex.sqrt.reflectedType), { depth: null, colors: true }))
|
||||
|
||||
// FIXME: cleanup
|
||||
// console.log()
|
||||
// console.log('split', split('hello**world**how**are**you', '**'))
|
||||
// console.log('split', split('hello(test**world)**how**are**you', '**'))
|
||||
// console.log('split', split('<T>(dep: { multiply: (a: T, b: T) => T; }) => (z: T) => T', '=>'))
|
||||
|
Loading…
Reference in New Issue
Block a user