From 77dfc8b182fc16db4af40556e42e0245a5a2b9d8 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 9 May 2025 16:05:12 +0200 Subject: [PATCH] split 'environment' example up into library and interactive prompt --- examples/environment.js | 266 +------------------------------------- examples/prompt/prompt.js | 243 ++++++++++++++++++++++++++++++++++ lib/environment/env.js | 60 +++++++++ 3 files changed, 308 insertions(+), 261 deletions(-) create mode 100644 examples/prompt/prompt.js create mode 100644 lib/environment/env.js diff --git a/examples/environment.js b/examples/environment.js index ba6e547..c30f9c4 100644 --- a/examples/environment.js +++ b/examples/environment.js @@ -1,265 +1,9 @@ -import { number, select } from "@inquirer/prompts"; -import { makeCompareFn } from "../lib/compare/dynamic.js"; -import { compareTypes } from "../lib/compare/type.js"; -import { assignFn } from "../lib/generics/generics.js"; -import { getInst, getType, newDynamic } from "../lib/primitives/dynamic.js"; -import { Bool, Double, Int, Type, UUID } from "../lib/primitives/primitive_types.js"; -import { eqType, getSymbol } from "../lib/primitives/type.js"; +import { module2Env } from "../lib/environment/env.js"; +import { Dynamic } from "../lib/primitives/primitive_types.js"; import { ModuleStd } from "../lib/stdlib.js"; -import { emptyDict, get, set } from "../lib/structures/dict.js"; -import { fold as foldList } from "../lib/structures/list.js"; -import { add, emptySet, fold as foldSet } from "../lib/structures/set.js"; -import { symbolFunction } from "../lib/structures/type_constructors.js"; -import { pretty, prettyT } from "../lib/util/pretty.js"; -import { genUUID } from "../lib/util/random.js"; +import { listInstances } from "./prompt/prompt.js"; -// console.log(ModuleStd); +const typeDict = module2Env(ModuleStd); -const addEntry = dict => i => t => { - const setOfInstances = get(dict)(t) || emptySet(makeCompareFn(t)); - return set(dict)(t)(add(setOfInstances)(i)); -}; - -const typeDict = foldList(typeDict => dynamic => { - try { - const t = getType(dynamic); - // add instance to type: - return addEntry( - addEntry(typeDict)(getInst(dynamic))(t) - )(t)(Type); - } catch (e) { - console.log('warning:', e.message); - return typeDict; - } -})(emptyDict(compareTypes))(ModuleStd); - -const functions = foldList(functions => dynamic => { - if (getSymbol(getType(dynamic)) === symbolFunction) { - return [...functions, dynamic]; - } - return functions; -})([])(ModuleStd); - -// console.log(typeDict); -// console.log(functions); - -const defaultSelectOptions = { - pageSize: 20, -} - -const selectInstance = async (type, msg) => { - const instances = get(typeDict)(type) || emptySet(makeCompareFn(type)); - const choices = foldSet(acc => instance => { - return [...acc, { - value: instance, - name: pretty(instance), - }]; - })(["(go back)", "(new)"])(instances); - const choice = await select({ - message: `${msg} ${pretty(type)}:`, - choices, - ...defaultSelectOptions, - }); - if (choice === "(go back)") { - return; - } - if (choice === "(new)") { - return createInstance(type); - } - return choice; -} - -const listInstances = async type => { - const choice = await selectInstance(type, "instances of"); - if (choice === undefined) { - return; - } - await proxyDynamic(newDynamic(choice)(type)); - return await listInstances(type); -} - -const proxyDynamic = async dynamic => { - const type = getType(dynamic) - if (eqType(type)(Type)) { - return listTypeOptions(getInst(dynamic)); - } - if (getSymbol(type) === symbolFunction) { - return listFunctionOptions(dynamic); - } - return listInstanceOptions(dynamic); -} - -const listTypeOptions = async type => { - const choice = await select({ - message: `type ${pretty(type)}:`, - choices: [ - "(go back)", - "new", - "list instances", - "treat as instance", - ], - }); - if (choice === "(go back)") { - return; - } - if (choice === "new") { - const i = await createInstance(type); - if (i !== undefined) { - const dynamic = newDynamic(i)(type); - await proxyDynamic(dynamic); - } - } - if (choice === "list instances") { - await listInstances(type); - } - if (choice === "treat as instance") { - await listInstanceOptions(newDynamic(type)(Type)); - } - return await listTypeOptions(type); -} - -const listInstanceOptions = async dynamic => { - const choice = await select({ - message: `instance ${pretty(dynamic)}:`, - choices: [ - "(go back)", - "transform", - "get type", - ], - }); - if (choice === "(go back)") { - return; - } - if (choice === "transform") { - await transform(dynamic); - } - if (choice === "get type") { - await listTypeOptions(getType(dynamic)); - } - return await listInstanceOptions(dynamic); -} - -const listFunctionOptions = async dynamic => { - const choice = await select({ - message: `function ${pretty(dynamic)}:`, - choices: [ - "(go back)", - "call", - ], - }); - if (choice === "(go back)") { - return; - } - if (choice === "call") { - await call(dynamic); - } -} - -const createInstance = async type => { - if (eqType(type)(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); - } - if (eqType(type)(Double)) { - const n = await number({ - message: `enter a number (leave empty to go back):`, - step: 'any', - }); - return n; - } - if (eqType(type)(Bool)) { - const b = await select({ - message: `select:`, - choices: [ - {value: false, name: 'false'}, - {value: true, name: 'true'}, - ], - }); - return b; - } - if (eqType(type)(UUID)) { - const descr = await input({message: "enter UUID description:"}); - return descr + '__' + genUUID(16); - } - console.log("no prompt handler for creating new", prettyT(type)); -}; - -const transform = async dynamic => { - const enabled = foldList(enabled => fun => { - try { - const outType = assignFn(getType(fun), getType(dynamic)); - return [...enabled, {fun, outType}]; - } catch (e) { - // console.log('warning:', e); - return enabled; - } - })([])(functions); - - const choice = await select({ - message: `select:`, - choices: [ - "(go back)", - ...enabled.map(({fun, outType}) => ({ - value: {fun, outType}, - name: `${getInst(fun).name} ${pretty(getInst(dynamic))} :: ${pretty(outType)}`, - })), - ], - }); - - if (choice === "(go back)") { - return; - } - const {fun, outType} = choice; - const outValue = getInst(fun)(getInst(dynamic)); - await proxyDynamic(newDynamic(outValue)(outType)); - return transform(dynamic); -} - -const call = async dynamic => { - const funType = getType(dynamic); - const allTypes = get(typeDict)(Type); - - // compatible input types - const inTypes = foldSet(types => inType => { - try { - const outType = assignFn(funType, inType); // may throw - return [...types, {inType, outType}]; - } catch (e) { - return types; - } - })([])(allTypes); - - const choice = inTypes.length === 1 - ? inTypes[0] - : await select({ - message: `types servable as input to ${pretty(funType)}:`, - choices: [ - "(go back)", - ...inTypes.map(({inType, outType}) => ({ - value: {inType, outType}, - name: pretty(inType), - })), - ], - }); - - if (choice === "(go back)") { - return; - } - const inValue = await selectInstance(choice.inType, "select input value of type"); - if (inValue === undefined) { - return; - } - const outValue = getInst(dynamic)(inValue); - await proxyDynamic(newDynamic(outValue)(choice.outType)); - return call(dynamic); -} - -await listInstances(Type); -// await transform(newDynamic(5)(Double)); +await listInstances(typeDict, Dynamic); diff --git a/examples/prompt/prompt.js b/examples/prompt/prompt.js new file mode 100644 index 0000000..abc5af0 --- /dev/null +++ b/examples/prompt/prompt.js @@ -0,0 +1,243 @@ +import { number, select } from "@inquirer/prompts"; + +import { makeCompareFn } from "../../lib/compare/dynamic.js"; +import { getFunctions, getInstances, getTypes, growEnv } from "../../lib/environment/env.js"; +import { assignFn } from "../../lib/generics/generics.js"; +import { getInst, getType, newDynamic } from "../../lib/primitives/dynamic.js"; +import { Bool, Double, Int, SymbolDynamic, Type, UUID } from "../../lib/primitives/primitive_types.js"; +import { eqType, getSymbol } from "../../lib/primitives/type.js"; +import { ModuleStd } from "../../lib/stdlib.js"; +import { fold as foldList } from "../../lib/structures/list.js"; +import { fold as foldSet } from "../../lib/structures/set.js"; +import { symbolFunction } from "../../lib/structures/type_constructors.js"; +import { pretty, prettyT } from "../../lib/util/pretty.js"; +import { genUUID } from "../../lib/util/random.js"; + +const defaultSelectOptions = { + pageSize: 13, +} + +const proxyDynamic = async (env, dynamic) => { + const newEnv = growEnv(env)(dynamic); + const type = getType(dynamic) + if (eqType(type)(Type)) { + return listTypeOptions(newEnv, getInst(dynamic)); + } + if (getSymbol(type) === symbolFunction) { + return listFunctionOptions(newEnv, dynamic); + } + if (getSymbol(type) === SymbolDynamic) { + return proxyDynamic(newEnv, getInst(dynamic)); + } + return listInstanceOptions(newEnv, dynamic); +} + +const selectInstance = async (env, type, msg) => { + const instances = getInstances(env)(type); + const choices = foldSet(acc => instance => { + return [...acc, { + value: instance, + name: pretty(instance), + }]; + })(["(go back)", "(new)"])(instances); + const choice = await select({ + message: `${msg} ${pretty(type)}:`, + choices, + ...defaultSelectOptions, + }); + if (choice === "(go back)") { + return; + } + if (choice === "(new)") { + return createInstance(type); + } + return choice; +} + +export const listInstances = async (env, type) => { + const choice = await selectInstance(env, type, "instances of"); + if (choice === undefined) { + return; + } + await proxyDynamic(env, newDynamic(choice)(type)); + return await listInstances(env, type); +} + +const listTypeOptions = async (env, type) => { + const choice = await select({ + message: `type ${pretty(type)}:`, + choices: [ + "(go back)", + "new", + "list instances", + "treat as instance", + ], + ...defaultSelectOptions, + }); + if (choice === "(go back)") { + return; + } + if (choice === "new") { + const i = await createInstance(type); + if (i !== undefined) { + const dynamic = newDynamic(i)(type); + await proxyDynamic(env, dynamic); + } + } + if (choice === "list instances") { + await listInstances(env, type); + } + if (choice === "treat as instance") { + await listInstanceOptions(newDynamic(type)(Type)); + } + return await listTypeOptions(env, type); +} + +const listInstanceOptions = async (env, dynamic) => { + const choice = await select({ + message: `instance ${pretty(dynamic)}:`, + choices: [ + "(go back)", + "transform", + "get type", + ], + ...defaultSelectOptions, + }); + if (choice === "(go back)") { + return; + } + if (choice === "transform") { + await transform(env, dynamic); + } + if (choice === "get type") { + await listTypeOptions(env, getType(dynamic)); + } + return await listInstanceOptions(env, dynamic); +} + +const listFunctionOptions = async (env, dynamic) => { + const choice = await select({ + message: `function ${pretty(dynamic)}:`, + choices: [ + "(go back)", + "call", + ], + ...defaultSelectOptions, + }); + if (choice === "(go back)") { + return; + } + if (choice === "call") { + await call(env, dynamic); + } +} + +const createInstance = async (type) => { + if (eqType(type)(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); + } + if (eqType(type)(Double)) { + const n = await number({ + message: `enter a number (leave empty to go back):`, + step: 'any', + }); + return n; + } + if (eqType(type)(Bool)) { + const b = await select({ + message: `select:`, + choices: [ + {value: false, name: 'false'}, + {value: true, name: 'true'}, + ], + ...defaultSelectOptions, + }); + return b; + } + if (eqType(type)(UUID)) { + const descr = await input({message: "enter UUID description:"}); + return descr + '__' + genUUID(16); + } + console.log("no prompt handler for creating new", prettyT(type)); +}; + +const transform = async (env, dynamic) => { + const allFunctions = getFunctions(env); + const enabled = foldList(enabled => fun => { + try { + const outType = assignFn(getType(fun), getType(dynamic)); + return [...enabled, {fun, outType}]; + } catch (e) { + // console.log('warning:', e); + return enabled; + } + })([])(allFunctions); + + const choice = await select({ + message: `select:`, + choices: [ + "(go back)", + ...enabled.map(({fun, outType}) => ({ + value: {fun, outType}, + name: `${getInst(fun).name} ${pretty(getInst(dynamic))} :: ${pretty(outType)}`, + })), + ], + ...defaultSelectOptions, + }); + + if (choice === "(go back)") { + return; + } + const {fun, outType} = choice; + const outValue = getInst(fun)(getInst(dynamic)); + await proxyDynamic(env, newDynamic(outValue)(outType)); + return transform(env, dynamic); +} + +const call = async (env, funDynamic) => { + const funType = getType(funDynamic); + const allTypes = getTypes(env); + + // compatible input types + const inTypes = foldSet(types => inType => { + try { + const outType = assignFn(funType, inType); // may throw + return [...types, {inType, outType}]; + } catch (e) { + return types; + } + })([])(allTypes); + + const choice = inTypes.length === 1 + ? inTypes[0] + : await select({ + message: `types assignable to ${pretty(funType.params[0](funType))}:`, + choices: [ + "(go back)", + ...inTypes.map(({inType, outType}) => ({ + value: {inType, outType}, + name: pretty(inType), + })), + ], + ...defaultSelectOptions, + }); + + if (choice === "(go back)") { + return; + } + const inValue = await selectInstance(env, choice.inType, "select input value of type"); + if (inValue === undefined) { + return; + } + const envWithInput = growEnv(env)(newDynamic(inValue)(choice.inType)); + const outValue = getInst(funDynamic)(inValue); + await proxyDynamic(envWithInput, newDynamic(outValue)(choice.outType)); + return call(env, funDynamic); +} diff --git a/lib/environment/env.js b/lib/environment/env.js new file mode 100644 index 0000000..c8e9cd9 --- /dev/null +++ b/lib/environment/env.js @@ -0,0 +1,60 @@ +import { makeCompareFn } from "../compare/dynamic.js"; +import { compareTypes } from "../compare/type.js"; +import { getInst, getType, newDynamic } from "../primitives/dynamic.js"; +import { Dynamic, Type } from "../primitives/primitive_types.js"; +import { getSymbol } from "../primitives/type.js"; +import { emptyDict, get, get as getDict, set as setDict } from "../structures/dict.js"; +import { fold as foldList } from "../structures/list.js"; +import { add, emptySet, fold as foldSet } from "../structures/set.js"; +import { symbolFunction } from "../structures/type_constructors.js"; + +const addEntry = env => i => t => { + const setOfInstances = getInstances(env)(t); + return setDict(env)(t)(add(setOfInstances)(i)); +}; + +export const growEnv = env => dynamic => { + const t = getType(dynamic); + const typeDictWithEntry = addEntry(env )(getInst(dynamic))(t ); + const typeDictWithType = addEntry(typeDictWithEntry)(t )(Type ); + const typeDictWithDynamic = addEntry(typeDictWithType )(dynamic )(Dynamic); + return typeDictWithDynamic +}; + +export const module2Env = module => { + return foldList(typeDict => dynamic => { + try { + return growEnv(typeDict)(dynamic); + } catch (e) { + console.log('warning:', e.message); + return typeDict; + } + })(emptyDict(compareTypes))(module); +}; + +export const getInstances = env => type => { + return getDict(env)(type) || emptySet(makeCompareFn(type)); +}; + +export const getTypes = env => { + return getDict(env)(Type) || emptySet(compareTypes); +}; + +export const getFunctions = env => { + const types = getTypes(env); + return foldSet(types => type => { + if (getSymbol(type) === symbolFunction) { + return [ + ...types, + ...foldSet + (functions => fn => [ + ...functions, + newDynamic(fn)(type), + ]) + ([]) + (getInstances(env)(type)), + ]; + } + return types; + })([])(types); +}; \ No newline at end of file