diff --git a/src/App.tsx b/src/App.tsx index 94867e6..613bd70 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import './App.css'; -import { CommandContext } from './CommandContext'; +import { GlobalContext } from './GlobalContext'; import { Editor, type EditorState } from './Editor'; import { extendedEnv } from './EnvContext'; import { biggerExample, initialEditorState, lambda2Params, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; @@ -161,14 +161,14 @@ export function App() { - + {}} suggestionPriority={() => 1} /> - + diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index 9ed749c..881e14d 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -1,14 +1,11 @@ -import { useContext, useInsertionEffect } from "react"; +import { useContext } from "react"; -import { Editor, type EditorState } from "./Editor"; -import { Value } from "./Value"; -import { type SetStateFn, type State2Props } from "./Editor"; -import { evalCallBlock, evalCallBlock2, evalEditorBlock, scoreResolved } from "./eval"; -import { type ResolvedType } from "./eval"; import "./CallBlock.css"; +import { Editor, type EditorState, type SetStateFn, type State2Props } from "./Editor"; import { EnvContext } from "./EnvContext"; -import type { SuggestionType } from "./InputBlock"; -import { UnifyError } from "dope2"; +import { evalCallBlock2, evalEditorBlock, scoreResolved, type ResolvedType } from "./eval"; +import { Value } from "./Value"; +import { GlobalContext } from "./GlobalContext"; export interface CallBlockState { kind: "call"; @@ -19,9 +16,7 @@ export interface CallBlockState { interface CallBlockProps< FnState=EditorState, InputState=EditorState, -> extends State2Props { - suggestionPriority: (suggestion: SuggestionType) => number; -} +> extends State2Props {} function headlessCallBlock(setState: (callback: SetStateFn) => void) { const setFn = (callback: SetStateFn) => { @@ -40,97 +35,51 @@ function headlessCallBlock(setState: (callback: SetStateFn - + {/* Sequence of input parameters */} - + { (resolved.kind === "error") && resolved.e.toString() + || (resolved.kind === "value") && + || "unknown" } + ; } -export function Output({resolved, children}) { - return - {children} - { (resolved.kind === "error") && resolved.e.toString() - || (resolved.kind === "value") && - || "unknown" } - ; -} - -export function CallBlockNoSugar({ state, setState, suggestionPriority }: CallBlockProps) { - const {setFn, setInput, onFnCancel, onInputCancel} - = headlessCallBlock(setState); - const env = useContext(EnvContext); - const resolved = evalEditorBlock(state, env); - const isOffending = (resolved.kind === "error") ? (resolved.depth===0) : false; - return - - - - - computePriority( - evalEditorBlock(state.fn, env), // fn *may* be set - inputSuggestion[2], // suggestions will be for input - suggestionPriority, // priority function we get from parent block - env, - )} - /> - - - - ; -} - -function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: SuggestionType) => number, env) { +function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) { const resolved = evalCallBlock2(fn, input, env); - return scoreResolved(resolved, outPriority); + const score = scoreResolved(resolved, outPriority); + return score; } -function FunctionHeader({ fn, setFn, input, onFnCancel, suggestionPriority }) { +function FunctionHeader({ state, setState, suggestionPriority }) { const env = useContext(EnvContext); - if (fn.kind === "call") { + const globalContext = useContext(GlobalContext); + const {setFn, onFnCancel} = headlessCallBlock(setState); + if (state.fn.kind === "call" && globalContext?.syntacticSugar) { // 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(setFn); - return computePriority( - fnSuggestion[2], - evalEditorBlock(fn.input, env), + state={state.fn} + setState={setFn} + suggestionPriority={(fnSuggestion: ResolvedType) => computePriority( + fnSuggestion, + evalEditorBlock(state.fn.input, env), suggestionPriority, env, )} @@ -138,54 +87,44 @@ function FunctionHeader({ fn, setFn, input, onFnCancel, suggestionPriority }) { } else { // end of recursion - draw function name - return ; - } -} - -function FunctionName({fn, setFn, onFnCancel, suggestionPriority, input}) { - const env = useContext(EnvContext); - return + return 𝑓𝑛 computePriority( - fnSuggestion[2], // suggestions will be for function - evalEditorBlock(input, env), // input *may* be set + (fnSuggestion: ResolvedType) => computePriority( + fnSuggestion, // suggestions will be for function + evalEditorBlock(state.input, env), // input *may* be set suggestionPriority, // priority function we get from parent block env, )} /> ; + } } -function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDepth, suggestionPriority }) { +function InputParams({ state, setState, suggestionPriority, depth, errorDepth }) { const env = useContext(EnvContext); + const globalContext = useContext(GlobalContext); + const {setFn, setInput, onInputCancel} = headlessCallBlock(setState); let nestedParams; - if (fn.kind === "call") { + if (state.fn.kind === "call" && globalContext?.syntacticSugar) { // Nest input of nested function - const { - setFn : setFnFn, - setInput : setFnInput, - } = headlessCallBlock(setFn); nestedParams = {/*todo*/}} - depth={depth+1} - errorDepth={errorDepth} + state={state.fn} + setState={setFn} suggestionPriority={ - (inputSuggestion: SuggestionType) => computePriority( - evalEditorBlock(fn.fn, env), - inputSuggestion[2], + (inputSuggestion: ResolvedType) => computePriority( + evalEditorBlock(state.fn.fn, env), + inputSuggestion, suggestionPriority, env, )} - />; + depth={depth+1} + errorDepth={errorDepth} + />; } else { nestedParams = <>>; @@ -195,13 +134,13 @@ function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDe {nestedParams} {/* Our own input param */} computePriority( - evalEditorBlock(fn, env), // fn *may* be set - inputSuggestion[2], // suggestions will be for input + (inputSuggestion: ResolvedType) => computePriority( + evalEditorBlock(state.fn, env), // fn *may* be set + inputSuggestion, // suggestions will be for input suggestionPriority, // priority function we get from parent block env, )} diff --git a/src/Editor.tsx b/src/Editor.tsx index 49c6eee..28f9baf 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -2,11 +2,11 @@ import { useContext, useEffect, useRef, useState } from "react"; import { getSymbol, getType, symbolFunction } from "dope2"; -import { CallBlock, CallBlockNoSugar, type CallBlockState } from "./CallBlock"; +import { CallBlock, type CallBlockState } from "./CallBlock"; import { InputBlock, type InputBlockState, type SuggestionType } from "./InputBlock"; import { Type } from "./Type"; -import { evalEditorBlock } from "./eval"; -import { CommandContext } from "./CommandContext"; +import { evalEditorBlock, type ResolvedType } from "./eval"; +import { GlobalContext } from "./GlobalContext"; import "./Editor.css"; import { EnvContext } from "./EnvContext"; import { LambdaBlock, type LambdaBlockState } from "./LambdaBlock"; @@ -25,10 +25,10 @@ export type SetStateFn = (state: InType) export interface State2Props { state: InType; setState: (callback: SetStateFn) => void; + suggestionPriority: (suggestion: ResolvedType) => number; } interface EditorProps extends State2Props { - suggestionPriority: (suggestion: SuggestionType) => number; onCancel: () => void; } @@ -69,7 +69,7 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr } }, [needCommand]); - const globalContext = useContext(CommandContext); + const globalContext = useContext(GlobalContext); const onCommand = (e: React.KeyboardEvent) => { const commands = ['e', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', 'L', '=', '.', 'c', 'a']; if (!commands.includes(e.key)) { @@ -163,20 +163,11 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr onCancel={onCancel} />; case "call": - if (globalContext?.syntacticSugar) { - return EditorState)=>void} - suggestionPriority={suggestionPriority} - />; - } - else { - return EditorState)=>void} - suggestionPriority={suggestionPriority} - />; - } + return EditorState)=>void} + suggestionPriority={suggestionPriority} + />; case "let": return (null); +export const GlobalContext = createContext(null); diff --git a/src/InputBlock.tsx b/src/InputBlock.tsx index b0a19d0..d5db3da 100644 --- a/src/InputBlock.tsx +++ b/src/InputBlock.tsx @@ -3,7 +3,7 @@ import { memo, useContext, useEffect, useMemo, useRef, useState } from "react"; import { getType, prettyT, trie } from "dope2"; import { EnvContext } from "./EnvContext"; -import type { Dynamic } from "./eval"; +import type { Dynamic, ResolvedType } from "./eval"; import "./InputBlock.css"; import { Type } from "./Type"; import type { State2Props } from "./Editor"; @@ -33,11 +33,10 @@ export type SuggestionType = ['literal'|'name', string, Dynamic]; export type PrioritizedSuggestionType = [number, ...SuggestionType]; interface InputBlockProps extends State2Props { - suggestionPriority: (suggestion: SuggestionType) => number; onCancel: () => void; } -const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) => number): PrioritizedSuggestionType[] => { +const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) => number): PrioritizedSuggestionType[] => { const literals = attemptParseLiteral(text, env); const ls: SuggestionType[] = [ @@ -46,11 +45,17 @@ const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) = // names ... trie.suggest(env.name2dyn)(text)(Infinity) - .map(([name,type]) => ["name", name, {...type, substitutions: new Map()}]), + .map(([name,type]) => [ + "name", + name, { + ...type, + substitutions: type.substitutions || new Map(), + kind: type.kind || "value", + }]), ] // return ls; return ls - .map(suggestion => [suggestionPriority(suggestion), ...suggestion] as PrioritizedSuggestionType) + .map(suggestion => [suggestionPriority(suggestion[2]), ...suggestion] as PrioritizedSuggestionType) .sort(([priorityA], [priorityB]) => priorityB - priorityA) } diff --git a/src/LambdaBlock.tsx b/src/LambdaBlock.tsx index 560a5cd..7ec7aa5 100644 --- a/src/LambdaBlock.tsx +++ b/src/LambdaBlock.tsx @@ -1,13 +1,14 @@ import { useContext, useEffect, useRef } from "react"; +import { growEnv } from "dope2"; + import { Editor, type EditorState, type State2Props } from "./Editor"; -import type { SuggestionType } from "./InputBlock"; import { EnvContext } from "./EnvContext"; -import { growEnv, TYPE_VARS } from "dope2"; +import { getUnusedTypeVar } from "./eval"; import { autoInputWidth } from "./util/dom_trickery"; import "./LambdaBlock.css"; -import { getUnusedTypeVar } from "./eval"; + export interface LambdaBlockState { @@ -19,9 +20,7 @@ export interface LambdaBlockState { interface LambdaBlockProps< FnState=EditorState, InputState=EditorState, -> extends State2Props { - suggestionPriority: (suggestion: SuggestionType) => number; -} +> extends State2Props {} export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockProps) { @@ -78,9 +77,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr state={state.expr} setState={setExpr} onCancel={() => setState(state => state.expr)} - suggestionPriority={(suggestion: SuggestionType) => { - return suggestionPriority(suggestion); - }} + suggestionPriority={suggestionPriority} /> diff --git a/src/LetInBlock.tsx b/src/LetInBlock.tsx index c76b2c5..58b1f20 100644 --- a/src/LetInBlock.tsx +++ b/src/LetInBlock.tsx @@ -1,15 +1,13 @@ import { useContext, useEffect, useRef } from "react"; - import { Editor, type EditorState } from "./Editor"; import { EnvContext } from "./EnvContext"; -import { evalEditorBlock, makeInnerEnv, scoreResolved } from "./eval"; +import { evalEditorBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval"; import { type State2Props } from "./Editor"; import { autoInputWidth } from "./util/dom_trickery"; +import { GlobalContext } from "./GlobalContext"; import "./LetInBlock.css"; -import type { SuggestionType } from "./InputBlock"; -import { CommandContext } from "./CommandContext"; export interface LetInBlockState { kind: "let"; @@ -18,9 +16,7 @@ export interface LetInBlockState { inner: EditorState; } -interface LetInBlockProps extends State2Props { - suggestionPriority: (suggestion: SuggestionType) => number; -} +interface LetInBlockProps extends State2Props {} export function LetInBlock({state, setState, suggestionPriority}: LetInBlockProps) { return @@ -43,7 +39,7 @@ export function LetInBlock({state, setState, suggestionPriority}: LetInBlockProp function DeclColumns({state, setState, suggestionPriority}) { const env = useContext(EnvContext); - const globalContext = useContext(CommandContext); + const globalContext = useContext(GlobalContext); const {name, value, inner} = state; const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); @@ -52,8 +48,8 @@ function DeclColumns({state, setState, suggestionPriority}) { setState(state => ({...state, name: e.target.value})); } - const valueSuggestionPriority = (suggestion: SuggestionType) => { - const innerEnv = makeInnerEnv(env, name, suggestion[2]); + const valueSuggestionPriority = (suggestion: ResolvedType) => { + const innerEnv = makeInnerEnv(env, name, suggestion); const resolved = evalEditorBlock(inner, innerEnv); return scoreResolved(resolved, suggestionPriority); }; @@ -103,7 +99,7 @@ function DeclColumns({state, setState, suggestionPriority}) { function InnerMost({state, setState, suggestionPriority}) { const env = useContext(EnvContext); - const globalContext = useContext(CommandContext); + const globalContext = useContext(GlobalContext); const setInner = callback => setState(state => ({...state, inner: callback(state.inner)})); const valueResolved = evalEditorBlock(state.value, env); const innerEnv = makeInnerEnv(env, state.name, valueResolved); diff --git a/src/eval.ts b/src/eval.ts index 8ced53f..497a3cd 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -66,7 +66,7 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv return { kind: found.kind || "value", ...found, - substitutions: new Map(), + substitutions: found.substitutions || new Map(), }; } } @@ -170,6 +170,7 @@ export function evalCallBlock(fn: EditorState, input: EditorState, env): Resolve export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, env): ResolvedType { const valueResolved = evalEditorBlock(value, env); // console.log('eval', name, '...', valueResolved.kind, valueResolved.e); + // const innerEnv = growEnv(env)(name)(valueResolved); const innerEnv = makeInnerEnv(env, name, valueResolved); return evalEditorBlock(inner, innerEnv); } @@ -281,14 +282,8 @@ export function attemptParseLiteral(text: string, env): Dynamic[] { .filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[]; } -export function scoreResolved(resolved: ResolvedType, outPriority: (s:SuggestionType) => number) { - const bias = outPriority(['literal', '', - { - // @ts-ignore - kind: "unknown", - t: resolved.t, - substitutions: new Map(), - }]); +export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) { + const bias = outPriority(resolved); if (resolved.kind === "value") { return 1 + bias;