feate: implement a basic parser for the reflected types
This commit is contained in:
parent
fd4ecd70ba
commit
1ca0ac42d0
@ -47,13 +47,19 @@ export class Dispatcher {
|
|||||||
// that's really possible, though.
|
// that's really possible, though.
|
||||||
) {
|
) {
|
||||||
console.log('Pretending to install', name, signature, '=>', returns)
|
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
|
//TODO: implement me
|
||||||
}
|
}
|
||||||
installType(name: TypeName, typespec: TypeSpecification) {
|
installType(name: TypeName, typespec: TypeSpecification) {
|
||||||
console.log('Pretending to install type', name, typespec)
|
console.log('Pretending to install type', name, typespec)
|
||||||
//TODO: implement me
|
//TODO: implement me
|
||||||
}
|
}
|
||||||
constructor(collection: SpecificationsGroup) {
|
constructor(collection: SpecificationsGroup) {
|
||||||
const implementations = []
|
const implementations = []
|
||||||
for (const key in collection) {
|
for (const key in collection) {
|
||||||
console.log('Working on', key)
|
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 {Dispatcher} from './core/Dispatcher.js'
|
||||||
import * as Specifications from './all.js'
|
import * as Specifications from './all.js'
|
||||||
|
|
||||||
@ -5,6 +6,7 @@ export default new Dispatcher(Specifications)
|
|||||||
|
|
||||||
import {Complex} from './Complex/type.js'
|
import {Complex} from './Complex/type.js'
|
||||||
import {absquare as absquare_complex} from './Complex/arithmetic.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 mockRealAdd = (a: number, b: number) => a+b
|
||||||
const mockComplexAbsquare = (z: Complex<number>) => z.re*z.re + z.im*z.im
|
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(16)=', sqrt(16))
|
||||||
console.log('Result of sqrt(-4)=', sqrt(-4))
|
console.log('Result of sqrt(-4)=', sqrt(-4))
|
||||||
|
|
||||||
// Check type of the generic square implementation
|
console.log()
|
||||||
console.log('Type of sqrt (number) is', Specifications.numbers.sqrt.reflectedType)
|
console.log('1) NUMBER SQRT')
|
||||||
console.log('Type of square is', Specifications.generic.square.reflectedType)
|
console.log('1.1) REFLECTED TYPE:', Specifications.numbers.sqrt.reflectedType)
|
||||||
console.log('Type of complex square root is', Specifications.complex.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