everything seems to work ... but code is dirty

This commit is contained in:
Joeri Exelmans 2025-05-19 23:59:21 +02:00
parent a19dbe1b34
commit 5b6bcf5ffa
10 changed files with 253 additions and 131 deletions

8
pnpm-lock.yaml generated
View file

@ -16,7 +16,7 @@ importers:
version: 5.2.5 version: 5.2.5
dope2: dope2:
specifier: git+https://deemz.org/git/joeri/dope2.git specifier: git+https://deemz.org/git/joeri/dope2.git
version: git+https://deemz.org/git/joeri/dope2.git#4fcfea409a3961733f80f8dbb2810efb3d8f2cf4 version: git+https://deemz.org/git/joeri/dope2.git#8cfbd6116ffe778efb02c37133a1ff633ae171df
react: react:
specifier: ^19.1.0 specifier: ^19.1.0
version: 19.1.0 version: 19.1.0
@ -597,8 +597,8 @@ packages:
deep-is@0.1.4: deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dope2@git+https://deemz.org/git/joeri/dope2.git#4fcfea409a3961733f80f8dbb2810efb3d8f2cf4: dope2@git+https://deemz.org/git/joeri/dope2.git#8cfbd6116ffe778efb02c37133a1ff633ae171df:
resolution: {commit: 4fcfea409a3961733f80f8dbb2810efb3d8f2cf4, repo: https://deemz.org/git/joeri/dope2.git, type: git} resolution: {commit: 8cfbd6116ffe778efb02c37133a1ff633ae171df, repo: https://deemz.org/git/joeri/dope2.git, type: git}
version: 0.0.1 version: 0.0.1
esbuild@0.25.4: esbuild@0.25.4:
@ -1431,7 +1431,7 @@ snapshots:
deep-is@0.1.4: {} deep-is@0.1.4: {}
dope2@git+https://deemz.org/git/joeri/dope2.git#4fcfea409a3961733f80f8dbb2810efb3d8f2cf4: dope2@git+https://deemz.org/git/joeri/dope2.git#8cfbd6116ffe778efb02c37133a1ff633ae171df:
dependencies: dependencies:
functional-red-black-tree: 1.0.1 functional-red-black-tree: 1.0.1

View file

@ -3,7 +3,7 @@ import './App.css';
import { GlobalContext } from './GlobalContext'; import { GlobalContext } from './GlobalContext';
import { ExprBlock, type ExprBlockState } from './ExprBlock'; import { ExprBlock, type ExprBlockState } from './ExprBlock';
import { extendedEnv } from './EnvContext'; import { extendedEnv } from './EnvContext';
import { biggerExample, higherOrder, initialEditorState, lambda2Params, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; import { biggerExample, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
import { evalEditorBlock } from "./eval"; import { evalEditorBlock } from "./eval";
const commands: [string, string[], string][] = [ const commands: [string, string[], string][] = [
@ -21,6 +21,9 @@ const examples: [string, ExprBlockState][] = [
["bigger example" , biggerExample ], ["bigger example" , biggerExample ],
["lambda 2 params" , lambda2Params ], ["lambda 2 params" , lambda2Params ],
["higher order" , higherOrder ], ["higher order" , higherOrder ],
["higher order 2" , higherOrder2Params ],
["push Bool" , pushBool ],
["inc" , inc ],
]; ];
type AppState = { type AppState = {
@ -35,16 +38,16 @@ const defaultState = {
function loadFromLocalStorage(): AppState { function loadFromLocalStorage(): AppState {
if (localStorage["appState"]) { if (localStorage["appState"]) {
try { // try {
const appState = JSON.parse(localStorage["appState"]); // may throw const appState = JSON.parse(localStorage["appState"]); // may throw
// if our state is corrupt, discover it eagerly: // if our state is corrupt, discover it eagerly:
evalEditorBlock(appState.history.at(-1), extendedEnv); // evalEditorBlock(appState.history.at(-1), extendedEnv);
return appState; // all good return appState; // all good
} // }
catch (e) { // catch (e) {
console.log('error recovering state from localStorage (resetting):', e); // console.log('error recovering state from localStorage (resetting):', e);
} // }
} }
return defaultState; return defaultState;
} }

View file

@ -28,7 +28,7 @@ function nestedFnProperties({state, setState, suggestionPriority}: CallBlockProp
} }
const fnSuggestionPriority = (fnSuggestion: ResolvedType) => computePriority( const fnSuggestionPriority = (fnSuggestion: ResolvedType) => computePriority(
fnSuggestion, fnSuggestion,
evalEditorBlock(state.input, env), evalEditorBlock(state.input, env)[0],
suggestionPriority, suggestionPriority,
env, env,
); );
@ -43,7 +43,7 @@ function nestedInputProperties({state, setState, suggestionPriority}: CallBlockP
setState(state => state.fn); // we become our function setState(state => state.fn); // we become our function
} }
const inputSuggestionPriorirty = (inputSuggestion: ResolvedType) => computePriority( const inputSuggestionPriorirty = (inputSuggestion: ResolvedType) => computePriority(
evalEditorBlock(state.fn, env), // fn *may* be set evalEditorBlock(state.fn, env)[0], // fn *may* be set
inputSuggestion, // suggestions will be for input inputSuggestion, // suggestions will be for input
suggestionPriority, // priority function we get from parent block suggestionPriority, // priority function we get from parent block
env, env,
@ -53,7 +53,7 @@ function nestedInputProperties({state, setState, suggestionPriority}: CallBlockP
export function CallBlock(props: CallBlockProps) { export function CallBlock(props: CallBlockProps) {
const env = useContext(EnvContext); const env = useContext(EnvContext);
const resolved = evalEditorBlock(props.state, env); const [resolved] = evalEditorBlock(props.state, env);
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}> return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
<FunctionHeader {...props} /> <FunctionHeader {...props} />
<div className="functionParams"> <div className="functionParams">
@ -73,7 +73,7 @@ export function CallBlock(props: CallBlockProps) {
} }
function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) { function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) {
const resolved = evalCallBlock2(fn, input, env); const [resolved] = evalCallBlock2(fn, input, env);
const score = scoreResolved(resolved, outPriority); const score = scoreResolved(resolved, outPriority);
return score; return score;
} }

View file

@ -183,7 +183,7 @@ export function ExprBlock({state, setState, onCancel, suggestionPriority}: ExprB
/>; />;
} }
} }
const resolved = evalEditorBlock(state, env); const [resolved] = evalEditorBlock(state, env);
return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}> return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
{renderBlock()} {renderBlock()}
<div className="typeSignature"> <div className="typeSignature">

View file

@ -50,7 +50,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName); const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName);
const exprResolved = evalEditorBlock(state.expr, staticInnerEnv); const [exprResolved] = evalEditorBlock(state.expr, staticInnerEnv);
const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType; const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType;
@ -62,6 +62,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
unification: new Map(), // <- is this correct? unification: new Map(), // <- is this correct?
}) })
// const {exprResolved, env: newEnv} = computeLambdaBlockType(state.paramName, state.expr, env);
return <span className="lambdaBlock"> return <span className="lambdaBlock">
<span className="keyword">&#955;</span> <span className="keyword">&#955;</span>

View file

@ -41,7 +41,7 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}
const valueSuggestionPriority = (suggestion: ResolvedType) => { const valueSuggestionPriority = (suggestion: ResolvedType) => {
const innerEnv = makeInnerEnv(env, name, suggestion); const innerEnv = makeInnerEnv(env, name, suggestion);
const resolved = evalEditorBlock(inner, innerEnv); const [resolved] = evalEditorBlock(inner, innerEnv);
return scoreResolved(resolved, suggestionPriority); return scoreResolved(resolved, suggestionPriority);
}; };
@ -51,7 +51,7 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}
}, []); }, []);
useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]); useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]);
const valueResolved = evalEditorBlock(value, env); const [valueResolved] = evalEditorBlock(value, env);
const innerEnv = makeInnerEnv(env, name, valueResolved); const innerEnv = makeInnerEnv(env, name, valueResolved);
return <> return <>
@ -91,7 +91,7 @@ function InnerMost({state, setState, suggestionPriority}) {
const env = useContext(EnvContext); const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext); const globalContext = useContext(GlobalContext);
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
const valueResolved = evalEditorBlock(state.value, env); const [valueResolved] = evalEditorBlock(state.value, env);
const innerEnv = makeInnerEnv(env, state.name, valueResolved); const innerEnv = makeInnerEnv(env, state.name, valueResolved);
const onCancel = () => setState(state => state.value); const onCancel = () => setState(state => state.value);
if (state.inner.kind === "let" && globalContext?.syntacticSugar) { if (state.inner.kind === "let" && globalContext?.syntacticSugar) {

View file

@ -128,4 +128,10 @@ export const lambda2Params: ExprBlockState = {
} }
}; };
export const higherOrder: ExprBlockState = {"kind":"let","inner":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"myBinaryApply","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":true}},"name":"myBinaryApply","value":{"kind":"lambda","paramName":"x","expr":{"kind":"lambda","paramName":"y","expr":{"kind":"lambda","paramName":"fn","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"y","value":{"kind":"name"},"focus":true}}}}}}; export const higherOrder: ExprBlockState = {"kind":"let","inner":{"kind":"input","text":"","value":{"kind":"text"},"focus":false},"name":"myBinaryApply","value":{"kind":"lambda","paramName":"x","expr":{"kind":"lambda","paramName":"fn","expr":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}}}}};
export const higherOrder2Params: ExprBlockState = {"kind":"let","inner":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"myBinaryApply","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":true}},"name":"myBinaryApply","value":{"kind":"lambda","paramName":"x","expr":{"kind":"lambda","paramName":"y","expr":{"kind":"lambda","paramName":"fn","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"y","value":{"kind":"name"},"focus":true}}}}}};
export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"Bool","value":{"kind":"name"},"focus":true}};
export const inc: ExprBlockState = {"kind":"let","inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}};

View file

@ -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 { ExprBlockState } from "./ExprBlock";
import type { InputValueType } from "./InputBlock"; 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 // the value of every block is either known (Dynamic), an error, or unknown
export type ResolvedType = Dynamic | DeepError | 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") { if (s.kind === "input") {
return evalInputBlock(s.text, s.value, env); 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") { if (value.kind === "literal") {
return parseLiteral(text, value.type, env); return parseLiteral(text, value.type, env);
} }
else if (value.kind === "name") { else if (value.kind === "name") {
const found = trie.get(env.names)(text); const found = trie.get(env.names)(text);
if (found) { 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 // kind === "text" -> unresolved
return { const [t, env2] = makeTypeVar(env, 'err')
return [{
kind: "error", kind: "error",
t: makeTypeVar(env, 'err')[0], t,
e: new Error(`'${text}' not found`), e: new NotFoundError(`'${text}' not found`),
depth: 0, depth: 0,
unification: new Map(), 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): ResolvedType { export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
if (getSymbol(fnResolved.t) !== symbolFunction) { if (getSymbol(fnResolved.t) !== symbolFunction) {
// worst outcome: we know nothing about the result! // not a function...
if (isTypeVar(fnResolved.t)) {
// ... but typeVars are OK (good guys!)
}
else {
// worst outcome
return makeError(env, return makeError(env,
new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`), new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`),
mergeUnifications(fnResolved.unification, inputResolved.unification), 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;
}
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 { try {
// fn is a function... // turn input in to a function
const [rewrittenFnType] = recomputeTypeVars([fnResolved.t], env.nextFreeTypeVar); const [abstractOutputType, env2] = makeTypeVar(env, "<out>");
const unification = (unifyLL(rewrittenFnType.params[0](rewrittenFnType), inputResolved.t)); const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType);
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);
if (IS_DEV) { 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('fnType :', prettyT(fnResolved.t));
console.log('rewrittenFnType :', prettyT(rewrittenFnType));
console.log('inputType :', prettyT(inputResolved.t)); 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('unification :', prettyU(unification));
console.log('subsetOfUnification :', prettyU(subsetOfUnification)); console.log('unificationInvR :', prettyRU(unificationR));
console.log('otherSubSetOfUnification:', prettyU(otherSubSetOfUnification)); console.log('unifiedFnType :', prettyT(unifiedFnType));
console.log('outType :', prettyT(outType)); 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('fn.unification :', prettyU(fnResolved.unification));
console.log('input.unification :', prettyU(inputResolved.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('grandUnification :', prettyU(grandUnification));
console.log('==================================') console.log('==================================')
} }
if (inputResolved.kind === "error") { if (inputResolved.kind === "error") {
return { // throw inputResolved.e;
return [{
kind: "error", kind: "error",
e: inputResolved.e, // bubble up the error e: inputResolved.e, // bubble up the error
depth: 0, depth: 0,
t: outType, t: outType,
unification: grandUnification, unification: grandUnification,
}; }, newEnv];
} }
if (fnResolved.kind === "error") { if (fnResolved.kind === "error") {
// throw fnResolved.e;
// also bubble up // also bubble up
return { return [{
kind: "error", kind: "error",
e: fnResolved.e, e: fnResolved.e,
depth: fnResolved.depth+1, depth: fnResolved.depth+1,
t: outType, t: outType,
unification: grandUnification, unification: grandUnification,
}; }, newEnv];
} }
// if the above statement did not throw => types are compatible... // if the above statement did not throw => types are compatible...
if (inputResolved.kind === "value" && fnResolved.kind === "value") { if (inputResolved.kind === "value" && fnResolved.kind === "value") {
const outValue = fnResolved.i(inputResolved.i); const outValue = fnResolved.i(inputResolved.i);
return { console.log('outValue:', outValue);
return [{
kind: "value", kind: "value",
i: outValue, i: outValue,
t: outType, t: outType,
unification: grandUnification, unification: grandUnification,
}; }, newEnv];
} }
else { else {
// we don't know the value, but we do know the type: // we don't know the value, but we do know the type:
return { return [{
kind: "unknown", kind: "unknown",
t: outType, t: outType,
unification: grandUnification, unification: grandUnification,
}; }, newEnv];
} }
} }
catch (e) { 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...? // 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); const outType = fnResolved.t.params[1](fnResolved.t);
try { try {
return { return [{
kind: "error", kind: "error",
e, e: e as Error,
depth: 0, depth: 0,
t: outType, t: outType,
unification: mergeUnifications(fnResolved.unification, inputResolved.unification), // may throw! unification: mergeUnifications(fnResolved.unification, inputResolved.unification), // may throw!
}; }, env];
} }
catch (e) { catch (e) {
if ((e instanceof UnifyError)) { if ((e instanceof UnifyError)) {
@ -180,46 +278,43 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
} }
throw e; 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 { export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] {
const valueResolved = evalEditorBlock(value, env); const [valueResolved] = evalEditorBlock(value, env);
const innerEnv = makeInnerEnv(env, name, valueResolved); const innerEnv = makeInnerEnv(env, name, valueResolved);
return evalEditorBlock(inner, innerEnv); 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 [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) { if (IS_DEV) {
console.log('========= evalLambdaBlock =========') console.log('====== begin evalLambdaBlock ======')
console.log('paramName :', paramName);
console.log('paramType :', prettyT(paramType)); console.log('paramType :', prettyT(paramType));
console.log('exprType :', prettyT(exprResolved.t)); console.log('staticInnerEnv:', staticInnerEnv);
console.log('lambdaType :', prettyT(lambdaT));
console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted));
console.log('===================================') console.log('===================================')
} }
// console.log('inner kind', exprResolved.kind, 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 reduced = reduceUnification(exprResolved.unification);
const lambdaTSubstituted = substitute(
lambdaT,
reduced,
[]); // <- not important
let lambdaResolved: ResolvedType;
if (exprResolved.kind === "error") { if (exprResolved.kind === "error") {
return { lambdaResolved = {
kind: "error", kind: "error",
t: lambdaTSubstituted, t: lambdaTSubstituted,
depth: 0, depth: 0,
@ -227,25 +322,41 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
unification: exprResolved.unification, unification: exprResolved.unification,
} }
} }
else {
const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted); const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted);
const fn = (x: any) => { lambdaResolved = {
kind: "value",
t: lambdaTSubstituted,
i: (x: any) => {
const innerEnv = makeInnerEnv(env, paramName, { const innerEnv = makeInnerEnv(env, paramName, {
kind: "value", kind: "value",
i: x, i: x,
t: paramTypeSubstituted, t: paramTypeSubstituted,
unification: new Map(), unification: new Map(),
}); });
const result = evalEditorBlock(expr, innerEnv); const [result] = evalEditorBlock(expr, innerEnv);
if (result.kind === "value") { if (result.kind === "value") {
return result.i; return result.i;
} }
} },
return {
kind: "value",
t: lambdaTSubstituted,
i: fn,
unification: exprResolved.unification, 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) { export function haveValue(resolved: ResolvedType) {
@ -253,7 +364,7 @@ export function haveValue(resolved: ResolvedType) {
return resolved.kind === "value"; return resolved.kind === "value";
} }
function parseLiteral(text: string, type: string, env): ResolvedType { function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] {
// dirty // dirty
if (type === "Int") { if (type === "Int") {
return parseAsInt(text, env); return parseAsInt(text, env);
@ -264,29 +375,29 @@ function parseLiteral(text: string, type: string, env): ResolvedType {
return makeError(env, new Error("Failed to parse")); return makeError(env, new Error("Failed to parse"));
} }
function parseAsDouble(text: string, env): ResolvedType { function parseAsDouble(text: string, env: Environment): [ResolvedType,Environment] {
if (text !== '') { if (text !== '') {
const num = Number(text); const num = Number(text);
if (!Number.isNaN(num)) { if (!Number.isNaN(num)) {
return { return [{
kind: "value", kind: "value",
i: num, i: num,
t: Double, t: Double,
unification: new Map(), unification: new Map(),
}; }, env];
} }
} }
return makeError(env, new Error("Failed to parse as Double")); 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 !== '') { if (text !== '') {
try { try {
return { return [{
kind: "value", kind: "value",
i: BigInt(text), i: BigInt(text),
t: Int, t: Int,
unification: new Map(), unification: new Map(),
}; // may throw }, env]; // may throw
} }
catch {} catch {}
} }
@ -295,9 +406,10 @@ function parseAsInt(text: string, env): ResolvedType {
const literalParsers = [parseAsDouble, parseAsInt]; 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)) 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) { 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 idx = env.nextFreeTypeVar;
const typeVar = TYPE_VARS[idx]; const typeVar = TYPE_VARS[idx];
const deepError: DeepError = { const deepError: DeepError = {
@ -353,9 +465,9 @@ function makeError(env: Environment, e: Error, unification: Unification=new Map(
e, e,
depth: 0, depth: 0,
}; };
return deepError; return [deepError, {
// , { names: env.names,
// names: trie.insert(env.names)('err')(deepError), nextFreeTypeVar: idx + 1,
// nextFreeTypeVar: idx + 1, typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]),
// }]; }];
} }

View file

@ -3,7 +3,7 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2023", "target": "ES2023",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"], "lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,

View file

@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023", "target": "ES2023",
"lib": ["ES2023"], "lib": ["ESNext"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,