dope2/examples/main.js

447 lines
12 KiB
JavaScript

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 "../primitives/types.js";
import { 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);
if (t.typeVars) {
// console.log("generic:", prettyT(t));
this.types.getdefault(t, true).add(GenericType);
}
else {
// console.log("non-generic:", prettyT(t));
this.types.getdefault(t, true).add(Type);
}
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, [inType]]));
}
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 callFunction(fn, fnT);
}
}
else {
i = choice.i;
t = choice.t;
}
const genT = t.typeVars ? t : makeGeneric(() => t);
const genFnT = fnT.typeVars ? fnT : makeGeneric(() => fnT);
const assignedFnType = assignFn(genFnT, 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);
let resultType;
if (fnT.typeVars) {
const res = onlyOccurring(fnT.type.params[1], fnT.typeVars);
resultType = res.typeVars.size > 0 ? res : res.type;
}
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();