diff --git a/package.json5 b/package.json5 index 97ced5b..b6575a1 100644 --- a/package.json5 +++ b/package.json5 @@ -23,13 +23,13 @@ url: 'https://code.studioinfinity.org/glen/typocomath.git', }, devDependencies: { - '@types/node': '20.6.2', + '@types/node': '^20.6.2', 'cpy-cli': '5.0.0', 'del-cli': '5.1.0', mkdirp: '3.0.1', - 'source-map': '0.7.4', + 'source-map': '^0.7.4', 'ts-macros': 'github:GoogleFeud/ts-macros', - 'ts-patch': '3.0.2', - typescript: '5.1.6', + 'ts-patch': '^3.0.2', + typescript: '^5.1.6', }, } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 948d6ca..8c865d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: devDependencies: '@types/node': - specifier: 20.6.2 + specifier: ^20.6.2 version: 20.6.2 cpy-cli: specifier: 5.0.0 @@ -18,16 +18,16 @@ devDependencies: specifier: 3.0.1 version: 3.0.1 source-map: - specifier: 0.7.4 + specifier: ^0.7.4 version: 0.7.4 ts-macros: specifier: github:GoogleFeud/ts-macros version: github.com/GoogleFeud/ts-macros/4f8c22db77e3b5840e3a2f285e30436f71c27e15(typescript@5.1.6) ts-patch: - specifier: 3.0.2 + specifier: ^3.0.2 version: 3.0.2 typescript: - specifier: 5.1.6 + specifier: ^5.1.6 version: 5.1.6 packages: diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 230a286..ec5491f 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -47,19 +47,13 @@ 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) diff --git a/src/core/parseReflectedType.ts b/src/core/parseReflectedType.ts deleted file mode 100644 index 18705fe..0000000 --- a/src/core/parseReflectedType.ts +++ /dev/null @@ -1,232 +0,0 @@ -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: - * - * '(dep: configDependency & { complex: ((re: number) => Complex) | ((re: number, im: number) => Complex); }) => (a: number) => number | Complex' - */ -export function parseReflectedType(name: string, reflectedType: string) : { fn: FunctionDef, dependencies: Record } { - 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 = 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) | ((re: number, im: number) => Complex)" - 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, b: RealType>) => Complex>" - 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)" returns "(re: number) => Complex" - 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(items: T[], key: string) : Record { - const obj: Record = {} - - items.forEach((item) => { - obj[item[key]] = item - }) - - return obj -} diff --git a/src/index.ts b/src/index.ts index 5eec64d..dc389e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -import { inspect} from 'node:util' import {Dispatcher} from './core/Dispatcher.js' import * as Specifications from './all.js' @@ -6,7 +5,6 @@ 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) => z.re*z.re + z.im*z.im @@ -33,23 +31,7 @@ const sqrt = Specifications.numbers.sqrt({ console.log('Result of sqrt(16)=', sqrt(16)) console.log('Result of sqrt(-4)=', sqrt(-4)) -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('(dep: { multiply: (a: T, b: T) => T; }) => (z: T) => T', '=>')) +// 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)