From ea8c015eff5d92253343e2f6bfcbb37f5aef25b6 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Thu, 15 May 2025 10:09:22 +0200 Subject: [PATCH] when sorting suggestions, not only function and input type are matched, but also output type (=input of surrounding function) --- src/App.tsx | 2 +- src/CallBlock.tsx | 150 ++++++++++++++++++++++++++++++++++----------- src/Editor.tsx | 8 +-- src/InputBlock.tsx | 23 ++++--- src/LetInBlock.tsx | 4 +- 5 files changed, 132 insertions(+), 55 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 87bef0e..ee63d66 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -154,7 +154,7 @@ export function App() { state={appState.history.at(-1)!} setState={pushHistory} onCancel={() => {}} - filter={() => true} + suggestionPriority={() => 0} /> diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index f032d22..aaf757a 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -3,11 +3,12 @@ import { useContext } from "react"; import { Editor, type EditorState } from "./Editor"; import { Value } from "./Value"; import { type SetStateFn, type State2Props } from "./Editor"; -import { DeepError, evalCallBlock, evalEditorBlock } from "./eval"; +import { DeepError, evalCallBlock, evalEditorBlock, haveValue } from "./eval"; import { type ResolvedType } from "./eval"; import "./CallBlock.css"; import { EnvContext } from "./EnvContext"; import type { SuggestionType } from "./InputBlock"; +import { getType, NotAFunctionError, unify, UnifyError } from "dope2"; export interface CallBlockState< FnState=EditorState, @@ -22,7 +23,7 @@ interface CallBlockProps< FnState=EditorState, InputState=EditorState, > extends State2Props,EditorState> { - filter: (suggestion: SuggestionType) => boolean; + suggestionPriority: (suggestion: SuggestionType) => number; } function headlessCallBlock(setState: (callback: SetStateFn) => void) { @@ -41,7 +42,7 @@ function headlessCallBlock(setState: (callback: SetStateFn + input={state.input} + suggestionPriority={suggestionPriority} + />
{/* Sequence of input parameters */} @@ -61,6 +64,7 @@ export function CallBlock({ state, setState, filter }: CallBlockProps) { onInputCancel={onInputCancel} depth={0} errorDepth={resolved instanceof DeepError ? (resolved.depth) : -1} + suggestionPriority={suggestionPriority} /> {/* Output (or Error) */} { resolved instanceof DeepError && resolved.e.toString() @@ -71,12 +75,17 @@ export function CallBlock({ state, setState, filter }: CallBlockProps) { ; } -function filterFnInputs(fn: ResolvedType, input: ResolvedType) { +function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: SuggestionType) => number) { const resolved = evalCallBlock(fn, input); - return (resolved && !(resolved instanceof DeepError)); + if ((resolved && !(resolved instanceof DeepError))) { + // The computed output becomes an input for the surrounding block, which may also have a priority function defined, for instance our output is the input to another function + return 1 + outPriority(['literal', '', resolved]); + } + return 0; // no fit } -function FunctionHeader({ fn, setFn, input, onFnCancel }) { +function FunctionHeader({ fn, setFn, input, onFnCancel, suggestionPriority }) { + const env = useContext(EnvContext); if (fn.kind === "call") { // if the function we're calling is itself the result of a function call, // then we are anonymous, and so we don't draw a function name @@ -91,10 +100,15 @@ function FunctionHeader({ fn, setFn, input, onFnCancel }) { fn={fn.fn} setFn={setFnFn} onFnCancel={onFnFnCancel} - input={fn.input} />; + input={fn.input} + suggestionPriority={(fnSuggestion: SuggestionType) => computePriority( + fnSuggestion[2], + evalEditorBlock(fn.input, env), + suggestionPriority, + )} + />; } else { - const env = useContext(EnvContext); // end of recursion - draw function name return  𝑓𝑛  @@ -102,43 +116,107 @@ function FunctionHeader({ fn, setFn, input, onFnCancel }) { state={fn} setState={setFn} onCancel={onFnCancel} - filter={(fnSuggestion: SuggestionType) => filterFnInputs(fnSuggestion[2], evalEditorBlock(input, env))} /> + suggestionPriority={ + (fnSuggestion: SuggestionType) => computePriority( + fnSuggestion[2], // suggestions will be for function + evalEditorBlock(input, env), // input *may* be set + suggestionPriority, // priority function we get from parent block + )} + /> ; } } -function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDepth }) { +function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDepth, suggestionPriority }) { const env = useContext(EnvContext); - return
- {(fn.kind === "call") && - // if the function we're calling is itself the result of a function call, - // then we render its input parameter nested in our own input parameter box, which is way more readable - - // recurse: - - } - {/* Our own input */} + let nestedParams; + if (fn.kind === "call") { + // Nest input of nested function + const { + setFn : setFnFn, + setInput : setFnInput, + } = headlessCallBlock(setFn); + nestedParams = {/*todo*/}} + depth={depth+1} + errorDepth={errorDepth} + suggestionPriority={ + (inputSuggestion: SuggestionType) => computePriority( + evalEditorBlock(fn.fn, env), + inputSuggestion[2], + suggestionPriority, + )} + />; + } + else { + nestedParams = <>; + } + const isOffending = depth === errorDepth; + return
+ {nestedParams} filterFnInputs(evalEditorBlock(fn, env), inputSuggestion[2])} + suggestionPriority={ + (inputSuggestion: SuggestionType) => computePriority( + evalEditorBlock(fn, env), // fn *may* be set + inputSuggestion[2], // suggestions will be for input + suggestionPriority, // priority function we get from parent block + )} />
; + // {(fn.kind === "call") && + // // if the function we're calling is itself the result of a function call, + // // then we render its input parameter nested in our own input parameter box, which is way more readable + + // // recurse: + // + // } + // {/* Our own input */} + // computePriority( + // evalEditorBlock(fn, env), // fn *may* be set + // inputSuggestion[2], // suggestions will be for input + // suggestionPriority, // priority function we get from parent block + // )} + // /> + //
; } -function NestedParams({fn, setFn, depth, errorDepth}) { - const { - setFn : setFnFn, - setInput : setFnInput, - } = headlessCallBlock(setFn); - return {/*todo*/}} - depth={depth+1} - errorDepth={errorDepth} - />; -} \ No newline at end of file +// function NestedParams({fn, setFn, depth, errorDepth, suggestionPriority}) { +// const env = useContext(EnvContext); +// const { +// setFn : setFnFn, +// setInput : setFnInput, +// } = headlessCallBlock(setFn); +// return {/*todo*/}} +// depth={depth+1} +// errorDepth={errorDepth} +// suggestionPriority={ +// (inputSuggestion: SuggestionType) => computePriority( +// evalEditorBlock(fn.fn, env), // fn *may* be set +// inputSuggestion[2], // suggestions will be for input +// suggestionPriority, // priority function we get from parent block +// )} +// />; +// } \ No newline at end of file diff --git a/src/Editor.tsx b/src/Editor.tsx index 57dbd25..f3e4d65 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -28,7 +28,7 @@ export interface State2Props { } interface EditorProps extends State2Props { - filter: (suggestion: SuggestionType) => boolean; + suggestionPriority: (suggestion: SuggestionType) => number; onCancel: () => void; } @@ -59,7 +59,7 @@ function removeFocus(state: EditorState): EditorState { return state; } -export function Editor({state, setState, onCancel, filter}: EditorProps) { +export function Editor({state, setState, onCancel, suggestionPriority}: EditorProps) { const env = useContext(EnvContext); const [needCommand, setNeedCommand] = useState(false); const commandInputRef = useRef(null); @@ -144,14 +144,14 @@ export function Editor({state, setState, onCancel, filter}: EditorProps) { return EditorState)=>void} - filter={filter} + suggestionPriority={suggestionPriority} onCancel={onCancel} />; case "call": return EditorState)=>void} - filter={filter} + suggestionPriority={suggestionPriority} />; case "let": return { - filter: (suggestion: SuggestionType) => boolean; + suggestionPriority: (suggestion: SuggestionType) => number; onCancel: () => void; } -const computeSuggestions = (text, env, filter): SuggestionType[] => { +const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) => number): PrioritizedSuggestionType[] => { const asDouble = parseDouble(text); const asInt = parseInt(text); @@ -46,14 +47,12 @@ const computeSuggestions = (text, env, filter): SuggestionType[] => { ... trie.suggest(env.name2dyn)(text)(Infinity).map(([name,type]) => ["name", name, type]), ] // return ls; - return [ - ...ls.filter(filter), // ones that match filter come first - ...ls.filter(s => !filter(s)), - ] - // .slice(0,30); + return ls + .map(suggestion => [suggestionPriority(suggestion), ...suggestion] as PrioritizedSuggestionType) + .sort(([priorityA], [priorityB]) => priorityB - priorityA) } -export function InputBlock({ state, setState, filter, onCancel }: InputBlockProps) { +export function InputBlock({ state, setState, suggestionPriority, onCancel }: InputBlockProps) { const {text, focus} = state; const env = useContext(EnvContext); const inputRef = useRef(null); @@ -61,7 +60,7 @@ export function InputBlock({ state, setState, filter, onCancel }: InputBlockProp const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not const singleSuggestion = trie.growPrefix(env.name2dyn)(text); - const suggestions = useMemo(() => computeSuggestions(text, env, filter), [text]); + const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text]); useEffect(() => autoInputWidth(inputRef, text), [inputRef, text]); @@ -146,7 +145,7 @@ export function InputBlock({ state, setState, filter, onCancel }: InputBlockProp onTextChange(e.target.value); }; - const onSelectSuggestion = ([kind, name, dynamic]: SuggestionType) => { + const onSelectSuggestion = ([priority, kind, name, dynamic]: PrioritizedSuggestionType) => { if (kind === "literal") { setState(state => ({ ...state, @@ -200,13 +199,13 @@ function Suggestions({ suggestions, onSelect, i, setI }) { }; return <>{(suggestions.length > 0) &&
- {suggestions.map(([kind, name, dynamic], j) => + {suggestions.map(([priority, kind, name, dynamic], j) =>
- ({kind}) {name} :: + ({priority}) ({kind}) {name} ::
)}
}; diff --git a/src/LetInBlock.tsx b/src/LetInBlock.tsx index 7b8cd55..aac23f1 100644 --- a/src/LetInBlock.tsx +++ b/src/LetInBlock.tsx @@ -62,7 +62,7 @@ export function LetInBlock({state, setState}: LetInBlockProps) { true} + suggestionPriority={() => 0} onCancel={() => {}} />  in @@ -72,7 +72,7 @@ export function LetInBlock({state, setState}: LetInBlockProps) { true} + suggestionPriority={() => 0} onCancel={() => {}} />