aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2023-01-08 22:53:09 +0530
committerAkshay Nair <phenax5@gmail.com>2023-01-08 22:53:09 +0530
commita41d70afe7692e4ef344bc3c599115009f2dad15 (patch)
tree64fb57effa636a60751ea27178cf9a63b3b434ab /src
parentc79c5bf7a44c59cd1e484a538e2dddf264012f57 (diff)
downloadts-types-lang-a41d70afe7692e4ef344bc3c599115009f2dad15.tar.gz
ts-types-lang-a41d70afe7692e4ef344bc3c599115009f2dad15.zip
refactor: splits runtime up to use context
Diffstat (limited to 'src')
-rw-r--r--src/context.ts80
-rw-r--r--src/eval.ts140
-rw-r--r--src/index.ts218
-rw-r--r--src/types.ts17
4 files changed, 244 insertions, 211 deletions
diff --git a/src/context.ts b/src/context.ts
new file mode 100644
index 0000000..35fa1ae
--- /dev/null
+++ b/src/context.ts
@@ -0,0 +1,80 @@
+import { Project, ScriptTarget, Type, Node, SyntaxKind } from 'ts-morph'
+import path from 'path'
+import { v4 as uuid } from 'uuid';
+import { Ctx } from './types';
+
+const RESULT_TYPE_NAME = '__$result'
+
+export const createContext = (): Ctx => {
+ 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 ts file to run')
+ }
+
+ const sourceFile = project.addSourceFileAtPath(path.resolve(filePath))
+
+ const entryPoint = sourceFile.getExportedDeclarations().get('main')?.[0]
+
+ if (!entryPoint) {
+ throw new Error('No "main" entrypoint defined in source file')
+ }
+
+ const typeToString = (ty: Type | undefined): string =>
+ ty ? typeChecker.compilerObject.typeToString(ty.compilerType) : ''
+
+ const [resultTypeNode] = sourceFile.addStatements(`type ${RESULT_TYPE_NAME} = {}`)
+
+ const getResultExpr = (resultKey?: string) => `${RESULT_TYPE_NAME}[${JSON.stringify(resultKey)}`
+
+ const addResult = (name: string, ty: string): Node | undefined =>
+ resultTypeNode
+ ?.asKind(SyntaxKind.TypeAliasDeclaration)
+ ?.getChildAtIndexIfKind(3, SyntaxKind.TypeLiteral)
+ ?.addProperty({
+ name: JSON.stringify(name),
+ type: `{ output: ${ty} }`,
+ })
+
+ const createResult = (ty: string): [string, Node | undefined] => {
+ const resultKey = uuid()
+ const node = addResult(resultKey, ty)
+ return [resultKey, node]
+ }
+
+ const customEffects: Record<string, (...args: Type[]) => any> = {}
+
+ return {
+ sourceFile,
+ typeChecker,
+ entryPoint,
+ typeToString,
+
+ createResult,
+ getResultExpr,
+ printResultNode: () => console.log(resultTypeNode?.print()),
+
+ addCustomEffect: (name, expr) => {
+ const func = eval(expr)
+ Object.assign(customEffects, { [name]: func })
+ },
+ runCustomEffect: async (name, args) => {
+ const output = await customEffects[name]?.(...args)
+ if (output) {
+ const [resultKey, _] = createResult(`${JSON.stringify(output)}`)
+ return [resultKey]
+ }
+ return []
+ },
+ hasCustomEffect: (name) => customEffects[name] !== undefined,
+ }
+}
+
diff --git a/src/eval.ts b/src/eval.ts
new file mode 100644
index 0000000..4ba7033
--- /dev/null
+++ b/src/eval.ts
@@ -0,0 +1,140 @@
+import { Type } from 'ts-morph'
+import { promises as fs } from 'fs'
+import readline from 'readline';
+
+import { match } from './util';
+import { Ctx } from './types';
+
+const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ terminal: false
+});
+
+const readLineFromStdin = (): Promise<string> => new Promise((res) =>
+ rl.on('line', res))
+
+export const evaluateType = async (ctx: Ctx, effTyp: Type): Promise<string[]> => {
+ const name = effTyp.getSymbol()?.getName()
+
+ return match(name, {
+ DefineEffect: async () => {
+ const [nameTyp, exprTyp] = effTyp.getTypeArguments()
+ const name = nameTyp?.getLiteralValue() as string
+ const exprStr = exprTyp?.getLiteralValue() as string
+
+ ctx.addCustomEffect(name, exprStr)
+ return []
+ },
+
+ Print: async () => {
+ console.log(...effTyp.getTypeArguments().map(ctx.typeToString));
+ return []
+ },
+
+ PutString: async () => {
+ const [strinTyp] = effTyp.getTypeArguments()
+ const typString = ctx.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(ctx.typeToString(labelTyp))
+ const value = ctx.typeToString(valueTyp)
+ console.log(label, value)
+ const [resultKey, _] = ctx.createResult(JSON.stringify(value))
+ return [resultKey]
+ },
+
+ ReadFile: async () => {
+ const [pathTyp] = effTyp.getTypeArguments()
+ const filePath = JSON.parse(ctx.typeToString(pathTyp))
+ const contents = await fs.readFile(filePath, 'utf-8')
+ const [resultKey, _] = ctx.createResult(JSON.stringify(contents))
+ return [resultKey]
+ },
+
+ WriteFile: async () => {
+ const [pathTyp, contentsTyp] = effTyp.getTypeArguments()
+ const filePath = JSON.parse(ctx.typeToString(pathTyp))
+ const contents = JSON.parse(ctx.typeToString(contentsTyp))
+ await fs.writeFile(filePath, contents)
+ return []
+ },
+
+ Bind: async () => {
+ const [inputTyp, chainToKind] = effTyp.getTypeArguments()
+ const [resultKey] = inputTyp ? await evaluateType(ctx, inputTyp) : []
+
+ const [_, compNode] = ctx.createResult(
+ `(${ctx.typeToString(chainToKind)} & { input: (${ctx.getResultExpr(resultKey)})['output'] })['return']`)
+ // TODO: Avoid using getTypeAtLocation?
+ const compTyp = compNode?.getType().getProperty('output')?.getTypeAtLocation(ctx.entryPoint)
+
+ return compTyp ? await evaluateType(ctx, compTyp) : []
+ },
+
+ GetEnv: async () => {
+ const [envTyp] = effTyp.getTypeArguments()
+ const envName = JSON.parse(ctx.typeToString(envTyp))
+ const [resultKey, _] = ctx.createResult(`${JSON.stringify(process.env[envName] ?? '')}`)
+ return [resultKey]
+ },
+
+ GetArgs: async () => {
+ const [resultKey, _] = ctx.createResult(`${JSON.stringify(process.argv.slice(2))}`)
+ return [resultKey]
+ },
+
+ ReadLine: async () => {
+ const line = await readLineFromStdin()
+ const [resultKey, _] = ctx.createResult(`${JSON.stringify(line)}`)
+ return [resultKey]
+ },
+
+ JsExpr: async () => {
+ const [exprTyp] = effTyp.getTypeArguments()
+ const exprStr = JSON.parse(ctx.typeToString(exprTyp))
+ const result = eval(`JSON.stringify(${exprStr})`)
+ const [resultKey, _] = ctx.createResult(`${result}`)
+ return [resultKey]
+ },
+
+ Seq: async () => {
+ const [effectTyps] = effTyp.getTypeArguments()
+ const effectResults = await evalList(ctx, effectTyps?.getTupleElements() ?? [])
+ const [resultKey, _] = ctx.createResult(`[
+ ${effectResults.map(ctx.getResultExpr).join(', ')}
+ ]`)
+ return [resultKey]
+ },
+
+ Do: async () => {
+ const [effectTyps] = effTyp.getTypeArguments()
+ const effectResults = await evalList(ctx, effectTyps?.getTupleElements() ?? [])
+ const lastResKey = effectResults[effectResults.length - 1]
+ const [resultKey, _] = ctx.createResult(`${ctx.getResultExpr(lastResKey)}['output']`)
+ return [resultKey]
+ },
+
+ _: async () => {
+ if (name && ctx.hasCustomEffect(name)) {
+ return ctx.runCustomEffect(name, effTyp.getTypeArguments())
+ }
+
+ console.log(`${name} result effect is unhandled`)
+ return []
+ },
+ })
+}
+
+export const evalList = async (ctx: Ctx, effectTyps: Type[]) => {
+ const effectResults: string[] = []
+ for (const item of effectTyps ?? []) {
+ effectResults.push(...(await evaluateType(ctx, item)))
+ }
+ return effectResults
+}
diff --git a/src/index.ts b/src/index.ts
index 34038bf..2fa1f96 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,218 +1,14 @@
-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<string> => 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<string, (...args: Type[]) => any> = {}
-
-const evaluateType = async (effTyp: Type, node: Node): Promise<string[]> => {
- 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
-}
+import { createContext } from './context';
+import { evalList } from './eval';
const main = async () => {
- if (typeRefNode) {
- const resultType = entryPoint?.getType()
-
- if (resultType) {
- const effects = resultType.isTuple() ? resultType.getTupleElements() : [resultType]
- await evalList(effects, typeRefNode)
- }
- }
+ const ctx = createContext()
+ const resultType = ctx.entryPoint.getType()
+ const effects = resultType.isTuple() ? resultType.getTupleElements() : [resultType]
+ await evalList(ctx, effects)
}
main()
- .then(() => {
- // console.log(entryPoint?.print())
- // console.log(resultTypeNode?.print())
- rl.close()
- process.exit(0)
- })
+ .then(() => process.exit(0))
.catch(e => (console.error(e), process.exit(1)))
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..a3cf3bb
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,17 @@
+import { ExportedDeclarations, Node, SourceFile, Type, TypeChecker } from "ts-morph"
+
+export interface Ctx {
+ sourceFile: SourceFile
+ typeChecker: TypeChecker
+ entryPoint: ExportedDeclarations
+ typeToString: (ty: Type | undefined) => string
+
+ createResult: (ty: string) => [string, Node | undefined]
+ getResultExpr: (key?: string) => string
+ printResultNode: () => void
+
+ addCustomEffect: (name: string, expr: string) => void
+ runCustomEffect: (name: string, args: Type[]) => Promise<string[]>
+ hasCustomEffect: (name: string) => boolean
+}
+