Compare commits

..

22 Commits

Author SHA1 Message Date
ab7fc4450e add another try 2023-09-11 17:50:10 +02:00
94a0737555 update pnpm lock file 2023-09-11 16:09:50 +02:00
24bcf34b60 try getting resolved type arguments (WIP) 2023-09-11 16:07:52 +02:00
232d5d4a96 make defining __infer__ redundant 2023-09-08 14:25:14 +02:00
7fc9d2a2f3 remove name argument from the reflect function 2023-09-08 14:10:03 +02:00
26631febf9 add a TODO explaining the plan 2023-09-01 20:54:39 +02:00
3967f5ce2b let TypeScript output ES modules 2023-09-01 18:43:03 +02:00
d50c1a9ccf some refinements in the plugin 2023-09-01 18:36:50 +02:00
cbb79d46fe cleanup old experiments and typescript-rtti, update readme 2023-09-01 18:21:45 +02:00
dea521029e Get a real TypeScript plugin working 2023-09-01 17:52:44 +02:00
2cb8bc0099 Cleanup a temporary console.log 2023-08-18 17:40:13 +02:00
3653077c95 Add an experiment reading out a JSDoc comment 2023-08-18 17:39:57 +02:00
16eb09fe61 chore: update a couple packages 2023-08-17 23:34:03 -07:00
6a063d7385 Add a useful resource to the list with articles 2023-03-14 10:15:56 +01:00
f8553aa748 Add another experiment infer2 (WIP) 2023-03-14 09:51:28 +01:00
aa044a54e7 Move the experiment into src/plugins and src/experiment 2023-03-13 16:31:41 +01:00
b9cfe706fc Experiment of creating a TypeScript plugin (WIP) 2023-03-13 16:04:11 +01:00
946b4a495f use CallSite reflection to get some information out of specifications (WIP) 2023-02-02 16:14:25 +01:00
86688ca129 fix: generate CommonJS output instead of ESM as a workaround for typescript-rtti issue #94 2023-02-02 15:52:15 +01:00
5872bd8537 chore: switch to the typescript and typescript-rtti versions used in the online playground 2023-02-02 15:07:47 +01:00
1f2a59c802 fix some exports not being understood by typescript-rtti 2023-02-02 15:05:55 +01:00
35a8c62ff2 Set up typescript-rtti (WIP) 2023-01-25 14:42:23 +01:00
26 changed files with 343 additions and 1302 deletions

1
.npmrc
View File

@ -1 +0,0 @@
shell-emulator=true

View File

@ -2,13 +2,50 @@
A final (?) prototype for a refactor of mathjs, culminating the picomath, pocomath, typomath series. Provides an extensible core with "fuzzy" types for its operations, that can at any time generate exact .d.ts file for its current state.
To build and run the prototype, run:
Convenience scripts:
```
pnpm install
pnpm build-and-run
```
* `pnpm build` -- compile the package
* `pnpm exec` -- run the compiled code produced by `pnpm build`
* `pnpm go` -- both of the above in sequence.
## experiment
Important installation note:
Have a look at the section under `/src/experiment` and `/src/plugins`:
after `pnpm install`, you must execute `npx ts-patch install` to activate the ts-macros compiler plugin.
- `src/plugins/typeInferPlugin.ts` is the actual plugin
- in `tsconfig.json` we configure TypeScript to run the plugin
- `src/experiment/arithmeticInfer.ts` with an example where we define `__infer__`
- after running TypeScript: look at `build/experiment/arithmeticInfer.ts` where the `__infer__` string literal is replaced with the actual types
### The idea
Create a TypeScript plugin which can replace a string literal like `__infer__` in a typed-function definition:
typed('square', '__infer__', <T>(dep: { ... } => { ... })
with the actual types, something like:
typed('square', '{ deps: { multiply: (a: T, b: T) => T; }; return: (a: T) => T }', <T>(dep: { ... } => { ... })
(We can discuss what syntax we like most, this is just a POC)
### How to run
pnpm build-and-run
### Read more
- https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin
- https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
- https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker
- https://github.com/Microsoft/TypeScript/wiki/Using-the-Language-Service-API
- https://stackoverflow.com/questions/63944135/typescript-compiler-api-how-to-get-type-with-resolved-type-arguments
- https://stackoverflow.com/questions/48886508/typechecker-api-how-do-i-find-inferred-type-arguments-to-a-function
- https://blog.logrocket.com/using-typescript-transforms-to-enrich-runtime-code-3fd2863221ed/
- https://github.com/itsdouges/typescript-transformer-handbook#transforms
### Interesting libraries
- https://github.com/GoogleFeud/ts-macros/
- https://ts-morph.com

View File

@ -1 +0,0 @@
{"type": "module"}

View File

@ -1,16 +1,11 @@
{
name: 'typocomath',
version: '0.0.2',
version: '0.0.1',
description: 'A hopeful final typescipt-pragmatic mathjs proof-of-concept',
main: 'index.ts',
scripts: {
test: 'echo "Error: no test specified" && exit 1',
build: 'mkdirp build && cpy etc/package.json build --flat && tsc',
start: 'node build',
go: 'pnpm clean && pnpm build && pnpm start',
clean: 'del-cli build',
'go': 'ttsc && echo {"type":"module"} > build/package.json && node build/index.js',
},
packageManager: 'pnpm',
keywords: [
'math',
'algebra',
@ -22,14 +17,12 @@
type: 'git',
url: 'https://code.studioinfinity.org/glen/typocomath.git',
},
dependencies: {
'@types/node': '20.5.7',
},
devDependencies: {
'@types/node': '20.8.4',
'cpy-cli': '5.0.0',
'del-cli': '5.1.0',
mkdirp: '3.0.1',
'source-map': '0.7.4',
'ts-macros': '2.6.0',
'ts-patch': '3.0.2',
typescript: '5.1.6',
'ts-node': '10.9.1',
ttypescript: '1.5.15',
typescript: '4.7.4',
},
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,2 @@
import * as Complex from './native.js'
import * as complex from './arithmetic.js'
export { complex }
export {Complex}
export * as Complex from './native.js'
export * as complex from './arithmetic.js'

View File

@ -2,7 +2,6 @@ import {Complex} from './type.js'
import type {
Dependencies, Signature, Returns, RealType, AliasOf
} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
declare module "../interfaces/type" {
interface Signatures<T> {
@ -79,10 +78,11 @@ export const sqrt =
& Dependencies<'zero' | 'complex', T>
& Dependencies<'absquare' | 're' | 'divideReal', Complex<T>>
& {
addTR: Signature<'addReal', T>,
addTR: Signature<'addReal', T>,
addRR: Signature<'add', RealType<T>>,
addCR: Signature<'addReal', Complex<T>>
}): Signature<'sqrt', Complex<T>> =>
}):
Signature<'sqrt', Complex<T>> =>
z => {
const myabs = dep.conservativeSqrt(dep.absquare(z))
const r = dep.re(z)
@ -98,7 +98,4 @@ export const sqrt =
return dep.divideReal(num, denom)
}
$reflect!([
add, addReal, unaryMinus, conj, subtract, multiply, absquare, divideReal,
reciprocal, divide, sqrt
])
export const conservativeSqrt = sqrt

View File

@ -1,13 +1,9 @@
import {Complex} from './type.js'
import type {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const isReal =
<T>(dep: Dependencies<'add' | 'equal' | 'isReal', T>):
Signature<'isReal', Complex<T>> =>
z => dep.isReal(z.re) && dep.equal(z.re, dep.add(z.re, z.im))
export const isSquare = (): Signature<'isSquare', Complex<unknown>> =>
z => true // FIXME: not correct for Complex<bigint> once we get there
$reflect!([isReal, isSquare])
export const isSquare: Signature<'isSquare', Complex<any>> = z => true // FIXME: not correct for Complex<bigint> once we get there

View File

@ -1,8 +1,6 @@
import {Complex} from './type.js'
import {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const equal =
<T>(dep: Dependencies<'equal', T>): Signature<'equal', Complex<T>> =>
(w, z) => dep.equal(w.re, z.re) && dep.equal(w.im, z.im)
$reflect!([equal])

View File

@ -2,12 +2,10 @@ 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<T> = { re: T; im: T; }
export const Complex_type = {
name: 'Complex', // just until we have reflection to tell us
test: <T>(dep: { testT: (z: unknown) => z is T }) =>
(z: unknown): z is Complex<T> =>
typeof z === 'object' && z != null && 're' in z && 'im' in z
@ -63,5 +61,3 @@ export const nan =
export const re =
<T>(dep: Dependencies<'re', T>): Signature<'re', Complex<T>> =>
z => dep.re(z.re)
$reflect!([complex, zero, one, nan, re])

11
src/core/$reflect.ts Normal file
View File

@ -0,0 +1,11 @@
export function $reflect<T>(arg: T, types?: string) : T {
// TODO: implement typed-function for real
if (!types) {
console.error('types should be resolved with runtime type information by the TypeScript plugin')
}
console.log(`INFER: Creating function with types ${types}`)
// TODO: here we can now turn the inputs into a real typed-function with a signature
return arg
}

View File

@ -5,8 +5,6 @@
* for specific types (including their own).
*/
import {parseReflectedType, ImplementationDef} from './parseReflectedType.js'
// First helper types and functions for the Dispatcher
type TypeName = string
@ -27,7 +25,6 @@ export function joinTypes(a: TypeName, b: TypeName) {
// Now types used in the Dispatcher class itself
type TypeSpecification = {
name: string, // just until we get reflection, then we can remove this property
before?: TypeName[],
test: ((x: unknown) => boolean)
| ((d: DependenciesType) => (x: unknown) => boolean),
@ -41,18 +38,18 @@ type SpecificationsGroup = Record<string, SpecObject>
export class Dispatcher {
installSpecification(
name: string,
defn: ImplementationDef,
signature: Signature,
returns: TypeName,
dependencies: Record<string, Signature>,
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, '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)
}
console.log('Pretending to install', name, signature, '=>', returns)
// @ts-ignore
// console.log(name, 'signature', reflect(signature))
// console.log(name, 'dependencies', reflect(dependencies))
//TODO: implement me
}
installType(name: TypeName, typespec: TypeSpecification) {
@ -60,25 +57,23 @@ export class Dispatcher {
//TODO: implement me
}
constructor(collection: SpecificationsGroup) {
const implementations = []
for (const key in collection) {
console.log('Working on', key)
for (const identifier in collection[key]) {
const item = collection[key][identifier]
if (typeof item === 'function') {
implementations.push([key, identifier, item])
} else {
console.log('Handling type', key, ':', identifier)
console.log('Handling', key, ':', identifier)
const parts = identifier.split('_')
if (parts[parts.length - 1] === 'type') {
parts.pop()
const name = parts.join('_')
this.installType(
item.name, collection[key][identifier] as TypeSpecification)
name, collection[key][identifier] as TypeSpecification)
} else {
const name = parts[0]
this.installSpecification(
name, ['dunno'], 'unsure', {},
collection[key][identifier] as Function)
}
}
}
for (const trio of implementations) {
const [k, id, imp] = trio
console.log('Handling implementation', id, 'from', k)
this.installSpecification(
id, parseReflectedType(id, imp.reflectedType), imp)
}
}
}

View File

@ -1,279 +0,0 @@
export type FunctionDef = {
name: string,
aliasOf?: string,
signatures: Array<{
args: Array<{ name: string, type: string }>
returns: string
}>
}
export type ImplementationDef = {
fn: FunctionDef,
dependencies: Record<string, FunctionDef>
genericParameter: string | null
}
/**
* 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): ImplementationDef {
console.log('For', name, 'parsing', reflectedType)
const [factoryArgs, fnsClause] = split(reflectedType, '=>', 2).map(trim)
const fn = parseAlias(name, fnsClause)
// extract the generic parameter like '<T>' at the start of the type
const genericParameter = factoryArgs.trim().startsWith('<')
? findBlockContents(factoryArgs, '<', '>')?.innerText || null
: null
const factoryArgsInner = findBlockContents(factoryArgs, '(', ')')
const depArg = split(factoryArgsInner.innerText, ':').map(trim)[1]
const depArgBlocks: string[] = depArg ? split(depArg, '&').map(trim) : []
const deps = depArgBlocks
.filter(depArgBlock => {
if (depArgBlock.startsWith('{') || depArgBlock === 'configDependency') {
return true
} else {
throw new SyntaxError(`Cannot parse dependency "${depArgBlock}"`)
}
})
.flatMap(parseDependencies)
const dependencies: Record<string, FunctionDef> = groupBy(deps, 'name')
return {fn, dependencies, genericParameter }
}
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)
}
// parse a dependency like "complex: ((re: number) => Complex<number>) | ((re: number, im: number) => Complex<number>)"
function parseDependency(dep: string): FunctionDef {
const [name, def] = split(dep, ':').map(trim)
return parseAlias(name, def)
}
// Parse a possibly aliased function
function parseAlias(name: string, alias: string): FunctionDef {
const { aliasOf, innerSignature } = parseAliasOf(alias)
return { name, signatures: parseSignatures(innerSignature), aliasOf }
}
// parse function signatures like ((re: number) => Complex<number>) | ((re: number, im: number) => Complex<number>)
// But also have to succeed on (a: number) => number | Complex<number>
// 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)
if (!returns) {
throw new SyntaxError(`Failed to find return type in '${signature}'`)
}
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<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)
}
}
/**
* 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 | 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 && (pieces === 0 || n < pieces)) {
i = skipBrackets(text, i)
delim.lastIndex = i
const result = delim.exec(text)
if (result) {
parts.push(text.substring(start, i))
n += 1
i += result[0].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

@ -0,0 +1,19 @@
import { $reflect } from '../core/$reflect.js'
import { Dependencies } from '../interfaces/type.js'
// unaryMinus dep is just for the experiment
// FIXME: the typescript plugin should resolve Dependencies<'multiply' | 'unaryMinus', T>
export const square = $reflect(<T>(dep: Dependencies<'multiply' | 'unaryMinus', T>): (a: T) => T =>
z => dep.multiply(z, z)
)
// export const square2 = function $reflect<T>(dep: Dependencies<'multiply' | 'unaryMinus', T>) {
// return (z: T) => dep.multiply(z, z)
// }
// export const square = $reflect(<T>(dep: {
// multiply: (a: T, b: T) => T,
// unaryMinus: (x: T) => T, // just for the experiment
// }): (a: T) => T =>
// z => dep.multiply(z, z)
// )

View File

@ -1,9 +1,5 @@
import type {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const square =
<T>(dep: Dependencies<'multiply', T>): Signature<'square', T> =>
z => dep.multiply(z, z)
// z => dep.fooBar(z, z) // fails as desired
// z => dep.multiply(z, 'foo') // still fails as desired
$reflect!([square])

View File

@ -1,7 +1,5 @@
import {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const unequal =
<T>(dep: Dependencies<'equal', T>): Signature<'unequal', T> =>
(x, y) => !dep.equal(x, y)
$reflect!([unequal])

View File

@ -1,53 +1,28 @@
import {inspect} from 'node:util'
import {Dispatcher} from './core/Dispatcher.js'
import * as Specifications from './all.js'
import { Complex } from './Complex/type.js'
import { absquare as absquare_complex } from './Complex/arithmetic.js'
import { square } from './experiment/arithmeticInfer.js'
export default new Dispatcher(Specifications)
import {Complex} from './Complex/type.js'
import {absquare as absquare_complex} from './Complex/arithmetic.js'
import {parseReflectedType} from './core/parseReflectedType.js'
const mockRealAdd = (a: number, b: number) => a+b
const mockComplexAbsquare = (z: Complex<number>) => z.re*z.re + z.im*z.im
const mockComplex = (re: number, im: number) => ({ re, im })
const config = {
predictable: false,
epsilon: 1e-14
}
const add = (a: number, b: number) => a + b
const multiply = (a: number, b: number) => a * b
const unaryMinus = (a: number) => -a
const absquare = (z: Complex<number>) => z.re * z.re + z.im * z.im
const quatAbsquare = absquare_complex({
add: mockRealAdd,
absquare: mockComplexAbsquare
add,
absquare
})
const myabs = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}})
const typeTest: typeof myabs = 7 // check myabs is just a number
console.log('Result is myabs=', myabs)
const result = quatAbsquare({re: {re: 0, im: 1}, im: {re:2, im: 3}})
const typeTest: typeof result = 7 // check myabs is just a number
const sqrt = Specifications.numbers.sqrt({
config,
complex: mockComplex
console.log()
console.log('Result is', result)
const mySquare = square({
multiply,
unaryMinus
})
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(`1.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(`1.1) REFLECTED TYPE: "${Specifications.complex.sqrt.reflectedType}"`)
console.log('3.2) PARSED TYPE:', inspect(parseReflectedType('sqrt', Specifications.complex.sqrt.reflectedType), { depth: null, colors: true }))
console.log('mySquare(4)=', mySquare(4))

View File

@ -1,5 +1,3 @@
import {$$typeToString} from 'ts-macros'
/*****
* Every typocomath type has some associated types; they need
* to be published in the following interface. The key is the
@ -15,10 +13,6 @@ import {$$typeToString} from 'ts-macros'
* but that's OK, the generic parameter doesn't hurt in those cases.
****/
type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = {
[P in TKey]: (k: T[P])=>void
} [TKey] extends ((k: infer I)=>void) ? I : never
export interface AssociatedTypes<T> {
undefined: {
type: undefined
@ -30,10 +24,10 @@ export interface AssociatedTypes<T> {
}
type AssociatedTypeNames = keyof AssociatedTypes<unknown>['undefined']
type ALookup<T, Name extends AssociatedTypeNames> = ValueIntersectionByKeyUnion<{
type ALookup<T, Name extends AssociatedTypeNames> = {
[K in keyof AssociatedTypes<T>]:
T extends AssociatedTypes<T>[K]['type'] ? AssociatedTypes<T>[K][Name] : unknown},
keyof AssociatedTypes<T>>
T extends AssociatedTypes<T>[K]['type'] ? AssociatedTypes<T>[K][Name] : never
}[keyof AssociatedTypes<T>]
// For everything to compile, zero and one must be subtypes of T:
export type ZeroType<T> = ALookup<T, 'zero'> & T
@ -75,16 +69,7 @@ export interface Signatures<T> {
type SignatureKey<T> = keyof Signatures<T>
export type Signature<Name extends SignatureKey<T>, T> = Signatures<T>[Name]
export type Returns<Name extends SignatureKey<T>, T> =
ReturnType<Signatures<T>[Name]>
type Deps<T> = T extends unknown ? { [K in keyof T]: T[K] } : never;
export type Dependencies<Name extends SignatureKey<T>, T> =
Deps<{[K in Name]: Signature<K, T>}>
export type Returns<Name extends SignatureKey<T>, T> = ReturnType<Signatures<T>[Name]>
export type Dependencies<Name extends SignatureKey<T>, T> = {[K in Name]: Signature<K, T>}
export type AliasOf<Name extends string, T> = T & {aliasOf?: Name}
// For defining implementations with type reflection
export function $reflect<ImplTuple>(tup: ImplTuple) {
+[[tup], <T>(elt: T) =>
elt.reflectedType = $$typeToString!<T>(true, false, true)]
}

View File

@ -1,3 +1 @@
import * as numbers from './native.js'
export {numbers}
export * as numbers from './native.js'

View File

@ -1,35 +1,25 @@
import type {configDependency} from '../core/Config.js'
import type {Dependencies, Signature} from '../interfaces/type.js'
import {$reflect} from '../interfaces/type.js'
export const add = (): Signature<'add', number> => (a, b) => a + b
const unaMinus = (a: number) => -a
export const unaryMinus = (): Signature<'unaryMinus', number> => unaMinus
export const conj = (): Signature<'conj', number> => a => a
export const subtract = (): Signature<'subtract', number> => (a, b) => a - b
export const multiply = (): Signature<'multiply', number> => (a, b) => a * b
export const absquare = (): Signature<'absquare', number> => a => a * a
export const reciprocal = (): Signature<'reciprocal', number> => a => 1 / a
export const divide = (): Signature<'divide', number> => (a, b) => a / b
export const add: Signature<'add', number> = (a, b) => a + b
export const unaryMinus: Signature<'unaryMinus', number> = a => -a
export const conj: Signature<'conj', number> = a => a
export const subtract: Signature<'subtract', number> = (a, b) => a - b
export const multiply: Signature<'multiply', number> = (a, b) => a * b
export const absquare: Signature<'absquare', number> = a => a * a
export const reciprocal: Signature<'reciprocal', number> = a => 1 / a
export const divide: Signature<'divide', number> = (a, b) => a / b
const basicSqrt = (a: number) => isNaN(a) ? NaN : Math.sqrt(a)
export const conservativeSqrt = (): Signature<'conservativeSqrt', number> =>
basicSqrt
export const conservativeSqrt: Signature<'conservativeSqrt', number> = basicSqrt
export const sqrt =
(dep: configDependency
& Dependencies<'complex', number>): Signature<'sqrt', number> => {
if (dep.config.predictable || !dep.complex) {
return basicSqrt
}
return a => {
if (isNaN(a)) return NaN
if (a >= 0) return Math.sqrt(a)
return dep.complex(0, Math.sqrt(unaMinus(a)))
}
(dep: configDependency & Dependencies<'complex', number>):
Signature<'sqrt', number> => {
if (dep.config.predictable || !dep.complex) return basicSqrt
return a => {
if (isNaN(a)) return NaN
if (a >= 0) return Math.sqrt(a)
return dep.complex(0, Math.sqrt(unaryMinus(a)))
}
}
$reflect!([
add, unaryMinus, conj, subtract, multiply, absquare, reciprocal, divide,
conservativeSqrt, sqrt
])

View File

@ -1,4 +1,2 @@
export * from './type.js'
export * from './arithmetic.js'
export * from './predicate.js'
export * from './relational.js'

View File

@ -1,7 +1,4 @@
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
$reflect!([isReal, isSquare])
export const isReal: Signature<'isReal', number> = (a) => true
export const isSquare: Signature<'isSquare', number> = (a) => a >= 0

View File

@ -1,6 +1,5 @@
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
@ -20,4 +19,3 @@ export const equal =
return false
}
$reflect!([equal])

View File

@ -1,8 +1,6 @@
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
before: ['Complex'],
test: (n: unknown): n is number => typeof n === 'number',
from: { string: (s: string) => +s }
@ -10,7 +8,7 @@ export const number_type = {
declare module "../interfaces/type" {
interface AssociatedTypes<T> {
number: {
numbers: {
type: number
zero: 0
one: 1
@ -21,9 +19,7 @@ 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
$reflect!([zero, one, nan, re])
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

View File

@ -0,0 +1,84 @@
import ts from 'typescript'
const transformer: ts.TransformerFactory<ts.SourceFile> = context => {
// TODO: get a reference to the program instance that the plugin is running in instead of creating a new program?
const program = ts.createProgram([], {})
const checker = program.getTypeChecker()
return sourceFile => {
const visitor = (node: ts.Node): ts.Node => {
// we're looking for a function call like $reflect(deps => ...)
// @ts-ignore
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.escapedText === '$reflect') {
console.log('PLUGIN: FOUND AN OCCURRENCE OF $reflect')
// console.log('PARENT')
// console.log(node)
// TODO: validate that argNode is an ArrowFunction
// @ts-ignore
const argNode = node.arguments[0]
// @ts-ignore
const returnType = argNode.type.getText(sourceFile)
console.log('PLUGIN: RETURN TYPE')
console.log(returnType)
// (a: number) => number
// @ts-ignore
const paramNode = argNode.parameters[0]
const paramTypeSrc = paramNode.type.getText(sourceFile)
console.log('PLUGIN: PARAM TYPE SRC', paramTypeSrc)
// {
// multiply: (a: number, b: number) => number,
// unaryMinus: (x: number) => number, // just for the experiment
// }
// WIP
// @ts-ignore
const type = checker.getTypeAtLocation(paramNode)
const paramType = checker.typeToString(type, undefined, ts.TypeFormatFlags.InTypeAlias)
// const paramType = checker.typeToString(type)
// TDOO: get checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)
console.log('PLUGIN: PARAM TYPE STRING', paramType)
// { multiply: (a: number, b: number) => number; unaryMinus: (x: number) => number; }
// WIP
// For a function definition
// const signature = checker.getResolvedSignature(node);
// if (signature != null) {
// // outputs -- (Ctor: Ctor<Bar>): void
// console.log(signature)
// console.log('TEST 1', checker.signatureToString(signature));
// // @ts-ignore
// // console.log('TEST 2', checker.getResolvedSignatureForStringLiteralCompletions(node));
// const params = signature.getParameters();
// for (const param of params) {
// const type = checker.getTypeOfSymbolAtLocation(param, node);
// // outputs -- Ctor<Bar>
// console.log('TEST 3', checker.typeToString(type));
// }
// }
// WIP
const type1 = checker.getTypeAtLocation(paramNode)
const type2 = checker.getApparentType(type1)
const typeStr = checker.typeToString(type2, undefined, ts.TypeFormatFlags.InTypeAlias)
console.log('PLUGIN: RESOLVED TYPE ARGUMENT', typeStr) // TODO: not yet working
const depsAndReturnType = `{ deps: ${paramType}; return: ${returnType} }`
// Now we insert a second argument to the $reflect function call: a string with the types
// @ts-ignore
node.arguments.push(ts.factory.createStringLiteral(depsAndReturnType))
return node
}
return ts.visitEachChild(node, visitor, context)
}
return ts.visitNode(sourceFile, visitor)
}
}
export default transformer

View File

@ -1,12 +1,15 @@
{
"compilerOptions": {
"target": "esnext",
"target": "ES2022",
"rootDir": "./src",
"outDir": "./build",
"moduleResolution": "nodenext",
"plugins": [ {
"transform": "ts-macros/dist/type-resolve",
"transformProgram": true
} ]
"esModuleInterop": true,
"allowJs": false,
"noImplicitAny": false,
"moduleResolution": "Node",
"plugins": [{
"transform": "./src/plugins/typeInferPlugin.ts",
"type": "raw"
}]
}
}