import { select, number, input } from '@inquirer/prompts'; import { ModulePoint } from "../lib/point.js"; import { DefaultMap } from "../util/defaultmap.js"; import { pretty } from '../util/pretty.js'; import { isFunction, prettyT } from '../structures/types.js'; import { ModuleStd } from '../stdlib.js'; import { Double, GenericType, Int, SymbolT, Type } from "../primitives/types.js"; import { eqType } from '../primitives/type.js'; import { Any } from '../typed.js'; import { assign, assignFn, makeGeneric, onlyOccurring } from '../generics/generics.js'; // import {emitKeypressEvents} from 'node:readline'; // // Configure readline to read from stdin // emitKeypressEvents(process.stdin); // process.stdin.setRawMode(true); // console.log('Press any key (ESC to exit)...'); // process.stdin.on('keypress', (str, key) => { // if (key.name === 'escape') { // console.log('Escape key pressed!'); // process.exit(); // } // }); const prettyIT = ({i, t}) => ({ strI: isType(i) ? prettyT(i) : pretty(i), strT: prettyT(t), // strI: pretty(i), // strT: pretty(t), }); const isType = i => i.typeVars || i.symbol; // ctx.types.getdefault(i).has(Type) // || ctx.types.getdefault(i).has(GenericType); class Context { constructor(mod) { this.mod = mod; this.types = new DefaultMap(() => new Set()); // instance to type this.instances = new DefaultMap(() => new Set()); // type to instance for (const {i, t} of mod.l) { const {strI, strT} = prettyIT({i,t}) // console.log(strI, '::', strT); this.types.getdefault(i, true).add(t); this.types.getdefault(i, true).add(Any); this.instances.getdefault(t, true).add(i); this.instances.getdefault(Any, true).add(i); } const addIfFunctionType = (t, originalT, add) => { if (isFunction(t)) { for (const fn of this.instances.getdefault(originalT)) { add(fn); } } } this.functions = []; for (const type of this.instances.getdefault(Type)) { addIfFunctionType(type, type, fn => this.functions.push({fn, type})); } this.genericFunctions = []; for (const genericType of this.instances.getdefault(GenericType)) { addIfFunctionType(genericType.type, genericType, fn => this.genericFunctions.push({fn, genericType})); } } addToCtx({i, t}) { return new Context({l:[ ...this.mod.l, {i, t}, ]}); } } let ctx = new Context({l:[ ...ModuleStd.l, ...ModulePoint.l, ]}); const toChoices = ([i, types]) => { return [...types].map(t => { const {strI, strT} = prettyIT({i, t}); return { value: {i, t}, name: strI, description: ` :: ${strT}`, short: `${strI} :: ${strT}`, }; }); }; async function topPrompt() { const action = await select({ message: "What do you want to do?", choices: [ "list all types", "list all generic types", "list all functions", "list all", ], }); if (action === "list all types") { await listInstances(Type); } if (action === "list all generic types") { await listInstances(GenericType); } if (action === "list all functions") { await listAllFunctions(); } if (action === "list all") { await listAll(); } return topPrompt(); } async function listAllFunctions() { const choice = await select({ message: "select function:", choices: [ "(go back)", ...ctx.functions.flatMap(({fn, type}) => toChoices([fn, [type]])), ...ctx.genericFunctions.flatMap(({fn, genericType}) => toChoices([fn, [genericType]])), ], }); if (choice === "(go back)") { return; } const {i, t} = choice; await functionOptions(i, t); return listAllFunctions(); } async function typeOptions(t, tt) { const choice = await select({ message: `actions for type ${prettyT(t)} :: ${prettyT(tt)}`, choices: [ "(go back)", "create instance", "list instances", // "list outgoing functions", // "list incoming functions", "print raw", "treat as instance", ], }); if (choice === "(go back)") { return; } else if (choice === "create instance") { const i = await createInstance(t); if (i !== undefined) { ctx = ctx.addToCtx({i, t}); return instanceOrTypeOrFnOptions({i, t}); } } else if (choice === "list instances") { await listInstances(t); } else if (choice === "print raw") { console.log(pretty(t)); } else if (choice === "treat as instance") { await instanceOptions(t, tt) } else { console.log("unimplemented:", choice); } return typeOptions(t, tt); } async function listAll() { const choice = await select({ message: `all instances:`, choices: [ "(go back)", ... [...ctx.types.m.keys()].flatMap(i => toChoices([i, ctx.types.getdefault(i)])), ], }) if (choice === "(go back)") { return; } return instanceOrTypeOrFnOptions(choice); } async function instanceOrTypeOrFnOptions({i, t}) { if (t.typeVars) { if (isFunction(t.type)) { return functionOptions(i, t); } } if (isFunction(t)) { return functionOptions(i, t); } if (t === Type || t === GenericType) { return typeOptions(i, t); } return instanceOptions(i,t); } async function listInstances(t) { const choice = await select({ message: `instances of ${prettyT(t)}:`, choices: [ "(go back)", ... [...ctx.instances.getdefault(t)].flatMap(i => toChoices([i, [t]])), ], }); if (choice === "(go back)") { return; } return instanceOrTypeOrFnOptions(choice); } async function listTypes(i) { const {strI} = prettyIT({i,t:Type}); const choice = await select({ message: `type(s) of ${strI}:`, choices: [ "(go back)", ... [...ctx.types.getdefault(i)].flatMap(t => toChoices([t, ctx.types.getdefault(t)])), ], }); if (choice === "(go back)") { return; } const {i: chosenType, t: typeOfChosenType} = choice; await typeOptions(chosenType, typeOfChosenType); return listTypes(i); } async function functionOptions(fn, fnT) { const {strI, strT} = prettyIT({i: fn, t: fnT}); const choice = await select({ message: `actions for function ${strI} :: ${strT}`, choices: [ "(go back)", "call", "treat as instance", ], }); if (choice === "(go back)") { return; } if (choice === "call") { await callFunction(fn, fnT); } if (choice === "treat as instance") { await instanceOptions(fn, fnT); } return functionOptions(fn, fnT); } async function createInstance(t) { if (t.typeVars && t.typeVars.size === 0) { t = t.type; // can treat as non-generic } if (eqType(t)(Int)) { const n = await number({ message: `enter an integer (leave empty to go back):`, step: 1, // only integers }); if (n === undefined) { return; } return BigInt(n); } else if (eqType(t)(Double)) { const n = await number({ message: `enter a number (leave empty to go back):`, step: 'any', }); return n; } else if (eqType(t)(SymbolT)) { console.log("Note: you are creating a new Symbol. Even if the description matches that of another symbol (e.g., \"Int\"), a new Symbol will be created that is unique and only equal to itself."); const symbolDescr = await input({message: "enter symbol description:"}); return Symbol(symbolDescr); } else { console.log("no prompt handler for creating new", prettyT(t)); } } async function callFunction(fn, fnT) { const {strI, strT} = prettyIT({i: fn, t: fnT}); let choices; let inType; if (fnT.typeVars) { // generic choices = [...ctx.types.m.entries()].flatMap(([i, types]) => { return [...types].flatMap(t => { const genT = t.typeVars ? t : makeGeneric(() => t); let assignedFnType; try { assignedFnType = assignFn(fnT, genT); } catch (e) { if (e.message.startsWith("cannot unify")) { // console.warn(e); return []; } throw e; } const assignedInType = onlyOccurring(assignedFnType.type.params[0], assignedFnType.typeVars); if (assignedInType.typeVars.size > 0) { return toChoices([i, [assignedInType]]); } else { return toChoices([i, [assignedInType.type]]); } }); }); inType = onlyOccurring(fnT.type.params[0], fnT.typeVars) } else { inType = fnT.params[0]; choices = [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)])); } const choice = await select({ message: `select parameter of type ${prettyT(inType)} for function ${strI} :: ${strT}`, choices: [ "(go back)", "(new)", ...choices, ], }); let i, t; if (choice === "(go back)") { return; } else if (choice === "(new)") { i = await createInstance(inType); t = inType; if (i === undefined) { return; } } else { i = choice.i; t = choice.t; } const genT = t.typeVars ? t : makeGeneric(() => t); const assignedFnType = assignFn(fnT, genT); await apply(i, fn, assignedFnType); return callFunction(fn, fnT); } async function instanceOptions(i,t) { const {strI, strT} = prettyIT({i,t}); const choice = await select({ message: `actions for instance ${strI} :: ${strT}`, choices: [ "(go back)", "transform", "list type(s)", ] }) if (choice === "(go back)") { return; } if (choice === "transform") { await transform(i, t) } if (choice === "list type(s)") { await listTypes(i); } return await instanceOptions(i,t); } async function transform(i, t) { const {strI, strT} = prettyIT({i, t}); // console.log(ctx.functionsFrom.getdefault(t)); const genT = t.typeVars ? t : makeGeneric(() => t); const choice = await select({ message: `choose transformation to perform on ${strI} :: ${strT}`, choices: [ "(go back)", ...ctx.functions .filter(({type}) => { // console.log(type.params[0], t); return eqType(type.params[0])(t) }) .flatMap(({fn, type}) => toChoices([fn, [type]])), ...ctx.genericFunctions .flatMap(({fn, genericType}) => { let fnType; try { fnType = assignFn(genericType, genT); } catch (e) { if (e.message.startsWith("cannot unify")) { // console.warn(e); return []; } throw e; } return toChoices([fn, [fnType]]); }), ], }); if (choice === "(go back)") { return; } const {i:fn,t:fnT} = choice; await apply(i, fn, fnT); return transform(i, t); } async function apply(i, fn, fnT) { const result = fn(i); // console.log(fn, '(', i, ')', '=', result); let resultType; // console.log(fnT); if (fnT.typeVars) { resultType = onlyOccurring(fnT.type.params[1], fnT.typeVars); } else { resultType = fnT.params[1]; } // update context with newly produced value ctx = ctx .addToCtx({i: result, t: resultType}) .addToCtx({i: resultType, t: resultType.typeVars ? GenericType : Type}); const {strI: strResult, strT: strResultType} = prettyIT({i: result, t: resultType}); console.log(`result = ${strResult} :: ${strResultType}`); return instanceOrTypeOrFnOptions({i: result, t: resultType}); } topPrompt();