Compare commits
2 commits
28e5032923
...
ec2944cdb7
| Author | SHA1 | Date | |
|---|---|---|---|
| ec2944cdb7 | |||
| d877b42a12 |
7 changed files with 186 additions and 290 deletions
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
|
@ -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#d3515d39a51d0baf9fcc775b8d2fb74c74e24868
|
version: git+https://deemz.org/git/joeri/dope2.git#70fb80a9fc8438a588d1fba957706a322d0900d0
|
||||||
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#d3515d39a51d0baf9fcc775b8d2fb74c74e24868:
|
dope2@git+https://deemz.org/git/joeri/dope2.git#70fb80a9fc8438a588d1fba957706a322d0900d0:
|
||||||
resolution: {commit: d3515d39a51d0baf9fcc775b8d2fb74c74e24868, repo: https://deemz.org/git/joeri/dope2.git, type: git}
|
resolution: {commit: 70fb80a9fc8438a588d1fba957706a322d0900d0, 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#d3515d39a51d0baf9fcc775b8d2fb74c74e24868:
|
dope2@git+https://deemz.org/git/joeri/dope2.git#70fb80a9fc8438a588d1fba957706a322d0900d0:
|
||||||
dependencies:
|
dependencies:
|
||||||
functional-red-black-tree: 1.0.1
|
functional-red-black-tree: 1.0.1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,20 @@
|
||||||
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 = {
|
||||||
|
names: module2Env(ModuleStd.concat([
|
||||||
["functionWith3Params", { i: functionWith3Params, t: mkType("Int->Int->Int->Int") }],
|
["functionWith3Params", { i: functionWith3Params, t: mkType("Int->Int->Int->Int") }],
|
||||||
["functionWith4Params", { i: functionWith4Params, t: mkType("Int->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);
|
||||||
|
|
@ -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.name2dyn)(text)(Infinity)
|
... trie.suggest(env.names)(text)(Infinity)
|
||||||
.map(([name,type]) => [
|
.map(([name,type]) => [
|
||||||
"name",
|
"name",
|
||||||
name, {
|
name, {
|
||||||
|
|
@ -53,8 +53,7 @@ const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) =>
|
||||||
kind: type.kind || "value",
|
kind: type.kind || "value",
|
||||||
}]),
|
}]),
|
||||||
]
|
]
|
||||||
// return ls;
|
// return []; // <-- uncomment to disable suggestions (useful for debugging)
|
||||||
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)
|
||||||
|
|
@ -67,7 +66,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.name2dyn)(text);
|
const singleSuggestion = trie.growPrefix(env.names)(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]);
|
||||||
|
|
@ -89,7 +88,12 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
|
||||||
}
|
}
|
||||||
|
|
||||||
const onTextChange = newText => {
|
const onTextChange = newText => {
|
||||||
setState(state => ({...state, text: newText}));
|
setState(state => ({...state,
|
||||||
|
text: newText,
|
||||||
|
value: (trie.get(env.names)(newText) ? {
|
||||||
|
kind: "name",
|
||||||
|
} : state.value),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// fired before onInput
|
// fired before onInput
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import { useContext, useEffect, useRef } from "react";
|
||||||
|
|
||||||
import { growEnv } from "dope2";
|
import { eqType, getSymbol, reduceUnification, trie } 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 { getUnusedTypeVar } from "./eval";
|
import { evalEditorBlock, makeInnerEnv, makeTypeVar } 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";
|
||||||
|
|
@ -47,11 +48,20 @@ 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 innerEnv = growEnv(env)(state.paramName)({
|
const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName);
|
||||||
|
|
||||||
|
const exprResolved = evalEditorBlock(state.expr, staticInnerEnv);
|
||||||
|
|
||||||
|
const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType;
|
||||||
|
|
||||||
|
const betterInnerEnv = eqType(paramType)(inferredParamType)
|
||||||
|
? staticInnerEnv
|
||||||
|
: makeInnerEnv(env, state.paramName, {
|
||||||
kind: "unknown",
|
kind: "unknown",
|
||||||
i: undefined,
|
t: inferredParamType,
|
||||||
t: getUnusedTypeVar(env),
|
unification: new Map(), // <- is this correct?
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
||||||
return <span className="lambdaBlock">
|
return <span className="lambdaBlock">
|
||||||
<span className="keyword">λ</span>
|
<span className="keyword">λ</span>
|
||||||
|
|
@ -66,11 +76,14 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
|
||||||
onChange={e => setParamName(e.target.value)}
|
onChange={e => setParamName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
<div className="typeSignature">
|
||||||
|
:: <Type type={inferredParamType} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<span className="keyword">:</span>
|
<span className="keyword">:</span>
|
||||||
|
|
||||||
<div className="lambdaInner">
|
<div className="lambdaInner">
|
||||||
<EnvContext value={innerEnv}>
|
<EnvContext value={betterInnerEnv}>
|
||||||
<ExprBlock
|
<ExprBlock
|
||||||
state={state.expr}
|
state={state.expr}
|
||||||
setState={setExpr}
|
setState={setExpr}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ 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</>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,184 +75,7 @@ export const tripleFunctionCallEditorState: ExprBlockState = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const biggerExample: 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}}}};
|
||||||
"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",
|
||||||
|
|
|
||||||
216
src/eval.ts
216
src/eval.ts
|
|
@ -1,19 +1,29 @@
|
||||||
import { assignFnSubstitutions, dict, Double, fnType, getSymbol, growEnv, Int, NotAFunctionError, prettyT, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } 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 } from "dope2";
|
||||||
|
|
||||||
import type { ExprBlockState } from "./ExprBlock";
|
import type { ExprBlockState } from "./ExprBlock";
|
||||||
import type { InputValueType, SuggestionType } from "./InputBlock";
|
import type { InputValueType } 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;
|
||||||
substitutions: Map<Type,Type>;
|
unification: Unification;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a dynamically typed value = tuple (instance, type)
|
// a dynamically typed value = tuple (instance, type)
|
||||||
|
|
@ -21,21 +31,15 @@ export interface Dynamic {
|
||||||
kind: "value",
|
kind: "value",
|
||||||
i: any;
|
i: any;
|
||||||
t: Type;
|
t: Type;
|
||||||
substitutions: Map<Type,Type>;
|
unification: Unification;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Unknown {
|
export interface Unknown {
|
||||||
kind: "unknown";
|
kind: "unknown";
|
||||||
t: Type;
|
t: Type;
|
||||||
substitutions: Map<Type,Type>;
|
unification: Unification;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
|
@ -43,17 +47,15 @@ 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);
|
||||||
}
|
}
|
||||||
if (s.kind === "call") {
|
else if (s.kind === "call") {
|
||||||
return evalCallBlock(s.fn, s.input, env);
|
return evalCallBlock(s.fn, s.input, env);
|
||||||
}
|
}
|
||||||
if (s.kind === "let") {
|
else if (s.kind === "let") {
|
||||||
return evalLetInBlock(s.value, s.name, s.inner, env);
|
return evalLetInBlock(s.value, s.name, s.inner, env);
|
||||||
}
|
}
|
||||||
if (s.kind === "lambda") {
|
else { // (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 {
|
||||||
|
|
@ -61,56 +63,65 @@ 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.name2dyn)(text);
|
const found = trie.get(env.names)(text);
|
||||||
if (found) {
|
if (found) {
|
||||||
return {
|
return found;
|
||||||
kind: found.kind || "value",
|
|
||||||
...found,
|
|
||||||
substitutions: found.substitutions || new Map(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// kind === "text" -> unresolved
|
// kind === "text" -> unresolved
|
||||||
return {
|
return {
|
||||||
kind: "error",
|
kind: "error",
|
||||||
t: getUnusedTypeVar(env),
|
t: makeTypeVar(env, 'err')[0],
|
||||||
e: new Error(`'${text}' not found`),
|
e: new Error(`'${text}' not found`),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
substitutions: new Map(),
|
unification: 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 {
|
return makeError(env,
|
||||||
kind: "error",
|
new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`),
|
||||||
e: new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`),
|
mergeUnifications(fnResolved.unification, inputResolved.unification),
|
||||||
t: getUnusedTypeVar(env),
|
)
|
||||||
substitutions: mergeMaps(fnResolved.substitutions, inputResolved.substitutions),
|
|
||||||
depth: 0,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// fn is a function...
|
// fn is a function...
|
||||||
const [_inType, inSubst, outType, _outSubst] = assignFnSubstitutions(fnResolved.t, inputResolved.t, getUnusedTypeVarIdx(env)); // may throw
|
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);
|
||||||
|
|
||||||
|
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('==================================')
|
||||||
console.log('fnResolvedT:', prettyT(fnResolved.t));
|
}
|
||||||
console.log('inputResolvedT:', prettyT(inputResolved.t));
|
|
||||||
console.log('_inType:',prettyT(_inType));
|
|
||||||
console.log('inSubst:', [...inSubst].map(([key,val]) => [key,prettyT(val)]));
|
|
||||||
console.log('outType:',prettyT(outType));
|
|
||||||
console.log('_outSubst:', [..._outSubst].map(([key,val]) => [key,prettyT(val)]));
|
|
||||||
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 {
|
||||||
|
|
@ -118,7 +129,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,
|
||||||
substitutions: mergedSubstitutions,
|
unification: grandUnification,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (fnResolved.kind === "error") {
|
if (fnResolved.kind === "error") {
|
||||||
|
|
@ -128,7 +139,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,
|
||||||
substitutions: mergedSubstitutions,
|
unification: grandUnification,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// if the above statement did not throw => types are compatible...
|
// if the above statement did not throw => types are compatible...
|
||||||
|
|
@ -138,7 +149,7 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
|
||||||
kind: "value",
|
kind: "value",
|
||||||
i: outValue,
|
i: outValue,
|
||||||
t: outType,
|
t: outType,
|
||||||
substitutions: mergedSubstitutions,
|
unification: grandUnification,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -146,7 +157,7 @@ export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: Resolved
|
||||||
return {
|
return {
|
||||||
kind: "unknown",
|
kind: "unknown",
|
||||||
t: outType,
|
t: outType,
|
||||||
substitutions: mergedSubstitutions,
|
unification: grandUnification,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,17 +165,25 @@ 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);
|
||||||
|
|
@ -174,35 +193,30 @@ 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 = getUnusedTypeVar(env);
|
const [paramType, staticInnerEnv] = makeTypeVar(env, paramName);
|
||||||
// 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);
|
||||||
const lambdaTSubstituted = substitute(lambdaT, exprResolved.substitutions, []);
|
// This is the only place in the code where we actually do something with the 'substitutions'. We compute the type of our lambda function:
|
||||||
|
const lambdaTSubstituted = substitute(
|
||||||
|
lambdaT,
|
||||||
|
reduceUnification(exprResolved.unification),
|
||||||
|
[]); // <- not important
|
||||||
|
|
||||||
|
if (IS_DEV) {
|
||||||
|
console.log('========= evalLambdaBlock =========')
|
||||||
|
console.log('paramType :', prettyT(paramType));
|
||||||
|
console.log('exprType :', prettyT(exprResolved.t));
|
||||||
|
console.log('lambdaType :', prettyT(lambdaT));
|
||||||
|
console.log('lambdaTypeSubsituted:', prettyT(lambdaTSubstituted));
|
||||||
|
console.log('===================================')
|
||||||
|
}
|
||||||
|
|
||||||
// console.log('inner kind', exprResolved.kind, paramName);
|
// console.log('inner kind', exprResolved.kind, paramName);
|
||||||
if (exprResolved.kind === "error") {
|
if (exprResolved.kind === "error") {
|
||||||
return {
|
return {
|
||||||
|
|
@ -210,8 +224,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
|
||||||
t: lambdaTSubstituted,
|
t: lambdaTSubstituted,
|
||||||
depth: 0,
|
depth: 0,
|
||||||
e: exprResolved.e,
|
e: exprResolved.e,
|
||||||
// substitutions: new Map(),
|
unification: exprResolved.unification,
|
||||||
substitutions: exprResolved.substitutions,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted);
|
const paramTypeSubstituted = lambdaTSubstituted.params[0](lambdaTSubstituted);
|
||||||
|
|
@ -220,7 +233,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
|
||||||
kind: "value",
|
kind: "value",
|
||||||
i: x,
|
i: x,
|
||||||
t: paramTypeSubstituted,
|
t: paramTypeSubstituted,
|
||||||
substitutions: new Map(),
|
unification: new Map(),
|
||||||
});
|
});
|
||||||
const result = evalEditorBlock(expr, innerEnv);
|
const result = evalEditorBlock(expr, innerEnv);
|
||||||
if (result.kind === "value") {
|
if (result.kind === "value") {
|
||||||
|
|
@ -231,7 +244,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env): R
|
||||||
kind: "value",
|
kind: "value",
|
||||||
t: lambdaTSubstituted,
|
t: lambdaTSubstituted,
|
||||||
i: fn,
|
i: fn,
|
||||||
substitutions: exprResolved.substitutions,
|
unification: exprResolved.unification,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,7 +261,7 @@ function parseLiteral(text: string, type: string, env): ResolvedType {
|
||||||
if (type === "Double") {
|
if (type === "Double") {
|
||||||
return parseAsDouble(text, env);
|
return parseAsDouble(text, env);
|
||||||
}
|
}
|
||||||
return entirelyUnknown(env);
|
return makeError(env, new Error("Failed to parse"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAsDouble(text: string, env): ResolvedType {
|
function parseAsDouble(text: string, env): ResolvedType {
|
||||||
|
|
@ -259,11 +272,11 @@ function parseAsDouble(text: string, env): ResolvedType {
|
||||||
kind: "value",
|
kind: "value",
|
||||||
i: num,
|
i: num,
|
||||||
t: Double,
|
t: Double,
|
||||||
substitutions: new Map(),
|
unification: new Map(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return entirelyUnknown(env);
|
return makeError(env, new Error("Failed to parse as Double"));
|
||||||
}
|
}
|
||||||
function parseAsInt(text: string, env): ResolvedType {
|
function parseAsInt(text: string, env): ResolvedType {
|
||||||
if (text !== '') {
|
if (text !== '') {
|
||||||
|
|
@ -272,12 +285,12 @@ function parseAsInt(text: string, env): ResolvedType {
|
||||||
kind: "value",
|
kind: "value",
|
||||||
i: BigInt(text),
|
i: BigInt(text),
|
||||||
t: Int,
|
t: Int,
|
||||||
substitutions: new Map(),
|
unification: new Map(),
|
||||||
}; // may throw
|
}; // may throw
|
||||||
}
|
}
|
||||||
catch {}
|
catch {}
|
||||||
}
|
}
|
||||||
return entirelyUnknown(env);
|
return makeError(env, new Error("Failed to parse as Int"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const literalParsers = [parseAsDouble, parseAsInt];
|
const literalParsers = [parseAsDouble, parseAsInt];
|
||||||
|
|
@ -304,10 +317,45 @@ export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedTy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeInnerEnv(env, name: string, value: ResolvedType) {
|
export function makeInnerEnv(env: Environment, name: string, value: ResolvedType): Environment {
|
||||||
if (name !== "" && value.kind === "value") {
|
if (name !== "") {
|
||||||
return growEnv(env)(name)(value);
|
return {
|
||||||
|
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,
|
||||||
|
// }];
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue