Compare commits

..

No commits in common. "ec2944cdb78f8b1e1817cea5afa0ab09006051ce" and "28e5032923530d00a0aaf8477c40716d5bdcd2a6" have entirely different histories.

7 changed files with 290 additions and 186 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#70fb80a9fc8438a588d1fba957706a322d0900d0 version: git+https://deemz.org/git/joeri/dope2.git#d3515d39a51d0baf9fcc775b8d2fb74c74e24868
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#70fb80a9fc8438a588d1fba957706a322d0900d0: dope2@git+https://deemz.org/git/joeri/dope2.git#d3515d39a51d0baf9fcc775b8d2fb74c74e24868:
resolution: {commit: 70fb80a9fc8438a588d1fba957706a322d0900d0, repo: https://deemz.org/git/joeri/dope2.git, type: git} resolution: {commit: d3515d39a51d0baf9fcc775b8d2fb74c74e24868, 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#70fb80a9fc8438a588d1fba957706a322d0900d0: dope2@git+https://deemz.org/git/joeri/dope2.git#d3515d39a51d0baf9fcc775b8d2fb74c74e24868:
dependencies: dependencies:
functional-red-black-tree: 1.0.1 functional-red-black-tree: 1.0.1

View file

@ -1,20 +1,13 @@
import { createContext } from "react"; import { createContext } from "react";
import { getDefaultTypeParser, module2Env, ModuleStd } from "dope2"; import { getDefaultTypeParser, module2Env, ModuleStd } from "dope2";
import type { Dynamic, Environment } from "./eval";
export const functionWith3Params = i => j => k => i+j+k; export const functionWith3Params = i => j => k => i+j+k;
export const functionWith4Params = i => j => k => l => i+j+k+l; export const functionWith4Params = i => j => k => l => i+j+k+l;
const mkType = getDefaultTypeParser(); const mkType = getDefaultTypeParser();
export const extendedEnv = module2Env(ModuleStd.concat([
export const extendedEnv: Environment = { ["functionWith3Params", { i: functionWith3Params, t: mkType("Int->Int->Int->Int") }],
names: module2Env(ModuleStd.concat([ ["functionWith4Params", { i: functionWith4Params, t: mkType("Int->Int->Int->Int->Int")}]
["functionWith3Params", { i: functionWith3Params, t: mkType("Int->Int->Int->Int") }], ]));
["functionWith4Params", { i: functionWith4Params, t: mkType("Int->Int->Int->Int->Int")}]
]).map(([name, {i,t}]) => [name, { kind: "value", i, t, unification: new Map() }] as [string, Dynamic])
).name2dyn,
nextFreeTypeVar: 0,
typeVars: new Set(),
};
export const EnvContext = createContext(extendedEnv); export const EnvContext = createContext(extendedEnv);

View file

@ -44,7 +44,7 @@ const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) =>
... literals.map((lit) => ["literal", text, lit]), ... literals.map((lit) => ["literal", text, lit]),
// names // names
... trie.suggest(env.names)(text)(Infinity) ... trie.suggest(env.name2dyn)(text)(Infinity)
.map(([name,type]) => [ .map(([name,type]) => [
"name", "name",
name, { name, {
@ -53,7 +53,8 @@ const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) =>
kind: type.kind || "value", kind: type.kind || "value",
}]), }]),
] ]
// return []; // <-- uncomment to disable suggestions (useful for debugging) // return ls;
return [];
return ls return ls
.map(suggestion => [suggestionPriority(suggestion[2]), ...suggestion] as PrioritizedSuggestionType) .map(suggestion => [suggestionPriority(suggestion[2]), ...suggestion] as PrioritizedSuggestionType)
.sort(([priorityA], [priorityB]) => priorityB - priorityA) .sort(([priorityA], [priorityB]) => priorityB - priorityA)
@ -66,7 +67,7 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
const [i, setI] = useState(0); // selected suggestion idx const [i, setI] = useState(0); // selected suggestion idx
const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not
const singleSuggestion = trie.growPrefix(env.names)(text); const singleSuggestion = trie.growPrefix(env.name2dyn)(text);
const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text, suggestionPriority, env]); const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text, suggestionPriority, env]);
useEffect(() => autoInputWidth(inputRef, text+singleSuggestion), [inputRef, text, singleSuggestion]); useEffect(() => autoInputWidth(inputRef, text+singleSuggestion), [inputRef, text, singleSuggestion]);
@ -88,12 +89,7 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
} }
const onTextChange = newText => { const onTextChange = newText => {
setState(state => ({...state, setState(state => ({...state, text: newText}));
text: newText,
value: (trie.get(env.names)(newText) ? {
kind: "name",
} : state.value),
}));
} }
// fired before onInput // fired before onInput

View file

@ -1,14 +1,13 @@
import { useContext, useEffect, useRef } from "react"; import { useContext, useEffect, useRef } from "react";
import { eqType, getSymbol, reduceUnification, trie } from "dope2"; import { growEnv } from "dope2";
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock"; import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
import { EnvContext } from "./EnvContext"; import { EnvContext } from "./EnvContext";
import { evalEditorBlock, makeInnerEnv, makeTypeVar } from "./eval"; import { getUnusedTypeVar } from "./eval";
import { autoInputWidth } from "./util/dom_trickery"; import { autoInputWidth } from "./util/dom_trickery";
import "./LambdaBlock.css"; import "./LambdaBlock.css";
import { Type } from "./Type";
export interface LambdaBlockState { export interface LambdaBlockState {
kind: "lambda"; kind: "lambda";
@ -48,20 +47,11 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
useEffect(() => autoInputWidth(nameRef, state.paramName, 60), [nameRef, state.paramName]); useEffect(() => autoInputWidth(nameRef, state.paramName, 60), [nameRef, state.paramName]);
const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName); const innerEnv = growEnv(env)(state.paramName)({
kind: "unknown",
const exprResolved = evalEditorBlock(state.expr, staticInnerEnv); i: undefined,
t: getUnusedTypeVar(env),
const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType; });
const betterInnerEnv = eqType(paramType)(inferredParamType)
? staticInnerEnv
: makeInnerEnv(env, state.paramName, {
kind: "unknown",
t: inferredParamType,
unification: new Map(), // <- is this correct?
})
return <span className="lambdaBlock"> return <span className="lambdaBlock">
<span className="keyword">&#955;</span> <span className="keyword">&#955;</span>
@ -76,14 +66,11 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
onChange={e => setParamName(e.target.value)} onChange={e => setParamName(e.target.value)}
/> />
</span> </span>
<div className="typeSignature">
&nbsp;::&nbsp;<Type type={inferredParamType} />
</div>
&nbsp; &nbsp;
<span className="keyword">:</span> <span className="keyword">:</span>
&nbsp; &nbsp;
<div className="lambdaInner"> <div className="lambdaInner">
<EnvContext value={betterInnerEnv}> <EnvContext value={innerEnv}>
<ExprBlock <ExprBlock
state={state.expr} state={state.expr}
setState={setExpr} setState={setExpr}

View file

@ -38,7 +38,6 @@ export function Value({dynamic}) {
case symbolUUID: case symbolUUID:
return <ValueUUID val={inst}/> return <ValueUUID val={inst}/>
default: default:
console.log("don't know how to show value:", dynamic);
return <>don't know how to show value</>; return <>don't know how to show value</>;
} }
} }

View file

@ -75,7 +75,184 @@ export const tripleFunctionCallEditorState: ExprBlockState = {
}, },
}; };
export const biggerExample: ExprBlockState = {"kind":"let","inner":{"kind":"let","inner":{"kind":"let","inner":{"kind":"let","inner":{"kind":"input","text":"","value":{"kind":"text"},"focus":false},"name":"myListInc","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"myList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"inc","value":{"kind":"name"},"focus":false}}},"input":{"kind":"input","text":"id","value":{"kind":"name"},"focus":true}}},"name":"myList","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"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":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"3","value":{"kind":"literal","type":"Int"},"focus":false}}},"name":"id","value":{"kind":"lambda","paramName":"x","expr":{"kind":"input","text":"x","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}}}}; export const biggerExample: ExprBlockState = {
"kind": "let",
"inner": {
"kind": "let",
"inner": {
"kind": "let",
"inner": {
"kind": "let",
"inner": {
"kind": "input",
"text": "",
"value": {
"kind": "text"
},
"focus": false
},
"name": "myListInc",
"value": {
"kind": "call",
"fn": {
"kind": "call",
"fn": {
"kind": "input",
"text": "list.map",
"value": {
"kind": "name"
},
"focus": false
},
"input": {
"kind": "input",
"text": "myList",
"value": {
"kind": "name"
},
"focus": false
}
},
"input": {
"kind": "input",
"text": "inc",
"value": {
"kind": "name"
},
"focus": false
}
}
},
"name": "myList",
"value": {
"kind": "call",
"fn": {
"kind": "call",
"fn": {
"kind": "input",
"text": "list.push",
"value": {
"kind": "name"
},
"focus": false
},
"input": {
"kind": "call",
"fn": {
"kind": "call",
"fn": {
"kind": "input",
"text": "list.push",
"value": {
"kind": "name"
},
"focus": false
},
"input": {
"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": "1",
"value": {
"kind": "literal",
"type": "Int"
},
"focus": false
}
}
},
"input": {
"kind": "input",
"text": "2",
"value": {
"kind": "literal",
"type": "Int"
},
"focus": false
}
}
},
"input": {
"kind": "input",
"text": "3",
"value": {
"kind": "literal",
"type": "Int"
},
"focus": false
}
}
},
"name": "id",
"value": {
"kind": "lambda",
"paramName": "x",
"expr": {
"kind": "input",
"text": "x",
"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
}
}
}
};
export const lambda2Params: ExprBlockState = { export const lambda2Params: ExprBlockState = {
"kind": "let", "kind": "let",

View file

@ -1,29 +1,19 @@
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 { assignFnSubstitutions, dict, Double, fnType, getSymbol, growEnv, Int, NotAFunctionError, prettyT, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } from "dope2";
import type { ExprBlockState } from "./ExprBlock"; import type { ExprBlockState } from "./ExprBlock";
import type { InputValueType } from "./InputBlock"; import type { InputValueType, SuggestionType } from "./InputBlock";
const IS_DEV = (import.meta.env.MODE === "development");
export interface Environment {
names: any;
nextFreeTypeVar: number;
typeVars: Set<string>;
}
interface Type { interface Type {
symbol: string; symbol: string;
params: any[]; params: any[];
}; };
type Unification = Map<string, any>;
export interface DeepError { export interface DeepError {
kind: "error"; kind: "error";
e: Error; e: Error;
depth: number; depth: number;
t: Type; t: Type;
unification: Unification; substitutions: Map<Type,Type>;
} }
// a dynamically typed value = tuple (instance, type) // a dynamically typed value = tuple (instance, type)
@ -31,15 +21,21 @@ export interface Dynamic {
kind: "value", kind: "value",
i: any; i: any;
t: Type; t: Type;
unification: Unification; substitutions: Map<Type,Type>;
}; };
export interface Unknown { export interface Unknown {
kind: "unknown"; kind: "unknown";
t: Type; t: Type;
unification: Unification; substitutions: Map<Type,Type>;
} }
export const entirelyUnknown = env => ({
kind: "unknown",
t: getUnusedTypeVar(env),
substitutions: new Map(),
} as 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;
@ -47,15 +43,17 @@ export const evalEditorBlock = (s: ExprBlockState, env): ResolvedType => {
if (s.kind === "input") { if (s.kind === "input") {
return evalInputBlock(s.text, s.value, env); return evalInputBlock(s.text, s.value, env);
} }
else if (s.kind === "call") { if (s.kind === "call") {
return evalCallBlock(s.fn, s.input, env); return evalCallBlock(s.fn, s.input, env);
} }
else if (s.kind === "let") { if (s.kind === "let") {
return evalLetInBlock(s.value, s.name, s.inner, env); return evalLetInBlock(s.value, s.name, s.inner, env);
} }
else { // (s.kind === "lambda") if (s.kind === "lambda") {
return evalLambdaBlock(s.paramName, s.expr, env); return evalLambdaBlock(s.paramName, s.expr, env);
} }
return entirelyUnknown(env); // todo
}; };
export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType { export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType {
@ -63,65 +61,56 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv
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.name2dyn)(text);
if (found) { if (found) {
return found; return {
kind: found.kind || "value",
...found,
substitutions: found.substitutions || new Map(),
};
} }
} }
// kind === "text" -> unresolved // kind === "text" -> unresolved
return { return {
kind: "error", kind: "error",
t: makeTypeVar(env, 'err')[0], t: getUnusedTypeVar(env),
e: new Error(`'${text}' not found`), e: new Error(`'${text}' not found`),
depth: 0, depth: 0,
unification: new Map(), substitutions: new Map(),
}; };
} }
const mergeMaps = (...maps: Map<Type,Type>[]) => {
return new Map(maps.flatMap(m => [...m]));
}
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env): ResolvedType { export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env): ResolvedType {
if (getSymbol(fnResolved.t) !== symbolFunction) { if (getSymbol(fnResolved.t) !== symbolFunction) {
// worst outcome: we know nothing about the result! // worst outcome: we know nothing about the result!
return makeError(env, return {
new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`), kind: "error",
mergeUnifications(fnResolved.unification, inputResolved.unification), e: new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`),
) t: getUnusedTypeVar(env),
substitutions: mergeMaps(fnResolved.substitutions, inputResolved.substitutions),
depth: 0,
};
} }
try { try {
// fn is a function... // fn is a function...
const [rewrittenFnType] = recomputeTypeVars([fnResolved.t], env.nextFreeTypeVar); const [_inType, inSubst, outType, _outSubst] = assignFnSubstitutions(fnResolved.t, inputResolved.t, getUnusedTypeVarIdx(env)); // may throw
const unification = (unifyLL(rewrittenFnType.params[0](rewrittenFnType), inputResolved.t));
const inputTypeVars = occurring(inputResolved.t); console.log('==================================')
const fnTypeVars = occurring(fnResolved.t); console.log('fnResolvedT:', prettyT(fnResolved.t));
const subsetOfUnification = new Map([...unification].filter(([typeVar]) => inputTypeVars.has(typeVar))); console.log('inputResolvedT:', prettyT(inputResolved.t));
const otherSubSetOfUnification = new Map([...unification].filter(([typeVar]) => fnTypeVars.has(typeVar))); console.log('_inType:',prettyT(_inType));
console.log('inSubst:', [...inSubst].map(([key,val]) => [key,prettyT(val)]));
const outType = substitute( console.log('outType:',prettyT(outType));
rewrittenFnType.params[1](rewrittenFnType), console.log('_outSubst:', [..._outSubst].map(([key,val]) => [key,prettyT(val)]));
reduceUnification(unification), console.log('==================================')
[]); // <- not important
const grandUnification = [fnResolved.unification, inputResolved.unification]
.reduce(mergeUnifications, unification);
if (IS_DEV) {
console.log('========= evalCallBlock2 =========')
console.log('fnType :', prettyT(fnResolved.t));
console.log('rewrittenFnType :', prettyT(rewrittenFnType));
console.log('inputType :', prettyT(inputResolved.t));
console.log('unification :', prettyU(unification));
console.log('subsetOfUnification :', prettyU(subsetOfUnification));
console.log('otherSubSetOfUnification:', prettyU(otherSubSetOfUnification));
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('grandUnification :', prettyU(grandUnification));
console.log('==================================')
}
// console.log('assignFn...', 'fn.t:', prettyT(fnResolved.t), 'input:', inputResolved, 'input.t:', prettyT(inputResolved.t), '\nout =', prettyT(outType), 'subst:', substitutions, substitutions.size);
const mergedSubstitutions = mergeMaps(inSubst, fnResolved.substitutions, inputResolved.substitutions);
if (inputResolved.kind === "error") { if (inputResolved.kind === "error") {
return { return {
@ -129,7 +118,7 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
e: inputResolved.e, // bubble up the error e: inputResolved.e, // bubble up the error
depth: 0, depth: 0,
t: outType, t: outType,
unification: grandUnification, substitutions: mergedSubstitutions,
}; };
} }
if (fnResolved.kind === "error") { if (fnResolved.kind === "error") {
@ -139,7 +128,7 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
e: fnResolved.e, e: fnResolved.e,
depth: fnResolved.depth+1, depth: fnResolved.depth+1,
t: outType, t: outType,
unification: grandUnification, substitutions: mergedSubstitutions,
}; };
} }
// if the above statement did not throw => types are compatible... // if the above statement did not throw => types are compatible...
@ -149,7 +138,7 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
kind: "value", kind: "value",
i: outValue, i: outValue,
t: outType, t: outType,
unification: grandUnification, substitutions: mergedSubstitutions,
}; };
} }
else { else {
@ -157,7 +146,7 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
return { return {
kind: "unknown", kind: "unknown",
t: outType, t: outType,
unification: grandUnification, substitutions: mergedSubstitutions,
}; };
} }
} }
@ -165,25 +154,17 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
if ((e instanceof UnifyError)) { if ((e instanceof UnifyError)) {
// 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 { return {
return { kind: "error",
kind: "error", e,
e, depth: 0,
depth: 0, t: outType,
t: outType, substitutions: mergeMaps(fnResolved.substitutions, inputResolved.substitutions),
unification: mergeUnifications(fnResolved.unification, inputResolved.unification), // may throw! };
};
}
catch (e) {
if ((e instanceof UnifyError)) {
return makeError(env, e);
}
throw e;
}
} }
throw e; throw e;
} }
}; }
export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env): ResolvedType { export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env): ResolvedType {
const fnResolved = evalEditorBlock(fn, env); const fnResolved = evalEditorBlock(fn, env);
@ -193,30 +174,35 @@ export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env): R
export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env): ResolvedType { export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env): ResolvedType {
const valueResolved = evalEditorBlock(value, env); const valueResolved = evalEditorBlock(value, env);
// console.log('eval', name, '...', valueResolved.kind, valueResolved.e);
// const innerEnv = growEnv(env)(name)(valueResolved);
const innerEnv = makeInnerEnv(env, name, valueResolved); const innerEnv = makeInnerEnv(env, name, valueResolved);
return evalEditorBlock(inner, innerEnv); return evalEditorBlock(inner, innerEnv);
} }
function getUnusedTypeVarIdx(env) {
for (let i=0; ; i++) {
if (!dict.has(env.typeDict)(TYPE_VARS[i])) {
return i;
}
}
}
export function getUnusedTypeVar(env) {
return TYPE_VARS[getUnusedTypeVarIdx(env)];
}
export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): ResolvedType { export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): ResolvedType {
const [paramType, staticInnerEnv] = makeTypeVar(env, paramName); const paramType = getUnusedTypeVar(env);
// static env: we only know the name and the type
const staticInnerEnv = growEnv(env)(paramName)({
kind: "unknown", // parameter value is not statically known
t: paramType,
substitutions: new Map(),
})
const exprResolved = evalEditorBlock(expr, staticInnerEnv); const exprResolved = evalEditorBlock(expr, staticInnerEnv);
const lambdaT = fnType(_ => paramType)(_ => exprResolved.t); 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, exprResolved.substitutions, []);
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('===================================')
}
// console.log('inner kind', exprResolved.kind, paramName); // console.log('inner kind', exprResolved.kind, paramName);
if (exprResolved.kind === "error") { if (exprResolved.kind === "error") {
return { return {
@ -224,7 +210,8 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
t: lambdaTSubstituted, t: lambdaTSubstituted,
depth: 0, depth: 0,
e: exprResolved.e, e: exprResolved.e,
unification: exprResolved.unification, // substitutions: new Map(),
substitutions: exprResolved.substitutions,
} }
} }
const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted); const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted);
@ -233,7 +220,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
kind: "value", kind: "value",
i: x, i: x,
t: paramTypeSubstituted, t: paramTypeSubstituted,
unification: new Map(), substitutions: new Map(),
}); });
const result = evalEditorBlock(expr, innerEnv); const result = evalEditorBlock(expr, innerEnv);
if (result.kind === "value") { if (result.kind === "value") {
@ -244,7 +231,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
kind: "value", kind: "value",
t: lambdaTSubstituted, t: lambdaTSubstituted,
i: fn, i: fn,
unification: exprResolved.unification, substitutions: exprResolved.substitutions,
}; };
} }
@ -261,7 +248,7 @@ function parseLiteral(text: string, type: string, env): ResolvedType {
if (type === "Double") { if (type === "Double") {
return parseAsDouble(text, env); return parseAsDouble(text, env);
} }
return makeError(env, new Error("Failed to parse")); return entirelyUnknown(env);
} }
function parseAsDouble(text: string, env): ResolvedType { function parseAsDouble(text: string, env): ResolvedType {
@ -272,11 +259,11 @@ function parseAsDouble(text: string, env): ResolvedType {
kind: "value", kind: "value",
i: num, i: num,
t: Double, t: Double,
unification: new Map(), substitutions: new Map(),
}; };
} }
} }
return makeError(env, new Error("Failed to parse as Double")); return entirelyUnknown(env);
} }
function parseAsInt(text: string, env): ResolvedType { function parseAsInt(text: string, env): ResolvedType {
if (text !== '') { if (text !== '') {
@ -285,12 +272,12 @@ function parseAsInt(text: string, env): ResolvedType {
kind: "value", kind: "value",
i: BigInt(text), i: BigInt(text),
t: Int, t: Int,
unification: new Map(), substitutions: new Map(),
}; // may throw }; // may throw
} }
catch {} catch {}
} }
return makeError(env, new Error("Failed to parse as Int")); return entirelyUnknown(env);
} }
const literalParsers = [parseAsDouble, parseAsInt]; const literalParsers = [parseAsDouble, parseAsInt];
@ -317,45 +304,10 @@ export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedTy
} }
} }
export function makeInnerEnv(env: Environment, name: string, value: ResolvedType): Environment { export function makeInnerEnv(env, name: string, value: ResolvedType) {
if (name !== "") { if (name !== "" && value.kind === "value") {
return { return growEnv(env)(name)(value);
names: trie.insert(env.names)(name)(value),
nextFreeTypeVar: env.nextFreeTypeVar,
typeVars: env.typeVars,
}
} }
return env; return env;
} }
export function makeTypeVar(env: Environment, name: string): [Type, Environment] {
const idx = env.nextFreeTypeVar;
const typeVar = TYPE_VARS[idx];
const unknown: Unknown = {
kind: "unknown",
t: typeVar,
unification: new Map(),
};
return [ typeVar, {
names: trie.insert(env.names)(name)(unknown),
nextFreeTypeVar: idx + 1,
typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]),
}];
}
function makeError(env: Environment, e: Error, unification: Unification=new Map()): DeepError {
const idx = env.nextFreeTypeVar;
const typeVar = TYPE_VARS[idx];
const deepError: DeepError = {
kind: "error",
t: typeVar,
unification: new Map(),
e,
depth: 0,
};
return deepError;
// , {
// names: trie.insert(env.names)('err')(deepError),
// nextFreeTypeVar: idx + 1,
// }];
}