diff options
Diffstat (limited to 'src/runtime.ts')
| -rw-r--r-- | src/runtime.ts | 135 |
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()) + |
