aboutsummaryrefslogtreecommitdiff
path: root/src/runtime.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime.ts')
-rw-r--r--src/runtime.ts135
1 files changed, 135 insertions, 0 deletions
diff --git a/src/runtime.ts b/src/runtime.ts
new file mode 100644
index 0000000..d03e7a9
--- /dev/null
+++ b/src/runtime.ts
@@ -0,0 +1,135 @@
+import { Project, ScriptTarget, Type, Node, StringLiteral, TypeFormatFlags, SyntaxKind } from 'ts-morph'
+import path from 'path'
+import fs from 'fs'
+
+const project = new Project({
+ compilerOptions: {
+ target: ScriptTarget.ES3,
+ },
+})
+
+const typeChecker = project.getTypeChecker()
+
+const sourceFile = project.addSourceFileAtPath(path.resolve("./src/index.ts"))
+
+sourceFile.addStatements('type Foobar = "aaaaaaaa";')
+
+const entryPoint = sourceFile.getExportedDeclarations().get('main')?.[0]
+
+const typeToString = (ty: Type | undefined): string =>
+ ty ? typeChecker.compilerObject.typeToString(ty.compilerType) : ''
+
+const getPropertyType = (n: Node, prop: string): Type | undefined => {
+ const tt = typeChecker.getTypeAtLocation(n)
+ const propSym = tt.getProperty(prop)
+ const ty = propSym && typeChecker.getTypeOfSymbolAtLocation(propSym, n)
+ return ty
+}
+
+const typeRefNode = entryPoint?.getLastChild()
+
+const RESULT_TYPE_NAME = '__$result'
+
+const [statement] = sourceFile.addStatements(`type ${RESULT_TYPE_NAME} = {}`)
+
+const addResult = (name: string, ty: string) => {
+ if (statement.isKind(SyntaxKind.TypeAliasDeclaration)) {
+ const value = statement.getChildAtIndex(3)
+ if (value.isKind(SyntaxKind.TypeLiteral)) {
+ value.addProperty({
+ name: JSON.stringify(name),
+ type: `{ output: ${ty} }`,
+ })
+ }
+ }
+}
+
+const accumulateResults = (effTyp: Type, node: Node): string[] => {
+ const name = effTyp.getSymbol()?.getName()
+
+ switch (name) {
+ case 'ReadFile':
+ const [pathTyp] = effTyp.getTypeArguments()
+ const filePath = JSON.parse(typeToString(pathTyp))
+ const contents = fs.readFileSync(filePath, 'utf-8')
+ const hash = Math.random().toFixed(8).slice(2)
+ addResult(hash, JSON.stringify(contents))
+ return [hash]
+
+ case 'ChainIO':
+ const inputTyp = effTyp.getProperty('input')?.getTypeAtLocation(node)
+ const inputResults = inputTyp && accumulateResults(inputTyp, node)
+ return [...(inputResults ?? [])]
+ default:
+ return []
+ }
+}
+
+const evalAccumulator = (effNode: Node, node: Node) => {
+ const effTyp = effNode.getType()
+ const name = effTyp.getSymbol()?.getName()
+
+ switch (name) {
+ case 'PrintString':
+ case 'Print':
+ console.log(...effTyp.getTypeArguments().map(typeToString));
+ return null
+
+ case 'ReadFile':
+ const [hash] = accumulateResults(effTyp, node)
+ effNode.replaceWithText(`${RESULT_TYPE_NAME}[${JSON.stringify(hash)}]`)
+ return null
+
+ case 'WriteFile':
+ const [pathTyp, contentsTyp] = effTyp.getTypeArguments()
+ const filePath = JSON.parse(typeToString(pathTyp))
+ const contents = JSON.parse(typeToString(contentsTyp))
+ fs.writeFileSync(filePath, contents)
+ return null
+
+ case 'ChainIO':
+ const inputTyp = effTyp.getProperty('input')?.getTypeAtLocation(node)
+ const chainToKind = effTyp.getProperty('chainTo')?.getTypeAtLocation(node)
+ console.log('wow')
+
+ const [hashRes] = inputTyp ? accumulateResults(inputTyp, node) : []
+ const chainRes = `(${typeToString(chainToKind)} & { input: ${RESULT_TYPE_NAME}[${JSON.stringify(hashRes)}]['output'] })['return']`
+
+ const updateEffNode = effNode.replaceWithText(chainRes)
+
+ evalAccumulator(updateEffNode, node)
+ return null
+ default:
+ console.log(`${name} effect is unhandled`)
+ }
+
+ return null
+}
+
+if (typeRefNode) {
+ const resultType = entryPoint?.getType()
+
+ if (typeRefNode && entryPoint && resultType?.getSymbol()?.getName() === 'Program') {
+ const exitCodeTy = getPropertyType(typeRefNode, 'exitCode')
+ const effectTypes = getPropertyType(typeRefNode, 'effects')
+ if (effectTypes?.isTuple()) {
+ console.log('Effects to run', effectTypes?.getTupleElements().map(eff => typeToString(eff)))
+
+ const effectNodes = entryPoint.getChildrenOfKind(SyntaxKind.TypeReference)
+ .flatMap(n => n.getChildrenOfKind(SyntaxKind.TupleType))
+ .flatMap(tt => tt.getChildrenOfKind(SyntaxKind.SyntaxList))
+ .flatMap(n => n.getChildren())
+ .filter(n => !n.isKind(SyntaxKind.CommaToken))
+
+ effectNodes.flatMap(n => evalAccumulator(n, typeRefNode))
+ }
+
+ console.log('Exited with code', exitCodeTy?.getLiteralValue())
+ } else {
+ const ty = typeChecker.getTypeAtLocation(typeRefNode)
+ console.log(typeToString(ty))
+ }
+}
+
+console.log(entryPoint?.print())
+