diff --git a/main.js b/main.js index b055154..4dc5327 100644 --- a/main.js +++ b/main.js @@ -1,61 +1,33 @@ import { select } from '@inquirer/prompts'; import { ModulePoint } from "./lib/point.js"; -import { DefaultMap, pretty } from './util.js'; +import { DefaultMap, pretty, prettyT } from './util.js'; import { symbolFunction } from './structures/types.js'; import { ModuleStd } from './stdlib.js'; +import { Type } from './type.js'; class Context { constructor(mod) { - this.functionsFrom = new DefaultMap(() => []); // type to outgoing function - this.functionsTo = new DefaultMap(() => []); // type to incoming function - this.types = new DefaultMap(() => []); // instance to type - this.instances = new DefaultMap(() => []); // type to instance + this.types = new DefaultMap(() => new Set()); // instance to type + this.instances = new DefaultMap(() => new Set()); // type to instance + + this.functionsFrom = new DefaultMap(() => new Set()); // type to outgoing function + this.functionsTo = new DefaultMap(() => new Set()); // type to incoming function for (const {i, t} of mod.l) { - this.types.getdefault(i, true).push(t); - this.instances.getdefault(t, true).push(i); + this.types.getdefault(i, true).add(t); + this.instances.getdefault(t, true).add(i); } - for (const [i, types] of this.instances.m.entries()) { - for (const t of types) { - if (t.symbol === symbolFunction) { - // 'i' is a function - this.functionsFrom.getdefault(t.params[0], true).push(i); - this.functionsFrom.getdefault(t.params[1], true).push(i); + for (const t of this.instances.m.keys()) { + if (t.symbol === symbolFunction) { + // '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); } } - } + } } - - // const callFunctionOnEveryValue = (fn, fnType, indent=1) => { - // const inType = getIn(fnType); - // const outType = getOut(fnType); - // console.log(); - // if (fn.name !== "") { - // console.log("--".repeat(indent), fn, ':', fnType); - // } - // for (const i of this.instances.getdefault(inType)) { - // try { - // const result = fn(i); - // console.log("--".repeat(indent+1), i, '->', result); - // if (this.types.getdefault(outType).includes(Function)) { - // if (indent < 5) { - // callFunctionOnEveryValue(result, outType, indent+2); - // } - // } - // } catch (e) { - // console.log("--".repeat(indent+1), i, `-> (exception: ${e})`); - // } - // } - // } - // for (const fntypes of this.functionsFrom.m.values()) { - // for (const fntype of fntypes) { - // for (const fn of this.instances.getdefault(fntype)) { - // callFunctionOnEveryValue(fn, fntype); - // } - // } - // } - // } } const ctx = new Context({l:[ @@ -63,49 +35,231 @@ const ctx = new Context({l:[ ...ModulePoint.l, ]}); -const makeChoice = ([i, t]) => { - return { - value: {i, t: t[0]}, - name: pretty(i), - description: ` :: ${pretty(t[0])}`, - short: `${pretty(i)} :: ${pretty(t[0])}`, - }; + + +const prettyIT = ({i, t}) => ({ + strI: isType(i) ? prettyT(i) : pretty(i), + strT: prettyT(t), +}) + +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}`, + }; + }); } -let {i,t} = await select({ - message: "Choose value:", - choices: [...ctx.types.entries()].map(makeChoice), -}); +const isType = i => ctx.types.getdefault(i).has(Type); -while (true) { - let fn, fnType; - if (ctx.types.getdefault(t).includes(Function)) { - fn = i; - fnType = t; - const s = await select({ - message: "Select input for function:", - choices: ctx.instances.getdefault(getIn(t)) - .map(i => [i, ctx.types.getdefault(i)]) - .map(makeChoice), - }); - i = s.i; - t = s.t; +async function topPrompt() { + const action = await select({ + message: "What do you want to do?", + choices: [ + "list all types", + "list all functions", + "list all", + ], + }); + if (action === "list all types") { + await listAllTypes(); } - else{ - fn = await select({ - message: "Choose function:", - choices: [...ctx.functionsFrom.getdefault(t).map(fn => ({ - value: fn, - name: pretty(fn), - description: `${pretty(fn)} :: ${pretty(ctx.types.getdefault(fn)[0])}`, - short: `${pretty(fn)} :: ${pretty(ctx.types.getdefault(fn)[0])}`, - }) - )], - }); - fnType = ctx.types.getdefault(fn)[0]; + if (action === "list all") { + await listAllInstances(); } + 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 typeOptions(t) { + const choice = await select({ + message: `actions for type ${prettyT(t)} :: Type`, + choices: [ + "(go back)", + "list instances", + "list outgoing functions", + "list incoming functions", + "treat as instance", + ], + }); + if (choice === "(go back)") { + return; + } + else if (choice === "list instances") { + await listInstances(t); + } + else if (choice === "treat as instance") { + await instanceOptions(t, Type) + } + else { + console.log("unimplemented:", choice); + } + return typeOptions(t); +} + +async function listAllInstances() { + 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.symbol === symbolFunction) { + return functionOptions(i, t); + } + if (isType(i)) { + return typeOptions(i); + } + 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} = choice; + await typeOptions(chosenType); + 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 callFunction(fn, fnT) { + const {strI, strT} = prettyIT({i: fn, t: fnT}); + const inType = fnT.params[0]; + const choice = await select({ + message: `select parameter for function ${strI} :: ${strT}`, + choices: [ + "(go back)", + ... [...ctx.instances.getdefault(inType)].flatMap(i => toChoices([i, ctx.types.getdefault(i)])), + ], + }); + if (choice === "(go back)") { + return; + } + const {i, t} = choice; + await apply(i, fn, fnT); + 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 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)])), + ], + }); + 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); - t = getOut(fnType); - console.log("result =", result, "::", t); - i = result; + const resultType = fnT.params[1]; + const {strI: strResult, strT: strResultType} = prettyIT({i: result, t: resultType}); + console.log(`result = ${strResult} :: ${strResultType}`); + return instanceOrTypeOrFnOptions({i: result, t: resultType}); } + +topPrompt(); diff --git a/primitives/char.js b/primitives/char.js index f84afc5..1bbc9d2 100644 --- a/primitives/char.js +++ b/primitives/char.js @@ -7,5 +7,10 @@ const eq = x => y => x === y; export const ModuleChar = {l:[ {i: Char, t: Type}, - ...typedFnType(eq, fnType => fnType({in: Char, out: fnType({in: Char, out: Bool})})), + ...typedFnType(eq, fnType => + fnType + (Char) + (fnType + (Char) + (Bool))), ]}; diff --git a/primitives/types.js b/primitives/types.js index bb9f0b0..adb5d3e 100644 --- a/primitives/types.js +++ b/primitives/types.js @@ -1,8 +1,7 @@ // to break up dependency cycles, primitive types are defined in their own JS module - export const Int = { symbol: Symbol('Int') , params: [] }; export const Bool = { symbol: Symbol('Bool') , params: [] }; export const Double = { symbol: Symbol('Double'), params: [] }; export const Byte = { symbol: Symbol('Byte') , params: [] }; -export const Char = { symbol: Symbol('Char') , params: [] };// Wrapper around function below. +export const Char = { symbol: Symbol('Char') , params: [] }; diff --git a/structures/function.js b/structures/function.js index 00a93f2..bfef251 100644 --- a/structures/function.js +++ b/structures/function.js @@ -2,6 +2,7 @@ import { Type } from "../type.js"; import { typedFnType } from "./types.js"; import { fnType } from "./types.js"; +// export const pprintFn = fnT => `` export const ModuleFunction = {l:[ // binary type constructor: Type -> Type -> Type diff --git a/structures/types.js b/structures/types.js index bbff7b4..1aefe60 100644 --- a/structures/types.js +++ b/structures/types.js @@ -1,5 +1,6 @@ // to break up dependency cycles, type constructors are defined in their own JS module +import { Type } from "../type.js"; import { DefaultMap } from "../util.js"; @@ -36,14 +37,14 @@ export const typedFnType2 = callback => { const t = callback(wrappedFnType); // force evaluation return [ t, - fnTs.map(fnT => ({ i: fnT, t: Function })), + fnTs.map(fnT => ({ i: fnT, t: Type })), ]; }; // Sum type -const symbolSum = Symbol("Sum"); +export const symbolSum = Symbol("Sum"); const sumTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ symbol: symbolSum, params: [leftType, rightType], @@ -54,7 +55,7 @@ export const sumType = leftType => rightType => sumTypeRegistry.getdefault(leftT // Product type -const symbolProduct = Symbol("Product"); +export const symbolProduct = Symbol("Product"); const productTypeRegistry = new DefaultMap(leftType => new DefaultMap(rightType => ({ symbol: symbolProduct, params: [leftType, rightType], @@ -65,7 +66,7 @@ export const prodType = leftType => rightType => productTypeRegistry.getdefault( // List type -const symbolList = Symbol('List'); +export const symbolList = Symbol('List'); const listTypeRegistry = new DefaultMap(elementType => ({ symbol: symbolList, params: [elementType], diff --git a/typeclasses/eq.js b/typeclasses/eq.js index 4b9482f..9256e6a 100644 --- a/typeclasses/eq.js +++ b/typeclasses/eq.js @@ -8,19 +8,21 @@ import { eqDictType } from "./eq_type"; export const getEq = numDict => numDict.eq; export const ModuleEq = {l:[ - ...typedFnType(eqDictType, fnType => fnType({in: Type, out: Type})), + // type constructor: Type -> Type + ...typedFnType(eqDictType, fnType => fnType(Type)(Type)), - ...typedFnType(getEq, fnType => makeGeneric(a => - fnType({ - in: eqDictType(a), - out: fnType({ - in: a, - out: fnType({ - in: a, - out: Bool, - }), - }), - }))), + // (EqDict a) -> a -> a -> Bool + ...typedFnType(getEq, fnType => + makeGeneric(a => + fnType + (eqDictType(a)) + (fnType + (a) + (fnType + (a) + (Bool) + ) + ))), ]}; // all our data (and types) are encoded such that we can test equality the same way: diff --git a/typeclasses/eq_type.js b/typeclasses/eq_type.js index 09e5614..5055d66 100644 --- a/typeclasses/eq_type.js +++ b/typeclasses/eq_type.js @@ -1,4 +1,8 @@ import { DefaultMap } from "../util.js"; -const eqDictTypeRegistry = new DefaultMap(a => ({ eqDict: a })); +const eqDictSymbol = Symbol('EqDict'); +const eqDictTypeRegistry = new DefaultMap(a => ({ + symbol: eqDictSymbol, + params: [a], +})); export const eqDictType = a => eqDictTypeRegistry.getdefault(a, true); diff --git a/typeclasses/num_type.js b/typeclasses/num_type.js index 54537c9..989040e 100644 --- a/typeclasses/num_type.js +++ b/typeclasses/num_type.js @@ -1,4 +1,8 @@ import { DefaultMap } from "../util.js"; -const numDictTypeRegistry = new DefaultMap(a => ({ numDict: a })); +const numDictSymbol = Symbol("NumDict"); +const numDictTypeRegistry = new DefaultMap(a => ({ + symbol: numDictSymbol, + params: [a], +})); export const numDictType = a => numDictTypeRegistry.getdefault(a, true); diff --git a/util.js b/util.js index 4fd92fb..6146ad8 100644 --- a/util.js +++ b/util.js @@ -47,7 +47,30 @@ export class DefaultMap { import { inspect } from 'node:util'; +import { symbolFunction, symbolList, symbolProduct, symbolSum } from './structures/types.js'; export function pretty(obj) { return inspect(obj, {colors: true, depth: null}); -} \ No newline at end of file +} + +export function prettyT(type) { + if (type.typeVars) { + return `∀${[...type.typeVars].map(prettyT).join(", ")}: ${prettyT(type.type)}`; + } + if (type.symbol === symbolFunction) { + return `${prettyT(type.params[0])} -> ${prettyT(type.params[1])}`; + } + if (type.symbol === symbolList) { + return `[${prettyT(type.params[0])}]`; + } + if (type.symbol === symbolProduct) { + return `(${prettyT(type.params[0])}, ${prettyT(type.params[1])})`; + } + if (type.symbol === symbolSum) { + return `(${prettyT(type.params[0])} | ${prettyT(type.params[1])})`; + } + if (type.params.length === 0) { + return type.symbol.description; + } + return `${type.symbol.description}(${type.params.map(prettyT).join(", ")})`; +}