interactive prompt can handle polymorphic types
This commit is contained in:
parent
a0e3aa0cb3
commit
4a4983f693
20 changed files with 485 additions and 276 deletions
298
scripts/main.js
298
scripts/main.js
|
|
@ -4,8 +4,37 @@ 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, Int, SymbolT, Type } from "../primitives/types.js";
|
||||
import { eqType } from '../type.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) {
|
||||
|
|
@ -14,64 +43,44 @@ class Context {
|
|||
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);
|
||||
}
|
||||
|
||||
this.functionsFrom = new DefaultMap(() => new Set()); // type to outgoing function
|
||||
this.functionsTo = new DefaultMap(() => new Set()); // type to incoming function
|
||||
|
||||
for (const t of this.instances.m.keys()) {
|
||||
const addIfFunctionType = (t, originalT, add) => {
|
||||
if (isFunction(t)) {
|
||||
// 't' is a function signature
|
||||
for (const fn of this.instances.getdefault(t)) {
|
||||
this.functionsFrom.getdefault(t.params[0], true).add(fn);
|
||||
this.functionsTo .getdefault(t.params[1], true).add(fn);
|
||||
for (const fn of this.instances.getdefault(originalT)) {
|
||||
add(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this.typeVarAssigns = new Map();
|
||||
|
||||
// for (const t of this.instances.m.keys()) {
|
||||
// if (t.typeVars) {
|
||||
// for (const t2 of this.instances.m.keys()) {
|
||||
// const genericT2 = (t2.typeVars === undefined)
|
||||
// ? makeGeneric(() => t2)
|
||||
// : t2;
|
||||
// try {
|
||||
// const unification = unify(t, t2);
|
||||
// console.log(unification);
|
||||
// } catch (e) {
|
||||
// // skip
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
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 prettyIT = ({i, t}) => ({
|
||||
strI: isType(i) ? prettyT(i) : pretty(i),
|
||||
strT: prettyT(t),
|
||||
// strI: pretty(i),
|
||||
// strT: pretty(t),
|
||||
});
|
||||
|
||||
const toChoices = ([i, types]) => {
|
||||
return [...types].map(t => {
|
||||
const {strI, strT} = prettyIT({i, t});
|
||||
|
|
@ -82,50 +91,33 @@ const toChoices = ([i, types]) => {
|
|||
short: `${strI} :: ${strT}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const isType = i => ctx.types.getdefault(i).has(Type);
|
||||
};
|
||||
|
||||
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);
|
||||
// await listAllTypes();
|
||||
}
|
||||
if (action === "list all generic types") {
|
||||
await listInstances(GenericType);
|
||||
}
|
||||
if (action === "list all functions") {
|
||||
await listAllFunctions();
|
||||
}
|
||||
if (action === "list all") {
|
||||
await listAllInstances();
|
||||
await listAll();
|
||||
}
|
||||
return topPrompt();
|
||||
}
|
||||
|
||||
// async function listAllTypes() {
|
||||
// const choice = await select({
|
||||
// message: "select type:",
|
||||
// choices: [
|
||||
// "(go back)",
|
||||
// ...[...ctx.instances.m.keys()].map(t => ({
|
||||
// value: t,
|
||||
// name: prettyT(t),
|
||||
// })),
|
||||
// ]
|
||||
// });
|
||||
// if (choice === "(go back)") {
|
||||
// return;
|
||||
// }
|
||||
// await typeOptions(choice);
|
||||
// return listAllTypes();
|
||||
// }
|
||||
|
||||
async function listAllFunctions() {
|
||||
const choice = await select({
|
||||
message: "select function:",
|
||||
|
|
@ -150,11 +142,12 @@ async function listAllFunctions() {
|
|||
return listAllFunctions();
|
||||
}
|
||||
|
||||
async function typeOptions(t) {
|
||||
async function typeOptions(t, tt) {
|
||||
const choice = await select({
|
||||
message: `actions for type ${prettyT(t)} :: Type`,
|
||||
message: `actions for type ${prettyT(t)} :: ${prettyT(tt)}`,
|
||||
choices: [
|
||||
"(go back)",
|
||||
"create instance",
|
||||
"list instances",
|
||||
// "list outgoing functions",
|
||||
// "list incoming functions",
|
||||
|
|
@ -165,6 +158,13 @@ async function typeOptions(t) {
|
|||
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);
|
||||
}
|
||||
|
|
@ -172,15 +172,15 @@ async function typeOptions(t) {
|
|||
console.log(pretty(t));
|
||||
}
|
||||
else if (choice === "treat as instance") {
|
||||
await instanceOptions(t, Type)
|
||||
await instanceOptions(t, tt)
|
||||
}
|
||||
else {
|
||||
console.log("unimplemented:", choice);
|
||||
}
|
||||
return typeOptions(t);
|
||||
return typeOptions(t, tt);
|
||||
}
|
||||
|
||||
async function listAllInstances() {
|
||||
async function listAll() {
|
||||
const choice = await select({
|
||||
message: `all instances:`,
|
||||
choices: [
|
||||
|
|
@ -195,11 +195,16 @@ async function listAllInstances() {
|
|||
}
|
||||
|
||||
async function instanceOrTypeOrFnOptions({i, t}) {
|
||||
if (t.typeVars) {
|
||||
if (isFunction(t.type)) {
|
||||
return functionOptions(i, t);
|
||||
}
|
||||
}
|
||||
if (isFunction(t)) {
|
||||
return functionOptions(i, t);
|
||||
}
|
||||
if (isType(i)) {
|
||||
return typeOptions(i);
|
||||
if (t === Type || t === GenericType) {
|
||||
return typeOptions(i, t);
|
||||
}
|
||||
return instanceOptions(i,t);
|
||||
}
|
||||
|
|
@ -230,8 +235,8 @@ async function listTypes(i) {
|
|||
if (choice === "(go back)") {
|
||||
return;
|
||||
}
|
||||
const {i: chosenType} = choice;
|
||||
await typeOptions(chosenType);
|
||||
const {i: chosenType, t: typeOfChosenType} = choice;
|
||||
await typeOptions(chosenType, typeOfChosenType);
|
||||
return listTypes(i);
|
||||
}
|
||||
|
||||
|
|
@ -257,53 +262,98 @@ async function functionOptions(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});
|
||||
const inType = fnT.params[0];
|
||||
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 for function ${strI} :: ${strT}`,
|
||||
message: `select parameter of type ${prettyT(inType)} for function ${strI} :: ${strT}`,
|
||||
choices: [
|
||||
"(go back)",
|
||||
"(new)",
|
||||
... [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)])),
|
||||
...choices,
|
||||
],
|
||||
});
|
||||
let i;
|
||||
let i, t;
|
||||
if (choice === "(go back)") {
|
||||
return;
|
||||
}
|
||||
if (choice === "(new)") {
|
||||
if (eqType(inType)(Int)) {
|
||||
const n = await number({
|
||||
message: `enter an integer (leave empty to go back):`,
|
||||
step: 1, // only integers
|
||||
});
|
||||
if (n === undefined) {
|
||||
return;
|
||||
}
|
||||
i = BigInt(n);
|
||||
}
|
||||
else if (eqType(inType)(Double)) {
|
||||
const n = await number({
|
||||
message: `enter a number (leave empty to go back):`,
|
||||
step: 'any',
|
||||
});
|
||||
if (n === undefined) {
|
||||
return;
|
||||
}
|
||||
i = n;
|
||||
}
|
||||
else if (eqType(inType)(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:"});
|
||||
i = Symbol(symbolDescr);
|
||||
}
|
||||
else {
|
||||
console.log("no prompt handler for creating new", prettyT(inType));
|
||||
return callFunction(fn, fnT);
|
||||
else if (choice === "(new)") {
|
||||
i = await createInstance(inType);
|
||||
t = inType;
|
||||
if (i === undefined) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await apply(i, fn, fnT);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -332,12 +382,35 @@ async function 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.functionsFrom.getdefault(t)].flatMap(fn => toChoices([fn, ctx.types.getdefault(fn)])),
|
||||
|
||||
...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)") {
|
||||
|
|
@ -352,9 +425,18 @@ async function transform(i, t) {
|
|||
async function apply(i, fn, fnT) {
|
||||
const result = fn(i);
|
||||
// console.log(fn, '(', i, ')', '=', result);
|
||||
const resultType = fnT.params[1];
|
||||
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});
|
||||
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});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue