diff --git a/examples/parser.js b/examples/parser.js new file mode 100644 index 0000000..e3d5701 --- /dev/null +++ b/examples/parser.js @@ -0,0 +1,15 @@ +import { parse } from "../parser/parser.js"; +import { prettyGenT, prettyT } from "../util/pretty.js"; + +console.log(prettyT(parse("Int"))); // Int + +console.log(prettyT(parse("Int * Bool"))); // (Int ⨯ Bool) + +console.log(prettyT(parse("(((((((Int)))) => ((Bool)))))"))); // (Int => Bool) + +console.log(prettyT(parse("#0((Int * #0) + Unit)"))) // #0((Int ⨯ #0) + Unit) + +console.log(prettyGenT(parse("∀a: #0((a * #0) + Unit"))); // ∀a: #0((a ⨯ #0) + Unit) + +console.log(prettyGenT(parse("∀a,b,c,d: (a*b) + (c*d)"))); // ∀a,b,c,d: ((a ⨯ b) + (c ⨯ d)) + diff --git a/examples/compare_types.js b/examples/recursive_types.js similarity index 93% rename from examples/compare_types.js rename to examples/recursive_types.js index d4cbcbe..5431b8d 100644 --- a/examples/compare_types.js +++ b/examples/recursive_types.js @@ -85,3 +85,12 @@ const unified = unify( makeGeneric(() => linkedListOfInt)); console.log(prettyGenT(unified)); // ∀: #0((Int ⨯ #0) + Unit) + +// unification (strange case) + +const unified2 = unify( + makeGeneric(() => listOfSetOfSelf), + genericList, +); + +console.log(prettyGenT(unified2)); // ∀: #0[{#0}] diff --git a/generics/generics.js b/generics/generics.js index 9b32d57..1d8089a 100644 --- a/generics/generics.js +++ b/generics/generics.js @@ -96,7 +96,7 @@ export const mergeTwoWay = (m1, m2) => { // fType, aType: generic types to unify // fStack, aStack: internal use. const __unify = (typeVars, fType, aType, fStack=[], aStack=[]) => { - console.log("__unify", {typeVars, fType, aType, fStack, aStack}); + // console.log("__unify", {typeVars, fType, aType, fStack, aStack}); if (typeVars.has(fType)) { // simplest case: formalType is a type paramater // => substitute with actualType @@ -182,12 +182,13 @@ const __unify = (typeVars, fType, aType, fStack=[], aStack=[]) => { export const unify = (fGenericType, aGenericType) => { let allTypeVars; [allTypeVars, fGenericType, aGenericType] = safeUnionTypeVars(fGenericType, aGenericType); - const {genericType} = __unify(allTypeVars, fGenericType.type, aGenericType.type); + const {genericType, substitutions} = __unify(allTypeVars, fGenericType.type, aGenericType.type); + // console.log('unification complete! substitutions:', substitutions); return recomputeTypeVars(genericType); }; export const substitute = (type, substitutions, stack=[]) => { - console.log('substitute...', {type, substitutions, stack}); + // console.log('substitute...', {type, substitutions, stack}); return substitutions.get(type) || { symbol: type.symbol, diff --git a/parser/parser.js b/parser/parser.js new file mode 100644 index 0000000..ed7c91f --- /dev/null +++ b/parser/parser.js @@ -0,0 +1,174 @@ +import { Bool, Char, Double, Int, Unit } from "../primitives/types.js"; +import { dictType, fnType, lsType, prodType, setType, sumType } from "../structures/types.js"; + +const bracketOperators = new Map([ + ['(', [')', null]], + ['[', [']', lsType]], + ['{', ['}', setType]], + + // can only occur at beginning + // we use these to extract the type variables + ['∀', [':', null]], +]); + +const infixOperators = new Map([ + ['+', sumType], + ['|', sumType], + ['⨯', prodType], + ['*', prodType], + ['→', fnType], + ['->', fnType], + ['⇒', dictType], + ['=>', dictType], + + // only used for type variables (e.g., ∀a,b,c:) + [',', fnX => fnY => { + const x = fnX(); + const y = fnY(); + return Array.isArray(x) ? x.concat(y) : [x].concat(y) + }], +]); + +const a = Symbol('a'); +const b = Symbol('b'); +const c = Symbol('c'); +const d = Symbol('d'); +const e = Symbol('e'); + +const primitives = new Map([ + ['Int', Int], + ['Double', Double], + ['Bool', Bool], + ['Char', Char], + ['Unit', Unit], + ['a', a], + ['b', b], + ['c', c], + ['d', d], + ['e', e], +]); + +const TOKENS = [ + ...bracketOperators.keys(), + ...[...bracketOperators.values()].map(v => v[0]), + ...infixOperators.keys(), + ...primitives.keys(), +]; + +// console.log('TOKENS =', TOKENS); + +const tokenize = expr => { + const tokens = []; + let i=0; + outerloop: while (i { + const bracket = bracketOperators.get(tokens[0]); + if (bracket === undefined) { + // no group, just a single token: + const [firstToken, ...rest] = tokens; + return [[firstToken], null, rest]; + } + else { + // find where group ends: + const [closing, fn] = bracket; + const opening = tokens[0] + let depth = 1; + let i = 1; + for (; i { + // console.log('parseGroup ', tokensInGroup, fn); + return (fn === null) + ? __parse(tokensInGroup, labels, label) + : fn(self => { + return __parse(tokensInGroup, extendLabels(labels, label, self)); + }); +} + +const extendLabels = (labels, label, self) => { + return (label === null) ? labels : new Map([...labels, [label, self]]) +}; + +const __parse = (tokens, labels = new Map(), label = null) => { + // console.log('parse ', tokens); + if (tokens[0].startsWith('#')) { + if (labels.has(tokens[0])) { + return labels.get(tokens[0]); + } + else { + // pass label and parse 'rest' + return __parse(tokens.slice(1), labels, tokens[0]); + } + } + if (tokens.length === 1) { + return primitives.get(tokens[0]); + } + else { + const [lhsTokens, fnGrp, rest] = consumeGroup(tokens); + if (rest.length === 0) { + return parseGroup(lhsTokens, fnGrp, labels, label); + } + const [operator, ...rhsTokens] = rest; + for (const [operatorChar, fn] of infixOperators) { + if (operator === operatorChar) { + return fn + (self => { + return parseGroup(lhsTokens, fnGrp, extendLabels(labels, label, self)); + })(self => { + return __parse(rhsTokens, extendLabels(labels, label, self)); + }); + } + } + throw new Error("unknown operator: "+operator) + } +}; + +export const parse = expr => { + const tokens = tokenize(expr); + if (tokens[0] === '∀') { + // generic type + const [typeVarTokens, _, rest] = consumeGroup(tokens); + const typeVars = [].concat(__parse(typeVarTokens)) + const type = __parse(rest); + return { typeVars, type }; + } + return __parse(tokens); +} diff --git a/structures/product.js b/structures/product.js index b0ad9dd..64548db 100644 --- a/structures/product.js +++ b/structures/product.js @@ -3,52 +3,8 @@ // A Product-type always has only two fields, called "left" and "right". // Product-types of more fields (called Structs) can be constructed by nesting Product-types. -import { makeGeneric } from "../generics/generics.js"; -import { GenericType, Type } from "../primitives/types.js"; -import { typedFnType } from "./types.js"; -import { prodType } from "./types.js"; // In JS, all products are encoded in the same way: export const newProduct = l => r => ({l, r}); export const getLeft = product => product.l; export const getRight = product => product.r; - -export const ModuleProduct = {l: [ - // binary type constructor - // Type -> Type -> Type - ...typedFnType(prodType, fnType => - fnType - (Type) - (fnType - (Type) - (Type) - ) - ), - - // a -> b -> (a, b) - ...typedFnType(newProduct, fnType => - makeGeneric((a, b) => - fnType - (a) - (fnType - (b) - (prodType(() => a)(() => b)) - ) - ), GenericType), - - // (a, b) -> a - ...typedFnType(getLeft, fnType => - makeGeneric((a, b) => - fnType - (prodType(() => a)(() => b)) - (a) - ), GenericType), - - // (a, b) -> b - ...typedFnType(getRight, fnType => - makeGeneric((a, b) => - fnType - (prodType(() => a)(() => b)) - (b) - ), GenericType), -]}; diff --git a/structures/product.types.js b/structures/product.types.js new file mode 100644 index 0000000..5ba90bd --- /dev/null +++ b/structures/product.types.js @@ -0,0 +1,27 @@ +import { makeGeneric } from "../generics/generics.js"; +import { Type, GenericType } from "../primitives/types.js"; +import { newProduct, getLeft, getRight } from "./product.js"; +import { typedFnType, prodType } from "./types.js"; + +export const ModuleProduct = { + l: [ + // binary type constructor + // Type -> Type -> Type + ...typedFnType(prodType, fnType => fnType(Type)(fnType(Type)(Type) + ) + ), + + // a -> b -> (a, b) + ...typedFnType(newProduct, fnType => makeGeneric((a, b) => fnType(a)(fnType(b)(prodType(() => a)(() => b)) + ) + ), GenericType), + + // (a, b) -> a + ...typedFnType(getLeft, fnType => makeGeneric((a, b) => fnType(prodType(() => a)(() => b))(a) + ), GenericType), + + // (a, b) -> b + ...typedFnType(getRight, fnType => makeGeneric((a, b) => fnType(prodType(() => a)(() => b))(b) + ), GenericType), + ] +}; diff --git a/structures/set.js b/structures/set.js index 69f6655..00a6717 100644 --- a/structures/set.js +++ b/structures/set.js @@ -1,7 +1,3 @@ -import { fnType, setType } from "./types.js"; -import { Int } from "../primitives/types.js"; -import { makeGeneric } from "../generics/generics.js"; - import createRBTree from "functional-red-black-tree"; import { inspect } from "node:util"; @@ -9,6 +5,7 @@ export class RBTreeWrapper { constructor(tree) { this.tree = tree; } + // pretty print to console [inspect.custom](depth, options, inspect) { const entries = []; this.tree.forEach((key,val) => {entries.push(`${inspect(key)} => ${inspect(val)}`);}); @@ -19,7 +16,6 @@ export class RBTreeWrapper { // (a -> a -> Int) -> Set(a) export const emptySet = compareFn => new RBTreeWrapper(createRBTree((x, y) => compareFn(x)(y))); -// const emptySetType = makeGeneric(a => fnType(() => fnType(a)(fnType(a)(Int)))(() => set(() => a))); export const has = set => key => set.tree.get(key) === true; export const add = set => key => set.tree.get(key) === true ? set : new RBTreeWrapper(set.tree.insert(key, true)); @@ -42,33 +38,3 @@ export const read = iter => ifNotDone => ifDone => { export const forEach = set => fn => { set.tree.forEach(key => { fn(key); }); }; - -export const ModuleSet = {l:[ - // // Type -> Type - // ...typedFnType(setType, fnType => - // fnType - // /* in */ (Type) - // /* out */ (Type) - // ), - - // {i: emptySet , t: emptySetType}, - // {i: emptySetType, t: GenericType }, - - // ...typedFnType(has, fnType => - // makeGeneric(a => - // fnType - // /* in */ (set(() => a)) - // /* out */ (fnType - // /* in */ (a) - // /* out */ (Bool) - // )), GenericType), - - // ...typedFnType(add, fnType => - // makeGeneric(a => - // fnType - // /* in */ (set(() => a)) - // /* out */ (fnType - // /* in */ (a) - // /* out */ (set(() => a)) - // )), GenericType), -]}; diff --git a/structures/set.types.js b/structures/set.types.js new file mode 100644 index 0000000..6d900c3 --- /dev/null +++ b/structures/set.types.js @@ -0,0 +1,31 @@ +import { makeGeneric } from "../generics/generics.js"; +import { Int } from "../primitives/types.js"; +import { emptySet, has, add } from "./set.js"; +import { fnType, setType } from "./types.js"; + +const emptySetType = makeGeneric(a => + fnType + // comparison function: + (_ => fnType + (_ => a) + (_ => fnType(_ => a)(_ => Int))) + // the set: + (_ => setType(_ => a)) +); + +export const ModuleSet = { + l: [ + // Type -> Type + ...typedFnType(setType, fnType => fnType(_ => Type)(_ => Type) + ), + + { i: emptySet, t: emptySetType }, + { i: emptySetType, t: GenericType }, + + ...typedFnType(has, fnType => makeGeneric(a => fnType(_ => setType(_ => a))(_ => fnType(_ => a)(_ => Bool) + )), GenericType), + + ...typedFnType(add, fnType => makeGeneric(a => fnType(setType(_ => a))(fnType(a)(setType(_ => a)) + )), GenericType), + ] +}; diff --git a/structures/types.js b/structures/types.js index f7f07ed..9a030ff 100644 --- a/structures/types.js +++ b/structures/types.js @@ -52,3 +52,8 @@ export const lsType = makeTypeConstructor(symbolList)(1); export const symbolSet = Symbol('Set'); export const setType = makeTypeConstructor(symbolSet)(1); + +// Dict type + +export const symbolDict = Symbol('Dict'); +export const dictType = makeTypeConstructor(symbolDict)(2); \ No newline at end of file diff --git a/type_constructor.js b/type_constructor.js index 3c5defc..9fda806 100644 --- a/type_constructor.js +++ b/type_constructor.js @@ -39,12 +39,16 @@ const __makeTypeConstructor = (symbol, nAry, params) => { if (nAry === 0) { return { symbol, params }; } - return typeParam => { - if (typeof typeParam !== 'function') { - throw new Error("all type params must be functions"); + // only for debugging, do we give the function a name + const fName = `${symbol.description.toLowerCase()}Type${params.length>0?params.length:''}`; + return { + [fName]: typeParam => { + if (typeof typeParam !== 'function') { + throw new Error("all type params must be functions"); + } + return __makeTypeConstructor(symbol, nAry-1, params.concat([typeParam])); } - return __makeTypeConstructor(symbol, nAry-1, params.concat([typeParam])); - } + }[fName]; } // Creates a new nominal type diff --git a/util/pretty.js b/util/pretty.js index 1138d45..05865d9 100644 --- a/util/pretty.js +++ b/util/pretty.js @@ -1,5 +1,5 @@ import { inspect } from 'node:util'; -import { symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from '../structures/types.js'; +import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from '../structures/types.js'; import { mapRecursiveStructure } from './util.js'; export function pretty(obj) { @@ -35,6 +35,7 @@ const renderType = (symbol, annot, params) => { [symbolFunction]: `${annot}(${params[0]} -> ${params[1]})`, [symbolSum] : `${annot}(${params[0]} + ${params[1]})`, [symbolProduct] : `${annot}(${params[0]} ⨯ ${params[1]})`, + [symbolDict] : `${annot}(${params[0]} => ${params[1]})`, }[symbol] || symbol.description; };