From 2a0815eeb378b0140ef2885514715fb00db7a11e Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sat, 7 Jan 2023 00:53:23 +0530 Subject: refactor: moves stdlib around + package stuff --- src/index.ts | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/runtime.ts | 213 -------------------------------------------------- src/stdlib/fs.ts | 6 -- src/stdlib/index.ts | 4 - src/stdlib/io.ts | 15 ---- src/stdlib/stdio.ts | 12 --- src/stdlib/sys.ts | 8 -- 7 files changed, 218 insertions(+), 258 deletions(-) create mode 100644 src/index.ts delete mode 100644 src/runtime.ts delete mode 100644 src/stdlib/fs.ts delete mode 100644 src/stdlib/index.ts delete mode 100644 src/stdlib/io.ts delete mode 100644 src/stdlib/stdio.ts delete mode 100644 src/stdlib/sys.ts (limited to 'src') diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..34038bf --- /dev/null +++ b/src/index.ts @@ -0,0 +1,218 @@ +import { Project, ScriptTarget, Type, Node, SyntaxKind } from 'ts-morph' +import path from 'path' +import { promises as fs } from 'fs' +import readline from 'readline'; +import { v4 as uuid } from 'uuid'; +import { match } from './util'; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); + +const readLineFromStdin = (): Promise => new Promise((res) => { + rl.on('line', res) +}) + +const project = new Project({ + compilerOptions: { + target: ScriptTarget.ES3, + }, +}) + +const typeChecker = project.getTypeChecker() + +const [filePath] = process.argv.slice(2) + +if (!filePath) { + throw new Error('Must specify runtime file') +} + +const sourceFile = project.addSourceFileAtPath(path.resolve(filePath)) + +const entryPoint = sourceFile.getExportedDeclarations().get('main')?.[0] + +const typeToString = (ty: Type | undefined): string => + ty ? typeChecker.compilerObject.typeToString(ty.compilerType) : '' + +const typeRefNode = entryPoint?.getLastChild() + +const RESULT_TYPE_NAME = '__$result' + +const [resultTypeNode] = sourceFile.addStatements(`type ${RESULT_TYPE_NAME} = {}`) + +const addResult = (name: string, ty: string): Node | undefined => { + if (resultTypeNode?.isKind(SyntaxKind.TypeAliasDeclaration)) { + const value = resultTypeNode.getChildAtIndex(3) + if (value.isKind(SyntaxKind.TypeLiteral)) { + return value.addProperty({ + name: JSON.stringify(name), + type: `{ output: ${ty} }`, + }) + } + } + return +} + +const customEffects: Record any> = {} + +const evaluateType = async (effTyp: Type, node: Node): Promise => { + const name = effTyp.getSymbol()?.getName() + // console.log(name) + + return match(name, { + DefineEffect: async () => { + const [nameTyp, exprTyp] = effTyp.getTypeArguments() + const name = nameTyp?.getLiteralValue() as string + const exprStr = exprTyp?.getLiteralValue() as string + const func = eval(exprStr) + + Object.assign(customEffects, { [name]: func }) + return [] + }, + + Print: async () => { + console.log(...effTyp.getTypeArguments().map(typeToString)); + return [] + }, + + PutString: async () => { + const [strinTyp] = effTyp.getTypeArguments() + const typString = typeToString(strinTyp) + const string = JSON.parse(!typString.startsWith('"') ? `"${typString}"` : typString) + process.stdout.write(string); + return [] + }, + + Debug: async () => { + const [labelTyp, valueTyp] = effTyp.getTypeArguments() + const label = JSON.parse(typeToString(labelTyp)) + const value = typeToString(valueTyp) + console.log(label, value) + // TODO: Return value + return [] + }, + + ReadFile: async () => { + const [pathTyp] = effTyp.getTypeArguments() + const filePath = JSON.parse(typeToString(pathTyp)) + const contents = await fs.readFile(filePath, 'utf-8') + const hash = uuid() + addResult(hash, JSON.stringify(contents)) + return [hash] + }, + + WriteFile: async () => { + const [pathTyp, contentsTyp] = effTyp.getTypeArguments() + const filePath = JSON.parse(typeToString(pathTyp)) + const contents = JSON.parse(typeToString(contentsTyp)) + await fs.writeFile(filePath, contents) + return [] + }, + + Bind: async () => { + const [inputTyp, chainToKind] = effTyp.getTypeArguments() + const [resultKey] = inputTyp ? await evaluateType(inputTyp, node) : [] + + const hash = uuid() + const compNode = addResult(hash, + `(${typeToString(chainToKind)} & { input: ${RESULT_TYPE_NAME}[${JSON.stringify(resultKey)}]['output'] })['return']`) + const compTyp = compNode?.getType().getProperty('output')?.getTypeAtLocation(node) + + return compTyp ? await evaluateType(compTyp, node) : [] + }, + + GetEnv: async () => { + const [envTyp] = effTyp.getTypeArguments() + const envName = JSON.parse(typeToString(envTyp)) + const hash = uuid() + addResult(hash, `${JSON.stringify(process.env[envName] ?? '')}`) + return [hash] + }, + + GetArgs: async () => { + const hash = uuid() + addResult(hash, `${JSON.stringify(process.argv.slice(2))}`) + return [hash] + }, + + ReadLine: async () => { + const line = await readLineFromStdin() + const hash = uuid() + addResult(hash, `${JSON.stringify(line)}`) + return [hash] + }, + + JsExpr: async () => { + const [exprTyp] = effTyp.getTypeArguments() + const exprStr = JSON.parse(typeToString(exprTyp)) + const result = eval(`JSON.stringify(${exprStr})`) + const hash = uuid() + addResult(hash, `${result}`) + return [hash] + }, + + Seq: async () => { + const [effectTyps] = effTyp.getTypeArguments() + const effectResults = await evalList(effectTyps?.getTupleElements() ?? [], node) + const hash = uuid() + addResult(hash, `[ + ${effectResults.map(r => `${RESULT_TYPE_NAME}[${JSON.stringify(r)}]`).join(', ')} + ]`) + return [hash] + }, + + Do: async () => { + const [effectTyps] = effTyp.getTypeArguments() + const effectResults = await evalList(effectTyps?.getTupleElements() ?? [], node) + const resultKey = effectResults[effectResults.length - 1] + const hash = uuid() + addResult(hash, `${RESULT_TYPE_NAME}[${JSON.stringify(resultKey)}]['output']`) + return [hash] + }, + + _: async () => { + if (name && customEffects[name]) { + const out = await customEffects[name]?.(...effTyp.getTypeArguments()) + if (out) { + const hash = uuid() + addResult(hash, `${JSON.stringify(out)}`) + return [hash] + } + } else { + console.log(`${name} result effect is unhandled`) + } + return [] + }, + }) +} + +const evalList = async (effectTyps: Type[], node: Node) => { + const effectResults: string[] = [] + for (const item of effectTyps ?? []) { + effectResults.push(...(await evaluateType(item, node))) + } + return effectResults +} + +const main = async () => { + if (typeRefNode) { + const resultType = entryPoint?.getType() + + if (resultType) { + const effects = resultType.isTuple() ? resultType.getTupleElements() : [resultType] + await evalList(effects, typeRefNode) + } + } +} + +main() + .then(() => { + // console.log(entryPoint?.print()) + // console.log(resultTypeNode?.print()) + rl.close() + process.exit(0) + }) + .catch(e => (console.error(e), process.exit(1))) + diff --git a/src/runtime.ts b/src/runtime.ts deleted file mode 100644 index 0a65bf3..0000000 --- a/src/runtime.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { Project, ScriptTarget, Type, Node, StringLiteral, TypeFormatFlags, SyntaxKind } from 'ts-morph' -import path from 'path' -import { promises as fs } from 'fs' -import readline from 'readline'; -import { v4 as uuid } from 'uuid'; -import { match } from './util'; - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false -}); - -const readLineFromStdin = (): Promise => new Promise((res) => { - rl.on('line', res) -}) - -const project = new Project({ - compilerOptions: { - target: ScriptTarget.ES3, - }, -}) - -const typeChecker = project.getTypeChecker() - -const [filePath] = process.argv.slice(2) - -const sourceFile = project.addSourceFileAtPath(path.resolve(filePath)) - -const entryPoint = sourceFile.getExportedDeclarations().get('main')?.[0] - -const typeToString = (ty: Type | undefined): string => - ty ? typeChecker.compilerObject.typeToString(ty.compilerType) : '' - -const typeRefNode = entryPoint?.getLastChild() - -const RESULT_TYPE_NAME = '__$result' - -const [resultTypeNode] = sourceFile.addStatements(`type ${RESULT_TYPE_NAME} = {}`) - -const addResult = (name: string, ty: string): Node | undefined => { - if (resultTypeNode.isKind(SyntaxKind.TypeAliasDeclaration)) { - const value = resultTypeNode.getChildAtIndex(3) - if (value.isKind(SyntaxKind.TypeLiteral)) { - return value.addProperty({ - name: JSON.stringify(name), - type: `{ output: ${ty} }`, - }) - } - } -} - -const customEffects: Record any> = {} - -const evaluateType = async (effTyp: Type, node: Node): Promise => { - const name = effTyp.getSymbol()?.getName() - // console.log(name) - - return match(name, { - DefineEffect: async () => { - const [nameTyp, exprTyp] = effTyp.getTypeArguments() - const name = nameTyp.getLiteralValue() as string - const exprStr = exprTyp.getLiteralValue() as string - const func = eval(exprStr) - - Object.assign(customEffects, { [name]: func }) - return [] - }, - - Print: async () => { - console.log(...effTyp.getTypeArguments().map(typeToString)); - return [] - }, - - PutString: async () => { - const [strinTyp] = effTyp.getTypeArguments() - const typString = typeToString(strinTyp) - const string = JSON.parse(!typString.startsWith('"') ? `"${typString}"` : typString) - process.stdout.write(string); - return [] - }, - - Debug: async () => { - const [labelTyp, valueTyp] = effTyp.getTypeArguments() - const label = JSON.parse(typeToString(labelTyp)) - const value = typeToString(valueTyp) - console.log(label, value) - // TODO: Return value - return [] - }, - - ReadFile: async () => { - const [pathTyp] = effTyp.getTypeArguments() - const filePath = JSON.parse(typeToString(pathTyp)) - const contents = await fs.readFile(filePath, 'utf-8') - const hash = uuid() - addResult(hash, JSON.stringify(contents)) - return [hash] - }, - - WriteFile: async () => { - const [pathTyp, contentsTyp] = effTyp.getTypeArguments() - const filePath = JSON.parse(typeToString(pathTyp)) - const contents = JSON.parse(typeToString(contentsTyp)) - await fs.writeFile(filePath, contents) - return [] - }, - - Bind: async () => { - const [inputTyp, chainToKind] = effTyp.getTypeArguments() - const [resultKey] = inputTyp ? await evaluateType(inputTyp, node) : [] - - const hash = uuid() - const compNode = addResult(hash, - `(${typeToString(chainToKind)} & { input: ${RESULT_TYPE_NAME}[${JSON.stringify(resultKey)}]['output'] })['return']`) - const compTyp = compNode?.getType().getProperty('output')?.getTypeAtLocation(node) - - return compTyp ? await evaluateType(compTyp, node) : [] - }, - - GetEnv: async () => { - const [envTyp] = effTyp.getTypeArguments() - const envName = JSON.parse(typeToString(envTyp)) - const hash = uuid() - addResult(hash, `${JSON.stringify(process.env[envName] ?? '')}`) - return [hash] - }, - - GetArgs: async () => { - const hash = uuid() - addResult(hash, `${JSON.stringify(process.argv.slice(2))}`) - return [hash] - }, - - ReadLine: async () => { - const line = await readLineFromStdin() - const hash = uuid() - addResult(hash, `${JSON.stringify(line)}`) - return [hash] - }, - - JsExpr: async () => { - const [exprTyp] = effTyp.getTypeArguments() - const exprStr = JSON.parse(typeToString(exprTyp)) - const result = eval(`JSON.stringify(${exprStr})`) - const hash = uuid() - addResult(hash, `${result}`) - return [hash] - }, - - Seq: async () => { - const [effectTyps] = effTyp.getTypeArguments() - const effectResults = await evalList(effectTyps?.getTupleElements() ?? [], node) - const hash = uuid() - addResult(hash, `[ - ${effectResults.map(r => `${RESULT_TYPE_NAME}[${JSON.stringify(r)}]`).join(', ')} - ]`) - return [hash] - }, - - Do: async () => { - const [effectTyps] = effTyp.getTypeArguments() - const effectResults = await evalList(effectTyps?.getTupleElements() ?? [], node) - const resultKey = effectResults[effectResults.length - 1] - const hash = uuid() - addResult(hash, `${RESULT_TYPE_NAME}[${JSON.stringify(resultKey)}]['output']`) - return [hash] - }, - - _: async () => { - if (name && customEffects[name]) { - const out = await customEffects[name](...effTyp.getTypeArguments()) - if (out) { - const hash = uuid() - addResult(hash, `${JSON.stringify(out)}`) - return [hash] - } - } else { - console.log(`${name} result effect is unhandled`) - } - return [] - }, - }) -} - -const evalList = async (effectTyps: Type[], node: Node) => { - const effectResults: string[] = [] - for (const item of effectTyps ?? []) { - effectResults.push(...(await evaluateType(item, node))) - } - return effectResults -} - -const main = async () => { - if (typeRefNode) { - const resultType = entryPoint?.getType() - - if (resultType) { - const effects = resultType.isTuple() ? resultType.getTupleElements() : [resultType] - await evalList(effects, typeRefNode) - } - } -} - -main() - .then(() => { - // console.log(entryPoint?.print()) - // console.log(resultTypeNode?.print()) - rl.close() - process.exit(0) - }) - .catch(e => (console.error(e), process.exit(1))) - diff --git a/src/stdlib/fs.ts b/src/stdlib/fs.ts deleted file mode 100644 index 1374da5..0000000 --- a/src/stdlib/fs.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Effect } from './io' - -export interface WriteFile<_Path extends string, _Content extends string> extends Effect { } - -export interface ReadFile<_Path extends string> extends Effect { } - diff --git a/src/stdlib/index.ts b/src/stdlib/index.ts deleted file mode 100644 index 45197d9..0000000 --- a/src/stdlib/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './io' -export * from './fs' -export * from './stdio' -export * from './sys' diff --git a/src/stdlib/io.ts b/src/stdlib/io.ts deleted file mode 100644 index c78204f..0000000 --- a/src/stdlib/io.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Effect { output: T } - -export interface Kind1 { - input: Inp - return: Out -} - -export interface Bind<_Eff extends Effect, _Fn extends Kind1> extends Effect { } - -export interface Seq<_Effs extends Effect[]> extends Effect { } - -export interface Do<_Effs extends Effect[]> extends Effect { } - -export interface DefineEffect<_Name extends string, _Func extends string> extends Effect { } - diff --git a/src/stdlib/stdio.ts b/src/stdlib/stdio.ts deleted file mode 100644 index 365aead..0000000 --- a/src/stdlib/stdio.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Effect } from './io' - -export interface PutString<_ extends string> extends Effect { } - -export interface Print<_ extends any> extends Effect { } - -export interface Debug<_ extends string, T> extends Effect { } - -export interface ReadLine extends Effect { } - -export type PutStringLn = PutString<`${S}\n`> - diff --git a/src/stdlib/sys.ts b/src/stdlib/sys.ts deleted file mode 100644 index 03541be..0000000 --- a/src/stdlib/sys.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Effect } from './io' - -export interface GetEnv<_Name extends string> extends Effect { } - -export interface GetArgs extends Effect { } - -export interface JsExpr<_Expr extends string> extends Effect { } - -- cgit v1.3.1