reorganize directory and file structure
This commit is contained in:
parent
1d826ea8d4
commit
48390b8556
99 changed files with 1155 additions and 1629 deletions
28
lib/compare/primitives.js
Normal file
28
lib/compare/primitives.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Total ordering of primitive types
|
||||
|
||||
export const compareNumbers = x => y => {
|
||||
if (typeof(x) !== 'number' || typeof(y) !== 'number') {
|
||||
throw new Error(`was only meant to compare numbers! got ${x} and ${y}`);
|
||||
}
|
||||
return (x < y) ? -1 : (x > y) ? 1 : 0;
|
||||
}
|
||||
|
||||
export const compareStrings = x => y => {
|
||||
if (typeof(x) !== 'string' || typeof(y) !== 'string') {
|
||||
throw new Error(`was only meant to compare strings! got ${x} and ${y}`);
|
||||
}
|
||||
return (x < y) ? -1 : (x > y) ? 1 : 0;
|
||||
}
|
||||
|
||||
export const compareBools = x => y => {
|
||||
if (typeof(x) !== 'boolean' || typeof(y) !== 'boolean') {
|
||||
throw new Error(`was only meant to compare booleans! got ${x} and ${y}`);
|
||||
}
|
||||
return x - y;
|
||||
};
|
||||
|
||||
// The Unit-type has only one instance, which is equal to itself:
|
||||
export const compareUnits = _ => _ => 0;
|
||||
|
||||
// Symbols are encoded as strings
|
||||
export const compareSymbols = a => b => compareStrings(a)(b);
|
||||
11
lib/compare/primitives.types.js
Normal file
11
lib/compare/primitives.types.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { compareBools, compareNumbers, compareSymbols, compareUnits } from "./primitives.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleComparePrimitives = [
|
||||
{i: compareNumbers, t: mkType("Double -> Double -> Int")},
|
||||
{i: compareBools , t: mkType("Bool -> Bool -> Int")},
|
||||
{i: compareUnits , t: mkType("Unit -> Unit -> Int")},
|
||||
{i: compareSymbols, t: mkType("SymbolT -> SymbolT -> Int")},
|
||||
];
|
||||
35
lib/compare/registry.js
Normal file
35
lib/compare/registry.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { getInst, getType } from "../primitives/dynamic.js";
|
||||
import { SymbolBool, SymbolChar, SymbolDouble, SymbolInt, SymbolType, SymbolUnit } from "../primitives/primitive_types.js";
|
||||
import { getHumanReadableName } from "../primitives/symbol.js";
|
||||
import { symbolDict, symbolList, symbolProduct, symbolSet, symbolSum } from "../structures/type_constructors.js";
|
||||
import { capitalizeFirstLetter } from "../util/util.js";
|
||||
import { compareBools, compareNumbers, compareStrings, compareUnits } from "./primitives.js";
|
||||
import { compareLists, compareProducts, compareSets, compareSums } from "./structures.js";
|
||||
import { compareTypes } from "./type.js";
|
||||
|
||||
const typeSymbolToCmp = new Map([
|
||||
[SymbolInt , compareNumbers],
|
||||
[SymbolChar , compareStrings],
|
||||
[SymbolDouble , compareNumbers],
|
||||
[SymbolBool , compareBools],
|
||||
[SymbolUnit , compareUnits],
|
||||
[SymbolType , compareTypes],
|
||||
|
||||
// these functions take extra comparison callbacks:
|
||||
[symbolList , compareLists],
|
||||
[symbolProduct , compareProducts],
|
||||
[symbolSum , compareSums],
|
||||
[symbolSet , compareSets],
|
||||
// [symbolDict , compareDicts], TODO
|
||||
]);
|
||||
|
||||
export const makeCompareFn = type => {
|
||||
return type.params.reduce(
|
||||
(acc, cur) => acc(makeCompareFn(cur(type))),
|
||||
typeSymbolToCmp.get(type.symbol)
|
||||
);
|
||||
};
|
||||
|
||||
export const compareDynamic = x => y =>
|
||||
compareTypes(getType(x))(getType(y))
|
||||
|| makeCompareFn(getType(x))(getInst(x))(getInst(y));
|
||||
66
lib/compare/structures.js
Normal file
66
lib/compare/structures.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Total ordering of composed types
|
||||
|
||||
import { compareNumbers } from "./primitives.js"
|
||||
import { get, length as lengthLs } from "../structures/list.js";
|
||||
import { read, length as lengthSet } from "../structures/set.js";
|
||||
import { newProduct, getLeft, getRight } from "../structures/product.js";
|
||||
import { match } from "../structures/sum.js";
|
||||
|
||||
// (a -> a -> Int) -> [a] -> [a] -> Int
|
||||
export const compareLists = compareElems => x => y => {
|
||||
return compareNumbers(lengthLs(x))(lengthLs(y))
|
||||
|| (() => {
|
||||
for (let i=0; i<lengthLs(x); i++) {
|
||||
const elemCmp = compareElems(get(x)(i))(get(y)(i));
|
||||
if (elemCmp !== 0) {
|
||||
return elemCmp;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
})();
|
||||
};
|
||||
|
||||
// (a -> a -> Int) -> (b -> b -> Int) -> (a, b) -> (a, b) -> Int
|
||||
export const compareProducts = compareLeft => compareRight => x => y => {
|
||||
return compareLeft (getLeft (x))(getLeft (y))
|
||||
|| compareRight(getRight(x))(getRight(y));
|
||||
};
|
||||
|
||||
// (a -> a -> Int) -> (b -> b -> Int) -> (a | b) -> (a | b) -> Int
|
||||
export const compareSums = compareLeft => compareRight => x => y => {
|
||||
// console.log("compareSums...", x, y)
|
||||
return match(x)
|
||||
(leftValueX => match(y)
|
||||
(leftValueY => compareLeft(leftValueX)(leftValueY)) // both are left
|
||||
((_rightValueY) => {
|
||||
// console.log("x is 'left' and y is 'right' => x < y")
|
||||
return -1;
|
||||
}) // x is 'left' and y is 'right' => x < y
|
||||
)
|
||||
(rightValueX => match(y)
|
||||
(_leftValueY => {
|
||||
// console.log("x is 'right' and y is 'left' => x > y");
|
||||
return 1;
|
||||
}) // x is 'right' and y is 'left' => x > y
|
||||
(rightValueY => compareRight(rightValueX)(rightValueY)) // both are right
|
||||
);
|
||||
};
|
||||
|
||||
// (a -> a -> Int) -> {a} -> {a} -> Int
|
||||
export const compareSets = compareElems => x => y => {
|
||||
return compareNumbers(lengthSet(x))(lengthSet(y))
|
||||
|| (() => {
|
||||
// sets have same size -> iterate over both sets and compare their elements pairwise
|
||||
// because of the underlying red-black tree, iteration happens in ordered fashion
|
||||
const iterate = iterX => iterY =>
|
||||
read(iterX)
|
||||
(keyX => nextX =>
|
||||
read(iterY)
|
||||
// we could also use the comparison function that is embedded in the set object,
|
||||
// but to be consistent with the other comparison-functions, we don't.
|
||||
(keyY => nextY => compareElems(keyX)(keyY) || iterate(nextX)(nextY))
|
||||
(0)) // end of set y (we'll never get here because sets are same size)
|
||||
(0); // end of set x
|
||||
return iterate(first(x))(first(y));
|
||||
})();
|
||||
};
|
||||
14
lib/compare/structures.types.js
Normal file
14
lib/compare/structures.types.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { compareLists, compareProducts, compareSets, compareSums } from "./structures.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleCompareStructures = [
|
||||
{i: compareLists, t: mkType("∀a: (a -> a -> Int) -> [a] -> [a] -> Int")},
|
||||
|
||||
{i: compareProducts, t: mkType("∀a,b: (a -> a -> Int) -> (b -> b -> Int) -> (a*b) -> (a*b) -> Int")},
|
||||
|
||||
{i: compareSums, t: mkType("∀a,b: (a -> a -> Int) -> (b -> b -> Int) -> (a+b) -> (a+b) -> Int")},
|
||||
|
||||
{i: compareSets, t: mkType("∀a: (a -> a -> Int) -> {a} -> {a} -> Int")},
|
||||
];
|
||||
38
lib/compare/type.js
Normal file
38
lib/compare/type.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { getParams, getSymbol } from "../primitives/type.js";
|
||||
import { compareBools, compareNumbers, compareSymbols } from "./primitives.js";
|
||||
import { compareLists } from "./structures.js";
|
||||
|
||||
const __compareTypes = state => typeX => typeY => {
|
||||
// tagX and tagY: just something unique & deterministic that can serve as JS map key
|
||||
const tagX = state.nextTag++;
|
||||
const tagY = state.nextTag++;
|
||||
state.tagsX.add(tagX);
|
||||
state.tagsY.add(tagY);
|
||||
state.comparing.set(tagX, tagY);
|
||||
return compareSymbols(getSymbol(typeX))(getSymbol(typeY))
|
||||
|| compareLists
|
||||
(paramOfX => paramOfY => {
|
||||
const pX = paramOfX(tagX);
|
||||
const pY = paramOfY(tagY);
|
||||
return compareBools(state.tagsX.has(pX))(state.tagsY.has(pY))
|
||||
|| (() => {
|
||||
if (state.tagsX.has(pX)) {
|
||||
// both sub-types have been visited already in an enclosing call
|
||||
// if they were being compared in the same enclosing call, we assume they are equal!
|
||||
// (we cannot compare them, that would result in endless recursion)
|
||||
return compareNumbers(state.comparing.get(pX))(pY);
|
||||
}
|
||||
// none have been visited -> recursively compare
|
||||
return __compareTypes(state)(pX)(pY);
|
||||
})();
|
||||
})
|
||||
(getParams(typeX))
|
||||
(getParams(typeY));
|
||||
};
|
||||
|
||||
export const compareTypes = typeX => typeY => __compareTypes({
|
||||
tagsX: new Set(),
|
||||
tagsY: new Set(),
|
||||
comparing: new Map(),
|
||||
nextTag: 0,
|
||||
})(typeX)(typeY);
|
||||
8
lib/compare/type.types.js
Normal file
8
lib/compare/type.types.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { compareTypes } from "./type.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleCompareTypes = [
|
||||
{i: compareTypes, t: mkType("Type -> Type -> Int")},
|
||||
];
|
||||
35
lib/compare/versioning.js
Normal file
35
lib/compare/versioning.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Total ordering of slots and values (versioning library).
|
||||
// Problem: A Value produced by a transformation can depend on other Values of any type!
|
||||
// So, we cannot statically know the entire type -> resort to dynamic typing for these?
|
||||
|
||||
export const compareSlots = compareElems => slotA => slotB => {
|
||||
if (slotA.depth < slotB.depth) {
|
||||
return -1;
|
||||
}
|
||||
if (slotB.depth < slotA.depth) {
|
||||
return 1;
|
||||
}
|
||||
return compareValues(compareElems)(slotA.value)(slotB.value);
|
||||
};
|
||||
|
||||
export const compareValues = compareElems => valA => valB => {
|
||||
if (valA.kind < valB.kind) {
|
||||
return -1;
|
||||
}
|
||||
if (valB.kind < valA.kind) {
|
||||
return 1;
|
||||
}
|
||||
if (valA.kind === "literal") {
|
||||
return compareElems(valA.out)(valB.out);
|
||||
}
|
||||
if (valA.kind === "read") {
|
||||
return compareSlots(compareElems)(valA.slot)(valB.slot);
|
||||
}
|
||||
if (valA.kind === "transformation") {
|
||||
const cmpIn = compareValues(compareElems)(valA.in)(valB.in);
|
||||
if (cmpIn !== 0) {
|
||||
return cmpIn;
|
||||
}
|
||||
return compareValues(compareElems)(valA.fn)(valB.fn);
|
||||
}
|
||||
};
|
||||
242
lib/generics/generics.js
Normal file
242
lib/generics/generics.js
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
import { eqType } from "../primitives/type.js";
|
||||
import { zip } from "../util/util.js";
|
||||
import { pretty, prettyT } from '../util/pretty.js';
|
||||
|
||||
// constructor for generic types
|
||||
// for instance, the type:
|
||||
// ∀a: a -> a -> Bool
|
||||
// is created by
|
||||
// makeGeneric(a => fnType(() => a)(() => fnType(() => a)(() => Bool)))
|
||||
export const makeGeneric = callback => {
|
||||
// type variables to make available:
|
||||
const typeVars = ['a', 'b', 'c', 'd', 'e'].map(Symbol);
|
||||
const type = callback(...typeVars);
|
||||
return onlyOccurring(type, new Set(typeVars));
|
||||
};
|
||||
|
||||
export const onlyOccurring = (type, typeVars) => ({
|
||||
typeVars: occurring(type, typeVars),
|
||||
type,
|
||||
});
|
||||
|
||||
const __occurring = state => typeVars => type => {
|
||||
if (typeVars.has(type)) {
|
||||
return new Set([type]);
|
||||
}
|
||||
const tag = state.nextTag++;
|
||||
state.seen.add(tag);
|
||||
return new Set(type.params.flatMap(p => {
|
||||
const innerType = p(tag);
|
||||
if (state.seen.has(innerType)) {
|
||||
return []; // no endless recursion!
|
||||
}
|
||||
return [...__occurring(state)(typeVars)(innerType)];
|
||||
}));
|
||||
};
|
||||
|
||||
// From the given set of type variables, return only those that occur in the given type.
|
||||
export const occurring = (type, typeVars) => {
|
||||
return __occurring({nextTag:0, seen: new Set()})(typeVars)(type);
|
||||
};
|
||||
|
||||
// Merge 2 substitution-mappings, uni-directional.
|
||||
const mergeOneWay = (m1, m2) => {
|
||||
const m1copy = new Map(m1);
|
||||
const m2copy = new Map(m2);
|
||||
for (const [var1, typ1] of m1copy.entries()) {
|
||||
if (m2copy.has(typ1)) {
|
||||
// typ1 is a typeVar for which we also have a substitution
|
||||
// -> fold substitutions
|
||||
m1copy.set(var1, m2.get(typ1));
|
||||
m2copy.delete(typ1);
|
||||
return [false, m1copy, m2copy, new Set([typ1])];
|
||||
}
|
||||
}
|
||||
return [true, m1copy, m2copy, new Set()]; // stable
|
||||
};
|
||||
|
||||
const checkConflict = (m1, m2) => {
|
||||
for (const [var1, typ1] of m1) {
|
||||
if (m2.has(var1)) {
|
||||
const other = m2.get(var1);
|
||||
if (!eqType(typ1, other)) {
|
||||
throw new Error(`conflicting substitution: ${pretty(typ1)}vs. ${pretty(other)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Merge 2 substitution-mappings, bi-directional.
|
||||
export const mergeTwoWay = (m1, m2) => {
|
||||
// console.log("mergeTwoWay", {m1, m2});
|
||||
checkConflict(m1, m2);
|
||||
// checkConflict(m2, m1); // <- don't think this is necessary...
|
||||
// actually merge
|
||||
let stable = false;
|
||||
let deletions = new Set();
|
||||
while (!stable) {
|
||||
let d;
|
||||
// notice we swap m2 and m1, so the rewriting can happen both ways:
|
||||
[stable, m2, m1, d] = mergeOneWay(m1, m2);
|
||||
deletions = deletions.union(d);
|
||||
}
|
||||
const result = {
|
||||
substitutions: new Map([...m1, ...m2]),
|
||||
deletions, // deleted type variables
|
||||
};
|
||||
// console.log("mergeTwoWay result =", result);
|
||||
return result;
|
||||
};
|
||||
|
||||
// Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog (hence the function name):
|
||||
// https://www.dai.ed.ac.uk/groups/ssp/bookpages/quickprolog/node12.html
|
||||
//
|
||||
// Parameters:
|
||||
// typeVars: all the type variables in both fType and aType
|
||||
// fType, aType: generic types to unify
|
||||
// fStack, aStack: internal use.
|
||||
const __unify = (typeVars, fType, aType, fStack=[], aStack=[]) => {
|
||||
// console.log("__unify", {typeVars, fType: prettyT(fType), aType: prettyT(aType), fStack, aStack});
|
||||
if (typeVars.has(fType)) {
|
||||
// simplest case: formalType is a type paramater
|
||||
// => substitute with actualType
|
||||
// console.log(`assign ${prettyT(aType)} to ${prettyT(fType)}`);
|
||||
return {
|
||||
substitutions: new Map([[fType, aType]]),
|
||||
genericType: {
|
||||
typeVars: typeVars.difference(new Set([fType])),
|
||||
type: aType,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (typeVars.has(aType)) {
|
||||
// same as above, but in the other direction
|
||||
// console.log(`assign ${prettyT(fType)} to ${prettyT(aType)}`);
|
||||
return {
|
||||
substitutions: new Map([[aType, fType]]),
|
||||
genericType: {
|
||||
typeVars: typeVars.difference(new Set([aType])),
|
||||
type: fType,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// recursively unify
|
||||
if (fType.symbol !== aType.symbol) {
|
||||
throw new Error(`cannot unify ${prettyT(fType)} and ${prettyT(aType)}`);
|
||||
}
|
||||
|
||||
const fTag = fStack.length;
|
||||
const aTag = aStack.length;
|
||||
|
||||
const unifications =
|
||||
zip(fType.params, aType.params)
|
||||
.map(([getFParam, getAParam]) => {
|
||||
const fParam = getFParam(fTag);
|
||||
const aParam = getAParam(aTag);
|
||||
// type recursively points to an enclosing type that we've already seen
|
||||
if (fStack[fParam] !== aStack[aParam]) {
|
||||
// note that both are also allowed not to be mapped (undefined)
|
||||
throw new Error("cannot unify: types differ in their recursion");
|
||||
}
|
||||
if (fStack[fParam] !== undefined) {
|
||||
const type = fStack[fParam];
|
||||
return () => ({
|
||||
substitutions: new Map(),
|
||||
genericType: {
|
||||
typeVars,
|
||||
type,
|
||||
},
|
||||
});
|
||||
}
|
||||
return parent => __unify(typeVars, fParam, aParam,
|
||||
[...fStack, parent],
|
||||
[...aStack, parent]);
|
||||
});
|
||||
|
||||
const unifiedParams = unifications.map(getParam => {
|
||||
return parent => getParam(parent).genericType.type;
|
||||
});
|
||||
const type = {
|
||||
symbol: fType.symbol,
|
||||
params: unifiedParams,
|
||||
};
|
||||
|
||||
const [unifiedSubstitutions, unifiedTypeVars] = unifications.reduce((acc, getParam) => {
|
||||
const self = Symbol(); // dirty, just need something unique
|
||||
const {substitutions, deletions} = mergeTwoWay(acc[0], getParam(self).substitutions);
|
||||
return [substitutions, acc[1]
|
||||
.difference(substitutions)
|
||||
.difference(deletions)];
|
||||
}, [new Map(), typeVars]);
|
||||
|
||||
return {
|
||||
substitutions: unifiedSubstitutions,
|
||||
genericType: {
|
||||
typeVars: unifiedTypeVars,
|
||||
type,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const unify = (fGenericType, aGenericType) => {
|
||||
let allTypeVars;
|
||||
[allTypeVars, fGenericType, aGenericType] = safeUnionTypeVars(fGenericType, aGenericType);
|
||||
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});
|
||||
return substitutions.get(type)
|
||||
|| {
|
||||
symbol: type.symbol,
|
||||
params: type.params.map(getParam => parent => {
|
||||
const param = getParam(stack.length);
|
||||
const have = stack[param];
|
||||
return (have !== undefined)
|
||||
? have
|
||||
: substitute(param, substitutions, [...stack, parent]);
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const assign = (genFnType, paramType) => {
|
||||
let allTypeVars;
|
||||
[allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType);
|
||||
const [inType, outType] = genFnType.type.params;
|
||||
const {substitutions} = __unify(allTypeVars, inType(genFnType.type), paramType.type);
|
||||
const substitutedOutType = substitute(outType(genFnType.type), substitutions);
|
||||
return recomputeTypeVars(onlyOccurring(substitutedOutType, allTypeVars));
|
||||
};
|
||||
|
||||
export const assignFn = (genFnType, paramType) => {
|
||||
let allTypeVars;
|
||||
[allTypeVars, genFnType, paramType] = safeUnionTypeVars(genFnType, paramType);
|
||||
const [inType] = genFnType.type.params;
|
||||
const {substitutions} = __unify(allTypeVars, inType, paramType.type);
|
||||
const substitutedFnType = substitute(genFnType.type, substitutions);
|
||||
return recomputeTypeVars(onlyOccurring(substitutedFnType, allTypeVars));
|
||||
};
|
||||
|
||||
export const recomputeTypeVars = (genType) => {
|
||||
const newTypeVars = ['a', 'b', 'c', 'd', 'e', 'f', 'g'].map(Symbol);
|
||||
let nextIdx = 0;
|
||||
const subst = new Map();
|
||||
for (const typeVarA of genType.typeVars) {
|
||||
subst.set(typeVarA, newTypeVars[nextIdx++]);
|
||||
}
|
||||
const substType = {
|
||||
typeVars: new Set(subst.values()),
|
||||
type: substitute(genType.type, subst),
|
||||
};
|
||||
return substType;
|
||||
};
|
||||
|
||||
export const safeUnionTypeVars = (genTypeA, genTypeB) => {
|
||||
const substTypeA = recomputeTypeVars(genTypeA);
|
||||
const substTypeB = recomputeTypeVars(genTypeB);
|
||||
const allTypeVars = substTypeA.typeVars.union(substTypeB.typeVars);
|
||||
return [allTypeVars, substTypeA, substTypeB];
|
||||
};
|
||||
21
lib/meta/type_constructor.js
Normal file
21
lib/meta/type_constructor.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { getHumanReadableName } from "../primitives/symbol.js";
|
||||
|
||||
const __makeTypeConstructor = (symbol, nAry, params) => {
|
||||
if (nAry === 0) {
|
||||
return { symbol, params };
|
||||
}
|
||||
// only for debugging, do we give the function a name
|
||||
const fName = `${getHumanReadableName(symbol).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]));
|
||||
}
|
||||
}[fName];
|
||||
}
|
||||
|
||||
// Creates a new nominal type
|
||||
// export const makeTypeConstructor = symbol => nAry => makeTypeConstructorInternal(symbol, nAry);
|
||||
export const makeTypeConstructor = symbol => nAry => __makeTypeConstructor(symbol, nAry, []);
|
||||
9
lib/meta/type_constructor.types.js
Normal file
9
lib/meta/type_constructor.types.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js"
|
||||
import { makeTypeConstructor } from "./type_constructor.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleTypeConstructor = [
|
||||
// Problem: number of parameters of returned function depends on the 'Int' parameter...
|
||||
// {i: makeTypeConstructor, t: mkType("SymbolT -> Int -> ??")}
|
||||
];
|
||||
206
lib/parser/type_parser.js
Normal file
206
lib/parser/type_parser.js
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
// A simple, hacked-together recursive parser for types.
|
||||
|
||||
import { Bool, Char, Double, Int, SymbolT, Type, Unit } from "../primitives/primitive_types.js";
|
||||
import { Dynamic } from "../primitives/primitive_types.js";
|
||||
import { dictType, fnType, lsType, prodType, sumType } from "../structures/type_constructors.js";
|
||||
import { setType } from "../structures/type_constructors.js";
|
||||
|
||||
export const makeTypeParser = ({
|
||||
// parser can be extended:
|
||||
extraPrimitives=[],
|
||||
extraBracketOperators=[],
|
||||
extraInfixOperators=[],
|
||||
}) => {
|
||||
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],
|
||||
['String', lsType(_ => Char)],
|
||||
['Module', lsType(_ => Dynamic)],
|
||||
['Unit', Unit],
|
||||
['Type', Type],
|
||||
['Dynamic', Dynamic],
|
||||
['SymbolT', SymbolT],
|
||||
['a', a],
|
||||
['b', b],
|
||||
['c', c],
|
||||
['d', d],
|
||||
['e', e],
|
||||
|
||||
...extraPrimitives,
|
||||
]);
|
||||
|
||||
const bracketOperators = new Map([
|
||||
['(', [')', null]],
|
||||
['[', [']', lsType]],
|
||||
['{', ['}', setType]],
|
||||
|
||||
// can only occur at beginning
|
||||
// we use these to extract the type variables
|
||||
['∀', [':', null]],
|
||||
|
||||
...extraBracketOperators,
|
||||
]);
|
||||
|
||||
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)
|
||||
}],
|
||||
|
||||
...extraInfixOperators,
|
||||
]);
|
||||
|
||||
|
||||
const TOKENS = [
|
||||
...bracketOperators.keys(),
|
||||
...[...bracketOperators.values()].map(v => v[0]),
|
||||
...infixOperators.keys(),
|
||||
...primitives.keys(),
|
||||
].toSorted((a,b) => {
|
||||
return (b.length - a.length); // try longer tokens first
|
||||
});
|
||||
|
||||
// console.log('TOKENS =', TOKENS);
|
||||
|
||||
const tokenize = expr => {
|
||||
const tokens = [];
|
||||
let i=0;
|
||||
outerloop: while (i<expr.length) {
|
||||
if (/\s/.test(expr[i])) {
|
||||
i++;
|
||||
continue outerloop; // skip whitespace
|
||||
}
|
||||
if (expr[i] === '#') {
|
||||
const label = '#' + parseInt(expr.slice(i+1));
|
||||
tokens.push(label);
|
||||
i += label.length;
|
||||
continue outerloop;
|
||||
}
|
||||
for (const token of TOKENS) {
|
||||
if (expr.startsWith(token, i)) {
|
||||
tokens.push(token);
|
||||
i += token.length;
|
||||
continue outerloop;
|
||||
}
|
||||
}
|
||||
throw new Error(`Couldn't match any token at position ${i} in\n ${expr}\n ${' '.repeat(i)}^`);
|
||||
}
|
||||
// console.log({tokens});
|
||||
return tokens;
|
||||
}
|
||||
|
||||
const consumeGroup = (tokens) => {
|
||||
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<tokens.length; i++) {
|
||||
if (tokens[i] === opening) {
|
||||
depth++;
|
||||
}
|
||||
else if (tokens[i] === closing) {
|
||||
depth--;
|
||||
}
|
||||
if (depth === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const tokensInGroup = tokens.slice(1, i); // don't include brackets
|
||||
const rest = tokens.slice(i+1);
|
||||
return [tokensInGroup, fn, rest];
|
||||
}
|
||||
}
|
||||
|
||||
const parseGroup = (tokensInGroup, fn, labels, label) => {
|
||||
// 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)
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return parse;
|
||||
}
|
||||
|
||||
let defaultParser;
|
||||
export const getDefaultTypeParser = () => {
|
||||
return defaultParser || (defaultParser = makeTypeParser({}));
|
||||
};
|
||||
73
lib/point.js
73
lib/point.js
|
|
@ -1,73 +0,0 @@
|
|||
import { Type } from "../primitives/types.js";
|
||||
import { typedFnType } from "../structures/types.js"
|
||||
import { Double } from "../primitives/types.js";
|
||||
import { makeTypeConstructor } from "../type_constructor.js";
|
||||
|
||||
// Create nominal types
|
||||
export const PointCartesian2D = makeTypeConstructor(Symbol('PointCartesian2D'))(0);
|
||||
export const PointPolar2D = makeTypeConstructor(Symbol('PointPolar2D'))(0);
|
||||
|
||||
export const cart2polar = ({x, y}) => {
|
||||
const r = Math.sqrt(x*x + y*y);
|
||||
const θ = Math.atan(y/x);
|
||||
return {r, θ};
|
||||
};
|
||||
|
||||
export const polar2cart = ({r, θ}) => {
|
||||
const x = r * Math.cos(θ);
|
||||
const y = r * Math.sin(θ);
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
export const translate = dx => dy => ({x, y}) => {
|
||||
return {left: x+dx, right: y+dy};
|
||||
}
|
||||
|
||||
export const rotate = dθ => ({r, θ}) => {
|
||||
return {r, θ: θ+dθ};
|
||||
}
|
||||
|
||||
export const scale = dr => ({r, θ}) => {
|
||||
return {r: r+dr, θ};
|
||||
}
|
||||
|
||||
const examplePoint = {x: 1, y: 2};
|
||||
|
||||
export const ModulePoint = {l:[
|
||||
{i: -1 , t: Double},
|
||||
{i: 2 , t: Double},
|
||||
{i: examplePoint , t: PointCartesian2D},
|
||||
|
||||
{i: PointCartesian2D , t: Type},
|
||||
{i: PointPolar2D , t: Type},
|
||||
|
||||
...typedFnType(cart2polar, fnType => fnType(() => PointCartesian2D)(() => PointPolar2D)),
|
||||
|
||||
...typedFnType(polar2cart, fnType => fnType(() => PointPolar2D)(() => PointCartesian2D)),
|
||||
|
||||
// Double -> Double -> PointCartesian2D -> PointCartesian2D
|
||||
...typedFnType(translate, fnType =>
|
||||
fnType
|
||||
(Double)
|
||||
(fnType
|
||||
(Double)
|
||||
(fnType
|
||||
(PointCartesian2D)
|
||||
(PointCartesian2D)))),
|
||||
|
||||
// Double -> PointPolar2D -> PointPolar2D
|
||||
...typedFnType(rotate, fnType =>
|
||||
fnType
|
||||
(Double)
|
||||
(fnType
|
||||
(PointPolar2D)
|
||||
(PointPolar2D))),
|
||||
|
||||
// Double -> PointPolar2D -> PointPolar2D
|
||||
...typedFnType(scale, fnType =>
|
||||
fnType
|
||||
(Double)
|
||||
(fnType
|
||||
(PointPolar2D)
|
||||
(PointPolar2D))),
|
||||
]};
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
import { prettyT, typedFnType } from "../structures/types.js"
|
||||
import { Double } from "../primitives/types.js";
|
||||
import { makeConstructor, makeConstructorType, makeGetters, makeGettersTypes, structType } from "../structures/struct.js";
|
||||
import { newProduct } from "../structures/product.js";
|
||||
import { makeTypeConstructor } from "../type_constructor.js";
|
||||
|
||||
const cartFields = [
|
||||
newProduct("x")(Double),
|
||||
newProduct("y")(Double),
|
||||
];
|
||||
|
||||
const polarFields = [
|
||||
newProduct("r")(Double),
|
||||
newProduct("θ")(Double),
|
||||
];
|
||||
|
||||
// Nominal types:
|
||||
export const NPoint2DCartesian = makeTypeConstructor(Symbol('Point2DCartesian'))(0);
|
||||
export const NPoint2DPolar = makeTypeConstructor(Symbol('Point2DPolar'))(0);
|
||||
|
||||
// Structural types:
|
||||
export const SPoint2DCartesian = structType(cartFields); // (Double, (Double, Unit))
|
||||
export const SPoint2DPolar = structType(polarFields); // (Double, (Double, Unit))
|
||||
|
||||
export const constructorPoint2DCartesian = makeConstructor(cartFields);
|
||||
export const constructorPoint2DPolar = makeConstructor(polarFields);
|
||||
|
||||
export const constructorPoint2DCartesianFnType = makeConstructorType(NPoint2DCartesian)(cartFields);
|
||||
export const constructorPoint2DPolarFnType = makeConstructorType(NPoint2DPolar)(polarFields);
|
||||
|
||||
export const [getX, getY] = makeGetters(cartFields);
|
||||
export const [getR, getΘ] = makeGetters(polarFields);
|
||||
|
||||
export const [getXFnType, getYFnType] = makeGettersTypes(NPoint2DCartesian)(cartFields);
|
||||
export const [getRFnType, getΘFnType] = makeGettersTypes(NPoint2DPolar)(polarFields);
|
||||
|
||||
export const cart2polar = cart => {
|
||||
const x = getX(cart);
|
||||
const y = getY(cart);
|
||||
const r = Math.sqrt(x*x + y*y);
|
||||
const θ = Math.atan(y/x);
|
||||
return constructorPoint2DPolar(r)(θ);
|
||||
};
|
||||
|
||||
export const polar2cart = polar => {
|
||||
const r = getR(polar);
|
||||
const θ = getΘ(polar);
|
||||
const x = r * Math.cos(θ);
|
||||
const y = r * Math.sin(θ);
|
||||
return constructorPoint2DCartesian(x)(y);
|
||||
};
|
||||
|
||||
export const cart2Str = cart => {
|
||||
const x = getX(cart);
|
||||
const y = getY(cart);
|
||||
return {l: `{x: ${x}, y: ${y}}`};
|
||||
};
|
||||
|
||||
export const polar2Str = polar => {
|
||||
const r = getR(polar);
|
||||
const θ = getΘ(polar);
|
||||
return {l: `{r: ${r}, θ: ${θ}}`};
|
||||
};
|
||||
|
||||
// export const translate = dx => dy => ({x, y}) => {
|
||||
// return {x: x+dx, y: y+dy};
|
||||
// };
|
||||
|
||||
// export const rotate = dθ => ({r, θ}) => {
|
||||
// return {r, θ: θ+dθ};
|
||||
// };
|
||||
|
||||
// export const scale = dr => ({r, θ}) => {
|
||||
// return {r: r+dr, θ};
|
||||
// };
|
||||
|
||||
// const examplePoint = {x: 1, y: 2};
|
||||
|
||||
const examplePointCart = constructorPoint2DCartesian(1)(2);
|
||||
const examplePointPolar = constructorPoint2DCartesian(0)(5);
|
||||
|
||||
console.log(prettyT(getXFnType));
|
||||
|
||||
export const ModulePoint = {l:[
|
||||
// {i: -1 , t: Double},
|
||||
// {i: 2 , t: Double},
|
||||
// {i: examplePoint , t: Point2DCartesian},
|
||||
|
||||
{i: examplePointCart, t: SPoint2DCartesian},
|
||||
{i: examplePointCart, t: NPoint2DCartesian},
|
||||
|
||||
{i: examplePointPolar, t: SPoint2DPolar},
|
||||
{i: examplePointPolar, t: NPoint2DPolar},
|
||||
|
||||
// {i: SPoint2DCartesian , t: Type},
|
||||
// {i: SPoint2DPolar , t: Type},
|
||||
|
||||
// {i: NPoint2DCartesian , t: Type},
|
||||
// {i: NPoint2DPolar , t: Type},
|
||||
|
||||
{i: getX, t: getXFnType},
|
||||
{i: getY, t: getYFnType},
|
||||
{i: getR, t: getRFnType},
|
||||
{i: getΘ, t: getΘFnType},
|
||||
|
||||
...typedFnType(cart2polar, fnType => fnType(() => NPoint2DCartesian)(() => NPoint2DPolar)),
|
||||
...typedFnType(polar2cart, fnType => fnType(() => NPoint2DPolar)(() => NPoint2DCartesian)),
|
||||
|
||||
// ...typedFnType(polar2cart, fnType => fnType(() => Point2DPolar)(() => Point2DCartesian)),
|
||||
|
||||
// // Double -> Double -> PointCartesian2D -> PointCartesian2D
|
||||
// ...typedFnType(translate, fnType =>
|
||||
// fnType
|
||||
// (Double)
|
||||
// (fnType
|
||||
// (Double)
|
||||
// (fnType
|
||||
// (Point2DCartesian)
|
||||
// (Point2DCartesian)))),
|
||||
|
||||
// ...typedFnType(cart2tuple, fnType => fnType(() => Point2DCartesian)(prodType(() => Double)(() => () => Double))),
|
||||
|
||||
// ...typedFnType(polar2tuple, fnType => fnType(() => Point2DPolar)(prodType(() => Double)(() => () => Double))),
|
||||
|
||||
// // Double -> PointPolar2D -> PointPolar2D
|
||||
// ...typedFnType(rotate, fnType =>
|
||||
// fnType
|
||||
// (Double)
|
||||
// (fnType
|
||||
// (Point2DPolar)
|
||||
// (Point2DPolar))),
|
||||
|
||||
// // Double -> PointPolar2D -> PointPolar2D
|
||||
// ...typedFnType(scale, fnType =>
|
||||
// fnType
|
||||
// (Double)
|
||||
// (fnType
|
||||
// (Point2DPolar)
|
||||
// (Point2DPolar))),
|
||||
]};
|
||||
3
lib/primitives/double.js
Normal file
3
lib/primitives/double.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const addDouble = x => y => x + y;
|
||||
export const mulDouble = x => y => x * y;
|
||||
export const eqDouble = x => y => x === y;
|
||||
10
lib/primitives/double.types.js
Normal file
10
lib/primitives/double.types.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { addDouble, eqDouble, mulDouble } from "./double.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleDouble = [
|
||||
{ i: addDouble, t: mkType("Double -> Double -> Double") },
|
||||
{ i: mulDouble, t: mkType("Double -> Double -> Double") },
|
||||
{ i: eqDouble, t: mkType("Double -> Double -> Bool") },
|
||||
];
|
||||
3
lib/primitives/dynamic.js
Normal file
3
lib/primitives/dynamic.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const newDynamic = i => t => ({i, t});
|
||||
export const getInst = lnk => lnk.i;
|
||||
export const getType = lnk => lnk.t;
|
||||
14
lib/primitives/dynamic.types.js
Normal file
14
lib/primitives/dynamic.types.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { getInst, getType, newDynamic } from "./dynamic.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleDynamic = [
|
||||
{ i: newDynamic, t: mkType("∀a: a -> Type -> Dynamic")},
|
||||
|
||||
// allows us to (unsafely) cast the result to the specific type...
|
||||
// (not sure if this is the right way to go)
|
||||
{ i: getInst, t: mkType("∀a: Dynamic -> a") },
|
||||
|
||||
{ i: getType, t: mkType("Dynamic -> Type") },
|
||||
];
|
||||
3
lib/primitives/int.js
Normal file
3
lib/primitives/int.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const addInt = x => y => x + y;
|
||||
export const mulInt = x => y => x * y;
|
||||
export const eqInt = x => y => x === y;
|
||||
10
lib/primitives/int.types.js
Normal file
10
lib/primitives/int.types.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { getDefaultTypeParser }from "../parser/type_parser.js";
|
||||
import { addInt, eqInt, mulInt } from "./int.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleInt = [
|
||||
{ i: addInt, t: mkType("Int -> Int -> Int") },
|
||||
{ i: mulInt, t: mkType("Int -> Int -> Int") },
|
||||
{ i: eqInt, t: mkType("Int -> Int -> Bool") },
|
||||
];
|
||||
40
lib/primitives/primitive_types.js
Normal file
40
lib/primitives/primitive_types.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// to break up dependency cycles, primitive types are defined in their own JS module
|
||||
|
||||
import { makeTypeConstructor } from "../meta/type_constructor.js";
|
||||
|
||||
export const SymbolInt = "Int__02a884563f7d480bb14c09be640dfe7a";
|
||||
export const SymbolBool = "Bool__d64c4865bead40439dad62727aaaac2d";
|
||||
export const SymbolDouble = "Double__be70f3c8f53f4419a7866d106faae091";
|
||||
export const SymbolByte = "Byte__bf9e8453cd554e81971880ba33dc9f27";
|
||||
export const SymbolChar = "Char__e47159519d3345119336b751fc8da1de";
|
||||
export const SymbolUnit = "Unit__a70ca021c32a4036a594d332aedfb029";
|
||||
export const SymbolBottom = "⊥__95beece951bc457781be8c5481d35dcc";
|
||||
export const SymbolSymbol = "Symbol__f67c077430e04e4fa40ed2e2b2a3040d";
|
||||
export const SymbolType = "Type__fdbea309d66f49b483b0dd4ceb785f7d";
|
||||
export const SymbolTop = "⊤__38709c3c0039468782103256d4730d1f";
|
||||
export const SymbolGenericType = "GenericType__e9d8010b82f64206afa83d0c163df5d2";
|
||||
export const SymbolDynamic = "Dynamic__3c16c415dba94228ada37dc9d446f54f";
|
||||
|
||||
export const Int = makeTypeConstructor(SymbolInt)(0);
|
||||
export const Bool = makeTypeConstructor(SymbolBool)(0);
|
||||
export const Double = makeTypeConstructor(SymbolDouble)(0);
|
||||
export const Byte = makeTypeConstructor(SymbolByte)(0);
|
||||
export const Char = makeTypeConstructor(SymbolChar)(0);
|
||||
|
||||
// Unit type has only 1 instance, the empty tuple.
|
||||
export const Unit = makeTypeConstructor(SymbolUnit)(0);
|
||||
|
||||
// Bottom type has no instances.
|
||||
export const Bottom = makeTypeConstructor(SymbolBottom)(0);
|
||||
|
||||
export const SymbolT = makeTypeConstructor(SymbolSymbol)(0);
|
||||
|
||||
// Types are typed by Top
|
||||
export const Type = makeTypeConstructor(SymbolType)(0);
|
||||
|
||||
export const GenericType = makeTypeConstructor(SymbolGenericType)(0);
|
||||
|
||||
// Everything is typed by Top
|
||||
export const Top = makeTypeConstructor(SymbolTop)(0);// A type-link, connecting a value to its Type.
|
||||
|
||||
export const Dynamic = makeTypeConstructor(SymbolDynamic)(0);
|
||||
31
lib/primitives/primitive_types.types.js
Normal file
31
lib/primitives/primitive_types.types.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { SymbolInt, SymbolT, SymbolBool, SymbolDouble, SymbolByte, SymbolChar, SymbolUnit, SymbolBottom, SymbolSymbol, SymbolType, SymbolGenericType, SymbolTop, Type, Int, Bool, Double, Byte, Char, Unit, Bottom, GenericType, Top, SymbolDynamic, Dynamic } from "./primitive_types.js";
|
||||
|
||||
export const ModulePrimitiveSymbols = [
|
||||
{ i: SymbolInt , t: SymbolT },
|
||||
{ i: SymbolBool , t: SymbolT },
|
||||
{ i: SymbolDouble , t: SymbolT },
|
||||
{ i: SymbolByte , t: SymbolT },
|
||||
{ i: SymbolChar , t: SymbolT },
|
||||
{ i: SymbolUnit , t: SymbolT },
|
||||
{ i: SymbolBottom , t: SymbolT },
|
||||
{ i: SymbolSymbol , t: SymbolT },
|
||||
{ i: SymbolType , t: SymbolT },
|
||||
{ i: SymbolGenericType , t: SymbolT },
|
||||
{ i: SymbolTop , t: SymbolT },
|
||||
{ i: SymbolDynamic , t: SymbolT },
|
||||
];
|
||||
|
||||
export const ModulePrimitiveTypes = [
|
||||
{ i: Int , t: Type },
|
||||
{ i: Bool , t: Type },
|
||||
{ i: Double , t: Type },
|
||||
{ i: Byte , t: Type },
|
||||
{ i: Char , t: Type },
|
||||
{ i: Unit , t: Type },
|
||||
{ i: Bottom , t: Type },
|
||||
{ i: SymbolT , t: Type },
|
||||
{ i: Type , t: Type },
|
||||
{ i: GenericType , t: Type },
|
||||
{ i: Top , t: Type },
|
||||
{ i: Dynamic , t: Type },
|
||||
];
|
||||
2
lib/primitives/symbol.js
Normal file
2
lib/primitives/symbol.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export const getHumanReadableName = symbol => symbol.slice(0, -34); // drop UUID
|
||||
export const eqSymbol = a => b => a === b;
|
||||
9
lib/primitives/symbol.types.js
Normal file
9
lib/primitives/symbol.types.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js"
|
||||
import { eqSymbol, getHumanReadableName } from "./symbol.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleSymbol = [
|
||||
{ i: getHumanReadableName, t: mkType("SymbolT -> String")},
|
||||
{ i: eqSymbol, t: mkType("SymbolT -> SymbolT -> Bool")},
|
||||
];
|
||||
6
lib/primitives/type.js
Normal file
6
lib/primitives/type.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { compareTypes } from "../compare/type.js";
|
||||
|
||||
export const getSymbol = type => type.symbol;
|
||||
export const getParams = type => type.params;
|
||||
|
||||
export const eqType = t1 => t2 => compareTypes(t1, t2) === 0;
|
||||
13
lib/primitives/type.types.js
Normal file
13
lib/primitives/type.types.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// a module is just a set of typed objects
|
||||
|
||||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { eqType, getParams, getSymbol } from "./type.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
// each 'typed object' is implicitly an instance of TypeLink (defined below)
|
||||
export const ModuleType = [
|
||||
{i: eqType , t: mkType("Type -> Type -> Bool")},
|
||||
{i: getSymbol, t: mkType("Type -> SymbolT")},
|
||||
{i: getParams, t: mkType("Type -> [Type -> Type]")},
|
||||
];
|
||||
3
lib/primitives/unit.js
Normal file
3
lib/primitives/unit.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const unit = {};
|
||||
|
||||
export const eqUnit = _ => _ => true;
|
||||
9
lib/primitives/unit.types.js
Normal file
9
lib/primitives/unit.types.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { eqUnit, unit } from "./unit.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleUnit = [
|
||||
{i: unit , t: mkType("Unit")},
|
||||
{i: eqUnit, t: mkType("Unit -> Unit -> Bool")},
|
||||
];
|
||||
48
lib/stdlib.js
Normal file
48
lib/stdlib.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { ModuleDouble } from "./primitives/double.types.js";
|
||||
import { ModuleDynamic } from "./primitives/dynamic.types.js";
|
||||
import { ModuleInt } from "./primitives/int.types.js";
|
||||
import { ModulePrimitiveSymbols, ModulePrimitiveTypes } from "./primitives/primitive_types.types.js";
|
||||
import { ModuleSymbol } from "./primitives/symbol.types.js";
|
||||
import { ModuleType } from "./primitives/type.types.js";
|
||||
import { ModuleUnit } from "./primitives/unit.types.js";
|
||||
|
||||
import { ModuleDict } from "./structures/dict.types.js"
|
||||
import { ModuleList } from "./structures/list.types.js"
|
||||
import { ModuleProduct } from "./structures/product.types.js"
|
||||
import { ModuleSet } from "./structures/set.types.js"
|
||||
import { ModuleSum } from "./structures/sum.types.js"
|
||||
import { ModuleStructuralSymbols, ModuleTypeConstructors } from "./structures/type_constructors.types.js";
|
||||
|
||||
import { ModuleCompareTypes } from "./compare/type.types.js";
|
||||
import { ModuleComparePrimitives } from "./compare/primitives.types.js";
|
||||
import { ModuleCompareStructures } from "./compare/structures.types.js";
|
||||
|
||||
export const ModuleStd = [
|
||||
// Symbols (for nominal types)
|
||||
...ModulePrimitiveSymbols,
|
||||
...ModuleStructuralSymbols,
|
||||
|
||||
// Nominal types
|
||||
...ModulePrimitiveTypes,
|
||||
...ModuleTypeConstructors,
|
||||
|
||||
// Operations on Primitives
|
||||
...ModuleDouble,
|
||||
...ModuleDynamic,
|
||||
...ModuleInt,
|
||||
...ModuleSymbol,
|
||||
...ModuleType,
|
||||
...ModuleUnit,
|
||||
|
||||
// Operations on Structures
|
||||
...ModuleDict,
|
||||
...ModuleList,
|
||||
...ModuleProduct,
|
||||
...ModuleSet,
|
||||
...ModuleSum,
|
||||
|
||||
// Comparison
|
||||
...ModuleCompareTypes,
|
||||
...ModuleComparePrimitives,
|
||||
...ModuleCompareStructures,
|
||||
];
|
||||
22
lib/structures/dict.js
Normal file
22
lib/structures/dict.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { RBTreeWrapper } from "../util/rbtree_wrapper.js";
|
||||
import { newProduct } from "./product.js";
|
||||
import { newLeft, newRight } from "./sum.js";
|
||||
|
||||
export const emptyDict = compareFn => RBTreeWrapper.new((x, y) => compareFn(x)(y));
|
||||
|
||||
export const has = dict => key => dict.tree.get(key) === true;
|
||||
export const set = dict => key => value => new RBTreeWrapper(dict.tree.remove(key).insert(key, value));
|
||||
export const remove = dict => key => new RBTreeWrapper(dict.tree.remove(key));
|
||||
export const length = dict => dict.tree.length;
|
||||
|
||||
export const first = dict => dict.tree.begin;
|
||||
export const last = dict => dict.tree.end;
|
||||
|
||||
export const read = iter => {
|
||||
if (iter !== undefined && iter.valid) {
|
||||
return newRight(newProduct(newProduct(iter.key)(iter.value))(iter.clone().next()));
|
||||
}
|
||||
else {
|
||||
return newLeft(unit);
|
||||
}
|
||||
};
|
||||
20
lib/structures/dict.types.js
Normal file
20
lib/structures/dict.types.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { makeTypeParser } from "../parser/type_parser.js";
|
||||
import { makeTypeConstructor } from "../meta/type_constructor.js";
|
||||
import { emptyDict, first, has, last, length, read, remove, set } from "./dict.js";
|
||||
|
||||
const dictIterator = makeTypeConstructor('DictIterator__d9d175b6bfd1283f00851a99787d0499')(2);
|
||||
|
||||
const mkType = makeTypeParser({
|
||||
extraInfixOperators: [['|=>|', dictIterator]],
|
||||
});
|
||||
|
||||
export const ModuleDict = [
|
||||
{ i: emptyDict , t: mkType("∀a,b: (a -> a -> Int) -> (a => b)") },
|
||||
{ i: has , t: mkType("∀a,b: (a => b) -> a -> Bool")},
|
||||
{ i: set , t: mkType("∀a,b: (a => b) -> a -> b -> (a => b)")},
|
||||
{ i: remove , t: mkType("∀a,b: (a => b) -> a -> (a => b)")},
|
||||
{ i: length , t: mkType("∀a,b: (a => b) -> Int")},
|
||||
{ i: first , t: mkType("∀a,b: (a => b) -> (a |=>| b)")},
|
||||
{ i: last , t: mkType("∀a,b: (a => b) -> (a |=>| b)")},
|
||||
{ i: read , t: mkType("∀a,b: (a |=>| b) -> (Unit + ((a*b) * (a |=>| b)))")},
|
||||
];
|
||||
37
lib/structures/enum.js
Normal file
37
lib/structures/enum.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { capitalizeFirstLetter } from "../util/util.js";
|
||||
import { newProduct as newProduct, getLeft } from "./product.js";
|
||||
import { newLeft, newRight, match } from "./sum.js";
|
||||
|
||||
const eatParameters = (numParams, result) => {
|
||||
if (numParams === 0) {
|
||||
return result;
|
||||
}
|
||||
else return () => eatParameters(numParams-1, result);
|
||||
}
|
||||
|
||||
export const makeMatchFn = variants => {
|
||||
if (variants.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const [_, ...remainingVariants] = variants;
|
||||
return sum => handler => {
|
||||
return match(sum)
|
||||
(leftValue => eatParameters(remainingVariants.length, handler(leftValue)))
|
||||
(rightValue => makeMatchFn(remainingVariants)(rightValue));
|
||||
};
|
||||
};
|
||||
|
||||
export const makeConstructors = variants => {
|
||||
if (variants.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const [variant, ...remainingVariants] = variants;
|
||||
const name = getLeft(variant);
|
||||
const ctorName = `new${capitalizeFirstLetter(name)}`;
|
||||
const constructor = { [ctorName]: val => newLeft(val) }[ctorName];
|
||||
return [
|
||||
constructor,
|
||||
...makeConstructors(remainingVariants).map(ctor =>
|
||||
({[ctor.name]: val => newRight(ctor(val))}[ctor.name])),
|
||||
];
|
||||
}
|
||||
20
lib/structures/enum.types.js
Normal file
20
lib/structures/enum.types.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { Bottom } from "../primitives/primitive_types.js";
|
||||
import { getRight } from "./product.js";
|
||||
import { sumType } from "./type_constructors.js";
|
||||
|
||||
// 'variants' is an array of (name: string, type: Type) pairs.
|
||||
// e.g., the list of variants:
|
||||
// [ { l: "price" , r: Int },
|
||||
// { l: "prices" , r: [Int] },
|
||||
// { l: "not_found", r: Unit } ]
|
||||
// results in the type:
|
||||
// (Int | ([Int] | (Unit | ⊥)))
|
||||
|
||||
export const enumType = variants => {
|
||||
if (variants.length === 0) {
|
||||
return Bottom; // empty enum is equal to Bottom-type (cannot be instantiated)
|
||||
}
|
||||
const [variant, ...rest] = variants;
|
||||
const variantType = getRight(variant);
|
||||
return sumType(() => variantType)(() => enumType(rest));
|
||||
};
|
||||
16
lib/structures/list.js
Normal file
16
lib/structures/list.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// 'normal' implementation
|
||||
export const emptyList = [];
|
||||
// const emptyListType = makeGeneric(a => lsType(() => a));
|
||||
export const get = ls => i => ls[i];
|
||||
export const put = ls => i => elem => ls.with(Number(i), elem);
|
||||
export const push = ls => elem => ls.concat([elem]);
|
||||
export const pop = ls => ls.pop();
|
||||
export const map = ls => fn => ls.map(elem => fn(elem));
|
||||
export const length = ls => ls.length;
|
||||
export const fold = ls => callback => initial => {
|
||||
let acc = initial;
|
||||
for (let i=0; i<ls.length; i++) {
|
||||
acc = callback(acc)(ls[i]);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
15
lib/structures/list.types.js
Normal file
15
lib/structures/list.types.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { getDefaultTypeParser }from "../parser/type_parser.js";
|
||||
import { emptyList, fold, get, length, map, pop, push, put } from "./list.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleList = [
|
||||
{ i: emptyList, t: mkType("∀a: [a]")},
|
||||
{ i: get , t: mkType("∀a: [a] -> Int -> a")},
|
||||
{ i: put , t: mkType("∀a: [a] -> Int -> a -> [a]")},
|
||||
{ i: push , t: mkType("∀a: [a] -> a -> [a]")},
|
||||
{ i: pop , t: mkType("∀a: [a] -> a")},
|
||||
{ i: map , t: mkType("∀a: [a] -> (a -> b) -> [b]")},
|
||||
{ i: length , t: mkType("∀a: [a] -> Int")},
|
||||
{ i: fold , t: mkType("∀a: [a] -> (b -> a -> b) -> b -> b")},
|
||||
];
|
||||
9
lib/structures/product.js
Normal file
9
lib/structures/product.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Product-type (also called: pair, tuple)
|
||||
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
10
lib/structures/product.types.js
Normal file
10
lib/structures/product.types.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { newProduct, getLeft, getRight } from "./product.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleProduct = [
|
||||
{ i: newProduct, t: mkType("∀a,b: a -> b -> (a * b)") },
|
||||
{ i: getLeft , t: mkType("∀a,b: (a * b) -> a" ) },
|
||||
{ i: getRight , t: mkType("∀a,b: (a * b) -> b" ) },
|
||||
];
|
||||
29
lib/structures/set.js
Normal file
29
lib/structures/set.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { newRight } from "./sum.js";
|
||||
import { newProduct } from "./product.js";
|
||||
import { unit } from "../primitives/unit.js";
|
||||
import { RBTreeWrapper } from "../util/rbtree_wrapper.js";
|
||||
|
||||
// (a -> a -> Int) -> Set(a)
|
||||
export const emptySet = compareFn => RBTreeWrapper.new((x, y) => compareFn(x)(y));
|
||||
|
||||
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));
|
||||
export const remove = set => key => new RBTreeWrapper(set.tree.remove(key));
|
||||
export const length = set => set.tree.length;
|
||||
|
||||
export const first = set => set.tree.begin;
|
||||
export const last = set => set.tree.end;
|
||||
|
||||
// test if iterator is 'done', and if not, get element and advance iterator.
|
||||
export const read = iter => {
|
||||
if (iter !== undefined && iter.valid) {
|
||||
return newRight(newProduct(iter.key)(iter.clone().next()));
|
||||
}
|
||||
else {
|
||||
return newLeft(unit);
|
||||
}
|
||||
};
|
||||
|
||||
export const forEach = set => fn => {
|
||||
set.tree.forEach(key => { fn(key); });
|
||||
};
|
||||
20
lib/structures/set.types.js
Normal file
20
lib/structures/set.types.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { makeTypeParser } from "../parser/type_parser.js";
|
||||
import { makeTypeConstructor } from "../meta/type_constructor.js";
|
||||
import { emptySet, has, add, remove, length, first, read, last } from "./set.js";
|
||||
|
||||
const setIterator = makeTypeConstructor('SetIterator__f6b0ddd78ed41c58e5a442f2681da011')(1);
|
||||
|
||||
const mkType = makeTypeParser({
|
||||
extraBracketOperators: [['<', ['>', setIterator]]],
|
||||
});
|
||||
|
||||
export const ModuleSet = [
|
||||
{ i: emptySet , t: mkType("∀a: (a -> a -> Int) -> {a}") },
|
||||
{ i: has , t: mkType("∀a: {a} -> a -> Bool")},
|
||||
{ i: add , t: mkType("∀a: {a} -> a -> {a}")},
|
||||
{ i: remove , t: mkType("∀a: {a} -> a -> {a}")},
|
||||
{ i: length , t: mkType("∀a: {a} -> Int")},
|
||||
{ i: first , t: mkType("∀a: {a} -> <a>")},
|
||||
{ i: last , t: mkType("∀a: {a} -> <a>")},
|
||||
{ i: read , t: mkType("∀a: <a> -> (Unit + (a * <a>))")},
|
||||
];
|
||||
33
lib/structures/struct.js
Normal file
33
lib/structures/struct.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { unit } from "../primitives/unit.js";
|
||||
import { capitalizeFirstLetter } from "../util/util.js";
|
||||
import { newProduct, getLeft, getRight } from "./product.js";
|
||||
|
||||
export const makeConstructor = nParams => {
|
||||
const internal = (nParams, ret) => {
|
||||
if (nParams === 0) {
|
||||
const result = ret(unit);
|
||||
return result;
|
||||
}
|
||||
return nextParam => {
|
||||
const wrappedName = 'wrapped_' + ret.name;
|
||||
const newRet = {
|
||||
[wrappedName]: inner => newProduct(nextParam)(ret(inner)),
|
||||
}[wrappedName];
|
||||
return internal(nParams-1, newRet);
|
||||
}
|
||||
};
|
||||
const id = x => x;
|
||||
return internal(nParams, id);
|
||||
};
|
||||
|
||||
export const makeGetters = fieldNames => {
|
||||
if (fieldNames.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const [fieldName, ...rest] = fieldNames;
|
||||
const getterName = `get${capitalizeFirstLetter(fieldName)}`;
|
||||
return [
|
||||
{ [getterName]: obj => getLeft(obj) }[getterName],
|
||||
...makeGetters(rest).map(getter => ({[getter.name]: obj => getter(getRight(obj))}[getter.name])),
|
||||
];
|
||||
};
|
||||
61
lib/structures/struct.types.js
Normal file
61
lib/structures/struct.types.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { newDynamic } from "../primitives/dynamic.js";
|
||||
import { Type, Unit } from "../primitives/primitive_types.js";
|
||||
import { zip } from "../util/util.js";
|
||||
import { map } from "./list.js";
|
||||
import { getLeft, getRight } from "./product.js";
|
||||
import { makeConstructor, makeGetters } from "./struct.js";
|
||||
import { fnType, prodType } from "./type_constructors.js";
|
||||
|
||||
|
||||
// 'fields' is an array of (name: string, type: Type) pairs.
|
||||
// e.g.:
|
||||
// [{l: "x", r: Double}, {l: "y", r: Double}]
|
||||
// results in the type (Double × (Double × Unit))
|
||||
|
||||
export const structType = fieldTypes => {
|
||||
if (fieldTypes.length === 0) {
|
||||
return Unit;
|
||||
}
|
||||
const [fieldType, ...rest] = fieldTypes;
|
||||
return prodType(_ => fieldType)(_ => structType(rest));
|
||||
};
|
||||
|
||||
export const makeConstructorType = fieldTypes => {
|
||||
if (fieldTypes.length === 0) {
|
||||
return structType(fieldTypes);
|
||||
}
|
||||
const [fieldType, ...rest] = fieldTypes;
|
||||
return fnType(_ => fieldType)(_ => makeConstructorType(rest));
|
||||
};
|
||||
|
||||
export const makeGettersTypes = fieldTypes => {
|
||||
const type = structType(fieldTypes);
|
||||
return fieldTypes.map(fieldType => {
|
||||
return fnType(_ => type)(_ => fieldType);
|
||||
});
|
||||
};
|
||||
|
||||
export const makeModuleStruct = fields => {
|
||||
const fieldNames = map(fields)(getLeft);
|
||||
const fieldTypes = map(fields)(getRight);
|
||||
const type = structType(fieldTypes);
|
||||
const ctor = makeConstructor(fields.length);
|
||||
const ctorType = makeConstructorType(fieldTypes);
|
||||
const getterTypes = makeGettersTypes(fieldTypes);
|
||||
const getters = makeGetters(fieldNames);
|
||||
const module = [
|
||||
{i: type, t: Type},
|
||||
{i: ctor, t: ctorType},
|
||||
...zip(getters, getterTypes)
|
||||
.map(([getter, getterType]) => newDynamic(getter)(getterType)),
|
||||
];
|
||||
return module;
|
||||
};
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleStruct = [
|
||||
{i: structType, t: mkType("[String*Type] -> Type")},
|
||||
{i: makeModuleStruct, t: mkType("[String*Type] -> [Dynamic]")},
|
||||
];
|
||||
11
lib/structures/sum.js
Normal file
11
lib/structures/sum.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Sum-type (also called: tagged union, disjoint union, variant type)
|
||||
// A Sum-type always has only two variants, called "left" and "right".
|
||||
// Sum-types of more variants (called Enums) can be constructed by nesting Sum-types.
|
||||
|
||||
export const newLeft = left => ({t: "L", v: left });
|
||||
export const newRight = right => ({t: "R", v: right});
|
||||
|
||||
export const match = sum => leftHandler => rightHandler =>
|
||||
sum.t === "L"
|
||||
? leftHandler(sum.v)
|
||||
: rightHandler(sum.v);
|
||||
10
lib/structures/sum.types.js
Normal file
10
lib/structures/sum.types.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { getDefaultTypeParser }from "../parser/type_parser.js";
|
||||
import { match, newLeft, newRight } from "./sum.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleSum = [
|
||||
{ i: newLeft , t: mkType("∀a,b: a -> (a + b)") },
|
||||
{ i: newRight , t: mkType("∀a,b: b -> (a + b)") },
|
||||
{ i: match , t: mkType("∀a,b,c: (a + b) -> (a -> c) -> (b -> c) -> c") },
|
||||
];
|
||||
17
lib/structures/type_constructors.js
Normal file
17
lib/structures/type_constructors.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// to break up dependency cycles, type constructors are defined in their own JS module
|
||||
|
||||
import { makeTypeConstructor } from "../meta/type_constructor.js";
|
||||
|
||||
export const symbolFunction = "Function__c2433e31fa574a2cb3b6b5d62ac9d4b2";
|
||||
export const symbolSum = "Sum__89b731efa6344ea0b6a8663a45cf3ea8";
|
||||
export const symbolProduct = "Product__89351ecdedfb4b05b2a5a6cc0c383e12";
|
||||
export const symbolList = "List__daa8de8a9047435e96034ec64f2da3a1";
|
||||
export const symbolSet = "Set__8fef2c1873df4327ac31bd61d2ecf7e0";
|
||||
export const symbolDict = "Dict__d7158547322549ac9f7f8176aec123dd";
|
||||
|
||||
export const fnType = makeTypeConstructor(symbolFunction)(2);
|
||||
export const sumType = makeTypeConstructor(symbolSum)(2);
|
||||
export const prodType = makeTypeConstructor(symbolProduct)(2);
|
||||
export const lsType = makeTypeConstructor(symbolList)(1);
|
||||
export const setType = makeTypeConstructor(symbolSet)(1);
|
||||
export const dictType = makeTypeConstructor(symbolDict)(2);
|
||||
23
lib/structures/type_constructors.types.js
Normal file
23
lib/structures/type_constructors.types.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { getDefaultTypeParser } from "../parser/type_parser.js";
|
||||
import { SymbolT } from "../primitives/primitive_types.js";
|
||||
import { dictType, fnType, lsType, prodType, setType, sumType, symbolDict, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSum } from "./type_constructors.js";
|
||||
|
||||
const mkType = getDefaultTypeParser();
|
||||
|
||||
export const ModuleStructuralSymbols = [
|
||||
{ i: symbolSet , t: SymbolT },
|
||||
{ i: symbolList , t: SymbolT },
|
||||
{ i: symbolProduct , t: SymbolT },
|
||||
{ i: symbolSum , t: SymbolT },
|
||||
{ i: symbolDict , t: SymbolT },
|
||||
{ i: symbolFunction , t: SymbolT },
|
||||
];
|
||||
|
||||
export const ModuleTypeConstructors = [
|
||||
{ i: setType , t: mkType("Type -> Type") },
|
||||
{ i: lsType , t: mkType("Type -> Type") },
|
||||
{ i: prodType , t: mkType("Type -> Type -> Type") },
|
||||
{ i: sumType , t: mkType("Type -> Type -> Type") },
|
||||
{ i: dictType , t: mkType("Type -> Type -> Type") },
|
||||
{ i: fnType , t: mkType("Type -> Type -> Type") },
|
||||
];
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { eqSymbol, getName } from "../primitives/symbol.js";
|
||||
import { Bool, SymbolT, Type } from "../primitives/types.js";
|
||||
import { String } from "../structures/list.js";
|
||||
import { typedFnType } from "../structures/types.js";
|
||||
|
||||
// The way instances of SymbolT are currently encoded, their constructor is not a valid DOPE function, because it is impure.
|
||||
// The only way to construct symbols is to do it in JS code.
|
||||
|
||||
// At some point, we should start encoding SymbolTs as UUIDs rather than JS-Symbols.
|
||||
|
||||
export const ModuleSymbol = {l:[
|
||||
{i: SymbolT, t: Type},
|
||||
|
||||
...typedFnType(getName, fnType =>
|
||||
fnType
|
||||
(SymbolT)
|
||||
(String)
|
||||
),
|
||||
|
||||
...typedFnType(eqSymbol, fnType => fnType(() => SymbolT)(fnType(SymbolT)(() => Bool))),
|
||||
]};
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { Int, SymbolT, Type } from "../primitives/types.js";
|
||||
import { typedFnType } from "../structures/types.js";
|
||||
import { makeTypeConstructor } from "../type_constructor.js";
|
||||
|
||||
// This function and its type signature cannot be in the same file as 'makeTypeConstructor' because then we get an import cycle among JS modules.
|
||||
export const ModuleTypeConstructor = {l:[
|
||||
...typedFnType(makeTypeConstructor, fnType =>
|
||||
fnType
|
||||
(SymbolT)
|
||||
(fnType
|
||||
(Int)
|
||||
(Type)
|
||||
)),
|
||||
]};
|
||||
18
lib/util/defaultmap.js
Normal file
18
lib/util/defaultmap.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
// export class DefaultMap {
|
||||
// constructor(defaultValue, ...rest) {
|
||||
// this.defaultValue = defaultValue;
|
||||
// this.m = new Map(rest);
|
||||
// }
|
||||
// getdefault(key, addToMapping = false) {
|
||||
// return this.m.get(key) || (() => {
|
||||
// const val = this.defaultValue(key);
|
||||
// if (addToMapping)
|
||||
// this.m.set(key, val);
|
||||
// return val;
|
||||
// })();
|
||||
// }
|
||||
// entries() {
|
||||
// return this.m.entries();
|
||||
// }
|
||||
// }
|
||||
49
lib/util/pretty.js
Normal file
49
lib/util/pretty.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { inspect } from 'node:util';
|
||||
import { symbolDict, symbolFunction, symbolList, symbolProduct, symbolSum } from '../structures/type_constructors.js';
|
||||
import { symbolSet } from "../structures/type_constructors.js";
|
||||
import { mapRecursiveStructure } from './util.js';
|
||||
import { getHumanReadableName } from '../primitives/symbol.js';
|
||||
|
||||
export function pretty(obj) {
|
||||
return inspect(obj, { colors: true, depth: null, breakLength: 120 });
|
||||
}
|
||||
|
||||
// Pretty print Type
|
||||
export const prettyT = type => {
|
||||
return mapRecursiveStructure(([type, m, seen], map) => {
|
||||
if (typeof type === "symbol") {
|
||||
// type variable
|
||||
return type.description;
|
||||
}
|
||||
// if (type.params === undefined) {
|
||||
// throw new Error("parameter is not a Type ... did you mean to call prettyGenT instead?")
|
||||
// }
|
||||
if (!m.has(type)) {
|
||||
m.add(type); // next time we encounter this type, we'll only render a placeholder
|
||||
const params = type.params.map(p => map([p(type), m, seen])());
|
||||
// if while visiting the children, we encountered ourselves, add annotation:
|
||||
const annot = seen.has(type) ? seen.get(type) : ``;
|
||||
return renderType(type.symbol, annot, params);
|
||||
}
|
||||
if (!seen.has(type)) {
|
||||
seen.set(type, `#${seen.size}`);
|
||||
}
|
||||
return seen.get(type);
|
||||
})([type, new Set(), new Map()])();
|
||||
};
|
||||
|
||||
const renderType = (symbol, annot, params) => {
|
||||
return {
|
||||
[symbolList] : `${annot}[${params[0]}]`,
|
||||
[symbolSet] : `${annot}{${params[0]}}`,
|
||||
[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] || getHumanReadableName(symbol);
|
||||
};
|
||||
|
||||
// Pretty print GenericType
|
||||
export const prettyGenT = genericType => {
|
||||
return `∀${[...genericType.typeVars].map(prettyT).sort((a, b) => a.localeCompare(b)).join(",")}: ${prettyT(genericType.type)}`;
|
||||
};
|
||||
16
lib/util/random.js
Normal file
16
lib/util/random.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// IMPURE
|
||||
export const genUUID = (len=16) => {
|
||||
const arr = crypto.getRandomValues(new Uint8Array(len));
|
||||
let result = "";
|
||||
for (let i=0; i<len; i++) {
|
||||
const unsignedByte = arr[i];
|
||||
if (unsignedByte < 16) {
|
||||
result += '0' + unsignedByte.toString(16);
|
||||
} else {
|
||||
result += unsignedByte.toString(16);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
console.log(genUUID())
|
||||
21
lib/util/rbtree_wrapper.js
Normal file
21
lib/util/rbtree_wrapper.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Tiny wrapper around function-red-black-tree that overrides the [inspect.custom] symbol so when we print it (during debugging) we just see the (key=>value)-pairs instead of the tree structure.
|
||||
|
||||
import createRBTree from "functional-red-black-tree";
|
||||
import { inspect } from "util";
|
||||
|
||||
export class RBTreeWrapper {
|
||||
constructor(tree) {
|
||||
this.tree = tree;
|
||||
}
|
||||
|
||||
static new(compareFn) {
|
||||
return new RBTreeWrapper(createRBTree(compareFn))
|
||||
}
|
||||
|
||||
// pretty print to console
|
||||
[inspect.custom](depth, options, inspect) {
|
||||
const entries = [];
|
||||
this.tree.forEach((key, val) => { entries.push(`${inspect(key)} => ${inspect(val)}`); });
|
||||
return `RBTree(${this.tree.length}) {${entries.join(', ')}}`;
|
||||
}
|
||||
}
|
||||
27
lib/util/util.js
Normal file
27
lib/util/util.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// zip two arrays
|
||||
export function zip(a, b) {
|
||||
return a.map((k, i) => [k, b[i]]);
|
||||
}
|
||||
|
||||
export function capitalizeFirstLetter(val) {
|
||||
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
|
||||
}
|
||||
|
||||
const _mapRecursiveStructure = mapping => transform => root => {
|
||||
const found = mapping.get(root);
|
||||
if (found) {
|
||||
// already mapped
|
||||
// return existing result to prevent endless recursion
|
||||
return found;
|
||||
}
|
||||
// note the indirection (wrapped in lamda), this allows the user to recursively map the children (which may refer to the root) without yet having finished mapping the root.
|
||||
let memo;
|
||||
const result = () => {
|
||||
// memoization is necessary for correctness
|
||||
return memo || (memo = transform(root, _mapRecursiveStructure(mapping)(transform)));
|
||||
};
|
||||
mapping.set(root, result);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const mapRecursiveStructure = _mapRecursiveStructure(new Map());
|
||||
64
lib/versioning/slot.js
Normal file
64
lib/versioning/slot.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { add, emptySet, forEach } from "../../structures/set.js";
|
||||
import { deepEqual } from "../util/util.js";
|
||||
import {inspect} from "node:util";
|
||||
import { compareSlots } from "../compare/versioning.js";
|
||||
|
||||
// UUID -> Value<a> -> Slot<a>
|
||||
export const newSlot = uuid => value => ({
|
||||
overwrites: uuid,
|
||||
value,
|
||||
depth: 1,
|
||||
[inspect.custom]: (depth, options, inspect) => `newSlot(${inspect(uuid)}, ${inspect(value)})`,
|
||||
});
|
||||
|
||||
// Slot<a> -> Value<a> -> Slot<a>
|
||||
export const overwrite = slot => value => ({
|
||||
overwrites: slot,
|
||||
value,
|
||||
depth: slot.depth + 1,
|
||||
// [inspect.custom]: (depth, options, inspect) => `overwrite(${inspect(slot)}, ${inspect(value)})`,
|
||||
});
|
||||
|
||||
const findLCA = slotA => slotB => {
|
||||
if (deepEqual(slotA, slotB)) {
|
||||
return slotA;
|
||||
}
|
||||
if (slotA.depth > slotB.depth) {
|
||||
return findLCA(slotA.overwrites)(slotB)
|
||||
}
|
||||
else {
|
||||
return findLCA(slotB.overwrites)(slotA)
|
||||
}
|
||||
};
|
||||
|
||||
// Slot<a> -> Slot<a> -> MergeResult<a>
|
||||
export const merge = compareElems => slotA => slotB => {
|
||||
const lca = findLCA(slotA)(slotB);
|
||||
if (lca === undefined) {
|
||||
throw new Error("Could not find LCA");
|
||||
}
|
||||
if (deepEqual(lca, slotA)) {
|
||||
return add(emptySet(compareSlots(compareElems)))(slotB);
|
||||
// return new Set([slotB]); // B is successor of A -> fast-forward
|
||||
}
|
||||
if (deepEqual(lca, slotB)) {
|
||||
return add(emptySet(compareSlots(compareElems)))(slotA);
|
||||
// return new Set([slotA]); // A is successor of B -> fast-forward
|
||||
}
|
||||
return add(add(emptySet(compareSlots(compareElems)))(slotA))(slotB);
|
||||
// return new Set([slotA, slotB]);
|
||||
};
|
||||
|
||||
// MergeResult<a> -> MergeResult<a> -> MergeResult<a>
|
||||
export const merge2 = compareElems => mA => mB => {
|
||||
let result = emptySet(compareSlots(compareElems));
|
||||
forEach(mA)(slotOfA => {
|
||||
forEach(mB)(slotOfB => {
|
||||
const merged = merge(compareElems)(slotOfA)(slotOfB);
|
||||
forEach(merged)(merged => {
|
||||
result = add(result)(merged);
|
||||
});
|
||||
});
|
||||
});
|
||||
return result;
|
||||
};
|
||||
27
lib/versioning/types.js
Normal file
27
lib/versioning/types.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { Dynamic } from "../primitives/dynamic.js";
|
||||
import { Int } from "../primitives/types.js";
|
||||
import { enumType } from "../structures/enum.js";
|
||||
import { newProduct } from "../structures/product.js";
|
||||
import { structType } from "../structures/struct.js";
|
||||
import { prodType } from "../structures/types.js";
|
||||
import { prettyT } from "../util/pretty.js";
|
||||
|
||||
const Slot = structType([
|
||||
newProduct("value")(() => Value),
|
||||
newProduct("depth")(() => Int),
|
||||
newProduct("overwrites")(() => Slot),
|
||||
]);
|
||||
|
||||
// A Value is either:
|
||||
// - a literal, without any dependencies.
|
||||
// - read from a slot. the Value then has a read-dependency on that slot.
|
||||
// - a transformation of another Value, by a function. the function is also a Value.
|
||||
const variants = [
|
||||
newProduct("literal", () => Dynamic),
|
||||
newProduct("read", () => Slot),
|
||||
newProduct("transformation", () => prodType(Value, Dynamic)),
|
||||
];
|
||||
const Value = enumType(variants);
|
||||
|
||||
// console.log(prettyT(Slot));
|
||||
// console.log(prettyT(Value));
|
||||
91
lib/versioning/value.js
Normal file
91
lib/versioning/value.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { fnType } from "../structures/types.js";
|
||||
import { deepEqual } from "../util/util.js";
|
||||
import { inspect } from "node:util";
|
||||
|
||||
// a -> Value<a>
|
||||
export const newLiteral = val => ({
|
||||
kind: "literal",
|
||||
out: val,
|
||||
[inspect.custom]: (depth, options, inspect) => `newLiteral(${inspect(val)})`,
|
||||
});
|
||||
|
||||
// Slot<a> -> Value<a>
|
||||
export const read = slot => ({
|
||||
kind: "read",
|
||||
slot,
|
||||
out: slot.value.out,
|
||||
[inspect.custom]: (depth, options, inspect) => `read(${inspect(slot)})`,
|
||||
});
|
||||
|
||||
// Value<a> -> Value<a -> b> -> Value<b>
|
||||
export const transform = input => fn => {
|
||||
const output = fn.out(input.out);
|
||||
const _inspect = (depth, options, inspect) => `transform(${inspect(input)}, ${inspect(fn)})`;
|
||||
if (input.kind === "literal") {
|
||||
// optimization: sandwich everything together
|
||||
return {
|
||||
kind: "literal",
|
||||
out: output,
|
||||
// [inspect.custom]: _inspect,
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
kind: "transformation",
|
||||
in: input,
|
||||
fn,
|
||||
out: output,
|
||||
// [inspect.custom]: _inspect,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Value<a> -> Set<Slot<Top>>
|
||||
export const getReadDependencies = value => {
|
||||
if (value.kind === "literal") {
|
||||
return new Set();
|
||||
}
|
||||
else if (value.kind === "read") {
|
||||
return new Set([value.slot]);
|
||||
}
|
||||
else if (value.kind === "transformation") {
|
||||
return new Set([
|
||||
...getReadDependencies(value.in),
|
||||
...getReadDependencies(value.fn),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
// for debugging
|
||||
export const verifyValue = (value, indent = 0) => {
|
||||
let success = true;
|
||||
const printIndent = (...args) => {
|
||||
console.log(" ".repeat(indent * 2), ...args);
|
||||
};
|
||||
const compare = (a, b, kind) => {
|
||||
if (typeof a === 'function' && typeof b === 'function') {
|
||||
printIndent("note: cannot compare functions", `(${kind})`);
|
||||
}
|
||||
else if (deepEqual(a, b)) {
|
||||
printIndent(`ok (${kind})`);
|
||||
}
|
||||
else {
|
||||
printIndent(`bad (${kind})`);
|
||||
success = false;
|
||||
}
|
||||
};
|
||||
if (value.kind === "literal") {
|
||||
compare(1, 1, "literal");
|
||||
}
|
||||
else if (value.kind === "read") {
|
||||
compare(value.out, value.slot.value.out, "read");
|
||||
}
|
||||
else if (value.kind === "transformation") {
|
||||
compare(value.fn.out(value.in.out),
|
||||
value.out, "transformation");
|
||||
|
||||
success &= verifyValue(value.in, indent + 1);
|
||||
success &= verifyValue(value.fn, indent + 1);
|
||||
}
|
||||
return success;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue