From c5c5def5983dad4ccc7f2b75bf11cdd478715f19 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Fri, 23 May 2025 19:48:24 +0200 Subject: [PATCH] greatly simplify and improve type inferencing - suggestions broken --- package.json | 4 +- pnpm-lock.yaml | 128 ++--- src/App.tsx | 8 +- src/CallBlock.tsx | 74 +-- src/EnvContext.ts | 9 +- src/ExprBlock.tsx | 20 +- src/InputBlock.tsx | 94 ++-- src/LambdaBlock.tsx | 15 +- src/LetInBlock.tsx | 56 ++- src/actions.ts | 4 +- src/eval.ts | 1088 +++++++++++++++++++++++-------------------- src/infer_type.ts | 216 +++++++++ 12 files changed, 1022 insertions(+), 694 deletions(-) create mode 100644 src/infer_type.ts diff --git a/package.json b/package.json index 277e896..b5d6bad 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,9 @@ }, "devDependencies": { "@eslint/js": "^9.27.0", - "@types/react": "^19.1.4", + "@types/react": "^19.1.5", "@types/react-dom": "^19.1.5", - "@vitejs/plugin-react-swc": "^3.9.0", + "@vitejs/plugin-react-swc": "^3.10.0", "eslint": "^9.27.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 77d2341..9926cde 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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#8cfbd6116ffe778efb02c37133a1ff633ae171df + version: git+https://deemz.org/git/joeri/dope2.git#0d3ccee7d5ce703e71fd3c05e1795c7968646a34 react: specifier: ^19.1.0 version: 19.1.0 @@ -28,14 +28,14 @@ importers: specifier: ^9.27.0 version: 9.27.0 '@types/react': - specifier: ^19.1.4 - version: 19.1.4 + specifier: ^19.1.5 + version: 19.1.5 '@types/react-dom': specifier: ^19.1.5 - version: 19.1.5(@types/react@19.1.4) + version: 19.1.5(@types/react@19.1.5) '@vitejs/plugin-react-swc': - specifier: ^3.9.0 - version: 3.9.0(vite@6.3.5) + specifier: ^3.10.0 + version: 3.10.0(vite@6.3.5) eslint: specifier: ^9.27.0 version: 9.27.0 @@ -286,6 +286,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@rolldown/pluginutils@1.0.0-beta.9': + resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} + '@rollup/rollup-android-arm-eabi@4.41.0': resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} cpu: [arm] @@ -386,68 +389,68 @@ packages: cpu: [x64] os: [win32] - '@swc/core-darwin-arm64@1.11.24': - resolution: {integrity: sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==} + '@swc/core-darwin-arm64@1.11.29': + resolution: {integrity: sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.11.24': - resolution: {integrity: sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==} + '@swc/core-darwin-x64@1.11.29': + resolution: {integrity: sha512-S3eTo/KYFk+76cWJRgX30hylN5XkSmjYtCBnM4jPLYn7L6zWYEPajsFLmruQEiTEDUg0gBEWLMNyUeghtswouw==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.11.24': - resolution: {integrity: sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==} + '@swc/core-linux-arm-gnueabihf@1.11.29': + resolution: {integrity: sha512-o9gdshbzkUMG6azldHdmKklcfrcMx+a23d/2qHQHPDLUPAN+Trd+sDQUYArK5Fcm7TlpG4sczz95ghN0DMkM7g==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.11.24': - resolution: {integrity: sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==} + '@swc/core-linux-arm64-gnu@1.11.29': + resolution: {integrity: sha512-sLoaciOgUKQF1KX9T6hPGzvhOQaJn+3DHy4LOHeXhQqvBgr+7QcZ+hl4uixPKTzxk6hy6Hb0QOvQEdBAAR1gXw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.11.24': - resolution: {integrity: sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==} + '@swc/core-linux-arm64-musl@1.11.29': + resolution: {integrity: sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.11.24': - resolution: {integrity: sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==} + '@swc/core-linux-x64-gnu@1.11.29': + resolution: {integrity: sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.11.24': - resolution: {integrity: sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==} + '@swc/core-linux-x64-musl@1.11.29': + resolution: {integrity: sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.11.24': - resolution: {integrity: sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==} + '@swc/core-win32-arm64-msvc@1.11.29': + resolution: {integrity: sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.11.24': - resolution: {integrity: sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==} + '@swc/core-win32-ia32-msvc@1.11.29': + resolution: {integrity: sha512-h+NjOrbqdRBYr5ItmStmQt6x3tnhqgwbj9YxdGPepbTDamFv7vFnhZR0YfB3jz3UKJ8H3uGJ65Zw1VsC+xpFkg==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.11.24': - resolution: {integrity: sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==} + '@swc/core-win32-x64-msvc@1.11.29': + resolution: {integrity: sha512-Q8cs2BDV9wqDvqobkXOYdC+pLUSEpX/KvI0Dgfun1F+LzuLotRFuDhrvkU9ETJA6OnD2+Fn/ieHgloiKA/Mn/g==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.11.24': - resolution: {integrity: sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==} + '@swc/core@1.11.29': + resolution: {integrity: sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -472,8 +475,8 @@ packages: peerDependencies: '@types/react': ^19.0.0 - '@types/react@19.1.4': - resolution: {integrity: sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==} + '@types/react@19.1.5': + resolution: {integrity: sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==} '@typescript-eslint/eslint-plugin@8.32.1': resolution: {integrity: sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==} @@ -522,8 +525,8 @@ packages: resolution: {integrity: sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitejs/plugin-react-swc@3.9.0': - resolution: {integrity: sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==} + '@vitejs/plugin-react-swc@3.10.0': + resolution: {integrity: sha512-ZmkdHw3wo/o/Rk05YsXZs/DJAfY2CdQ5DUAjoWji+PEr+hYADdGMCGgEAILbiKj+CjspBTuTACBcWDrmC8AUfw==} peerDependencies: vite: ^4 || ^5 || ^6 @@ -597,8 +600,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#8cfbd6116ffe778efb02c37133a1ff633ae171df: - resolution: {commit: 8cfbd6116ffe778efb02c37133a1ff633ae171df, repo: https://deemz.org/git/joeri/dope2.git, type: git} + dope2@git+https://deemz.org/git/joeri/dope2.git#0d3ccee7d5ce703e71fd3c05e1795c7968646a34: + resolution: {commit: 0d3ccee7d5ce703e71fd3c05e1795c7968646a34, repo: https://deemz.org/git/joeri/dope2.git, type: git} version: 0.0.1 esbuild@0.25.4: @@ -1160,6 +1163,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@rolldown/pluginutils@1.0.0-beta.9': {} + '@rollup/rollup-android-arm-eabi@4.41.0': optional: true @@ -1220,51 +1225,51 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.41.0': optional: true - '@swc/core-darwin-arm64@1.11.24': + '@swc/core-darwin-arm64@1.11.29': optional: true - '@swc/core-darwin-x64@1.11.24': + '@swc/core-darwin-x64@1.11.29': optional: true - '@swc/core-linux-arm-gnueabihf@1.11.24': + '@swc/core-linux-arm-gnueabihf@1.11.29': optional: true - '@swc/core-linux-arm64-gnu@1.11.24': + '@swc/core-linux-arm64-gnu@1.11.29': optional: true - '@swc/core-linux-arm64-musl@1.11.24': + '@swc/core-linux-arm64-musl@1.11.29': optional: true - '@swc/core-linux-x64-gnu@1.11.24': + '@swc/core-linux-x64-gnu@1.11.29': optional: true - '@swc/core-linux-x64-musl@1.11.24': + '@swc/core-linux-x64-musl@1.11.29': optional: true - '@swc/core-win32-arm64-msvc@1.11.24': + '@swc/core-win32-arm64-msvc@1.11.29': optional: true - '@swc/core-win32-ia32-msvc@1.11.24': + '@swc/core-win32-ia32-msvc@1.11.29': optional: true - '@swc/core-win32-x64-msvc@1.11.24': + '@swc/core-win32-x64-msvc@1.11.29': optional: true - '@swc/core@1.11.24': + '@swc/core@1.11.29': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.21 optionalDependencies: - '@swc/core-darwin-arm64': 1.11.24 - '@swc/core-darwin-x64': 1.11.24 - '@swc/core-linux-arm-gnueabihf': 1.11.24 - '@swc/core-linux-arm64-gnu': 1.11.24 - '@swc/core-linux-arm64-musl': 1.11.24 - '@swc/core-linux-x64-gnu': 1.11.24 - '@swc/core-linux-x64-musl': 1.11.24 - '@swc/core-win32-arm64-msvc': 1.11.24 - '@swc/core-win32-ia32-msvc': 1.11.24 - '@swc/core-win32-x64-msvc': 1.11.24 + '@swc/core-darwin-arm64': 1.11.29 + '@swc/core-darwin-x64': 1.11.29 + '@swc/core-linux-arm-gnueabihf': 1.11.29 + '@swc/core-linux-arm64-gnu': 1.11.29 + '@swc/core-linux-arm64-musl': 1.11.29 + '@swc/core-linux-x64-gnu': 1.11.29 + '@swc/core-linux-x64-musl': 1.11.29 + '@swc/core-win32-arm64-msvc': 1.11.29 + '@swc/core-win32-ia32-msvc': 1.11.29 + '@swc/core-win32-x64-msvc': 1.11.29 '@swc/counter@0.1.3': {} @@ -1276,11 +1281,11 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/react-dom@19.1.5(@types/react@19.1.4)': + '@types/react-dom@19.1.5(@types/react@19.1.5)': dependencies: - '@types/react': 19.1.4 + '@types/react': 19.1.5 - '@types/react@19.1.4': + '@types/react@19.1.5': dependencies: csstype: 3.1.3 @@ -1361,9 +1366,10 @@ snapshots: '@typescript-eslint/types': 8.32.1 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-react-swc@3.9.0(vite@6.3.5)': + '@vitejs/plugin-react-swc@3.10.0(vite@6.3.5)': dependencies: - '@swc/core': 1.11.24 + '@rolldown/pluginutils': 1.0.0-beta.9 + '@swc/core': 1.11.29 vite: 6.3.5 transitivePeerDependencies: - '@swc/helpers' @@ -1431,7 +1437,7 @@ snapshots: deep-is@0.1.4: {} - dope2@git+https://deemz.org/git/joeri/dope2.git#8cfbd6116ffe778efb02c37133a1ff633ae171df: + dope2@git+https://deemz.org/git/joeri/dope2.git#0d3ccee7d5ce703e71fd3c05e1795c7968646a34: dependencies: functional-red-black-tree: 1.0.1 diff --git a/src/App.tsx b/src/App.tsx index d36970d..df1a6b8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,7 @@ import { ExprBlock, type ExprBlockState } from './ExprBlock'; import { GlobalContext } from './GlobalContext'; import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations"; import { actionShortcuts } from './actions'; -import { scoreResolved, type ResolvedType } from './eval'; +// import { scoreResolved, type ResolvedType } from './eval'; const examples: [string, ExprBlockState][] = [ @@ -62,6 +62,8 @@ export function App() { setAppState(_ => defaultState); } + // factoryReset(); + const pushHistory = (callback: (p: ExprBlockState) => ExprBlockState) => { setAppState(({history}) => { const newState = callback(history.at(-1)!); @@ -166,9 +168,7 @@ export function App() { state={appState.history.at(-1)!} setState={pushHistory} onCancel={() => {}} - score={(resolved: ResolvedType) => { - return scoreResolved(resolved, () => 0); - }} + score={() => 0} /> diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index ba01e9a..0fda412 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -1,7 +1,7 @@ import { useContext } from "react"; import { EnvContext } from "./EnvContext"; -import { addFocusRightMost, evalCallBlock2, evalExprBlock, recomputeTypeVarsForEnv, scoreResolved, type Environment, type ResolvedType } from "./eval"; +// import { addFocusRightMost, evalCallBlock2, evalExprBlock, recomputeTypeVarsForEnv, scoreResolved, type Environment, type ResolvedType } from "./eval"; import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock"; import { GlobalContext } from "./GlobalContext"; import { Value } from "./Value"; @@ -9,6 +9,8 @@ import { Value } from "./Value"; import { getActions } from "./actions"; import "./CallBlock.css"; import { CallContext } from "./CallContext"; +import { inferType, inferTypeCall, type Environment } from "./infer_type"; +import { Type } from "./Type"; export interface CallBlockState { kind: "call"; @@ -21,20 +23,19 @@ export interface CallBlockProps< InputState=ExprBlockState, > extends State2Props {} -function nestedFnProperties({state, setState, score}: CallBlockProps, env) { +function nestedFnProperties({state, setState, score}: CallBlockProps, env: Environment) { const setFn = (callback: SetStateFn) => { setState(state => ({...state, fn: callback(state.fn)})); - } + }; const onFnCancel = () => { setState(state => state.input); // we become our input - } - const scoreFn = (fnSuggestion: ResolvedType) => { - return computePriority( - fnSuggestion, - evalExprBlock(state.input, env)[0], - score, - env, - ); + }; + const scoreFn = (fnSuggestion: ExprBlockState) => { + return score({ + kind: "call", + fn: fnSuggestion, + input: state.input, + }); }; return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn}; } @@ -42,36 +43,37 @@ function nestedFnProperties({state, setState, score}: CallBlockProps, env) { function nestedInputProperties({state, setState, score}: CallBlockProps, env: Environment) { const setInput = (callback: SetStateFn) => { setState(state => ({...state, input: callback(state.input)})); - } + }; const onInputCancel = () => { - setState(state => addFocusRightMost(state.fn)); // we become our function - } - const scoreInput = (inputSuggestion: ResolvedType) => { - return computePriority( - evalExprBlock(state.fn, env)[0], // fn *may* be set - inputSuggestion, // suggestions will be for input - score, // priority function we get from parent block - env, - ); - } + setState(state => /*addFocusRightMost*/(state.fn)); // we become our function + }; + const scoreInput = (inputSuggestion: ExprBlockState) => { + return score({ + kind: "call", + fn: state.fn, + input: inputSuggestion, + }); + }; return {state: state.input, setState: setInput, onCancel: onInputCancel, score: scoreInput}; } -function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) { - // dirty, but works: - const [fnR, env2] = recomputeTypeVarsForEnv('', fn, env); - const [inR, env3] = recomputeTypeVarsForEnv('', input, env2); - const [resolved] = evalCallBlock2(fnR, inR, env3); - const score = scoreResolved(resolved, outPriority); - return score; -} +// function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) { +// // dirty, but works: +// const [fnR, env2] = recomputeTypeVarsForEnv('', fn, env); +// const [inR, env3] = recomputeTypeVarsForEnv('', input, env2); +// const [resolved] = evalCallBlock2(fnR, inR, env3); +// const score = scoreResolved(resolved, outPriority); +// return score; +// } export function CallBlock(props: CallBlockProps) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); const addParam = getActions(globalContext, props.setState).c; - const [resolved] = evalExprBlock(props.state, env); - return + // const [resolved] = evalExprBlock(props.state, env); + // return + const typeInfo = inferTypeCall(props.state, env); + return
@@ -80,12 +82,14 @@ export function CallBlock(props: CallBlockProps) { - { (resolved.kind === "error") && resolved.e.toString() + {/* { (resolved.kind === "error") && resolved.e.toString() || (resolved.kind === "value") && - || "unknown" } + || "unknown" } */} + ::
diff --git a/src/EnvContext.ts b/src/EnvContext.ts index a23d4c4..e540318 100644 --- a/src/EnvContext.ts +++ b/src/EnvContext.ts @@ -1,6 +1,7 @@ import { createContext } from "react"; import { getDefaultTypeParser, module2Env, ModuleStd } from "dope2"; -import type { Dynamic, Environment } from "./eval"; +// import type { Dynamic, Environment } from "./eval"; +import type { Environment } from "./infer_type"; export const functionWith3Params = i => j => k => i+j+k; export const functionWith4Params = i => j => k => l => i+j+k+l; @@ -14,10 +15,10 @@ export const extendedEnv: Environment = { ["leqZero", {i: n => leq => otherwise => (n<=0)?leq({}):otherwise({}), t: mkType("Int -> (Unit -> a) -> (Unit -> a) -> a")}], ["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]) + ]).map(([name, {i,t}]) => [name, { kind: "value", i, t, unification: new Map() }] as [string, any]) ).name2dyn, - nextFreeTypeVar: 0, - typeVars: new Set(), + // nextFreeTypeVar: 0, + typevars: new Set(), }; export const EnvContext = createContext(extendedEnv); \ No newline at end of file diff --git a/src/ExprBlock.tsx b/src/ExprBlock.tsx index 0967780..21346be 100644 --- a/src/ExprBlock.tsx +++ b/src/ExprBlock.tsx @@ -9,11 +9,12 @@ import { InputBlock, type InputBlockProps, type InputBlockState } from "./InputB import { LambdaBlock, type LambdaBlockProps, type LambdaBlockState } from "./LambdaBlock"; import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInBlock"; import { Type } from "./Type"; -import { evalExprBlock, type ResolvedType } from "./eval"; +// import { evalExprBlock, type ResolvedType } from "./eval"; import "./ExprBlock.css"; import { Input } from "./Input"; import { getActions } from "./actions"; +import { inferType } from "./infer_type"; export type ExprBlockState = InputBlockState @@ -26,7 +27,7 @@ export type SetStateFn = (state: InTy export interface State2Props { state: InType; setState: (callback: SetStateFn) => void; - score: (suggestion: ResolvedType) => number; + score: (suggestion: ExprBlockState) => number; } interface ExprBlockProps extends State2Props { @@ -44,19 +45,22 @@ export function ExprBlock(props: ExprBlockProps) { lambda: () => , }; - const [resolved] = evalExprBlock(props.state, env); + // const [resolved] = evalExprBlock(props.state, env); + // const typeInfo = inferType(props.state, env); const actions = getActions(globalContext, props.setState); const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) => [shortcut, (e) => { e.preventDefault(); action(); }])) - return + // return + + return {renderBlock[props.state.kind]()} {/* @ts-ignore */} -
-  ::  + {/*
*/} + {/*  ::  */} {/* @ts-ignore */} - {resolved.__debug &&
{resolved.__debug}
} -
+ {/* {resolved.__debug &&
{resolved.__debug}
} */} + {/*
*/} { onCancel: () => void; } -const computeSuggestions = ( - text: string, - env: Environment, - score: InputBlockProps['score'], -): PrioritizedSuggestionType[] => { - const literals = attemptParseLiteral(text, env); - const ls: SuggestionType[] = [ - // literals - ... literals.map((resolved) => ["literal", text, resolved]), +// const computeSuggestions = ( +// text: string, +// env: Environment, +// score: InputBlockProps['score'], +// ): PrioritizedSuggestionType[] => { +// const literals = attemptParseLiteral(text, env); +// const ls: SuggestionType[] = [ +// // literals +// ... literals.map((resolved) => ["literal", text, resolved]), - // names - ... trie.suggest(env.names)(text)(Infinity) - .map(([name, resolved]) => ["name", name, resolved]), - ] - // return []; // <-- uncomment to disable suggestions (useful for debugging) - return ls - .map((suggestion: SuggestionType) => - [score(suggestion[2]), ...suggestion] as PrioritizedSuggestionType) - .sort(([priorityA], [priorityB]) => priorityB - priorityA) -} +// // names +// ... trie.suggest(env.names)(text)(Infinity) +// .map(([name, resolved]) => ["name", name, resolved]), +// ] +// // return []; // <-- uncomment to disable suggestions (useful for debugging) +// return ls +// .map((suggestion: SuggestionType) => +// [score(suggestion[2]), ...suggestion] as PrioritizedSuggestionType) +// .sort(([priorityA], [priorityB]) => priorityB - priorityA) +// } -export function InputBlock({ state, setState, score, onCancel }: InputBlockProps) { +export function InputBlock({ state, setState, /*score,*/ onCancel }: InputBlockProps) { const {text, focus} = state; const globalContext = useContext(GlobalContext); const env = useContext(EnvContext); @@ -69,7 +70,8 @@ export function InputBlock({ state, setState, score, onCancel }: InputBlockProps const [i, setI] = useState(0); // selected suggestion idx const singleSuggestion = trie.growPrefix(env.names)(text); - const suggestions = useMemo(() => computeSuggestions(text, env, score), [text, score, env]); + // const suggestions = useMemo(() => computeSuggestions(text, env, score), [text, score, env]); + const suggestions = useMemo(() => [], []); useEffect(() => { @@ -94,21 +96,21 @@ export function InputBlock({ state, setState, score, onCancel }: InputBlockProps } const onSelectSuggestion = () => { - const [_priority, kind, name, dynamic] = suggestions[i]; - if (kind === "literal") { - setState(state => ({ - ...state, - text: name, - value: {kind, type: prettyT(getType(dynamic))}, - })); - } - else { - setState(state => ({ - ...state, - text: name, - value: {kind}, - })) - } + // const [_priority, kind, name, dynamic] = suggestions[i]; + // if (kind === "literal") { + // setState(state => ({ + // ...state, + // text: name, + // value: {kind, type: prettyT(getType(dynamic))}, + // })); + // } + // else { + // setState(state => ({ + // ...state, + // text: name, + // value: {kind}, + // })) + // } }; const extraHandlers = { @@ -134,7 +136,9 @@ export function InputBlock({ state, setState, score, onCancel }: InputBlockProps }, }; - return
+ :: + } function Suggestions({ suggestions, onSelect, i, setI }) { @@ -170,10 +176,10 @@ interface SuggestionProps { j: number; onSelect: any; highlighted: boolean; - suggestion: PrioritizedSuggestionType; + // suggestion: PrioritizedSuggestionType; } -function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kind, text, resolved] }: SuggestionProps) { +function Suggestion({ setI, j, onSelect, highlighted, /*suggestion: [priority, kind, text, resolved]*/ }: SuggestionProps) { const onMouseEnter = j => () => { setI(j); }; @@ -186,7 +192,7 @@ function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kin className={(highlighted ? " selected" : "")} onMouseEnter={onMouseEnter(j)} onMouseDown={onMouseDown(j)}> - ({priority}) ({kind}) {text} :: + {/* ({priority}) ({kind}) {text} :: */} } diff --git a/src/LambdaBlock.tsx b/src/LambdaBlock.tsx index f969b9a..2201dc8 100644 --- a/src/LambdaBlock.tsx +++ b/src/LambdaBlock.tsx @@ -1,14 +1,15 @@ import { useContext } from "react"; -import { eqType, getSymbol, reduceUnification } from "dope2"; +// import { eqType, getSymbol, reduceUnification } from "dope2"; import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock"; import { EnvContext } from "./EnvContext"; -import { evalExprBlock, evalLambdaBlock, makeInnerEnv, makeTypeVar } from "./eval"; +// import { evalExprBlock, evalLambdaBlock, makeInnerEnv, makeTypeVar } from "./eval"; import "./LambdaBlock.css"; import { Type } from "./Type"; import { Input } from "./Input"; +import { inferTypeLambda } from "./infer_type"; export interface LambdaBlockState { kind: "lambda"; @@ -36,9 +37,13 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) { expr: callback(state.expr), })); - const [lambdaResolved, _, innerEnv] = evalLambdaBlock(state.paramName, state.expr, env); + const {paramType, innerEnv} = inferTypeLambda(state, env); - const inferredParamType = lambdaResolved.t.params[0](lambdaResolved.t); + // const [lambdaResolved, _, innerEnv] = evalLambdaBlock(state.paramName, state.expr, env); + + // const inferredParamType = lambdaResolved.t.params[0](lambdaResolved.t); + + // const innerEnv = env; // todo: change this return λ @@ -55,7 +60,7 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) { />
-  ::  +  :: 
  : diff --git a/src/LetInBlock.tsx b/src/LetInBlock.tsx index 6ab4cb1..ae09fac 100644 --- a/src/LetInBlock.tsx +++ b/src/LetInBlock.tsx @@ -2,12 +2,14 @@ import { useContext } from "react"; import { ExprBlock, type ExprBlockState } from "./ExprBlock"; import { EnvContext } from "./EnvContext"; -import { evalExprBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval"; +// import { evalExprBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval"; import { type State2Props } from "./ExprBlock"; import { GlobalContext } from "./GlobalContext"; import "./LetInBlock.css"; import { Input } from "./Input"; +import { inferTypeLet } from "./infer_type"; +import { Type } from "./Type"; export interface LetInBlockState { kind: "let"; @@ -31,49 +33,54 @@ export function LetInBlock(props: LetInBlockProps) {
} -function DeclColumns({state: {name, value, inner, focus}, setState, score}) { +function DeclColumns({state, setState, score}) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); const setValue = callback => setState(state => ({...state, value: callback(state.value)})); - const valueSuggestionPriority = (suggestion: ResolvedType) => { - const innerEnv = makeInnerEnv(env, name, suggestion); - const [resolved] = evalExprBlock(inner, innerEnv); - return scoreResolved(resolved, score); - }; + // const valueSuggestionPriority = (suggestion: ResolvedType) => { + // const innerEnv = makeInnerEnv(env, name, suggestion); + // const [resolved] = evalExprBlock(inner, innerEnv); + // return scoreResolved(resolved, score); + // }; - const [valueResolved] = evalExprBlock(value, env); - const innerEnv = makeInnerEnv(env, name, valueResolved); + // const [valueResolved] = evalExprBlock(value, env); + // const innerEnv = makeInnerEnv(env, name, valueResolved); + + const {paramType, innerEnv} = inferTypeLet(state, env); + + // const innerEnv = env; // todo: change this return <> let  - {}} - onCancel={() => {}} - onTextChange={name => setState(state => ({...state, name}))} - extraHandlers={{}} - /> + {}} + onCancel={() => {}} + onTextChange={name => setState(state => ({...state, name}))} + extraHandlers={{}} + /> + ::  =  0} onCancel={() => setState(state => state.inner)} // keep inner /> - {inner.kind === "let" && + {state.inner.kind === "let" && globalContext?.syntacticSugar && @@ -86,8 +93,9 @@ function InnerMost({state, setState, score}) { const env = useContext(EnvContext); const globalContext = useContext(GlobalContext); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); - const [valueResolved] = evalExprBlock(state.value, env); - const innerEnv = makeInnerEnv(env, state.name, valueResolved); + // const [valueResolved] = evalExprBlock(state.value, env); + // const innerEnv = makeInnerEnv(env, state.name, valueResolved); + const innerEnv = env; // todo: change this const onCancel = () => setState(state => state.value); if (state.inner.kind === "let" && globalContext?.syntacticSugar) { return diff --git a/src/actions.ts b/src/actions.ts index 8510b0e..d83dca1 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -1,5 +1,7 @@ import { initialEditorState } from "./configurations"; -import { removeFocus } from "./eval"; +// import { removeFocus } from "./eval"; + +const removeFocus = state => state; export const actionShortcuts: [string, string[], string][] = [ ["call" , ['c'], "expr ⌴" ], diff --git a/src/eval.ts b/src/eval.ts index afba21b..35457b1 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -1,559 +1,635 @@ -import { Double, eqType, fnType, getHumanReadableName, getSymbol, Int, isTypeVar, mergeUnifications, NotAFunctionError, occurring, prettyT, prettyU, recomputeTypeVarsWithInverse, reduceUnification, substitute, symbolFunction, transitivelyGrow, trie, TYPE_VARS, UNBOUND_SYMBOLS, UnifyError, unifyLL } from "dope2"; +// import { Double, eqType, fnType, getHumanReadableName, getSymbol, Int, isTypeVar, occurring, prettyT, recomputeTypeVarsWithInverse, substitute, symbolFunction, transitivelyGrow, trie, TYPE_VARS, UNBOUND_SYMBOLS } from "dope2"; -import type { ExprBlockState } from "./ExprBlock"; -import type { InputValueType } from "./InputBlock"; +// import type { ExprBlockState } from "./ExprBlock"; +// import type { InputValueType } from "./InputBlock"; -const IS_DEV = (import.meta.env.MODE === "development"); -const VERBOSE = true && IS_DEV; +// const IS_DEV = (import.meta.env.MODE === "development"); +// const VERBOSE = true && IS_DEV; -export interface Environment { - names: any; - nextFreeTypeVar: number; - typeVars: Set; -} +// export interface Environment { +// names: any; +// nextFreeTypeVar: number; +// typeVars: Set; +// } -interface Type { - symbol: string; - params: any[]; -}; +// interface Type { +// symbol: string; +// params: any[]; +// }; -type Unification = Map; +// type Unification = Map; -export interface DeepError { - kind: "error"; - e: Error; - depth: number; - t: Type; - unification: Unification; -} +// export interface DeepError { +// kind: "error"; +// e: Error; +// depth: number; +// t: Type; +// unification: Unification; +// } -// a dynamically typed value = tuple (instance, type) -export interface Dynamic { - kind: "value", - i: any; - t: Type; - unification: Unification; -}; +// // a dynamically typed value = tuple (instance, type) +// export interface Dynamic { +// kind: "value", +// i: any; +// t: Type; +// unification: Unification; +// }; -export interface Unknown { - kind: "unknown"; - t: Type; - unification: Unification; -} +// export interface Unknown { +// kind: "unknown"; +// t: Type; +// unification: Unification; +// } -// the value of every block is either known (Dynamic), an error, or unknown -export type ResolvedType = Dynamic | DeepError | Unknown; +// // the value of every block is either known (Dynamic), an error, or unknown +// export type ResolvedType = Dynamic | DeepError | Unknown; -class NotFoundError extends Error {} +// class NotFoundError extends Error {} -export const evalExprBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => { - if (s.kind === "input") { - return evalInputBlock(s.text, s.value, env); - } - else if (s.kind === "call") { - return evalCallBlock(s.fn, s.input, env); - } - else if (s.kind === "let") { - return evalLetInBlock(s.value, s.name, s.inner, env); - } - else { // (s.kind === "lambda") - const [resolved, env2] = evalLambdaBlock(s.paramName, s.expr, env); - return [resolved, env2]; - } -}; +// export const evalExprBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => { +// if (s.kind === "input") { +// return evalInputBlock(s.text, s.value, env); +// } +// else if (s.kind === "call") { +// return evalCallBlock(s.fn, s.input, env); +// } +// else if (s.kind === "let") { +// return evalLetInBlock(s.value, s.name, s.inner, env); +// } +// else { // (s.kind === "lambda") +// const [resolved, env2] = evalLambdaBlock(s.paramName, s.expr, env); +// return [resolved, env2]; +// } +// }; -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, env]; // don't rewrite lambda parameters - } - } - const [t, env2] = makeTypeVar(env, 'err') - return [{ - kind: "error", - t, - e: new NotFoundError(`'${text}' not found`), - depth: 0, - unification: new Map(), - }, env2]; -} +// 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, env]; // don't rewrite lambda parameters +// } +// } +// const [t, env2] = makeTypeVar(env, 'err') +// return [{ +// kind: "error", +// t, +// e: new NotFoundError(`'${text}' not found`), +// depth: 0, +// unification: new Map(), +// }, env2]; +// } -export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] { - let [fnResolved, env2] = evalExprBlock(fn, env); - if (fnResolved.kind === "value") { - [fnResolved, env2] = recomputeTypeVarsForEnv("", fnResolved, env2); - } - let [inputResolved, env3] = evalExprBlock(input, env2); - if (inputResolved.kind === "value") { - [inputResolved, env3] = recomputeTypeVarsForEnv("", inputResolved, env3); - } - const result = evalCallBlock2(fnResolved, inputResolved, env3); - if (VERBOSE) { - // @ts-ignore - result[0].__debug = (result[0].__debug || '') + ` -==== evalCallBlock ==== - env.typeVars : ${[...env.typeVars].map(getHumanReadableName)} - env.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])} - fn.kind : ${fn.kind} - fn.t : ${prettyT(fnResolved.t)} -env2.typeVars : ${[...env2.typeVars].map(getHumanReadableName)} -env2.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env2.nextFreeTypeVar])} -input.kind : ${input.kind} -input.t : ${prettyT(inputResolved.t)} -env3.typeVars : ${[...env3.typeVars].map(getHumanReadableName)} -env3.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env3.nextFreeTypeVar])} -=======================`; - } - return result; -} +// export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] { +// let __debug = ` +// ========= evalCallBlock ========= +// env.typeVars : ${[...env.typeVars].map(getHumanReadableName)} +// env.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])} +// fn.kind : ${fn.kind} +// input.kind : ${input.kind}`; +// let [fnResolved, env2] = evalExprBlock(fn, env); +// if (VERBOSE) { +// __debug += ` +// ==== evalCallBlock (fn) ==== +// fn.t : ${prettyT(fnResolved.t)} +// env2.typeVars : ${[...env2.typeVars].map(getHumanReadableName)} +// env2.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env2.nextFreeTypeVar])}`; +// } +// if (fnResolved.kind !== "unknown") { +// let fnInverse; +// [fnResolved, env2, fnInverse] = recomputeTypeVarsForEnv("", fnResolved, env2); +// if (VERBOSE) { +// __debug += ` +// --- recomp --- +// fn.t : ${prettyT(fnResolved.t)} +// fnInverse : ${[...fnInverse].map(([symbol,type])=>`${getHumanReadableName(symbol)} => ${getHumanReadableName(type)})`).join(',')} +// env2.typeVars : ${[...env2.typeVars].map(getHumanReadableName)} +// env2.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env2.nextFreeTypeVar])}`; +// } +// } +// let [inputResolved, env3] = evalExprBlock(input, env2); +// if (VERBOSE) { +// __debug += ` +// ==== evalCallBlock (input) ==== +// input.t : ${prettyT(inputResolved.t)} +// env3.typeVars : ${[...env3.typeVars].map(getHumanReadableName)} +// env3.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env3.nextFreeTypeVar])}`; +// } +// if (inputResolved.kind !== "unknown") { +// let inputInverse; +// [inputResolved, env3, inputInverse] = recomputeTypeVarsForEnv("", inputResolved, env3); +// if (VERBOSE) { +// __debug += ` +// --- recomp --- +// input.t : ${prettyT(inputResolved.t)} +// inputInverse : ${[...inputInverse].map(([symbol,type])=>`${getHumanReadableName(symbol)} => ${getHumanReadableName(type)})`).join(',')} +// env3.typeVars : ${[...env3.typeVars].map(getHumanReadableName)} +// env3.nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env3.nextFreeTypeVar])}`; +// } +// } +// const result = evalCallBlock2(fnResolved, inputResolved, env3); +// if (VERBOSE) { +// // @ts-ignore +// result[0].__debug = __debug + (result[0].__debug || ''); +// } +// return result; +// } -export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] { - if (getSymbol(fnResolved.t) !== symbolFunction) { - // not a function... - if (isTypeVar(fnResolved.t)) { - // ... but typeVars are OK (good guys!) - } - else { - // worst outcome - return makeError(env, - new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`), - mergeUnifications(fnResolved.unification, inputResolved.unification), - ) - } - } - // it's is a function, continue... - return evalCallBlock3(fnResolved, inputResolved, env); -}; +// export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] { +// if (getSymbol(fnResolved.t) !== symbolFunction) { +// // not a function... +// if (isTypeVar(fnResolved.t)) { +// // ... but typeVars are OK (good guys!) +// } +// else { +// // worst outcome +// return makeError(env, +// new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`), +// mergeUnifications(fnResolved.unification, inputResolved.unification), +// ) +// } +// } +// // it's is a function, continue... +// return evalCallBlock3(fnResolved, inputResolved, env); +// }; -const highestTypeVar = type => { - let highest = -1; - for (const typeVar of occurring(type)) { - highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar)); - } - return highest; -} -const highestTypeVar2 = typeVars => { - let highest = -1; - for (const typeVar of typeVars) { - highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar)); - } - return highest; -} +// 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]) - ); -} +// const inverseUnification = (uni, inverse) => { +// return new Map([...uni] +// .filter(([symbol]) => !inverse.has(inverse.get(symbol))) +// .map(([symbol, types]) => [inverse.get(symbol) || symbol, types]) +// ); +// } -export 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), - }; +// export function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, env: Environment): [ResolvedType,Environment,any] { +// const [[newType], inverse] = recomputeTypeVarsWithInverse([resolved.t], findTempIdx(env)); +// const newResolved: ResolvedType = { +// ...resolved, +// t: newType, +// unification: inverseUnification(resolved.unification, inverse), +// }; - // hacky - const typeVars = env.typeVars.union(occurring(newType)) as Set; - const newEnv: Environment = { - // names: trie.insert(env.names)(name)(newResolved), - names: env.names, - typeVars, - nextFreeTypeVar: highestTypeVar2(typeVars) + 1, - }; +// // hacky +// const typeVars = env.typeVars.union(occurring(newType)) as Set; +// const newEnv: Environment = { +// // names: trie.insert(env.names)(name)(newResolved), +// names: env.names, +// typeVars, +// nextFreeTypeVar: highestTypeVar2(typeVars) + 1, +// }; - return [newResolved, newEnv]; -} +// return [newResolved, newEnv, inverse]; +// } -function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] { - let __debug = ''; - try { - if (occurring(fnResolved.t).intersection(occurring(inputResolved.t)).size > 0) { - throw new Error(`Precondition failed: function (${prettyT(fnResolved.t)}) and its input (${prettyT(inputResolved.t)}) have overlapping typevars!`); - } +// function removeTypeVarsFromEnv(toRemove, env: Environment): Environment { +// const newTypeVars = env.typeVars.difference(toRemove); +// return { +// names: env.names, +// typeVars: newTypeVars, +// nextFreeTypeVar: highestTypeVar2(newTypeVars) + 1, +// } +// } - // turn input in to a function - const [abstractOutputType, env2] = makeTypeVar(env, ""); - const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType); +// function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] { +// let __debug = ''; +// try { +// if (occurring(fnResolved.t).intersection(occurring(inputResolved.t)).size > 0) { +// throw new Error(`Precondition failed: function (${prettyT(fnResolved.t)}) and its input (${prettyT(inputResolved.t)}) have overlapping typevars!`); +// } - if (VERBOSE) { - __debug += `========= evalCallBlock3 ========= -env.typeVars : ${[...env.typeVars].map(getHumanReadableName)} -nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])} -fnKind : ${fnResolved.kind} -inputKind : ${inputResolved.kind} -fnType : ${prettyT(fnResolved.t)} -inputType : ${prettyT(inputResolved.t)} -matchFnType : ${prettyT(matchFnType)}`; - } +// const typeVarsOfFnAndInput = occurring(fnResolved.t).union(occurring(inputResolved.t)); - // unify both functions - const unification = /*transitivelyGrow*/(unifyLL(fnResolved.t, matchFnType)); +// // turn input in to a function +// const [abstractOutputType, env2] = makeTypeVar(env, ""); +// const matchFnType = fnType(_ => inputResolved.t)(_ => abstractOutputType); - if (VERBOSE) { - __debug += ` -unification : ${prettyU(unification)}`; - } +// if (VERBOSE) { +// __debug += ` +// ========= evalCallBlock3 ========= +// env.typeVars : ${[...env.typeVars].map(getHumanReadableName)} +// nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])} +// fnKind : ${fnResolved.kind} +// inputKind : ${inputResolved.kind} +// fnType : ${prettyT(fnResolved.t)} +// inputType : ${prettyT(inputResolved.t)} +// matchFnType : ${prettyT(matchFnType)}`; +// } - const unificationR = reduceUnification(unification); - const unifiedFnType = substitute( - // matchFnType, - fnResolved.t, - unificationR, []); +// // unify both functions +// const unification = /*transitivelyGrow*/(unifyLL(fnResolved.t, matchFnType)); - const outType = unifiedFnType.params[1](unifiedFnType); +// if (VERBOSE) { +// __debug += ` +// unification : ${prettyU(unification)}`; +// } - const newEnv = (outType === abstractOutputType) ? env2 : env; +// const unificationR = reduceUnification(unification); +// const unifiedFnType = substitute( +// // matchFnType, +// fnResolved.t, +// unificationR, []); - // 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)); +// const outType = unifiedFnType.params[1](unifiedFnType); + +// const typeVarsOfOutType = occurring(outType); + +// const removedTypeVars = typeVarsOfFnAndInput.difference(typeVarsOfOutType); + +// // const newEnv = (outType === abstractOutputType) ? env2 : env; +// const newEnv = removeTypeVarsFromEnv(removedTypeVars, 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 (VERBOSE) { - __debug += ` -unificationInvR : ${prettyRU(unificationR)} -unifiedFnType : ${prettyT(unifiedFnType)} -outType : ${prettyT(outType)} -fn.unification : ${prettyU(fnResolved.unification)} -input.unification : ${prettyU(inputResolved.unification)} -unificationWithoutOutType: ${prettyU(unificationWithoutOutType)}`; - } +// if (VERBOSE) { +// __debug += ` +// unificationR : ${prettyRU(unificationR)} +// unifiedFnType : ${prettyT(unifiedFnType)} +// outType : ${prettyT(outType)} +// removedTypeVars : ${[...removedTypeVars].map(getHumanReadableName)} +// fn.unification : ${prettyU(fnResolved.unification)} +// input.unification : ${prettyU(inputResolved.unification)} +// unificationWithoutOutType: ${prettyU(unificationWithoutOutType)}`; +// } - const grandUnification = transitivelyGrow([fnResolved.unification, inputResolved.unification] - .reduce(mergeUnifications, unificationWithoutOutType)); +// const grandUnification = transitivelyGrow([fnResolved.unification, inputResolved.unification] +// .reduce(mergeUnifications, unificationWithoutOutType)); - if (VERBOSE) { - __debug += ` -grandUnification : ${prettyU(grandUnification)}`; - } +// if (VERBOSE) { +// __debug += ` +// grandUnification : ${prettyU(grandUnification)} +// newEnv.typeVars : ${[...newEnv.typeVars].map(getHumanReadableName)} +// newEnv.newFreeTypeVar : ${getHumanReadableName(UNBOUND_SYMBOLS[newEnv.nextFreeTypeVar])}`; +// } - if (inputResolved.kind === "error") { - // throw inputResolved.e; - return [{ - kind: "error", - e: inputResolved.e, // bubble up the error - depth: 0, - t: outType, - unification: grandUnification, - // @ts-ignore - __debug, - }, newEnv]; - } - if (fnResolved.kind === "error") { - // throw fnResolved.e; - // also bubble up - return [{ - kind: "error", - e: fnResolved.e, - depth: fnResolved.depth+1, - t: outType, - unification: grandUnification, - // @ts-ignore - __debug, - }, newEnv]; - } - // if the above statement did not throw => types are compatible... - if (inputResolved.kind === "value" && fnResolved.kind === "value") { - const outValue = fnResolved.i(inputResolved.i); - // console.log('outValue:', outValue); - return [{ - kind: "value", - i: outValue, - t: outType, - unification: grandUnification, - // @ts-ignore - __debug, - }, newEnv]; - } - else { - // we don't know the value, but we do know the type: - return [{ - kind: "unknown", - t: outType, - unification: grandUnification, - // @ts-ignore - __debug, - }, newEnv]; - } - } - catch (e) { - // if ((e instanceof UnifyError)) { - if (VERBOSE) { - __debug += '\n\nUnifyError! ' + (e as Error).message; - } - const err = makeError(env, e as Error); - // @ts-ignore - err[0].__debug = __debug; - return err; - // } - throw e; - } -} +// if (inputResolved.kind === "error") { +// // throw inputResolved.e; +// return [{ +// kind: "error", +// e: inputResolved.e, // bubble up the error +// depth: 0, +// t: outType, +// unification: grandUnification, +// // @ts-ignore +// __debug, +// }, newEnv]; +// } +// if (fnResolved.kind === "error") { +// // throw fnResolved.e; +// // also bubble up +// return [{ +// kind: "error", +// e: fnResolved.e, +// depth: fnResolved.depth+1, +// t: outType, +// unification: grandUnification, +// // @ts-ignore +// __debug, +// }, newEnv]; +// } +// // if the above statement did not throw => types are compatible... +// if (inputResolved.kind === "value" && fnResolved.kind === "value") { +// const outValue = fnResolved.i(inputResolved.i); +// // console.log('outValue:', outValue); +// return [{ +// kind: "value", +// i: outValue, +// t: outType, +// unification: grandUnification, +// // @ts-ignore +// __debug, +// }, newEnv]; +// } +// else { +// // we don't know the value, but we do know the type: +// return [{ +// kind: "unknown", +// t: outType, +// unification: grandUnification, +// // @ts-ignore +// __debug, +// }, newEnv]; +// } +// } +// catch (e) { +// // if ((e instanceof UnifyError)) { +// if (VERBOSE) { +// __debug += '\n\nUnifyError! ' + (e as Error).message; +// } +// const err = makeError(env, e as Error); +// // @ts-ignore +// err[0].__debug = __debug; +// return err; +// // } +// throw e; +// } +// } -export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] { - const [valueResolved] = evalExprBlock(value, env); - const innerEnv = makeInnerEnv(env, name, valueResolved); - return evalExprBlock(inner, innerEnv); -} +// export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] { +// const [valueResolved] = evalExprBlock(value, env); +// const innerEnv = makeInnerEnv(env, name, valueResolved); +// return evalExprBlock(inner, innerEnv); +// } -const prettyRU = (rUni: Map) => { - return '{'+[...rUni].map(([symbol,type]) => `${getHumanReadableName(symbol)} => ${prettyT(type)}`).join(', ')+'}'; -} +// const prettyRU = (rUni: Map) => { +// return '{'+[...rUni].map(([symbol,type]) => `${getHumanReadableName(symbol)} => ${prettyT(type)}`).join(', ')+'}'; +// } -export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment,Environment] { - let [paramType, innerEnv] = makeTypeVar(env, paramName); - let round = 0; - let __debug = ''; - while (true) { - // fixpoint computation... - const [exprResolved] = evalExprBlock(expr, innerEnv); - 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 +// function fixPoint(varName: string, expr: ExprBlockState, env: Environment) { +// let [varType, innerEnv] = makeTypeVar(env, varName); +// let round = 0; +// let __debug = ''; +// while (true) { +// const [innerResolved] = evalExprBlock(expr, innerEnv); +// const newInnerT = substitute(innerResolved.t, reduceUnification(innerResolved.unification), []); + +// round++; +// if (round === 10) { +// throw new Error("too many rounds!"); +// } +// } +// } + +// export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: Environment): [ResolvedType,Environment,Environment] { +// let [paramType, innerEnv] = makeTypeVar(env, paramName); +// let round = 0; +// let __debug = ''; +// while (true) { +// // fixpoint computation... +// const [exprResolved] = evalExprBlock(expr, innerEnv); +// const lambdaT = fnType(_ => paramType)(_ => exprResolved.t); +// // This is the only place in the code where we actually do something with the 'substitutions'. We compute the type of our lambda function: +// const reduced = reduceUnification(exprResolved.unification); +// const lambdaTSubstituted = substitute( +// lambdaT, +// reduced, +// []); // <- not important - let lambdaResolved: ResolvedType; - if (exprResolved.kind === "error") { - lambdaResolved = { - kind: "error", - t: lambdaTSubstituted, - depth: 0, - e: exprResolved.e, - unification: exprResolved.unification, - } - } - else { - lambdaResolved = { - kind: "value", - t: lambdaTSubstituted, - i: (x: any) => { - const innerEnv = makeInnerEnv(env, paramName, { - kind: "value", - i: x, - t: lambdaTSubstituted, - unification: new Map(), - }); - const [result] = evalExprBlock(expr, innerEnv); - if (result.kind === "value") { - return result.i; - } - }, - unification: exprResolved.unification, - } - } +// let lambdaResolved: ResolvedType; +// if (exprResolved.kind === "error") { +// lambdaResolved = { +// kind: "error", +// t: lambdaTSubstituted, +// depth: 0, +// e: exprResolved.e, +// unification: exprResolved.unification, +// } +// } +// else { +// lambdaResolved = { +// kind: "value", +// t: lambdaTSubstituted, +// i: (x: any) => { +// const innerEnv = makeInnerEnv(env, paramName, { +// kind: "value", +// i: x, +// t: lambdaTSubstituted, +// unification: new Map(), +// }); +// const [result] = evalExprBlock(expr, innerEnv); +// if (result.kind === "value") { +// return result.i; +// } +// }, +// unification: exprResolved.unification, +// } +// } - const [betterResolved, newInnerEnv] = recomputeTypeVarsForEnv("", lambdaResolved, env); +// // const [betterResolved, newInnerEnv] = recomputeTypeVarsForEnv("", lambdaResolved, env); +// const [betterResolved, newInnerEnv] = [lambdaResolved, env]; - const inferredParamType = betterResolved.t.params[0](betterResolved.t); +// const inferredParamType = betterResolved.t.params[0](betterResolved.t); - if (VERBOSE) { - __debug += ` -====== evalLambdaBlock (round ${round}) ====== -env.typeVars : ${[...env.typeVars].map(getHumanReadableName)} -nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])} -paramName : ${paramName} -paramType : ${prettyT(paramType)} -staticInnerEnv: ${innerEnv} -paramType : ${prettyT(paramType)} -exprType : ${prettyT(exprResolved.t)} -exprUnification : ${prettyU(exprResolved.unification)} -exprUnificationR : ${prettyRU(reduced)} -lambdaType : ${prettyT(lambdaT)} -lambdaTypeSubsituted: ${prettyT(lambdaTSubstituted)} -betterResolvedT : ${prettyT(betterResolved.t)} -inferredParamType : ${prettyT(inferredParamType)}`; - } +// if (VERBOSE) { +// __debug += ` +// ====== evalLambdaBlock (round ${round}) ====== +// env.typeVars : ${[...env.typeVars].map(getHumanReadableName)} +// nextFreeTypeVar: ${getHumanReadableName(UNBOUND_SYMBOLS[env.nextFreeTypeVar])} +// paramName : ${paramName} +// paramType : ${prettyT(paramType)} +// innerEnv.typevars : ${[...innerEnv.typeVars].map(getHumanReadableName)} +// paramType : ${prettyT(paramType)} +// exprType : ${prettyT(exprResolved.t)} +// exprUnification : ${prettyU(exprResolved.unification)} +// exprUnificationR : ${prettyRU(reduced)} +// lambdaType : ${prettyT(lambdaT)} +// lambdaTypeSubsituted: ${prettyT(lambdaTSubstituted)} +// betterResolvedT : ${prettyT(betterResolved.t)} +// inferredParamType : ${prettyT(inferredParamType)}`; +// } - if (eqType(paramType)(inferredParamType)) { - // reached fixpoint - // @ts-ignore - lambdaResolved.__debug = __debug; - return [lambdaResolved, env, innerEnv]; - } - else { - paramType = inferredParamType; - innerEnv = { - ...newInnerEnv, - names: trie.insert(newInnerEnv.names)(paramName)({ - kind: "unknown", - t: inferredParamType, - unification: new Map(), // <-- ?? - }), - }; - round++; - if (round === 10) { - throw new Error("too many rounds!"); - } - } - } -} +// if (eqType(paramType)(inferredParamType)) { +// // reached fixpoint +// // @ts-ignore +// lambdaResolved.__debug = __debug; +// return [lambdaResolved, env, innerEnv]; +// } +// else { +// paramType = inferredParamType; +// innerEnv = { +// ...newInnerEnv, +// names: trie.insert(newInnerEnv.names)(paramName)({ +// kind: "unknown", +// t: inferredParamType, +// unification: new Map(), // <-- ?? +// }), +// }; +// round++; +// if (round === 10) { +// throw new Error("too many rounds!"); +// } +// } +// } +// } -function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] { - // dirty - if (type === "Int") { - return parseAsInt(text, env); - } - if (type === "Double") { - return parseAsDouble(text, env); - } - return makeError(env, new Error("Failed to parse")); -} +// function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] { +// // dirty +// if (type === "Int") { +// return parseAsInt(text, env); +// } +// if (type === "Double") { +// return parseAsDouble(text, env); +// } +// return makeError(env, new Error("Failed to parse")); +// } -function parseAsDouble(text: string, env: Environment): [ResolvedType,Environment] { - if (text !== '') { - const num = Number(text); - if (!Number.isNaN(num)) { - 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: Environment): [ResolvedType,Environment] { - if (text !== '') { - try { - return [{ - kind: "value", - i: BigInt(text), - t: Int, - unification: new Map(), - }, env]; // may throw - } - catch {} - } - return makeError(env, new Error("Failed to parse as Int")); -} +// function parseAsDouble(text: string, env: Environment): [ResolvedType,Environment] { +// if (text !== '') { +// const num = Number(text); +// if (!Number.isNaN(num)) { +// 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: Environment): [ResolvedType,Environment] { +// if (text !== '') { +// try { +// return [{ +// kind: "value", +// i: BigInt(text), +// t: Int, +// unification: new Map(), +// }, env]; // may throw +// } +// catch {} +// } +// return makeError(env, new Error("Failed to parse as Int")); +// } -const literalParsers = [parseAsDouble, parseAsInt]; +// const literalParsers = [parseAsDouble, parseAsInt]; -export function attemptParseLiteral(text: string, env: Environment): Dynamic[] { - return literalParsers.map(parseFn => parseFn(text, env)) - .map(([resolved]) => resolved) - .filter((resolved) => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[]; -} +// export function attemptParseLiteral(text: string, env: Environment): Dynamic[] { +// return literalParsers.map(parseFn => parseFn(text, env)) +// .map(([resolved]) => resolved) +// .filter((resolved) => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[]; +// } -export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) { - const bias = outPriority(resolved); +// export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) { +// const bias = outPriority(resolved); - if (resolved.kind === "value") { - return bias; - } - else if (resolved.kind === "unknown") { - return bias; - } - if (resolved.e instanceof UnifyError) { - return -1 + bias; // parameter doesn't match - } - else { - return -2 + bias; // even worse: fn is not a function! - } -} +// if (resolved.kind === "value") { +// return bias; +// } +// else if (resolved.kind === "unknown") { +// return bias; +// } +// if (resolved.e instanceof UnifyError) { +// return -1 + bias; // parameter doesn't match +// } +// else { +// return -2 + bias; // even worse: fn is not a function! +// } +// } -export function makeInnerEnv(env: Environment, name: string, value: ResolvedType): Environment { - if (name !== "") { - return { - names: trie.insert(env.names)(name)(value), - nextFreeTypeVar: env.nextFreeTypeVar, - typeVars: env.typeVars, - } - } - return env; -} +// export function makeInnerEnv(env: Environment, name: string, value: ResolvedType): Environment { +// if (name !== "") { +// return { +// names: trie.insert(env.names)(name)(value), +// nextFreeTypeVar: env.nextFreeTypeVar, +// typeVars: env.typeVars, +// } +// } +// 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 findTempIdx(env: Environment) { +// let idx = 16; +// while (env.typeVars.has(UNBOUND_SYMBOLS[idx])) { +// idx++; +// } +// return idx; +// } -function makeError(env: Environment, e: Error, unification: Unification=new Map()): [DeepError, Environment] { - 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: env.names, - nextFreeTypeVar: idx + 1, - typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]), - }]; -} +// export function makeTypeVar(env: Environment, name: string): [Type, Environment] { +// let idx = 0; +// while (env.typeVars.has(UNBOUND_SYMBOLS[idx])) { +// idx++; +// } +// 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]]), +// }]; +// } -export function removeFocus(state: ExprBlockState): ExprBlockState { - if (state.kind === "input") { - return { ...state, focus: false }; - } - else if (state.kind === "call") { - return { - ...state, - fn: removeFocus(state.fn), - input: removeFocus(state.input), - }; - } - else if (state.kind === "lambda") { - return { - ...state, - focus: false, - expr: removeFocus(state.expr), - }; - } - else { // state.kind === "let" - return { - ...state, - focus: false, - value: removeFocus(state.value), - inner: removeFocus(state.inner), - } - } -} +// function makeError(env: Environment, e: Error, unification: Unification=new Map()): [DeepError, Environment] { +// let idx = 0; +// while (env.typeVars.has(UNBOUND_SYMBOLS[idx])) { +// idx++; +// } +// const typeVar = TYPE_VARS[idx]; +// const deepError: DeepError = { +// kind: "error", +// t: typeVar, +// unification: new Map(), +// e, +// depth: 0, +// }; +// return [deepError, { +// names: env.names, +// nextFreeTypeVar: idx + 1, +// typeVars: new Set([...env.typeVars, UNBOUND_SYMBOLS[idx]]), +// }]; +// } -export function addFocusRightMost(state: ExprBlockState) : ExprBlockState { - if (state.kind === "input") { - return { ...state, focus: true }; - } - else if (state.kind === "call") { - return { - ... state, - input: addFocusRightMost(state.input), - }; - } - else if (state.kind === "lambda") { - return { - ...state, - expr: addFocusRightMost(state.expr), - }; - } - else { // state.kind === "let" - return { - ...state, - inner: addFocusRightMost(state.inner), - } - } -} +// export function removeFocus(state: ExprBlockState): ExprBlockState { +// if (state.kind === "input") { +// return { ...state, focus: false }; +// } +// else if (state.kind === "call") { +// return { +// ...state, +// fn: removeFocus(state.fn), +// input: removeFocus(state.input), +// }; +// } +// else if (state.kind === "lambda") { +// return { +// ...state, +// focus: false, +// expr: removeFocus(state.expr), +// }; +// } +// else { // state.kind === "let" +// return { +// ...state, +// focus: false, +// value: removeFocus(state.value), +// inner: removeFocus(state.inner), +// } +// } +// } + +// export function addFocusRightMost(state: ExprBlockState) : ExprBlockState { +// if (state.kind === "input") { +// return { ...state, focus: true }; +// } +// else if (state.kind === "call") { +// return { +// ... state, +// input: addFocusRightMost(state.input), +// }; +// } +// else if (state.kind === "lambda") { +// return { +// ...state, +// expr: addFocusRightMost(state.expr), +// }; +// } +// else { // state.kind === "let" +// return { +// ...state, +// inner: addFocusRightMost(state.inner), +// } +// } +// } diff --git a/src/infer_type.ts b/src/infer_type.ts new file mode 100644 index 0000000..007e5ad --- /dev/null +++ b/src/infer_type.ts @@ -0,0 +1,216 @@ +import { Double, eqType, fnType, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, prettySS, recomputeTypeVars, substitute, trie, TYPE_VARS, UNBOUND_SYMBOLS, unify } from "dope2"; + +import type { CallBlockState } from "./CallBlock"; +import type { ExprBlockState } from "./ExprBlock"; +import type { InputBlockState } from "./InputBlock"; +import type { LambdaBlockState } from "./LambdaBlock"; +import type { LetInBlockState } from "./LetInBlock"; + +export interface Environment { + names: any; + typevars: Set; +} + +export interface Type { + symbol: string; + params: ((t: Type) => Type)[]; +}; + +export type Substitutions = Map; + +export interface TypeInfo { + type: Type; + subs: Substitutions; + newEnv: Environment; + err?: IncompatibleTypesError; +} + +export interface LambdaTypeInfo extends TypeInfo { + paramType: Type; + innerEnv: Environment; +} + +export function inferType(s: ExprBlockState, env: Environment): TypeInfo { + if (s.kind === "input") { + return inferTypeInput(s, env); + } + else if (s.kind === "call") { + return inferTypeCall(s, env); + } + else if (s.kind === "let") { + return inferTypeLet(s, env); + } + else { // (s.kind === "lambda") + return inferTypeLambda(s, env); + } +} + +export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfo { + if (s.value.kind === "literal") { + const type = { + Int: Int, + Double: Double, + }[s.value.type] as Type; + return { + type, + subs: new Map(), + newEnv: env, + } + } + else if (s.value.kind === "name") { + const found = trie.get(env.names)(s.text); + if (found) { + let type = found.t; + let newEnv = env; + if (found.kind !== "unknown") { + [type, newEnv] = rewriteType(found.t, env); + } + return { + type, + subs: new Map(), + newEnv, + }; + } + } + // kind === "text", or name not found + const [type, newEnv] = typeUnknown(env); + return { + type, + subs: new Map(), + newEnv, + } +} + +// @ts-ignore +export function inferTypeCall(s: CallBlockState, env: Environment): TypeInfo { + const fnTypeInfo = inferType(s.fn, env); + const inputTypeInfo = inferType(s.input, fnTypeInfo.newEnv); + const [returnType, envWithReturn] = typeUnknown(inputTypeInfo.newEnv); + const fakeFnType = fnType(_ => inputTypeInfo.type)(_ => returnType); + try { + const subs = unify( + fnTypeInfo.type, + fakeFnType); + console.log("subs:", prettySS(subs)); + let type, newEnv; + if (subs.has(returnType!.symbol)) { + type = subs.get(returnType!.symbol); + subs.delete(returnType!.symbol); + newEnv = inputTypeInfo.newEnv + } + else { + type = returnType; + newEnv = envWithReturn; + } + const mergedSubs = mergeSubstitutionsN([ + fnTypeInfo.subs, + inputTypeInfo.subs, + subs, + ]); + return { + type, + subs: mergedSubs, + newEnv, + }; + } + catch (e) { + if (e instanceof IncompatibleTypesError) { + const [type, newEnv] = typeUnknown(env); + return { + type, + subs: new Map(), + newEnv, + err: e, + } + } + } +} + +export function inferTypeLet(s: LetInBlockState, env: Environment): LambdaTypeInfo { + const valTypeInfo = inferType(s.value, env); + const innerEnv = { + names: trie.insert(env.names)(s.name)({kind: "value", t: valTypeInfo.type}), + typevars: env.typevars, + }; + return { + ...inferType(s.inner, innerEnv), + paramType: valTypeInfo.type, + innerEnv, + }; +} + + +export function inferTypeLambda(s: LambdaBlockState, env: Environment): LambdaTypeInfo { + let [paramType] = typeUnknown(env); + const paramTypeVar = paramType.symbol; + + let iterations = 1; + while (true) { + const innerEnv = { + names: trie.insert(env.names)(s.paramName)({kind: "unknown", t: paramType}), + typevars: env.typevars.union(occurring(paramType) as Set), + }; + const typeInfo = inferType(s.expr, innerEnv); + const subsWithoutPType = new Map(typeInfo.subs); + subsWithoutPType.delete(paramTypeVar); + const inferredPType = substitute(paramType, typeInfo.subs, []); + const inferredPType2 = rewriteInferredType(inferredPType, env); + if (eqType(inferredPType2)(paramType)) { + return { + type: fnType(_ => paramType)(_ => typeInfo.type), + subs: subsWithoutPType, + newEnv: env, // no change + paramType, + innerEnv, + }; + } + if ((iterations++) == 4) { + throw new Error("too many iterations!"); + } + // console.log("-----------------", iterations); + // console.log("paramType:", prettyT(paramType)); + // console.log("inferredPType:", prettyT(inferredPType)); + // console.log("inferredPType2:", prettyT(inferredPType2)); + // console.log("env:", [...env.typevars].map(getHumanReadableName)); + // console.log("innerEnv:", [...innerEnv.typevars].map(getHumanReadableName)); + // console.log("-----------------"); + paramType = inferredPType2; + } +} + +// Helpers +const highestTypeVar2 = (typevars: Iterable) => { + let highest = -1; + for (const typeVar of typevars) { + highest = Math.max(highest, UNBOUND_SYMBOLS.indexOf(typeVar)); + } + return highest; +} +function rewriteType(type: Type, env: Environment): [Type, Environment] { + const [recomputed] = recomputeTypeVars([type], highestTypeVar2(env.typevars)+1); + return [type, { + names: env.names, + typevars: env.typevars.union(occurring(recomputed)), + }]; +} +function typeUnknown(env: Environment): [Type, Environment] { + const type = TYPE_VARS[highestTypeVar2(env.typevars)+1]; + const newEnv = { + names: env.names, + typevars: new Set([...env.typevars, type.symbol]), + }; + return [type, newEnv]; +} +function rewriteInferredType(type: Type, env: Environment) { + const substitutions = new Map(); + let i = 0; + for (const o of occurring(type)) { + while (env.typevars.has(UNBOUND_SYMBOLS[i])) { + i++; + } + if (!env.typevars.has(o)) { + substitutions.set(o, TYPE_VARS[i]); + } + } + return substitute(type, substitutions, []); +}