diff --git a/src/App.css b/src/App.css index 75075ee..f4787b2 100644 --- a/src/App.css +++ b/src/App.css @@ -57,6 +57,6 @@ footer a { } .factoryReset { - background-color: red; + background-color: rgb(255, 0, 0); color: black; } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 3b8be84..58ba6a2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,10 @@ import { useEffect, useState } from 'react'; -import './App.css' -import { Editor, type EditorState } from './Editor' -import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; +import './App.css'; import { CommandContext } from './CommandContext'; -import { deserialize, serialize } from './types'; +import { Editor, type EditorState } from './Editor'; import { extendedEnv } from './EnvContext'; -import { useEffectBetter } from './util/use_effect_better'; +import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; +import { evalEditorBlock } from "./eval"; const commands: [string, string[], string][] = [ ["call" , ['c' ], "call" ], @@ -19,54 +18,77 @@ const examples: [string, EditorState][] = [ ["push to list", nonEmptyEditorState], ["function w/ 4 params", tripleFunctionCallEditorState]]; +type AppState = { + history: EditorState[], + future: EditorState[], +} + +const defaultState = { + history: [initialEditorState], + future: [], +}; + +function loadFromLocalStorage(): AppState { + if (localStorage["appState"]) { + try { + const appState = JSON.parse(localStorage["appState"]); // may throw + // if our state is corrupt, discover it eagerly: + evalEditorBlock(appState.history.at(-1), extendedEnv); + + return appState; // all good + } + catch (e) { + console.log('error recovering state from localStorage (resetting):', e); + } + } + return defaultState; +} + export function App() { - // const [history, setHistory] = useState([initialEditorState]); - // const [history, setHistory] = useState([nonEmptyEditorState]); - // const [history, setHistory] = useState([tripleFunctionCallEditorState]); - // const [future, setFuture] = useState([]); - // load from localStorage - const [history, setHistory] = useState( - localStorage["history"] - ? JSON.parse(localStorage["history"]).map(s => deserialize(s, extendedEnv)) - : [initialEditorState] - ); - const [future, setFuture] = useState( - localStorage["future"] - ? JSON.parse(localStorage["future"]).map(s => deserialize(s, extendedEnv)) - : [] - ); + const [appState, setAppState] = useState(loadFromLocalStorage()); - useEffectBetter(() => { + useEffect(() => { // persist accross reloads - localStorage["history"] = JSON.stringify(history.map(serialize)); - localStorage["future"] = JSON.stringify(future.map(serialize)); - }, [history, future]); + localStorage["appState"] = JSON.stringify(appState); + }, [appState]); + + const factoryReset = () => { + setAppState(_ => defaultState); + } const pushHistory = (callback: (p: EditorState) => EditorState) => { - const newState = callback(history.at(-1)!); - setHistory(history.concat([newState])); - setFuture([]); + setAppState(({history}) => { + const newState = callback(history.at(-1)!); + return { + history: history.concat([newState]), + future: [], + }; + }); }; const onUndo = () => { - setFuture(future.concat(history.at(-1)!)); // add - setHistory(history.slice(0,-1)); // remove + setAppState(({history, future}) => ({ + history: history.slice(0,-1), + future: future.concat(history.at(-1)!), + })); }; const onRedo = () => { - setHistory(history.concat(future.at(-1)!)); // add - setFuture(future.slice(0,-1)); // remove + setAppState(({history, future}) => ({ + history: history.concat(future.at(-1)!), + future: future.slice(0,-1), + })); }; const onKeyDown = (e) => { if (e.key === "Z" && e.ctrlKey) { if (e.shiftKey) { - if (future.length > 0) { + if (appState.future.length > 0) { onRedo(); } } else { - if (history.length > 1) { + if (appState.history.length > 1) { onUndo(); } } @@ -75,8 +97,8 @@ export function App() { }; useEffect(() => { - window['APP_STATE'] = history; // useful for debugging - }, [history]); + window['APP_STATE'] = appState.history; // useful for debugging + }, [appState.history]); useEffect(() => { window.onkeydown = onKeyDown; @@ -95,14 +117,17 @@ export function App() { const onSelectExample = (e: React.SyntheticEvent) => { // @ts-ignore - pushHistory(_ => examples[e.target.value][1]); + if (e.target.value) { + // @ts-ignore + pushHistory(_ => examples[e.target.value][1]); + } } return ( <>
- - + + { commands.map(([_, keys, descr], i) => @@ -111,22 +136,22 @@ export function App() { ) } - +
{}} filter={() => true} diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index 09da3b4..2f7e75c 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -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,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) => { - 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 + = headlessCallBlock({ state, setState }); + const env = useContext(EnvContext); + const resolved = evalEditorBlock(state, env); + return {/* Output (or Error) */} - { state.resolved instanceof DeepError && state.resolved.e.toString() - || state.resolved && <> - {/* ☑ */} + { resolved instanceof DeepError && resolved.e.toString() + || resolved && <> } @@ -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  𝑓𝑛  @@ -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))} /> ; } } function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDepth }) { + const env = useContext(EnvContext); return
{(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])} />
; } diff --git a/src/Editor.tsx b/src/Editor.tsx index beafac5..6f54503 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -1,17 +1,18 @@ +import { useContext, useEffect, useRef, useState } from "react"; + import { getSymbol, getType, symbolFunction } from "dope2"; -import { useContext, useEffect, useReducer, useRef, useState } from "react"; import { CallBlock, type CallBlockState } from "./CallBlock"; -import { InputBlock, type InputBlockState } from "./InputBlock"; +import { InputBlock, type InputBlockState, type SuggestionType } from "./InputBlock"; import { Type } from "./Type"; -import { DeepError, type Dynamic, type State2Props } from "./types"; - -import "./Editor.css"; -import { LetInBlock, type LetInBlockState } from "./LetInBlock"; -import { focusNextElement, focusPrevElement } from "./util/dom_trickery"; -import type { LambdaBlockState } from "./LambdaBlock"; -import { initialEditorState } from "./configurations"; +import { DeepError, evalEditorBlock } from "./eval"; import { CommandContext } from "./CommandContext"; +import "./Editor.css"; +import { EnvContext } from "./EnvContext"; +import type { LambdaBlockState } from "./LambdaBlock"; +import { LetInBlock, type LetInBlockState } from "./LetInBlock"; +import { initialEditorState } from "./configurations"; +import { focusNextElement, focusPrevElement } from "./util/dom_trickery"; export type EditorState = InputBlockState @@ -19,8 +20,15 @@ export type EditorState = | LetInBlockState | LambdaBlockState; +export type SetStateFn = (state: InType) => OutType; + +export interface State2Props { + state: InType; + setState: (callback: SetStateFn) => void; +} + interface EditorProps extends State2Props { - filter: (suggestion: [string, Dynamic]) => boolean; + filter: (suggestion: SuggestionType) => boolean; onCancel: () => void; } @@ -52,6 +60,7 @@ function removeFocus(state: EditorState): EditorState { } export function Editor({state, setState, onCancel, filter}: EditorProps) { + const env = useContext(EnvContext); const [needCommand, setNeedCommand] = useState(false); const commandInputRef = useRef(null); useEffect(() => { @@ -59,21 +68,6 @@ export function Editor({state, setState, onCancel, filter}: EditorProps) { commandInputRef.current?.focus(); } }, [needCommand]); - // const onMyResolve = (editorState: EditorState) => { - // setState(editorState); - // onResolve(editorState); - // return; - - - // if (editorState.resolved) { - // setNeedCommand(true); - // } - // else { - // // unresolved - // setNeedCommand(false); - // onResolve(editorState); // pass up the fact that we're unresolved - // } - // } const globalContext = useContext(CommandContext); const onCommand = (e: React.KeyboardEvent) => { @@ -167,12 +161,13 @@ export function Editor({state, setState, onCancel, filter}: EditorProps) { return <>; } } + const resolved = evalEditorBlock(state, env); return <> {renderBlock()} { - (state.resolved && !(state.resolved instanceof DeepError)) + (resolved && !(resolved instanceof DeepError)) ?
- :: + ::
: <> } diff --git a/src/InputBlock.tsx b/src/InputBlock.tsx index 6cdd29b..1fac000 100644 --- a/src/InputBlock.tsx +++ b/src/InputBlock.tsx @@ -1,34 +1,49 @@ -import { Double, getType, Int, newDynamic, trie } from "dope2"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; + +import { Double, getType, Int, newDynamic, prettyT, trie } from "dope2"; + +import { EnvContext } from "./EnvContext"; +import type { Dynamic } from "./eval"; +import "./InputBlock.css"; +import { Type } from "./Type"; +import type { State2Props } from "./Editor"; import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery"; import { parseDouble, parseInt } from "./util/parse"; -import { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { Type } from "./Type"; - -import "./InputBlock.css"; -import type { Dynamic, State2Props } from "./types"; -import { EnvContext } from "./EnvContext"; +interface Literal { + kind: "literal"; + type: string; // todo: store (and serialize) real type +}; +interface Name { + kind: "name"; +} +interface Text { + kind: "text"; +} +export type InputValueType = Literal | Name | Text; export interface InputBlockState { kind: "input"; text: string; - resolved: undefined | Dynamic; + value: InputValueType; focus: boolean } +export type SuggestionType = ['literal'|'name', string, Dynamic]; + interface InputBlockProps extends State2Props { - filter: (suggestion: [string, Dynamic]) => boolean; + filter: (suggestion: SuggestionType) => boolean; onCancel: () => void; } -const computeSuggestions = (text, env, filter) => { +const computeSuggestions = (text, env, filter): SuggestionType[] => { const asDouble = parseDouble(text); const asInt = parseInt(text); const ls = [ - ... (asDouble ? [[asDouble.toString(), newDynamic(asDouble)(Double)]] : []), - ... (asInt ? [[asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []), - ... trie.suggest(env.name2dyn)(text)(Infinity), + ... (asDouble ? [["literal", asDouble.toString(), newDynamic(asDouble)(Double)]] : []), + ... (asInt ? [["literal", asInt.toString(), newDynamic(BigInt(asInt))(Int)]] : []), + ... trie.suggest(env.name2dyn)(text)(Infinity).map(([name,type]) => ["name", name, type]), ] // return ls; return [ @@ -39,7 +54,7 @@ const computeSuggestions = (text, env, filter) => { } export function InputBlock({ state, setState, filter, onCancel }: InputBlockProps) { - const {text, resolved, focus} = state; + const {text, focus} = state; const env = useContext(EnvContext); const inputRef = useRef(null); const [i, setI] = useState(0); // selected suggestion idx @@ -68,7 +83,7 @@ export function InputBlock({ state, setState, filter, onCancel }: InputBlockProp const onTextChange = newText => { const found = trie.get(env.name2dyn)(newText); - setState(state => ({...state, text: newText, resolved: found})); + setState(state => ({...state, text: newText})); } // fired before onInput @@ -131,8 +146,21 @@ export function InputBlock({ state, setState, filter, onCancel }: InputBlockProp onTextChange(e.target.value); }; - const onSelectSuggestion = ([name, dynamic]) => { - setState(state => ({...state, text: name, resolved: dynamic})); + const onSelectSuggestion = ([kind, name, dynamic]: SuggestionType) => { + if (kind === "literal") { + setState(state => ({ + ...state, + text: name, + value: {kind, type: prettyT(getType(dynamic))}, + })); + } + else { + setState(state => ({ + ...state, + text: name, + value: {kind}, + })) + } }; return @@ -158,7 +186,6 @@ export function InputBlock({ state, setState, filter, onCancel }: InputBlockProp spellCheck={false}/> {/* Single 'grey' suggestion */} {singleSuggestion} - {/* { resolved && <>☑} */}
; } @@ -173,13 +200,13 @@ function Suggestions({ suggestions, onSelect, i, setI }) { }; return <>{(suggestions.length > 0) &&
- {suggestions.map(([name, dynamic], j) => + {suggestions.map(([kind, name, dynamic], j) =>
- {name} :: + ({kind}) {name} ::
)}
}; diff --git a/src/LambdaBlock.tsx b/src/LambdaBlock.tsx index 2b5e2f2..c742c26 100644 --- a/src/LambdaBlock.tsx +++ b/src/LambdaBlock.tsx @@ -1,5 +1,6 @@ import type { EditorState } from "./Editor"; -import type { Dynamic, ResolvedType } from "./types"; +import type { Dynamic } from "./eval"; +import type { ResolvedType } from "./eval"; export interface LambdaBlockState { diff --git a/src/LetInBlock.tsx b/src/LetInBlock.tsx index 63cb79f..7b8cd55 100644 --- a/src/LetInBlock.tsx +++ b/src/LetInBlock.tsx @@ -1,19 +1,20 @@ import { useContext, useEffect, useRef } from "react"; + +import { growEnv } from "dope2"; + import { Editor, type EditorState } from "./Editor"; import { EnvContext } from "./EnvContext"; -import { DeepError, type ResolvedType, type State2Props } from "./types"; -import { growEnv } from "dope2"; +import { DeepError, evalEditorBlock, type ResolvedType } from "./eval"; +import { type State2Props } from "./Editor"; import { autoInputWidth } from "./util/dom_trickery"; import "./LetInBlock.css"; -import { useEffectBetter } from "./util/use_effect_better"; export interface LetInBlockState { kind: "let"; name: string; value: EditorState; inner: EditorState; - resolved: ResolvedType; } interface LetInBlockProps extends State2Props { @@ -29,7 +30,8 @@ export function makeInnerEnv(env, name: string, value: ResolvedType) { export function LetInBlock({state, setState}: LetInBlockProps) { const {name, value, inner} = state; const env = useContext(EnvContext); - const innerEnv = makeInnerEnv(env, name, value.resolved); + const valueResolved = evalEditorBlock(value, env); + const innerEnv = makeInnerEnv(env, name, valueResolved); const nameRef = useRef(null); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); @@ -43,11 +45,6 @@ export function LetInBlock({state, setState}: LetInBlockProps) { nameRef.current?.focus(); }, []); - useEffectBetter(() => { - // bubble up - setState(state => ({...state, resolved: inner.resolved})); - }, [inner.resolved]) - useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]); diff --git a/src/configurations.ts b/src/configurations.ts index aee1221..cc89ab0 100644 --- a/src/configurations.ts +++ b/src/configurations.ts @@ -1,18 +1,12 @@ -import { apply, Int, trie } from "dope2"; import type { EditorState } from "./Editor"; -import { extendedEnv } from "./EnvContext"; export const initialEditorState: EditorState = { kind: "input", text: "", - resolved: undefined, + value: { kind: "text" }, focus: true, }; -const listPush = trie.get(extendedEnv.name2dyn)("list.push"); -const listEmptyList = trie.get(extendedEnv.name2dyn)("list.emptyList"); -const fourtyTwo = { i: 42n, t: Int }; - export const nonEmptyEditorState: EditorState = { kind: "call", fn: { @@ -20,33 +14,24 @@ export const nonEmptyEditorState: EditorState = { fn: { kind: "input", text: "list.push", - resolved: listPush, + value: { kind: "name" }, focus: false, }, input: { kind: "input", text: "list.emptyList", - resolved: listEmptyList, + value: { kind: "name" }, focus: false, }, - resolved: apply(listEmptyList)(listPush), - // focus: , }, input: { kind: "input", text: "42", - resolved: fourtyTwo, + value: { kind: "literal", type: "Int" }, focus: false, }, - // resolved: apply(fourtyTwo)(apply(listEmptyList)(listPush)), - resolved: undefined, }; -const functionWith4Params = trie.get(extendedEnv.name2dyn)("functionWith4Params"); -const fourtyThree = { i: 43n, t: Int }; -const fourtyFour = { i: 44n, t: Int }; -const fourtyFive = { i: 45n, t: Int }; - export const tripleFunctionCallEditorState: EditorState = { kind: "call", fn: { @@ -58,38 +43,34 @@ export const tripleFunctionCallEditorState: EditorState = { fn: { kind: "input", text: "functionWith4Params", - resolved: functionWith4Params, + value: { kind: "name" }, focus: false, }, input: { kind: "input", text: "42", - resolved: fourtyTwo, + value: { kind: "literal", type: "Int" }, focus: false, }, - resolved: apply(fourtyTwo)(functionWith4Params), }, input: { kind: "input", text: "43", - resolved: fourtyThree, + value: { kind: "literal", type: "Int" }, focus: false, }, - resolved: apply(fourtyThree)(apply(fourtyTwo)(functionWith4Params)), }, input: { kind: "input", text: "44", - resolved: fourtyFour, + value: { kind: "literal", type: "Int" }, focus: false, }, - resolved: apply(fourtyFour)(apply(fourtyThree)(apply(fourtyTwo)(functionWith4Params))), }, input: { kind: "input", text: "45", - resolved: fourtyFive, + value: { kind: "literal", type: "Int" }, focus: false, }, - resolved: apply(fourtyFive)(apply(fourtyFour)(apply(fourtyThree)(apply(fourtyTwo)(functionWith4Params)))), }; diff --git a/src/eval.ts b/src/eval.ts new file mode 100644 index 0000000..9c90e6f --- /dev/null +++ b/src/eval.ts @@ -0,0 +1,92 @@ +import { apply, Double, Int, NotAFunctionError, trie, UnifyError } from "dope2"; + +import type { EditorState } from "./Editor"; +import type { InputValueType } from "./InputBlock"; +import { makeInnerEnv } from "./LetInBlock"; +import { parseDouble, parseInt } from "./util/parse"; + +export class DeepError { + e: Error; + depth: number; + constructor(e, depth) { + this.e = e; + this.depth = depth; + } +}; + +// a dynamically typed value = tuple (instance, type) +export interface Dynamic { + i: any; + t: any; +}; + +// the value of every block is either known (Dynamic), an error, or unknown +export type ResolvedType = Dynamic | DeepError | undefined; + +export const evalEditorBlock = (s: EditorState, env): ResolvedType => { + if (s.kind === "input") { + return evalInputBlock(s.text, s.value, env); + } + if (s.kind === "call") { + const fn = evalEditorBlock(s.fn, env); + const input = evalEditorBlock(s.input, env); + return evalCallBlock(fn, input); + } + if (s.kind === "let") { + const value = evalEditorBlock(s.value, env); + const innerEnv = makeInnerEnv(env, s.name, value) + return evalEditorBlock(s.inner, innerEnv); + } + if (s.kind === "lambda") { + const expr = evalEditorBlock(s.expr, env); + return undefined; // todo + } +}; + +export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType { + if (value.kind === "literal") { + return parseLiteral(text, value.type); + } + else if (value.kind === "name") { + return trie.get(env.name2dyn)(text); + } + else { // kind === "text" -> unresolved + return; + } +} + +export function evalCallBlock(fn: ResolvedType, input: ResolvedType) { + if (haveValue(input) && haveValue(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 parseLiteral(text: string, type: string) { + // dirty + if (type === "Int") { + return { i: parseInt(text), t: Int }; + } + if (type === "Double") { + return { i: parseDouble(text), t: Double }; + } +} + +export function haveValue(resolved: ResolvedType) { + return resolved && !(resolved instanceof DeepError); +} + diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index e5809dc..0000000 --- a/src/types.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Double, getDefaultTypeParser, Int, prettyT, trie } from "dope2"; -import type { EditorState } from "./Editor"; -import { resolveCallBlock } from "./CallBlock"; -import { makeInnerEnv } from "./LetInBlock"; -import { parseDouble, parseInt } from "./util/parse"; - -export interface Dynamic { - i: any; - t: any; -} - -export type SetStateFn = (state: InType) => OutType; - -export interface State2Props { - state: InType; - setState: (callback: SetStateFn) => void; -} - -export class DeepError { - e: Error; - depth: number; - constructor(e, depth) { - this.e = e; - this.depth = depth; - } -}; - -export type ResolvedType = Dynamic | DeepError | undefined; - -export const serialize = (s: EditorState) => { - if (s.kind === "input") { - return { - ...s, - // dirty: we write out the type in case the value is a literal and needs to be parsed - type: s.resolved && prettyT(s.resolved.t), - resolved: undefined, - }; - } - if (s.kind === "call") { - return { - ...s, - fn: serialize(s.fn), - input: serialize(s.input), - resolved: undefined, - }; - } - if (s.kind === "let") { - return { - ...s, - value: serialize(s.value), - inner: serialize(s.inner), - resolved: undefined, - }; - } - if (s.kind === "lambda") { - return { - ...s, - expr: serialize(s.expr), - resolved: undefined, - } - } -}; - -function parseLiteral(text: string, type: string) { - // dirty - if (type === "Int") { - return {i: parseInt(text), t: Int}; - } - if (type === "Double") { - return {i: parseDouble(text), t: Double}; - } -} - -export const deserialize = (s, env) => { - if (s.kind === "input") { - return { - ...s, - resolved: trie.get(env.name2dyn)(s.text) || parseLiteral(s.text, s.type), - }; - } - if (s.kind === "call") { - const fn = deserialize(s.fn, env); - const input = deserialize(s.input, env); - return { - ...s, - fn, - input, - resolved: resolveCallBlock(fn.resolved, input.resolved), - }; - } - if (s.kind === "let") { - const value = deserialize(s.value, env); - const inner = deserialize(s.inner, makeInnerEnv(env, s.name, value.resolved)); - return { - ...s, - value, - inner, - resolved: inner.resolved, - }; - } - if (s.kind === "lambda") { - return { - ...s, - expr: deserialize(s.expr, env), - resolved: undefined, - } - } -}; diff --git a/src/util/use_effect_better.ts b/src/util/use_effect_better.ts deleted file mode 100644 index 4230b61..0000000 --- a/src/util/use_effect_better.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useEffect, useRef } from "react" - -// like useEffect, but doesn't run on first render - -export const useEffectBetter = (callback, deps) => { - // detect development mode, where render function is always called twice: - const firstRender = useRef(import.meta.env.MODE === "development" ? 2 : 1); - useEffect(() => { - if (firstRender.current > 0) { - firstRender.current -= 1; - } - else { - callback(); - } - }, deps); -}