feate: implement a basic parser for the reflected types

This commit is contained in:
Jos de Jong 2023-09-21 21:01:38 +02:00
parent fd4ecd70ba
commit 1ca0ac42d0
3 changed files with 261 additions and 5 deletions

View File

@ -47,6 +47,12 @@ 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) {

View 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
}

View File

@ -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', '=>'))