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
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
|
@ -16,7 +16,7 @@ importers:
|
|||
version: 5.2.5
|
||||
dope2:
|
||||
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:
|
||||
specifier: ^19.1.0
|
||||
version: 19.1.0
|
||||
|
|
@ -597,8 +597,8 @@ packages:
|
|||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
dope2@git+https://deemz.org/git/joeri/dope2.git#4fcfea409a3961733f80f8dbb2810efb3d8f2cf4:
|
||||
resolution: {commit: 4fcfea409a3961733f80f8dbb2810efb3d8f2cf4, repo: https://deemz.org/git/joeri/dope2.git, type: git}
|
||||
dope2@git+https://deemz.org/git/joeri/dope2.git#8cfbd6116ffe778efb02c37133a1ff633ae171df:
|
||||
resolution: {commit: 8cfbd6116ffe778efb02c37133a1ff633ae171df, repo: https://deemz.org/git/joeri/dope2.git, type: git}
|
||||
version: 0.0.1
|
||||
|
||||
esbuild@0.25.4:
|
||||
|
|
@ -1431,7 +1431,7 @@ snapshots:
|
|||
|
||||
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:
|
||||
functional-red-black-tree: 1.0.1
|
||||
|
||||
|
|
|
|||
17
src/App.tsx
17
src/App.tsx
|
|
@ -3,7 +3,7 @@ import './App.css';
|
|||
import { GlobalContext } from './GlobalContext';
|
||||
import { ExprBlock, type ExprBlockState } from './ExprBlock';
|
||||
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";
|
||||
|
||||
const commands: [string, string[], string][] = [
|
||||
|
|
@ -21,6 +21,9 @@ const examples: [string, ExprBlockState][] = [
|
|||
["bigger example" , biggerExample ],
|
||||
["lambda 2 params" , lambda2Params ],
|
||||
["higher order" , higherOrder ],
|
||||
["higher order 2" , higherOrder2Params ],
|
||||
["push Bool" , pushBool ],
|
||||
["inc" , inc ],
|
||||
];
|
||||
|
||||
type AppState = {
|
||||
|
|
@ -35,16 +38,16 @@ const defaultState = {
|
|||
|
||||
function loadFromLocalStorage(): AppState {
|
||||
if (localStorage["appState"]) {
|
||||
try {
|
||||
// try {
|
||||
const appState = JSON.parse(localStorage["appState"]); // may throw
|
||||
// if our state is corrupt, discover it eagerly:
|
||||
evalEditorBlock(appState.history.at(-1), extendedEnv);
|
||||
// evalEditorBlock(appState.history.at(-1), extendedEnv);
|
||||
|
||||
return appState; // all good
|
||||
}
|
||||
catch (e) {
|
||||
console.log('error recovering state from localStorage (resetting):', e);
|
||||
}
|
||||
// }
|
||||
// catch (e) {
|
||||
// console.log('error recovering state from localStorage (resetting):', e);
|
||||
// }
|
||||
}
|
||||
return defaultState;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function nestedFnProperties({state, setState, suggestionPriority}: CallBlockProp
|
|||
}
|
||||
const fnSuggestionPriority = (fnSuggestion: ResolvedType) => computePriority(
|
||||
fnSuggestion,
|
||||
evalEditorBlock(state.input, env),
|
||||
evalEditorBlock(state.input, env)[0],
|
||||
suggestionPriority,
|
||||
env,
|
||||
);
|
||||
|
|
@ -43,7 +43,7 @@ function nestedInputProperties({state, setState, suggestionPriority}: CallBlockP
|
|||
setState(state => state.fn); // we become our function
|
||||
}
|
||||
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
|
||||
suggestionPriority, // priority function we get from parent block
|
||||
env,
|
||||
|
|
@ -53,7 +53,7 @@ function nestedInputProperties({state, setState, suggestionPriority}: CallBlockP
|
|||
|
||||
export function CallBlock(props: CallBlockProps) {
|
||||
const env = useContext(EnvContext);
|
||||
const resolved = evalEditorBlock(props.state, env);
|
||||
const [resolved] = evalEditorBlock(props.state, env);
|
||||
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
|
||||
<FunctionHeader {...props} />
|
||||
<div className="functionParams">
|
||||
|
|
@ -73,7 +73,7 @@ export function CallBlock(props: CallBlockProps) {
|
|||
}
|
||||
|
||||
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);
|
||||
return score;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 : "")}>
|
||||
{renderBlock()}
|
||||
<div className="typeSignature">
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
|
|||
|
||||
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;
|
||||
|
||||
|
|
@ -62,6 +62,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
|
|||
unification: new Map(), // <- is this correct?
|
||||
})
|
||||
|
||||
// const {exprResolved, env: newEnv} = computeLambdaBlockType(state.paramName, state.expr, env);
|
||||
|
||||
return <span className="lambdaBlock">
|
||||
<span className="keyword">λ</span>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}
|
|||
|
||||
const valueSuggestionPriority = (suggestion: ResolvedType) => {
|
||||
const innerEnv = makeInnerEnv(env, name, suggestion);
|
||||
const resolved = evalEditorBlock(inner, innerEnv);
|
||||
const [resolved] = evalEditorBlock(inner, innerEnv);
|
||||
return scoreResolved(resolved, suggestionPriority);
|
||||
};
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ function DeclColumns({state: {name, value, inner}, setState, suggestionPriority}
|
|||
}, []);
|
||||
useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]);
|
||||
|
||||
const valueResolved = evalEditorBlock(value, env);
|
||||
const [valueResolved] = evalEditorBlock(value, env);
|
||||
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
||||
|
||||
return <>
|
||||
|
|
@ -91,7 +91,7 @@ function InnerMost({state, setState, suggestionPriority}) {
|
|||
const env = useContext(EnvContext);
|
||||
const globalContext = useContext(GlobalContext);
|
||||
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 onCancel = () => setState(state => state.value);
|
||||
if (state.inner.kind === "let" && globalContext?.syntacticSugar) {
|
||||
|
|
|
|||
|
|
@ -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}}}};
|
||||
296
src/eval.ts
296
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): ResolvedType {
|
||||
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||
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,
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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('====== begin evalLambdaBlock ======')
|
||||
console.log('paramName :', paramName);
|
||||
console.log('paramType :', prettyT(paramType));
|
||||
console.log('exprType :', prettyT(exprResolved.t));
|
||||
console.log('lambdaType :', prettyT(lambdaT));
|
||||
console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted));
|
||||
console.log('staticInnerEnv:', staticInnerEnv);
|
||||
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") {
|
||||
return {
|
||||
lambdaResolved = {
|
||||
kind: "error",
|
||||
t: lambdaTSubstituted,
|
||||
depth: 0,
|
||||
|
|
@ -227,25 +322,41 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
|
|||
unification: exprResolved.unification,
|
||||
}
|
||||
}
|
||||
else {
|
||||
const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted);
|
||||
const fn = (x: any) => {
|
||||
lambdaResolved = {
|
||||
kind: "value",
|
||||
t: lambdaTSubstituted,
|
||||
i: (x: any) => {
|
||||
const innerEnv = makeInnerEnv(env, paramName, {
|
||||
kind: "value",
|
||||
i: x,
|
||||
t: paramTypeSubstituted,
|
||||
unification: new Map(),
|
||||
});
|
||||
const result = evalEditorBlock(expr, innerEnv);
|
||||
const [result] = evalEditorBlock(expr, innerEnv);
|
||||
if (result.kind === "value") {
|
||||
return result.i;
|
||||
}
|
||||
}
|
||||
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]]),
|
||||
}];
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"lib": ["ESNext"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue