parser for types + start moving all types to separate modules
This commit is contained in:
parent
8eec5b9239
commit
1d826ea8d4
11 changed files with 277 additions and 88 deletions
15
examples/parser.js
Normal file
15
examples/parser.js
Normal file
|
|
@ -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))
|
||||
|
||||
|
|
@ -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}]
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
174
parser/parser.js
Normal file
174
parser/parser.js
Normal file
|
|
@ -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<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)
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -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),
|
||||
]};
|
||||
|
|
|
|||
27
structures/product.types.js
Normal file
27
structures/product.types.js
Normal file
|
|
@ -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),
|
||||
]
|
||||
};
|
||||
|
|
@ -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),
|
||||
]};
|
||||
|
|
|
|||
31
structures/set.types.js
Normal file
31
structures/set.types.js
Normal file
|
|
@ -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),
|
||||
]
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue