import { apply, assignFn, getSymbol, getType, NotAFunctionError, symbolFunction, UnifyError } from "dope2"; import { Editor, type EditorState } from "./Editor"; import { Value } from "./Value"; import type { Dynamic, SetStateFn, State2Props } from "./util/extra"; import { useEffect } from "react"; import "./CallBlock.css"; type ResolvedType = Dynamic | Error | undefined; export interface CallBlockState< FnState=EditorState, InputState=EditorState, > { kind: "call"; fn: FnState; input: InputState; resolved: ResolvedType; } interface CallBlockProps< FnState=EditorState, InputState=EditorState, > extends State2Props,EditorState> { } function have(resolved: ResolvedType) { return resolved && !(resolved instanceof Error); } function headlessCallBlock({state, setState}: CallBlockProps) { const {fn, input} = state; const setFn = (callback: SetStateFn) => { setState(state => ({...state, fn: callback(state.fn)})); } const setInput = (callback: SetStateFn) => { setState(state => ({...state, input: callback(state.input)})); } const setResolved = (callback: SetStateFn) => { setState(state => ({...state, resolved: callback(state.resolved)})); } useEffect(() => { // Here we do something spooky: we update the state in response to state change... // The reason this shouldn't give problems is because we update the state in such a way that the changes only 'trickle up', rather than getting stuck in a cycle. if (have(input.resolved) && have(fn.resolved)) { try { const outputResolved = apply(input.resolved)(fn.resolved); // may throw setResolved(() => outputResolved); // success } catch (e) { if (!(e instanceof UnifyError) && !(e instanceof NotAFunctionError)) { throw e; } setResolved(() => e as Error); // eval error } } else if (input.resolved instanceof Error) { setResolved(() => input.resolved); // bubble up the error } else if (fn.resolved instanceof Error) { // @ts-ignore setResolved(() => { // @ts-ignore return Object.assign(fn.resolved, {depth: (fn.resolved.depth || 0) + 1}); }); // bubble up the error } else { // no errors and at least one is undefined: setResolved(() => undefined); // chill out } }, [input.resolved, fn.resolved]); const onFnCancel = () => { setState(state => state.input); // we become our input } const onInputCancel = () => { setState(state => state.fn); // we become our function } return {setFn, setInput, onFnCancel, onInputCancel}; } export function CallBlock({ state, setState }: CallBlockProps) { const {setFn, setInput, onFnCancel, onInputCancel} = headlessCallBlock({ state, setState }); // @ts-ignore console.log('depth:', state.resolved?.depth); return
{/* Sequence of input parameters */} {/* Output (or Error) */} { state.resolved instanceof Error && state.resolved.toString() || state.resolved && <>☑}
; } function filterFnInputs(fn: Dynamic|Error|undefined, input: Dynamic|Error|undefined) { if (!have(fn) || !have(input)) { return false; } const fnType = getType(fn); if (getSymbol(fnType) !== symbolFunction) { return false; // filter out non-functions already } try { assignFn(fnType, getType(input)); // may throw return true; } catch (e) { if (!(e instanceof UnifyError)) { throw e; } return false; } } function FunctionHeader({ fn, setFn, input, onFnCancel }) { 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 // recurse: const { setFn : setFnFn, onFnCancel : onFnFnCancel, } = headlessCallBlock({state: fn, setState: setFn}); return ; } else { // end of recursion - draw function name return  𝑓𝑛  filterFnInputs(fnCandidate, input.resolved)} /> ; } } function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDepth }) { 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 */} filterFnInputs(fn.resolved, inputCandidate)} />
; } function NestedParams({fn, setFn, depth, errorDepth}) { const { setFn : setFnFn, setInput : setFnInput, } = headlessCallBlock({state: fn, setState: setFn}); return {/*todo*/}} depth={depth+1} errorDepth={errorDepth} />; }