diff options
Diffstat (limited to 'src/eval.ts')
| -rw-r--r-- | src/eval.ts | 221 |
1 files changed, 36 insertions, 185 deletions
diff --git a/src/eval.ts b/src/eval.ts index 8b53982..fdffed7 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -1,22 +1,28 @@ import { Type } from 'ts-morph' -import { promises as fs } from 'fs' -import readline from 'readline' import { match } from './util' import { Ctx } from './types' +import * as builtins from './eval-env/builtins' -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false, +type EffDefn = { + default: (ctx: Ctx, args: Type[]) => Record<string, () => Promise<string[]>>, + cleanup: () => void, +} +const mergeEffDefns = (a: EffDefn, b: EffDefn): EffDefn => ({ + default: (ctx: Ctx, args: Type[]) => ({ + ...a.default(ctx, args), + ...b.default(ctx, args), + }), + cleanup: () => { + a.cleanup() + b.cleanup() + }, }) -export const cleanup = () => { - rl.close() -} +const cleanupActions = new Set<() => void>() +export const cleanup = () => cleanupActions.forEach(f => f()) -const readLineFromStdin = (): Promise<string> => - new Promise((res) => rl.on('line', res)) +let prevEnv: string export const evaluateType = async ( ctx: Ctx, @@ -28,191 +34,36 @@ export const evaluateType = async ( // console.log(ctx.typeToString(effTyp)) // console.log(name, args.map(ctx.typeToString)) - return match(name, { - DefineEffect: async () => { - const [nameTyp, exprTyp] = args - const name = nameTyp?.getLiteralValue() as string - const exprStr = exprTyp?.getLiteralValue() as string - - ctx.addCustomEffect(name, exprStr) - return [] - }, - - CreateRef: async () => { - const val = ctx.typeToString(args[0]) - const refKey = ctx.createRef(val) - const [resultKey, _] = ctx.createResult(JSON.stringify(refKey)) - return [resultKey] - }, - - GetRef: async () => { - const refKey = ctx.getTypeValue(args[0]) - const val = ctx.getRef(refKey) - if (!val) throw new Error('Ref has been deleted') - const [resultKey, _] = ctx.createResult(val) - return [resultKey] - }, - - SetRef: async () => { - const [ keyTy, valTyp ] = args - ctx.setRef(ctx.getTypeValue(keyTy), ctx.typeToString(valTyp)) - return [] - }, - - DeleteRef: async () => { - ctx.deleteRef(ctx.getTypeValue(args[0])) - return [] - }, - - Pure: async () => { - const [valTyp] = args - const [resultKey, _] = ctx.createResult(ctx.typeToString(valTyp)) - return [resultKey] - }, - - Print: async () => { - console.log(...args.map(ctx.typeToString)) - return [] - }, - - PutString: async () => { - const [strinTyp] = args - const typString = ctx.getTypeValue(strinTyp) ?? ctx.typeToString(strinTyp) - process.stdout.write(typString) - return [] - }, - - Debug: async () => { - const [labelTyp, valueTyp] = args - const label = ctx.getTypeValue(labelTyp) - const value = ctx.typeToString(valueTyp) - console.log(label, value) - const [resultKey, _] = ctx.createResult(JSON.stringify(value)) - return [resultKey] - }, - - ReadFile: async () => { - const [pathTyp] = args - const filePath = ctx.getTypeValue(pathTyp) - const contents = await fs.readFile(filePath, 'utf-8') - const [resultKey, _] = ctx.createResult(JSON.stringify(contents)) - return [resultKey] - }, - - WriteFile: async () => { - const [pathTyp, contentsTyp] = args - const filePath = ctx.getTypeValue(pathTyp) - const contents = ctx.getTypeValue(contentsTyp) - await fs.writeFile(filePath, contents) - return [] - }, - - Bind: async () => { - const [inputTyp, chainToKind] = args - const [resultKey] = inputTyp ? await evaluateType(ctx, inputTyp) : [] - - // TODO: Handle resultKey undefined case - 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] = args - const envName = ctx.getTypeValue(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] - }, - - Exit: async () => { - process.exit(args[0] && ctx.getTypeValue(args[0])) - }, - - Try: async () => { - const [effTyp, catchK] = args - - try { - if (!effTyp) throw new Error('wow') - return await evaluateType(ctx, effTyp) - } catch(e) { - const error = JSON.stringify((e as any)?.message ?? e) - const catchResExpr = `(${ctx.typeToString(catchK)} & { input: ${error} })['return']` - const [resultKey, _] = ctx.createResult(catchResExpr) - return [resultKey] - } - }, - - Throw: async () => { - throw args[0] && ctx.getTypeValue(args[0]) - }, - - ReadLine: async () => { - const line = await readLineFromStdin() - const [resultKey, _] = ctx.createResult(`${JSON.stringify(line)}`) - return [resultKey] + const envDefns = await match(ctx.currentEnv, { + node: () => import('./eval-env/node') as Promise<EffDefn>, + 'test.node': async () => mergeEffDefns( + await import('./eval-env/test'), + await import('./eval-env/node'), + ), + _: async () => { + throw new Error(`Invalid env: ${ctx.currentEnv}`) }, + }) - JsExpr: async () => { - const [exprTyp] = args - const exprStr = ctx.getTypeValue(exprTyp) - const result = eval(`JSON.stringify(${exprStr})`) - const [resultKey, _] = ctx.createResult(`${result}`) - return [resultKey] - }, + const { default: envEffects, cleanup } = mergeEffDefns(builtins as unknown as EffDefn, envDefns) - Seq: async () => { - const [effectTyps] = args - const effectResults = await evalList( - ctx, - effectTyps?.getTupleElements() ?? [] - ) - const [resultKey, _] = ctx.createResult(`[ - ${effectResults.map(ctx.getResultExpr).join(', ')} - ]`) - return [resultKey] - }, + // Update cleanup if env has changed + if (prevEnv !== ctx.currentEnv) { + cleanupActions.add(cleanup) + prevEnv = ctx.currentEnv + } - Do: async () => { - const [effectTyps] = args - const effectResults = await evalList( - ctx, - effectTyps?.getTupleElements() ?? [] - ) - // TODO: Use last type's result instead of last result key - const lastResKey = effectResults[effectResults.length - 1] - const [resultKey, _] = ctx.createResult( - `(${ctx.getResultExpr(lastResKey)})['output']` - ) - return [resultKey] - }, + return match(name, { + // TODO: Allow overriding effects + ...envEffects(ctx, args), _: async () => { if (name && ctx.hasCustomEffect(name)) { return ctx.runCustomEffect(name, args) } - console.log(`${name} effect is not handled`) console.log(ctx.typeToString(effTyp)) - // TODO: Maybe throw? - return [] + throw new Error(`${name} effect is not handled`) }, }) } |
