From e8509527384e4c8ee0cd0a332c9c7bd497268640 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Sat, 17 May 2025 09:25:13 +0200 Subject: [PATCH] nicer looking --- pnpm-lock.yaml | 8 +- src/App.tsx | 9 ++- src/CallBlock.css | 25 +++--- src/Editor.css | 26 +++++- src/Editor.tsx | 43 +++++++--- src/InputBlock.tsx | 4 +- src/LambdaBlock.css | 0 src/LambdaBlock.tsx | 76 ++++++++++++++++-- src/LetInBlock.css | 19 ++++- src/LetInBlock.tsx | 119 ++++++++++++++++++---------- src/Value.css | 10 +++ src/Value.tsx | 11 ++- src/configurations.ts | 179 ++++++++++++++++++++++++++++++++++++++++++ src/eval.ts | 122 +++++++++++++++++++++++----- 14 files changed, 547 insertions(+), 104 deletions(-) create mode 100644 src/LambdaBlock.css diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0542d9..4fe0828 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: dope2: specifier: git+https://deemz.org/git/joeri/dope2.git - version: git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381 + version: git+https://deemz.org/git/joeri/dope2.git#0096bb5559224b4c9bbe74317e07dc71cfc09c70 react: specifier: ^19.1.0 version: 19.1.0 @@ -633,8 +633,8 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - dope2@git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381: - resolution: {commit: d75bf9f0f200769a5248ace8e4e2ace04fd60381, repo: https://deemz.org/git/joeri/dope2.git, type: git} + dope2@git+https://deemz.org/git/joeri/dope2.git#0096bb5559224b4c9bbe74317e07dc71cfc09c70: + resolution: {commit: 0096bb5559224b4c9bbe74317e07dc71cfc09c70, repo: https://deemz.org/git/joeri/dope2.git, type: git} version: 0.0.1 dunder-proto@1.0.1: @@ -1762,7 +1762,7 @@ snapshots: depd@2.0.0: {} - dope2@git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381: + dope2@git+https://deemz.org/git/joeri/dope2.git#0096bb5559224b4c9bbe74317e07dc71cfc09c70: dependencies: functional-red-black-tree: 1.0.1 diff --git a/src/App.tsx b/src/App.tsx index becf6ef..c9d3a0a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,20 +3,23 @@ import './App.css'; import { CommandContext } from './CommandContext'; import { Editor, type EditorState } from './Editor'; import { extendedEnv } from './EnvContext'; -import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; +import { biggerExample, initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; import { evalEditorBlock } from "./eval"; const commands: [string, string[], string][] = [ ["call" , ['c' ], "call" ], ["eval" , ['e','Tab','Enter'], "eval" ], ["transform", ['t', '.' ], "transform" ], - ["let" , ['l', '=', 'a' ], "let ... in ..."], + ["let" , ['l', '=' ], "let ... in ..."], + ["lambda" , ['a' ], "lambda" ], ]; const examples: [string, EditorState][] = [ ["empty editor", initialEditorState], ["push to list", nonEmptyEditorState], - ["function w/ 4 params", tripleFunctionCallEditorState]]; + ["function w/ 4 params", tripleFunctionCallEditorState], + ["bigger example", biggerExample], +]; type AppState = { history: EditorState[], diff --git a/src/CallBlock.css b/src/CallBlock.css index 0e3b4ce..a6b2346 100644 --- a/src/CallBlock.css +++ b/src/CallBlock.css @@ -1,11 +1,16 @@ .functionBlock { border: solid 1px darkgray; display: inline-block; - margin: 4px; + margin: 2px; color: black; background-color: white; + vertical-align: top; } +/* * { + vertical-align: text-top; +} */ + .functionName { /* text-align: center; */ background-color: white; @@ -14,11 +19,11 @@ .inputParam:after { content: ""; position: absolute; - border: solid transparent; - border-width: 10px; - right: -19px; - top: 0; - bottom:0; + right: 0; + clip-path: polygon(1% 0%, 100% 50%, 0% 100%); + height: 100%; + aspect-ratio: 0.25/1; + transform: translateX(99%); } .inputParam { margin-right: 20px; @@ -30,7 +35,7 @@ /* Count nested level AFTER .outputParam (resets the depth) */ .outputParam > .inputParam:after { - border-left-color: rgb(242, 253, 146); + background-color: rgb(242, 253, 146); } .outputParam > .inputParam { background-color: rgb(242, 253, 146); @@ -39,19 +44,19 @@ background-color: rgb(180, 248, 214); } .outputParam > .inputParam > .inputParam:after { - border-left-color: rgb(180, 248, 214); + background-color: rgb(180, 248, 214); } .outputParam > .inputParam > .inputParam > .inputParam { background-color: rgb(153, 212, 214); } .outputParam > .inputParam > .inputParam > .inputParam:after { - border-left-color: rgb(153, 212, 214); + background-color: rgb(153, 212, 214); } .outputParam > .inputParam > .inputParam > .inputParam > .inputParam { background-color: rgb(111, 186, 209); } .outputParam > .inputParam > .inputParam > .inputParam > .inputParam:after { - border-left-color: rgb(111, 186, 209); + background-color: rgb(111, 186, 209); } .typeAnnot { diff --git a/src/Editor.css b/src/Editor.css index 8e616d2..f083c9a 100644 --- a/src/Editor.css +++ b/src/Editor.css @@ -1,8 +1,30 @@ +.editor { + +} + .typeSignature { + display: none; + position: absolute; + z-index: 1; + background-color: white; + /* border: 1px solid black; */ +} + +.editor:hover > .typeSignature { display: inline-block; } .commandInput { - width: 90px; + width: 30px; margin-left: 10px; -} \ No newline at end of file +} + +.keyword { + color: blue; + font-weight: bold; + /* vertical-align: top; */ +} + +* { + /* vertical-align: top; */ +} diff --git a/src/Editor.tsx b/src/Editor.tsx index 1f4495b..e6319be 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -9,7 +9,7 @@ import { evalEditorBlock } from "./eval"; import { CommandContext } from "./CommandContext"; import "./Editor.css"; import { EnvContext } from "./EnvContext"; -import type { LambdaBlockState } from "./LambdaBlock"; +import { LambdaBlock, type LambdaBlockState } from "./LambdaBlock"; import { LetInBlock, type LetInBlockState } from "./LetInBlock"; import { initialEditorState } from "./configurations"; import { focusNextElement, focusPrevElement } from "./util/dom_trickery"; @@ -71,9 +71,11 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr const globalContext = useContext(CommandContext); const onCommand = (e: React.KeyboardEvent) => { + console.log(e); + // const type = getType(state.resolved); // const commands = getCommands(type); - const commands = ['e', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', '=', '.', 'c']; + const commands = ['e', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', 'L', '=', '.', 'c', 'a']; if (!commands.includes(e.key)) { return; } @@ -124,18 +126,35 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr } // l -> Let ... in ... // = -> assign to name - if (e.key === 'l' || e.key === '=') { + if (e.key === 'l' || e.key === '=' && !e.shiftKey) { // we become LetInBlock setState(state => ({ kind: "let", inner: removeFocus(initialEditorState), name: "", value: removeFocus(state), - resolved: undefined, - })); + })); globalContext?.doHighlight.let(); return; } + if (e.key === 'L' || e.key === '=' && e.shiftKey) { + setState(state => ({ + kind: "let", + inner: removeFocus(state), + name: "", + value: removeFocus(initialEditorState), + })); + } + // a -> lAmbdA + if (e.key === "a") { + setState(state => ({ + kind: "lambda", + paramName: "", + expr: removeFocus(state), + })); + globalContext?.doHighlight.lambda(); + return; + } }; const renderBlock = () => { @@ -169,22 +188,26 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr suggestionPriority={suggestionPriority} />; case "lambda": - return <>; + return EditorState)=>void} + suggestionPriority={suggestionPriority} + />; } } const resolved = evalEditorBlock(state, env); - return <> + return {renderBlock()}
-  ::  +
`} + placeholder={``} onKeyDown={onCommand} value={""} onChange={() => {}} /> - ; +
; } diff --git a/src/InputBlock.tsx b/src/InputBlock.tsx index dc8fe16..c4e49af 100644 --- a/src/InputBlock.tsx +++ b/src/InputBlock.tsx @@ -40,13 +40,13 @@ interface InputBlockProps extends State2Props { const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) => number): PrioritizedSuggestionType[] => { const literals = attemptParseLiteral(text); - const ls = [ + const ls: SuggestionType[] = [ // literals ... literals.map((lit) => ["literal", text, lit]), // names ... trie.suggest(env.name2dyn)(text)(Infinity) - .map(([name,type]) => ["name", name, type]), + .map(([name,type]) => ["name", name, {...type, substitutions: new Map()}]), ] // return ls; return ls diff --git a/src/LambdaBlock.css b/src/LambdaBlock.css new file mode 100644 index 0000000..e69de29 diff --git a/src/LambdaBlock.tsx b/src/LambdaBlock.tsx index 0b8c83e..663e84b 100644 --- a/src/LambdaBlock.tsx +++ b/src/LambdaBlock.tsx @@ -1,6 +1,12 @@ -import type { EditorState } from "./Editor"; -import type { Dynamic } from "./eval"; -import type { ResolvedType } from "./eval"; +import { useContext, useEffect, useRef } from "react"; + +import { Editor, type EditorState, type State2Props } from "./Editor"; +import type { SuggestionType } from "./InputBlock"; +import { EnvContext } from "./EnvContext"; +import { growEnv, TYPE_VARS } from "dope2"; +import { autoInputWidth } from "./util/dom_trickery"; + +import "./LambdaBlock.css"; export interface LambdaBlockState { @@ -9,6 +15,66 @@ export interface LambdaBlockState { expr: EditorState; } -export function LambdaBlock { +interface LambdaBlockProps< + FnState=EditorState, + InputState=EditorState, +> extends State2Props { + suggestionPriority: (suggestion: SuggestionType) => number; +} + + +export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockProps) { + const env = useContext(EnvContext); + const nameRef = useRef(null); + + const setParamName = paramName => setState(state => ({ + ...state, + paramName, + })); + + const setExpr = callback => setState(state => ({ + ...state, + expr: callback(state.expr), + })); + + useEffect(() => { + nameRef.current?.focus(); + }, []); + + useEffect(() => autoInputWidth(nameRef, state.paramName, 60), [nameRef, state.paramName]); + + const innerEnv = growEnv(env)(state.paramName)({ + kind: "unknown", + i: undefined, + t: TYPE_VARS[0], + }); -} \ No newline at end of file + return + λ +   + + setParamName(e.target.value)} + /> + +   + : +   + + + setState(state => state.expr)} + suggestionPriority={(suggestion: SuggestionType) => { + return suggestionPriority(suggestion); + }} + /> + + + +} diff --git a/src/LetInBlock.css b/src/LetInBlock.css index 27cf5f3..9832fe6 100644 --- a/src/LetInBlock.css +++ b/src/LetInBlock.css @@ -1,6 +1,17 @@ - -.keyword { - color: blue; - /* margin: 0 2px 0 2px; */ +.letIn { + display: inline-block; + border: 1px solid darkgrey; } +.decl { + display: grid; + grid-template-columns: auto auto auto auto; +} + +.column.rightAlign { + /* text-align: right; */ +} + +.column { + vertical-align: top; +} diff --git a/src/LetInBlock.tsx b/src/LetInBlock.tsx index abd4850..8af494e 100644 --- a/src/LetInBlock.tsx +++ b/src/LetInBlock.tsx @@ -1,10 +1,9 @@ import { useContext, useEffect, useRef } from "react"; -import { growEnv } from "dope2"; import { Editor, type EditorState } from "./Editor"; import { EnvContext } from "./EnvContext"; -import { evalEditorBlock, evalLetInBlock, scoreResolved, type ResolvedType } from "./eval"; +import { evalEditorBlock, evalLetInBlock, makeInnerEnv, scoreResolved } from "./eval"; import { type State2Props } from "./Editor"; import { autoInputWidth } from "./util/dom_trickery"; @@ -22,40 +21,54 @@ interface LetInBlockProps extends State2Props { suggestionPriority: (suggestion: SuggestionType) => number; } -export function makeInnerEnv(env, name: string, value: ResolvedType) { - - if (value.kind === "value") { - return growEnv(env)(name)(value) - } - return env; +export function LetInBlock({state, setState, suggestionPriority}: LetInBlockProps) { + return +
+ +
+
+ +
+
} -export function LetInBlock({state, setState, suggestionPriority}: LetInBlockProps) { - const {name, value, inner} = state; +function DeclColumns({state, setState, suggestionPriority}) { const env = useContext(EnvContext); - const valueResolved = evalEditorBlock(value, env); - const innerEnv = makeInnerEnv(env, name, valueResolved); - const nameRef = useRef(null); + const {name, value, inner} = state; const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); const setValue = callback => setState(state => ({...state, value: callback(state.value)})); - const onChangeName = (e: React.ChangeEvent) => { setState(state => ({...state, name: e.target.value})); } + const valueSuggestionPriority = (suggestion: SuggestionType) => { + const innerEnv = makeInnerEnv(env, name, suggestion[2]); + const resolved = evalEditorBlock(inner, innerEnv); + return scoreResolved(resolved, suggestionPriority); + }; + const innerSuggestionPriority = suggestionPriority; + + const nameRef = useRef(null); useEffect(() => { nameRef.current?.focus(); }, []); - useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]); - console.log({innerEnv}); - - return -
- let -   + const valueResolved = evalEditorBlock(state.value, env); + const innerEnv = makeInnerEnv(env, state.name, valueResolved); + + return <> + let  + -  =  - { - const innerEnv = makeInnerEnv(env, name, suggestion[2]); - const resolved = evalEditorBlock(inner, innerEnv); - return scoreResolved(resolved, suggestionPriority); - }} - onCancel={() => setState(state => state.inner)} // keep inner - /> - in -
-
- + +  =  + setState(state => state.inner)} // keep inner + /> + + {/* in */} + {inner.kind === "let" && + + { - return suggestionPriority(suggestion) - }} - onCancel={() => setState(state => state.value)} // keep value + suggestionPriority={innerSuggestionPriority} /> -
-
+ } + ; } + +function InnerMost({state, setState, suggestionPriority}) { + const env = useContext(EnvContext); + const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); + const valueResolved = evalEditorBlock(state.value, env); + const innerEnv = makeInnerEnv(env, state.name, valueResolved); + const onCancel = () => setState(state => state.value); + if (state.inner.kind === "let") { + return + + ; + } + else { + return + + + } +} \ No newline at end of file diff --git a/src/Value.css b/src/Value.css index 33a1068..edb375c 100644 --- a/src/Value.css +++ b/src/Value.css @@ -6,4 +6,14 @@ padding-left: 2px; padding-right: 2px; background-color: white; +} + +.valueUUID { + border-radius: 10px; + background-color: lightyellow; + border: 1px solid black; + margin-left: 2px; + margin-right: 2px; + padding-left: 2px; + padding-right: 2px; } \ No newline at end of file diff --git a/src/Value.tsx b/src/Value.tsx index a7e9b6e..cf1b1f7 100644 --- a/src/Value.tsx +++ b/src/Value.tsx @@ -1,6 +1,7 @@ -import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight, dict, Bool, set, Unit} from "dope2"; +import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight, dict, Bool, set, Unit, symbolType, symbolUUID, getHumanReadableName} from "dope2"; import "./Value.css"; +import { Type } from "./Type"; export function Value({dynamic}) { const type = getType(dynamic); @@ -32,7 +33,10 @@ export function Value({dynamic}) { return ; case symbolList: return ; - + case symbolType: + return ; + case symbolUUID: + return default: return <>don't know how to show value; } @@ -74,3 +78,6 @@ function ValueProduct({val, leftType, rightType}) { function ValueUnit() { return <>{'()'}; } +function ValueUUID({val}) { + return {getHumanReadableName(val)}; +} \ No newline at end of file diff --git a/src/configurations.ts b/src/configurations.ts index cc89ab0..eda6120 100644 --- a/src/configurations.ts +++ b/src/configurations.ts @@ -74,3 +74,182 @@ export const tripleFunctionCallEditorState: EditorState = { focus: false, }, }; + +export const biggerExample: EditorState = { + "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 + } + } + } +}; \ No newline at end of file diff --git a/src/eval.ts b/src/eval.ts index af1859d..5039e88 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -1,29 +1,36 @@ -import { apply, assignFn, Double, getSymbol, Int, makeGeneric, NotAFunctionError, prettyT, symbolFunction, trie, UnifyError } from "dope2"; +import { assignFnSubstitutions, Double, fnType, getSymbol, growEnv, Int, makeGeneric, NotAFunctionError, prettyT, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } from "dope2"; import type { EditorState } from "./Editor"; -import type { InputValueType } from "./InputBlock"; -import { makeInnerEnv } from "./LetInBlock"; +import type { InputValueType, SuggestionType } from "./InputBlock"; + +interface Type { + symbol: string; + params: any[]; +}; export interface DeepError { kind: "error"; e: Error; depth: number; - t: any; + t: Type; + substitutions: Map; } // a dynamically typed value = tuple (instance, type) export interface Dynamic { kind: "value", i: any; - t: any; + t: Type; + substitutions: Map; }; export interface Unknown { kind: "unknown"; - t: any; + t: Type; + substitutions: Map; } -export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a) }; +export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a), substitutions: new Map() }; // the value of every block is either known (Dynamic), an error, or unknown export type ResolvedType = Dynamic | DeepError | Unknown; @@ -41,8 +48,8 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => { return evalLetInBlock(s.value, s.name, s.inner, env); } if (s.kind === "lambda") { - const expr = evalEditorBlock(s.expr, env); - // todo + return evalLambdaBlock(s.paramName, s.expr, env); + } return entirelyUnknown; // todo }; @@ -54,7 +61,11 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv else if (value.kind === "name") { const found = trie.get(env.name2dyn)(text); if (found) { - return { kind: "value", ...found }; + return { + kind: found.kind || "value", + ...found, + substitutions: new Map(), + }; } else { return entirelyUnknown; } @@ -64,6 +75,10 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv } } +const mergeMaps = (...maps: Map[]) => { + return new Map(maps.flatMap(m => [...m])); +} + export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedType { if (getSymbol(fn.t) !== symbolFunction) { if (fn.kind === "unknown") { @@ -74,12 +89,15 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy kind: "error", e: new NotAFunctionError(`${prettyT(fn.t)} is not a function type!`), t: entirelyUnknown.t, + substitutions: mergeMaps(fn.substitutions, input.substitutions), depth: 0, }; } try { // fn is a function... - const outType = assignFn(fn.t, input.t); // may throw + const [outType, substitutions] = assignFnSubstitutions(fn.t, input.t); // may throw + + const mergedSubstitutions = mergeMaps(substitutions, fn.substitutions, input.substitutions); if (input.kind === "error") { return { @@ -87,6 +105,7 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy e: input.e, // bubble up the error depth: 0, t: outType, + substitutions: mergedSubstitutions, }; } if (fn.kind === "error") { @@ -96,16 +115,26 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy e: fn.e, depth: fn.depth+1, t: outType, + substitutions: mergedSubstitutions, }; } // if the above statement did not throw => types are compatible... if (input.kind === "value" && fn.kind === "value") { const outValue = fn.i(input.i); - return { kind: "value", i: outValue, t: outType }; + return { + kind: "value", + i: outValue, + t: outType, + substitutions: mergedSubstitutions, + }; } else { // we don't know the value, but we do know the type: - return { kind: "unknown", t: outType }; + return { + kind: "unknown", + t: outType, + substitutions: mergedSubstitutions, + }; } } catch (e) { @@ -117,18 +146,49 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy e, depth: 0, t: outType, + substitutions: mergeMaps(fn.substitutions, input.substitutions), }; } throw e; } } -export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, env) { +export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, env): ResolvedType { const valueResolved = evalEditorBlock(value, env); const innerEnv = makeInnerEnv(env, name, valueResolved) return evalEditorBlock(inner, innerEnv); } +export function evalLambdaBlock(paramName: string, expr: EditorState, env): ResolvedType { + const fn = (x: any) => { + const innerEnv = makeInnerEnv(env, paramName, { + kind: "value", + i: x, + t: TYPE_VARS[0], + substitutions: new Map(), + }); + const result = evalEditorBlock(expr, innerEnv); + if (result.kind === "value") { + return result.i; + } + } + // static env: we only know the name and the type + const staticInnerEnv = makeInnerEnv(env, paramName, { + kind: "unknown", // parameter value is not statically known + t: TYPE_VARS[0], + substitutions: new Map(), + }); + const abstractOutput = evalEditorBlock(expr, staticInnerEnv); + const t = fnType(_ => entirelyUnknown.t)(_ => abstractOutput.t); + const T = substitute(t, abstractOutput.substitutions, []) + return { + kind: "value", + t: T, + i: fn, + substitutions: new Map(), + }; +} + export function haveValue(resolved: ResolvedType) { // return resolved && !(resolved instanceof DeepError); return resolved.kind === "value"; @@ -149,7 +209,12 @@ function parseAsDouble(text: string): ResolvedType { if (text !== '') { const num = Number(text); if (!Number.isNaN(num)) { - return { kind: "value", i: num, t: Double }; + return { + kind: "value", + i: num, + t: Double, + substitutions: new Map(), + }; } } return entirelyUnknown; @@ -157,7 +222,12 @@ function parseAsDouble(text: string): ResolvedType { function parseAsInt(text: string): ResolvedType { if (text !== '') { try { - return { kind: "value", i: BigInt(text), t: Int }; // may throw + return { + kind: "value", + i: BigInt(text), + t: Int, + substitutions: new Map(), + }; // may throw } catch {} } @@ -171,10 +241,14 @@ export function attemptParseLiteral(text: string): Dynamic[] { .filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[]; } -export function scoreResolved(resolved: ResolvedType, outPriority) { +export function scoreResolved(resolved: ResolvedType, outPriority: (s:SuggestionType) => number) { const bias = outPriority(['literal', '', - // @ts-ignore: // TODO fix this - {t: resolved.t}]); + { + // @ts-ignore + kind: "unknown", + t: resolved.t, + substitutions: new Map(), + }]); if (resolved.kind === "value") { return 1 + bias; @@ -188,4 +262,12 @@ export function scoreResolved(resolved: ResolvedType, outPriority) { else { return -2 + bias; // even worse: fn is not a function! } -} \ No newline at end of file +} + +export function makeInnerEnv(env, name: string, value: ResolvedType) { + if (name !== "" && value.kind === "value") { + return growEnv(env)(name)(value); + } + return env; +} +