233 lines
5.9 KiB
TypeScript
233 lines
5.9 KiB
TypeScript
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
|
|
}
|