everything seems to work ... but code is dirty
This commit is contained in:
parent
a19dbe1b34
commit
5b6bcf5ffa
10 changed files with 253 additions and 131 deletions
328
src/eval.ts
328
src/eval.ts
|
|
@ -1,4 +1,4 @@
|
|||
import { Double, fnType, getHumanReadableName, getSymbol, Int, mergeUnifications, NotAFunctionError, occurring, prettyT, prettyU, recomputeTypeVars, reduceUnification, substitute, symbolFunction, trie, TYPE_VARS, UNBOUND_SYMBOLS, UnifyError, unifyLL, transitivelyGrow } from "dope2";
|
||||
import { Double, fnType, getHumanReadableName, getSymbol, Int, mergeUnifications, NotAFunctionError, occurring, prettyT, prettyU, recomputeTypeVars, reduceUnification, substitute, symbolFunction, trie, TYPE_VARS, UNBOUND_SYMBOLS, UnifyError, unifyLL, transitivelyGrow, isTypeVar, set, compareTypes, recomputeTypeVarsWithInverse } from "dope2";
|
||||
|
||||
import type { ExprBlockState } from "./ExprBlock";
|
||||
import type { InputValueType } from "./InputBlock";
|
||||
|
|
@ -43,7 +43,15 @@ export interface Unknown {
|
|||
// the value of every block is either known (Dynamic), an error, or unknown
|
||||
export type ResolvedType = Dynamic | DeepError | Unknown;
|
||||
|
||||
export const evalEditorBlock = (s: ExprBlockState, env): ResolvedType => {
|
||||
// export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => {
|
||||
// const [resolved] = proxyEditorBlock(s, env);
|
||||
// const [t, newEnv] = recomputeTypeVarsForEnv(resolved.t, env);
|
||||
// return [{...resolved, t }, newEnv];
|
||||
// };
|
||||
|
||||
class NotFoundError extends Error {}
|
||||
|
||||
export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => {
|
||||
if (s.kind === "input") {
|
||||
return evalInputBlock(s.text, s.value, env);
|
||||
}
|
||||
|
|
@ -58,121 +66,211 @@ export const evalEditorBlock = (s: ExprBlockState, env): ResolvedType => {
|
|||
}
|
||||
};
|
||||
|
||||
export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType {
|
||||
export function evalInputBlock(text: string, value: InputValueType, env: Environment): [ResolvedType,Environment] {
|
||||
if (value.kind === "literal") {
|
||||
return parseLiteral(text, value.type, env);
|
||||
}
|
||||
else if (value.kind === "name") {
|
||||
const found = trie.get(env.names)(text);
|
||||
if (found) {
|
||||
return found;
|
||||
if (found.kind === "unknown") {
|
||||
console.log('returning', text, 'as-is');
|
||||
return [found, env]; // don't rewrite lambda parameters
|
||||
}
|
||||
console.log('rewriting', text);
|
||||
return recomputeTypeVarsForEnv(text, found, env);
|
||||
}
|
||||
}
|
||||
// kind === "text" -> unresolved
|
||||
return {
|
||||
const [t, env2] = makeTypeVar(env, 'err')
|
||||
return [{
|
||||
kind: "error",
|
||||
t: makeTypeVar(env, 'err')[0],
|
||||
e: new Error(`'${text}' not found`),
|
||||
t,
|
||||
e: new NotFoundError(`'${text}' not found`),
|
||||
depth: 0,
|
||||
unification: new Map(),
|
||||
};
|
||||
}, env2];
|
||||
}
|
||||
|
||||
export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||
const [fnResolved, env2] = evalEditorBlock(fn, env);
|
||||
const [inputResolved, env3] = evalEditorBlock(input, env2);
|
||||
console.log('==== evalCallBlock ====');
|
||||
console.log('env :', env);
|
||||
console.log('fnResolved :', fnResolved);
|
||||
console.log('env2 :', env2);
|
||||
console.log('inputResolved:', inputResolved);
|
||||
console.log('env3 :', env3);
|
||||
console.log('=======================');
|
||||
return evalCallBlock2(fnResolved, inputResolved, env3);
|
||||
}
|
||||
|
||||
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||
if (getSymbol(fnResolved.t) !== symbolFunction) {
|
||||
// not a function...
|
||||
if (isTypeVar(fnResolved.t)) {
|
||||
// ... but typeVars are OK (good guys!)
|
||||
}
|
||||
else {
|
||||
// worst outcome
|
||||
return makeError(env,
|
||||
new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`),
|
||||
mergeUnifications(fnResolved.unification, inputResolved.unification),
|
||||
)
|
||||
}
|
||||
}
|
||||
// it's is a function, continue...
|
||||
return evalCallBlock3(fnResolved, inputResolved, env);
|
||||
};
|
||||
|
||||
const highestTypeVar = type => {
|
||||
let highest = -1;
|
||||
for (const typeVar of occurring(type)) {
|
||||
highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar));
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
const highestTypeVar2 = typeVars => {
|
||||
let highest = -1;
|
||||
for (const typeVar of typeVars) {
|
||||
highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar));
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
|
||||
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env): ResolvedType {
|
||||
if (getSymbol(fnResolved.t) !== symbolFunction) {
|
||||
// worst outcome: we know nothing about the result!
|
||||
return makeError(env,
|
||||
new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`),
|
||||
mergeUnifications(fnResolved.unification, inputResolved.unification),
|
||||
)
|
||||
}
|
||||
const inverseUnification = (uni, inverse) => {
|
||||
return new Map([...uni]
|
||||
.filter(([symbol]) => !inverse.has(inverse.get(symbol)))
|
||||
.map(([symbol, types]) => [inverse.get(symbol) || symbol, types])
|
||||
);
|
||||
}
|
||||
|
||||
function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||
const [[newType], inverse] = recomputeTypeVarsWithInverse([resolved.t], env.nextFreeTypeVar);
|
||||
const newResolved: ResolvedType = {
|
||||
...resolved,
|
||||
t: newType,
|
||||
unification: inverseUnification(resolved.unification, inverse),
|
||||
};
|
||||
|
||||
// hacky
|
||||
const typeVars = env.typeVars.union(occurring(newType)) as Set<string>;
|
||||
const newEnv: Environment = {
|
||||
names: trie.insert(env.names)(name)(newResolved),
|
||||
typeVars,
|
||||
nextFreeTypeVar: highestTypeVar2(typeVars) + 1,
|
||||
};
|
||||
|
||||
return [newResolved, newEnv];
|
||||
}
|
||||
|
||||
function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||
try {
|
||||
// fn is a function...
|
||||
const [rewrittenFnType] = recomputeTypeVars([fnResolved.t], env.nextFreeTypeVar);
|
||||
const unification = (unifyLL(rewrittenFnType.params[0](rewrittenFnType), inputResolved.t));
|
||||
|
||||
const inputTypeVars = occurring(inputResolved.t);
|
||||
const fnTypeVars = occurring(fnResolved.t);
|
||||
const subsetOfUnification = new Map([...unification].filter(([typeVar]) => inputTypeVars.has(typeVar)));
|
||||
const otherSubSetOfUnification = new Map([...unification].filter(([typeVar]) => fnTypeVars.has(typeVar)));
|
||||
|
||||
const outType = substitute(
|
||||
rewrittenFnType.params[1](rewrittenFnType),
|
||||
reduceUnification(unification),
|
||||
[]); // <- not important
|
||||
|
||||
const grandUnification = [fnResolved.unification, inputResolved.unification]
|
||||
.reduce(mergeUnifications, unification);
|
||||
// turn input in to a function
|
||||
const [abstractOutputType, env2] = makeTypeVar(env, "<out>");
|
||||
const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType);
|
||||
|
||||
if (IS_DEV) {
|
||||
console.log('========= evalCallBlock2 =========')
|
||||
console.log('========= evalCallBlock3 =========')
|
||||
console.log('env :', env);
|
||||
console.log('fnKind :', fnResolved.kind);
|
||||
console.log('inputKind :', inputResolved.kind);
|
||||
console.log('fnType :', prettyT(fnResolved.t));
|
||||
console.log('rewrittenFnType :', prettyT(rewrittenFnType));
|
||||
console.log('inputType :', prettyT(inputResolved.t));
|
||||
console.log('matchFnType :', prettyT(matchFnType));
|
||||
}
|
||||
|
||||
// unify both functions
|
||||
const unification = /*transitivelyGrow*/(unifyLL(fnResolved.t, matchFnType));
|
||||
|
||||
const unificationR = reduceUnification(unification);
|
||||
const unifiedFnType = substitute(
|
||||
// matchFnType,
|
||||
fnResolved.t,
|
||||
unificationR, []);
|
||||
|
||||
const outType = unifiedFnType.params[1](unifiedFnType);
|
||||
|
||||
const newEnv = (outType === abstractOutputType) ? env2 : env;
|
||||
|
||||
// we don't want to 'bubble up' our outType substitution, because it's just a temporary variable
|
||||
const unificationWithoutOutType = new Map([...unification].filter(([symbol]) => symbol !== abstractOutputType.symbol));
|
||||
|
||||
if (IS_DEV) {
|
||||
console.log('unification :', prettyU(unification));
|
||||
console.log('subsetOfUnification :', prettyU(subsetOfUnification));
|
||||
console.log('otherSubSetOfUnification:', prettyU(otherSubSetOfUnification));
|
||||
console.log('unificationInvR :', prettyRU(unificationR));
|
||||
console.log('unifiedFnType :', prettyT(unifiedFnType));
|
||||
console.log('outType :', prettyT(outType));
|
||||
// console.log('inputTypeVars :', `{${[...inputTypeVars].map(getHumanReadableName).join(', ')}}`);
|
||||
// console.log('fnTypeVars :', `{${[...fnTypeVars].map(getHumanReadableName).join(', ')}}`);
|
||||
console.log('fn.unification :', prettyU(fnResolved.unification));
|
||||
console.log('input.unification :', prettyU(inputResolved.unification));
|
||||
console.log('unificationWithoutOutType:', prettyU(unificationWithoutOutType));
|
||||
}
|
||||
|
||||
const grandUnification = [fnResolved.unification, inputResolved.unification]
|
||||
.reduce(mergeUnifications, unificationWithoutOutType);
|
||||
|
||||
// const grandUnification = unificationWithoutOutType;
|
||||
|
||||
if (IS_DEV) {
|
||||
console.log('grandUnification :', prettyU(grandUnification));
|
||||
console.log('==================================')
|
||||
}
|
||||
|
||||
|
||||
if (inputResolved.kind === "error") {
|
||||
return {
|
||||
// throw inputResolved.e;
|
||||
return [{
|
||||
kind: "error",
|
||||
e: inputResolved.e, // bubble up the error
|
||||
depth: 0,
|
||||
t: outType,
|
||||
unification: grandUnification,
|
||||
};
|
||||
}, newEnv];
|
||||
}
|
||||
if (fnResolved.kind === "error") {
|
||||
// throw fnResolved.e;
|
||||
// also bubble up
|
||||
return {
|
||||
return [{
|
||||
kind: "error",
|
||||
e: fnResolved.e,
|
||||
depth: fnResolved.depth+1,
|
||||
t: outType,
|
||||
unification: grandUnification,
|
||||
};
|
||||
}, newEnv];
|
||||
}
|
||||
// if the above statement did not throw => types are compatible...
|
||||
if (inputResolved.kind === "value" && fnResolved.kind === "value") {
|
||||
const outValue = fnResolved.i(inputResolved.i);
|
||||
return {
|
||||
console.log('outValue:', outValue);
|
||||
return [{
|
||||
kind: "value",
|
||||
i: outValue,
|
||||
t: outType,
|
||||
unification: grandUnification,
|
||||
};
|
||||
}, newEnv];
|
||||
}
|
||||
else {
|
||||
// we don't know the value, but we do know the type:
|
||||
return {
|
||||
return [{
|
||||
kind: "unknown",
|
||||
t: outType,
|
||||
unification: grandUnification,
|
||||
};
|
||||
}, newEnv];
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if ((e instanceof UnifyError)) {
|
||||
// if ((e instanceof UnifyError)) {
|
||||
console.log('UnifyError!', (e as Error).message);
|
||||
// even though fn was incompatible with the given parameter, we can still suppose that our output-type will be that of fn...?
|
||||
const outType = fnResolved.t.params[1](fnResolved.t);
|
||||
try {
|
||||
return {
|
||||
return [{
|
||||
kind: "error",
|
||||
e,
|
||||
e: e as Error,
|
||||
depth: 0,
|
||||
t: outType,
|
||||
unification: mergeUnifications(fnResolved.unification, inputResolved.unification), // may throw!
|
||||
};
|
||||
}, env];
|
||||
}
|
||||
catch (e) {
|
||||
if ((e instanceof UnifyError)) {
|
||||
|
|
@ -180,46 +278,43 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
|
|||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
// }
|
||||
// throw e;
|
||||
}
|
||||
};
|
||||
|
||||
export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env): ResolvedType {
|
||||
const fnResolved = evalEditorBlock(fn, env);
|
||||
const inputResolved = evalEditorBlock(input, env);
|
||||
return evalCallBlock2(fnResolved, inputResolved, env);
|
||||
}
|
||||
|
||||
export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env): ResolvedType {
|
||||
const valueResolved = evalEditorBlock(value, env);
|
||||
export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||
const [valueResolved] = evalEditorBlock(value, env);
|
||||
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
||||
return evalEditorBlock(inner, innerEnv);
|
||||
}
|
||||
|
||||
const prettyRU = (rUni: Map<string, Type>) => {
|
||||
return '{'+[...rUni].map(([symbol,type]) => `${getHumanReadableName(symbol)} => ${prettyT(type)}`).join(', ')+'}';
|
||||
}
|
||||
|
||||
export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): ResolvedType {
|
||||
export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||
const [paramType, staticInnerEnv] = makeTypeVar(env, paramName);
|
||||
const exprResolved = evalEditorBlock(expr, staticInnerEnv);
|
||||
const lambdaT = fnType(_ => paramType)(_ => exprResolved.t);
|
||||
// This is the only place in the code where we actually do something with the 'substitutions'. We compute the type of our lambda function:
|
||||
const lambdaTSubstituted = substitute(
|
||||
lambdaT,
|
||||
reduceUnification(exprResolved.unification),
|
||||
[]); // <- not important
|
||||
|
||||
if (IS_DEV) {
|
||||
console.log('========= evalLambdaBlock =========')
|
||||
console.log('paramType :', prettyT(paramType));
|
||||
console.log('exprType :', prettyT(exprResolved.t));
|
||||
console.log('lambdaType :', prettyT(lambdaT));
|
||||
console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted));
|
||||
console.log('====== begin evalLambdaBlock ======')
|
||||
console.log('paramName :', paramName);
|
||||
console.log('paramType :', prettyT(paramType));
|
||||
console.log('staticInnerEnv:', staticInnerEnv);
|
||||
console.log('===================================')
|
||||
}
|
||||
|
||||
// console.log('inner kind', exprResolved.kind, paramName);
|
||||
if (exprResolved.kind === "error") {
|
||||
return {
|
||||
const [exprResolved] = evalEditorBlock(expr, staticInnerEnv);
|
||||
const lambdaT = fnType(_ => paramType)(_ => exprResolved.t);
|
||||
// This is the only place in the code where we actually do something with the 'substitutions'. We compute the type of our lambda function:
|
||||
const reduced = reduceUnification(exprResolved.unification);
|
||||
const lambdaTSubstituted = substitute(
|
||||
lambdaT,
|
||||
reduced,
|
||||
[]); // <- not important
|
||||
let lambdaResolved: ResolvedType;
|
||||
if (exprResolved.kind === "error") {
|
||||
lambdaResolved = {
|
||||
kind: "error",
|
||||
t: lambdaTSubstituted,
|
||||
depth: 0,
|
||||
|
|
@ -227,25 +322,41 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
|
|||
unification: exprResolved.unification,
|
||||
}
|
||||
}
|
||||
const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted);
|
||||
const fn = (x: any) => {
|
||||
const innerEnv = makeInnerEnv(env, paramName, {
|
||||
else {
|
||||
const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted);
|
||||
lambdaResolved = {
|
||||
kind: "value",
|
||||
i: x,
|
||||
t: paramTypeSubstituted,
|
||||
unification: new Map(),
|
||||
});
|
||||
const result = evalEditorBlock(expr, innerEnv);
|
||||
if (result.kind === "value") {
|
||||
return result.i;
|
||||
t: lambdaTSubstituted,
|
||||
i: (x: any) => {
|
||||
const innerEnv = makeInnerEnv(env, paramName, {
|
||||
kind: "value",
|
||||
i: x,
|
||||
t: paramTypeSubstituted,
|
||||
unification: new Map(),
|
||||
});
|
||||
const [result] = evalEditorBlock(expr, innerEnv);
|
||||
if (result.kind === "value") {
|
||||
return result.i;
|
||||
}
|
||||
},
|
||||
unification: exprResolved.unification,
|
||||
}
|
||||
}
|
||||
return {
|
||||
kind: "value",
|
||||
t: lambdaTSubstituted,
|
||||
i: fn,
|
||||
unification: exprResolved.unification,
|
||||
};
|
||||
|
||||
// const [lambdaResolvedNormalized, resultEnv] = recomputeTypeVarsForEnv(paramName, lambdaResolved, env);
|
||||
|
||||
if (IS_DEV) {
|
||||
console.log('======= end evalLambdaBlock =======')
|
||||
console.log('paramType :', prettyT(paramType));
|
||||
console.log('exprType :', prettyT(exprResolved.t));
|
||||
console.log('exprUnification :', prettyU(exprResolved.unification));
|
||||
console.log('exprUnificationR :', prettyRU(reduced));
|
||||
console.log('lambdaType :', prettyT(lambdaT));
|
||||
console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted));
|
||||
// console.log('normalizedT :', prettyT(lambdaResolvedNormalized.t));
|
||||
console.log('===================================')
|
||||
}
|
||||
return [lambdaResolved, env];
|
||||
}
|
||||
|
||||
export function haveValue(resolved: ResolvedType) {
|
||||
|
|
@ -253,7 +364,7 @@ export function haveValue(resolved: ResolvedType) {
|
|||
return resolved.kind === "value";
|
||||
}
|
||||
|
||||
function parseLiteral(text: string, type: string, env): ResolvedType {
|
||||
function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] {
|
||||
// dirty
|
||||
if (type === "Int") {
|
||||
return parseAsInt(text, env);
|
||||
|
|
@ -264,29 +375,29 @@ function parseLiteral(text: string, type: string, env): ResolvedType {
|
|||
return makeError(env, new Error("Failed to parse"));
|
||||
}
|
||||
|
||||
function parseAsDouble(text: string, env): ResolvedType {
|
||||
function parseAsDouble(text: string, env: Environment): [ResolvedType,Environment] {
|
||||
if (text !== '') {
|
||||
const num = Number(text);
|
||||
if (!Number.isNaN(num)) {
|
||||
return {
|
||||
return [{
|
||||
kind: "value",
|
||||
i: num,
|
||||
t: Double,
|
||||
unification: new Map(),
|
||||
};
|
||||
}, env];
|
||||
}
|
||||
}
|
||||
return makeError(env, new Error("Failed to parse as Double"));
|
||||
}
|
||||
function parseAsInt(text: string, env): ResolvedType {
|
||||
function parseAsInt(text: string, env: Environment): [ResolvedType,Environment] {
|
||||
if (text !== '') {
|
||||
try {
|
||||
return {
|
||||
return [{
|
||||
kind: "value",
|
||||
i: BigInt(text),
|
||||
t: Int,
|
||||
unification: new Map(),
|
||||
}; // may throw
|
||||
}, env]; // may throw
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
|
@ -295,9 +406,10 @@ function parseAsInt(text: string, env): ResolvedType {
|
|||
|
||||
const literalParsers = [parseAsDouble, parseAsInt];
|
||||
|
||||
export function attemptParseLiteral(text: string, env): Dynamic[] {
|
||||
export function attemptParseLiteral(text: string, env: Environment): Dynamic[] {
|
||||
return literalParsers.map(parseFn => parseFn(text, env))
|
||||
.filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
|
||||
.map(([resolved]) => resolved)
|
||||
.filter((resolved) => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
|
||||
}
|
||||
|
||||
export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) {
|
||||
|
|
@ -343,7 +455,7 @@ export function makeTypeVar(env: Environment, name: string): [Type, Environment]
|
|||
}];
|
||||
}
|
||||
|
||||
function makeError(env: Environment, e: Error, unification: Unification=new Map()): DeepError {
|
||||
function makeError(env: Environment, e: Error, unification: Unification=new Map()): [DeepError, Environment] {
|
||||
const idx = env.nextFreeTypeVar;
|
||||
const typeVar = TYPE_VARS[idx];
|
||||
const deepError: DeepError = {
|
||||
|
|
@ -353,9 +465,9 @@ function makeError(env: Environment, e: Error, unification: Unification=new Map(
|
|||
e,
|
||||
depth: 0,
|
||||
};
|
||||
return deepError;
|
||||
// , {
|
||||
// names: trie.insert(env.names)('err')(deepError),
|
||||
// nextFreeTypeVar: idx + 1,
|
||||
// }];
|
||||
return [deepError, {
|
||||
names: env.names,
|
||||
nextFreeTypeVar: idx + 1,
|
||||
typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]),
|
||||
}];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue