diff --git a/package.json5 b/package.json5 index 5e1b58b..02e46f3 100644 --- a/package.json5 +++ b/package.json5 @@ -1,6 +1,6 @@ { name: 'typocomath', - version: '0.0.1', + version: '0.0.2', description: 'A hopeful final typescipt-pragmatic mathjs proof-of-concept', main: 'index.ts', scripts: { diff --git a/src/Complex/predicate.ts b/src/Complex/predicate.ts index ffcefaa..fe26906 100644 --- a/src/Complex/predicate.ts +++ b/src/Complex/predicate.ts @@ -1,9 +1,13 @@ import {Complex} from './type.js' import type {Dependencies, Signature} from '../interfaces/type.js' +import {$reflect} from '../interfaces/type.js' export const isReal = (dep: Dependencies<'add' | 'equal' | 'isReal', T>): Signature<'isReal', Complex> => z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im)) -export const isSquare: Signature<'isSquare', Complex> = z => true // FIXME: not correct for Complex once we get there +export const isSquare = (): Signature<'isSquare', Complex> => + z => true // FIXME: not correct for Complex once we get there + +$reflect!([isReal, isSquare]) diff --git a/src/Complex/relational.ts b/src/Complex/relational.ts index 78550d7..ae0cebc 100644 --- a/src/Complex/relational.ts +++ b/src/Complex/relational.ts @@ -1,6 +1,8 @@ import {Complex} from './type.js' import {Dependencies, Signature} from '../interfaces/type.js' +import {$reflect} from '../interfaces/type.js' export const equal = (dep: Dependencies<'equal', T>): Signature<'equal', Complex> => (w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im) +$reflect!([equal]) diff --git a/src/Complex/type.ts b/src/Complex/type.ts index 10f76b6..998003e 100644 --- a/src/Complex/type.ts +++ b/src/Complex/type.ts @@ -2,6 +2,7 @@ import {joinTypes, typeOfDependency} from '../core/Dispatcher.js' import type { ZeroType, OneType, NaNType, Dependencies, Signature, Returns } from '../interfaces/type.js' +import {$reflect} from '../interfaces/type.js' export type Complex = { re: T; im: T; } @@ -62,3 +63,5 @@ export const nan = export const re = (dep: Dependencies<'re', T>): Signature<'re', Complex> => z => dep.re(z.re) + +$reflect!([complex, zero, one, nan, re]) diff --git a/src/core/Dispatcher.ts b/src/core/Dispatcher.ts index 230a286..4ede7cd 100644 --- a/src/core/Dispatcher.ts +++ b/src/core/Dispatcher.ts @@ -5,6 +5,8 @@ * for specific types (including their own). */ +import {parseReflectedType, ImplementationDef} from './parseReflectedType.js' + // First helper types and functions for the Dispatcher type TypeName = string @@ -39,19 +41,17 @@ type SpecificationsGroup = Record export class Dispatcher { installSpecification( name: string, - signature: Signature, - returns: TypeName, - dependencies: Record, + defn: ImplementationDef, behavior: Function // possible todo: constrain this type based // on the signature, return type, and dependencies. Not sure if // 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 + console.log('Pretending to install', name, 'with signatures') + for (const signature of defn.fn.signatures) { + console.log(' ', signature.args, '=>', signature.returns) + } + if (defn.fn.aliasOf) { + console.log(' As an alias of', defn.fn.aliasOf) } //TODO: implement me } @@ -77,7 +77,8 @@ export class Dispatcher { for (const trio of implementations) { const [k, id, imp] = trio console.log('Handling implementation', id, 'from', k) - this.installSpecification(id, ['dunno'], 'unsure', {}, imp) + this.installSpecification( + id, parseReflectedType(id, imp.reflectedType), imp) } } } diff --git a/src/core/parseReflectedType.ts b/src/core/parseReflectedType.ts index 18705fe..e4b72a3 100644 --- a/src/core/parseReflectedType.ts +++ b/src/core/parseReflectedType.ts @@ -1,14 +1,16 @@ -export interface FunctionDef { +export type FunctionDef { name: string, + aliasOf?: string, signatures: Array<{ args: Array<{ name: string, type: string }> - // FIXME: remove undefined in the end - returns: string | undefined + returns: string }> } -export interface DependencyDef extends FunctionDef { - aliasOf: string | undefined + +export type ImplementationDef = { + fn: FunctionDef, + dependencies: Record } /** @@ -16,101 +18,116 @@ export interface DependencyDef extends FunctionDef { * * '(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) +export function parseReflectedType(name: string, reflectedType: string): ImplementationDef { + console.log('For', name, 'parsing', reflectedType) + const [factoryArgs, fnsClause] = split(reflectedType, '=>', 2).map(trim) + const fn = parseAlias(name, fnsClause) const factoryArgsInner = findBlockContents(factoryArgs, '(', ')') const depArg = split(factoryArgsInner.innerText, ':').map(trim)[1] - const depArgBlocks = split(depArg, '&').map(trim) + const depArgBlocks: string[] = depArg ? split(depArg, '&').map(trim) : [] const deps = depArgBlocks .filter(depArgBlock => { - if (depArgBlock.startsWith('{')) { + if (depArgBlock.startsWith('{') || depArgBlock === 'configDependency') { return true } else { - console.error(`ERROR: Cannot parse dependency "${depArgBlock}"`) + throw new SyntaxError(`Cannot parse dependency "${depArgBlock}"`) } }) .flatMap(parseDependencies) - const dependencies: Record = groupBy(deps, 'name') + const dependencies: Record = groupBy(deps, 'name') - return { - fn: { name, signatures: [{args, returns}] }, - dependencies + return {fn, dependencies} +} + +function parseDependencies(deps: string): FunctionDef[] { + if (deps === 'configDependency') { + return [{name: 'config', signatures: [{args: [], returns: 'Config'}]}] } + const inner = findBlockContents(deps, '{', '}').innerText + return split(inner, ';') + .map(trim) + .filter(notEmpty) + .map(parseDependency) +} - 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): FunctionDef { + const [name, def] = split(dep, ':').map(trim) - // parse a dependency like "complex: ((re: number) => Complex) | ((re: number, im: number) => Complex)" - function parseDependency(dep: string) { - const [name, def] = split(dep, ':').map(trim) + return parseAlias(name, def) +} - const { aliasOf, innerSignature } = parseAliasOf(def) +// Parse a possibly aliased function +function parseAlias(name: string, alias: string): FunctionDef { + const { aliasOf, innerSignature } = parseAliasOf(alias) - const signatures = split(innerSignature, '|') - .map(trim) - .map(stripParenthesis) - .map(signature => { - const [argsBlock, returns] = split(signature, '=>').map(trim) + return { name, signatures: parseSignatures(innerSignature), aliasOf } +} - if (!returns) { - console.warn(`ERROR: failed to find return type in '${signature}'`) - } +// parse function signatures like ((re: number) => Complex) | ((re: number, im: number) => Complex) +// But also have to succeed on (a: number) => number | Complex +// That's why we only split on an alternation bar `|` that's followed by +// a parenthesis; that way we avoid splitting a union return type. Note +// this is not necessarily foolproof, as there could be a return type that +// is a union with a complicated piece that has to be enclosed in parens; +// but so far it seems to work in practice. +function parseSignatures(sigs: string) { + return split(sigs, /[|]\s*(?=[(])/) + .map(trim) + .map(stripParenthesis) + .map(signature => { + const [argsBlock, returns] = split(signature, '=>').map(trim) - 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 + if (!returns) { + throw new SyntaxError(`Failed to find return type in '${signature}'`) } - } - const inner = findBlockContents(signature, '<', '>').innerText.trim() - const [aliasOfWithQuotes, innerSignature] = split(inner, ',').map(trim) + return { + args: parseArgs(argsBlock), + returns + } + }) +} + +// 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, - aliasOf: aliasOfWithQuotes.substring(1, aliasOfWithQuotes.length - 1) // remove double quotes + innerSignature: signature, + aliasOf: undefined } } - // 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 + 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 @@ -143,21 +160,45 @@ function findBlockContents(text: string, blockStart: string, blockEnd: string, s } } +/** + * Given a string, generate a string source for a regexp that will match + * exactly the given string. + * Uses the fact that the only characters that need to be escaped in + * a character class are \, ], and ^ + */ +function regexpQuote(s: string) { + const special = '\\]^' + let re = '' + for (const char of s) { + if (special.includes(char)) re += `\\${char}` + else re += `[${char}]` + } + return re +} + /** * 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[] { +export function split( + text: string, delimiter: string | RegExp, pieces = 0): string[] { + const delim: RegExp = typeof delimiter === 'string' + ? new RegExp(regexpQuote(delimiter), 'y') + : new RegExp(delimiter.source, 'y') const parts: string[] = [] let i = 0 + let n = 1 let start = 0 - while (i < text.length) { + while (i < text.length && (pieces === 0 || n < pieces)) { i = skipBrackets(text, i) - if (matchSubString(text, delimiter, i)) { + delim.lastIndex = i + const result = delim.exec(text) + if (result) { parts.push(text.substring(start, i)) - i += delimiter.length + n += 1 + i += result[0].length start = i } diff --git a/src/generic/relational.ts b/src/generic/relational.ts index 714cb49..a05c535 100644 --- a/src/generic/relational.ts +++ b/src/generic/relational.ts @@ -1,5 +1,7 @@ import {Dependencies, Signature} from '../interfaces/type.js' +import {$reflect} from '../interfaces/type.js' export const unequal = (dep: Dependencies<'equal', T>): Signature<'unequal', T> => (x, y) => !dep.equal(x, y) +$reflect!([unequal]) diff --git a/src/numbers/native.ts b/src/numbers/native.ts index 10cd111..ea2f66b 100644 --- a/src/numbers/native.ts +++ b/src/numbers/native.ts @@ -1,2 +1,4 @@ export * from './type.js' export * from './arithmetic.js' +export * from './predicate.js' +export * from './relational.js' diff --git a/src/numbers/predicate.ts b/src/numbers/predicate.ts index 03bd80f..0a51d65 100644 --- a/src/numbers/predicate.ts +++ b/src/numbers/predicate.ts @@ -1,4 +1,7 @@ import type {Signature} from '../interfaces/type.js' +import {$reflect} from '../interfaces/type.js' -export const isReal: Signature<'isReal', number> = (a) => true -export const isSquare: Signature<'isSquare', number> = (a) => a >= 0 +export const isReal = (): Signature<'isReal', number> => (a) => true +export const isSquare = (): Signature<'isSquare', number> => (a) => a >= 0 + +$reflect!([isReal, isSquare]) diff --git a/src/numbers/relational.ts b/src/numbers/relational.ts index 8f1ac92..9dc9891 100644 --- a/src/numbers/relational.ts +++ b/src/numbers/relational.ts @@ -1,5 +1,6 @@ import {configDependency} from '../core/Config.js' import {Signature} from '../interfaces/type.js' +import {$reflect} from '../interfaces/type.js' const DBL_EPSILON = Number.EPSILON || 2.2204460492503130808472633361816E-16 @@ -19,3 +20,4 @@ export const equal = return false } +$reflect!([equal]) diff --git a/src/numbers/type.ts b/src/numbers/type.ts index f234a8f..fde9337 100644 --- a/src/numbers/type.ts +++ b/src/numbers/type.ts @@ -1,4 +1,5 @@ import type { Signature } from '../interfaces/type.js' +import {$reflect} from '../interfaces/type.js' export const number_type = { name: 'number', // just until we have reflection to tell us @@ -20,7 +21,9 @@ declare module "../interfaces/type" { } // I don't like the redundancy of repeating 'zero'; any way to eliminate that? -export const zero: Signature<'zero', number> = (a) => 0 -export const one: Signature<'one', number> = (a) => 1 -export const nan: Signature<'nan', number> = (a) => NaN -export const re: Signature<'re', number> = (a) => a +export const zero = (): Signature<'zero', number> => (a) => 0 +export const one = (): Signature<'one', number> => (a) => 1 +export const nan = (): Signature<'nan', number> => (a) => NaN +export const re = (): Signature<'re', number> => (a) => a + +$reflect!([zero, one, nan, re])