greatly simplify state + cleanup code
This commit is contained in:
parent
2d0deca127
commit
5964510036
11 changed files with 268 additions and 321 deletions
|
|
@ -1,12 +1,13 @@
|
|||
import { apply, assignFn, getSymbol, getType, NotAFunctionError, symbolFunction, UnifyError } from "dope2";
|
||||
import { useContext } from "react";
|
||||
|
||||
import { Editor, type EditorState } from "./Editor";
|
||||
import { Value } from "./Value";
|
||||
import { DeepError, type ResolvedType, type SetStateFn, type State2Props } from "./types";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { type SetStateFn, type State2Props } from "./Editor";
|
||||
import { DeepError, evalCallBlock, evalEditorBlock } from "./eval";
|
||||
import { type ResolvedType } from "./eval";
|
||||
import "./CallBlock.css";
|
||||
import { useEffectBetter } from "./util/use_effect_better";
|
||||
import { EnvContext } from "./EnvContext";
|
||||
import type { SuggestionType } from "./InputBlock";
|
||||
|
||||
export interface CallBlockState<
|
||||
FnState=EditorState,
|
||||
|
|
@ -15,7 +16,6 @@ export interface CallBlockState<
|
|||
kind: "call";
|
||||
fn: FnState;
|
||||
input: InputState;
|
||||
resolved: ResolvedType;
|
||||
}
|
||||
|
||||
interface CallBlockProps<
|
||||
|
|
@ -24,49 +24,13 @@ interface CallBlockProps<
|
|||
> extends State2Props<CallBlockState<FnState,InputState>,EditorState> {
|
||||
}
|
||||
|
||||
export function resolveCallBlock(fn: ResolvedType, input: ResolvedType) {
|
||||
if (have(input) && have(fn)) {
|
||||
try {
|
||||
const outputResolved = apply(input)(fn); // may throw
|
||||
return outputResolved; // success
|
||||
}
|
||||
catch (e) {
|
||||
if (!(e instanceof UnifyError) && !(e instanceof NotAFunctionError)) {
|
||||
throw e;
|
||||
}
|
||||
return new DeepError(e, 0); // eval error
|
||||
}
|
||||
}
|
||||
else if (input instanceof DeepError) {
|
||||
return input; // bubble up the error
|
||||
}
|
||||
else if (fn instanceof DeepError) {
|
||||
return new DeepError(fn.e, fn.depth+1);
|
||||
}
|
||||
}
|
||||
|
||||
function have(resolved: ResolvedType) {
|
||||
return resolved && !(resolved instanceof DeepError);
|
||||
}
|
||||
|
||||
function headlessCallBlock({state, setState}: CallBlockProps) {
|
||||
const {fn, input} = state;
|
||||
function headlessCallBlock({setState}: CallBlockProps) {
|
||||
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<ResolvedType>) => {
|
||||
setState(state => ({...state, resolved: callback(state.resolved)}));
|
||||
}
|
||||
|
||||
useEffectBetter(() => {
|
||||
// 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.
|
||||
setResolved(() => resolveCallBlock(fn.resolved, input.resolved));
|
||||
}, [fn.resolved, input.resolved]);
|
||||
|
||||
const onFnCancel = () => {
|
||||
setState(state => state.input); // we become our input
|
||||
}
|
||||
|
|
@ -78,8 +42,10 @@ function headlessCallBlock({state, setState}: CallBlockProps) {
|
|||
|
||||
export function CallBlock({ state, setState }: CallBlockProps) {
|
||||
const {setFn, setInput, onFnCancel, onInputCancel}
|
||||
= headlessCallBlock({ state, setState });
|
||||
return <span className={"functionBlock" + ((state.resolved instanceof DeepError) ? " unifyError" : "")}>
|
||||
= headlessCallBlock({ state, setState });
|
||||
const env = useContext(EnvContext);
|
||||
const resolved = evalEditorBlock(state, env);
|
||||
return <span className={"functionBlock" + ((resolved instanceof DeepError) ? " unifyError" : "")}>
|
||||
<FunctionHeader
|
||||
fn={state.fn}
|
||||
setFn={setFn}
|
||||
|
|
@ -93,12 +59,11 @@ export function CallBlock({ state, setState }: CallBlockProps) {
|
|||
input={state.input} setInput={setInput}
|
||||
onInputCancel={onInputCancel}
|
||||
depth={0}
|
||||
errorDepth={state.resolved instanceof DeepError ? (state.resolved.depth) : -1}
|
||||
errorDepth={resolved instanceof DeepError ? (resolved.depth) : -1}
|
||||
/>
|
||||
{/* Output (or Error) */}
|
||||
{ state.resolved instanceof DeepError && state.resolved.e.toString()
|
||||
|| state.resolved && <><Value dynamic={state.resolved} />
|
||||
{/* ☑ */}
|
||||
{ resolved instanceof DeepError && resolved.e.toString()
|
||||
|| resolved && <><Value dynamic={resolved} />
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -106,22 +71,8 @@ export function CallBlock({ state, setState }: CallBlockProps) {
|
|||
}
|
||||
|
||||
function filterFnInputs(fn: ResolvedType, input: ResolvedType) {
|
||||
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;
|
||||
}
|
||||
const resolved = evalCallBlock(fn, input);
|
||||
return (resolved && !(resolved instanceof Error));
|
||||
}
|
||||
|
||||
function FunctionHeader({ fn, setFn, input, onFnCancel }) {
|
||||
|
|
@ -142,6 +93,7 @@ function FunctionHeader({ fn, setFn, input, onFnCancel }) {
|
|||
input={fn.input} />;
|
||||
}
|
||||
else {
|
||||
const env = useContext(EnvContext);
|
||||
// end of recursion - draw function name
|
||||
return <span className="functionName">
|
||||
𝑓𝑛
|
||||
|
|
@ -149,12 +101,13 @@ function FunctionHeader({ fn, setFn, input, onFnCancel }) {
|
|||
state={fn}
|
||||
setState={setFn}
|
||||
onCancel={onFnCancel}
|
||||
filter={([_, fnCandidate]) => filterFnInputs(fnCandidate, input.resolved)} />
|
||||
filter={(fnSuggestion: SuggestionType) => filterFnInputs(fnSuggestion[2], evalEditorBlock(input, env))} />
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
|
||||
function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDepth }) {
|
||||
const env = useContext(EnvContext);
|
||||
return <div className={"inputParam" + (depth === errorDepth ? " offending" : "")}>
|
||||
{(fn.kind === "call") &&
|
||||
// if the function we're calling is itself the result of a function call,
|
||||
|
|
@ -168,7 +121,7 @@ function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDe
|
|||
state={input}
|
||||
setState={setInput}
|
||||
onCancel={onInputCancel}
|
||||
filter={([_, inputCandidate]) => filterFnInputs(fn.resolved, inputCandidate)}
|
||||
filter={(inputSuggestion: SuggestionType) => filterFnInputs(evalEditorBlock(fn, env), inputSuggestion[2])}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue