move everything
This commit is contained in:
parent
3ff7e76694
commit
9050581a10
25 changed files with 37 additions and 42 deletions
112
src/component/expr/CallBlock.css
Normal file
112
src/component/expr/CallBlock.css
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
.functionBlock {
|
||||
border: solid 1px darkgray;
|
||||
display: inline-block;
|
||||
margin: 2px;
|
||||
color: black;
|
||||
background-color: white;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.functionName {
|
||||
/* text-align: center; */
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.inputParam:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
clip-path: polygon(1% 0%, 100% 50%, 0% 100%);
|
||||
height: 100%;
|
||||
width: var(--param-arrow-width);
|
||||
right: calc(var(--param-arrow-width)*(-1) + .2px);
|
||||
}
|
||||
.inputParam {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
position: relative; /* to ensure the :after (which is absolute) is relative to ourselves */
|
||||
flex-grow: 1;
|
||||
--param-arrow-width: 8px;
|
||||
margin-right: calc(var(--param-arrow-width)*2);
|
||||
}
|
||||
|
||||
/* Count nested level AFTER .outputParam (resets the depth) */
|
||||
.outputParam > .inputParam:after {
|
||||
background-color: rgb(242, 253, 146);
|
||||
}
|
||||
.outputParam > .inputParam {
|
||||
background-color: rgb(242, 253, 146);
|
||||
}
|
||||
.outputParam > .inputParam > .inputParam {
|
||||
background-color: rgb(180, 248, 214);
|
||||
}
|
||||
.outputParam > .inputParam > .inputParam:after {
|
||||
background-color: rgb(180, 248, 214);
|
||||
}
|
||||
.outputParam > .inputParam > .inputParam > .inputParam {
|
||||
background-color: rgb(153, 212, 214);
|
||||
}
|
||||
.outputParam > .inputParam > .inputParam > .inputParam:after {
|
||||
background-color: rgb(153, 212, 214);
|
||||
}
|
||||
.outputParam > .inputParam > .inputParam > .inputParam > .inputParam {
|
||||
background-color: rgb(111, 186, 209);
|
||||
}
|
||||
.outputParam > .inputParam > .inputParam > .inputParam > .inputParam:after {
|
||||
background-color: rgb(111, 186, 209);
|
||||
}
|
||||
|
||||
.typeAnnot {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.outputParam {
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
padding: 0px;
|
||||
display: inline-block;
|
||||
background-color: rgb(233, 224, 205);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.functionBlock.unifyError > .functionParams > .outputParam {
|
||||
background-color: pink;
|
||||
}
|
||||
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam {
|
||||
background-color: pink;
|
||||
color: black;
|
||||
}
|
||||
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam:after {
|
||||
background-color: pink;
|
||||
}
|
||||
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam {
|
||||
background-color: pink;
|
||||
color: black;
|
||||
}
|
||||
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam:after {
|
||||
background-color: pink;
|
||||
}
|
||||
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam > .inputParam {
|
||||
background-color: pink;
|
||||
color: black;
|
||||
}
|
||||
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam > .inputParam:after {
|
||||
background-color: pink;
|
||||
}
|
||||
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam > .inputParam > .inputParam {
|
||||
background-color: pink;
|
||||
color: black;
|
||||
}
|
||||
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam > .inputParam > .inputParam:after {
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
.inputParam.offending {
|
||||
background-color: darkred !important;
|
||||
color: white !important;
|
||||
}
|
||||
.inputParam.offending:after {
|
||||
background-color: darkred !important;
|
||||
}
|
||||
135
src/component/expr/CallBlock.tsx
Normal file
135
src/component/expr/CallBlock.tsx
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { useContext } from "react";
|
||||
|
||||
import { EnvContext } from "../../context/EnvContext";
|
||||
// import { addFocusRightMost, evalCallBlock2, evalExprBlock, recomputeTypeVarsForEnv, scoreResolved, type Environment, type ResolvedType } from "./eval";
|
||||
import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock";
|
||||
import { GlobalContext } from "../../context/GlobalContext";
|
||||
|
||||
import { getActions } from "../app/actions";
|
||||
import "./CallBlock.css";
|
||||
import { CallContext } from "../../context/CallContext";
|
||||
import { inferTypeCall, type Environment } from "../../eval/infer_type";
|
||||
import { Type } from "../other/Type";
|
||||
|
||||
export interface CallBlockState {
|
||||
kind: "call";
|
||||
fn: ExprBlockState;
|
||||
input: ExprBlockState;
|
||||
}
|
||||
|
||||
export interface CallBlockProps<
|
||||
FnState=ExprBlockState,
|
||||
InputState=ExprBlockState,
|
||||
> extends State2Props<CallBlockState,ExprBlockState> {}
|
||||
|
||||
function nestedFnProperties({state, setState, score}: CallBlockProps, env: Environment) {
|
||||
const setFn = (callback: SetStateFn) => {
|
||||
setState(state => ({...state, fn: callback(state.fn)}));
|
||||
};
|
||||
const onFnCancel = () => {
|
||||
setState(state => state.input); // we become our input
|
||||
};
|
||||
const scoreFn = (fnSuggestion: ExprBlockState) => {
|
||||
return score({
|
||||
kind: "call",
|
||||
fn: fnSuggestion,
|
||||
input: state.input,
|
||||
});
|
||||
};
|
||||
return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn};
|
||||
}
|
||||
|
||||
function nestedInputProperties({state, setState, score}: CallBlockProps, env: Environment) {
|
||||
const setInput = (callback: SetStateFn) => {
|
||||
setState(state => ({...state, input: callback(state.input)}));
|
||||
};
|
||||
const onInputCancel = () => {
|
||||
setState(state => /*addFocusRightMost*/(state.fn)); // we become our function
|
||||
};
|
||||
const scoreInput = (inputSuggestion: ExprBlockState) => {
|
||||
return score({
|
||||
kind: "call",
|
||||
fn: state.fn,
|
||||
input: inputSuggestion,
|
||||
});
|
||||
};
|
||||
return {state: state.input, setState: setInput, onCancel: onInputCancel, score: scoreInput};
|
||||
}
|
||||
|
||||
// function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) {
|
||||
// // dirty, but works:
|
||||
// const [fnR, env2] = recomputeTypeVarsForEnv('<fn>', fn, env);
|
||||
// const [inR, env3] = recomputeTypeVarsForEnv('<in>', input, env2);
|
||||
// const [resolved] = evalCallBlock2(fnR, inR, env3);
|
||||
// const score = scoreResolved(resolved, outPriority);
|
||||
// return score;
|
||||
// }
|
||||
|
||||
export function CallBlock(props: CallBlockProps) {
|
||||
const env = useContext(EnvContext);
|
||||
const globalContext = useContext(GlobalContext);
|
||||
const addParam = getActions(globalContext, props.setState).c;
|
||||
// const [resolved] = evalExprBlock(props.state, env);
|
||||
// return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
|
||||
const typeInfo = inferTypeCall(props.state, env);
|
||||
return <span className={"functionBlock"}>
|
||||
<CallContext value={{addParam}}>
|
||||
<FunctionHeader {...props} addParam={addParam} />
|
||||
<div className="functionParams">
|
||||
<div className="outputParam">
|
||||
{/* Sequence of input parameters */}
|
||||
<InputParams
|
||||
{...props}
|
||||
depth={0}
|
||||
// errorDepth={(resolved.kind === "error") ? (resolved.depth) : -1}
|
||||
errorDepth={-1}
|
||||
addParam={addParam}
|
||||
/>
|
||||
{/* { (resolved.kind === "error") && resolved.e.toString()
|
||||
|| (resolved.kind === "value") && <Value dynamic={resolved} />
|
||||
|| "unknown" } */}
|
||||
:: <Type type={typeInfo.type} />
|
||||
</div>
|
||||
</div>
|
||||
</CallContext>
|
||||
</span>;
|
||||
}
|
||||
|
||||
function FunctionHeader(props) {
|
||||
const env = useContext(EnvContext);
|
||||
const globalContext = useContext(GlobalContext);
|
||||
const nestedProperties = nestedFnProperties(props, env);
|
||||
if (props.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
|
||||
return <FunctionHeader {...nestedProperties} />;
|
||||
}
|
||||
else {
|
||||
// end of recursion - draw function name
|
||||
return <span className="functionName">
|
||||
𝑓𝑛
|
||||
<ExprBlock {...nestedProperties} />
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
|
||||
function InputParams({ depth, errorDepth, ...rest }) {
|
||||
const env = useContext(EnvContext);
|
||||
const globalContext = useContext(GlobalContext);
|
||||
const isOffending = depth === errorDepth;
|
||||
return <div className={"inputParam" + (isOffending ? " offending" : "")}>
|
||||
{rest.state.fn.kind === "call"
|
||||
&& globalContext?.syntacticSugar
|
||||
&& <InputParams
|
||||
{...nestedFnProperties(rest as CallBlockProps, env)}
|
||||
depth={depth+1}
|
||||
errorDepth={errorDepth}
|
||||
/>}
|
||||
{/* Our own input param */}
|
||||
<EnvContext value={inferTypeCall(rest.state, env).inputEnv}>
|
||||
<ExprBlock
|
||||
{...nestedInputProperties(rest as CallBlockProps, env)}
|
||||
/>
|
||||
</EnvContext>
|
||||
</div>;
|
||||
}
|
||||
56
src/component/expr/ExprBlock.css
Normal file
56
src/component/expr/ExprBlock.css
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
.editor {
|
||||
padding: 2px;;
|
||||
}
|
||||
.editor.error {
|
||||
border: 1px solid red;
|
||||
display: inline-block;
|
||||
}
|
||||
.editor.unknown {
|
||||
border: 1px dashed dodgerblue;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
*:hover:not(:has(> *:hover)) {
|
||||
/* useful for debugging: */
|
||||
/* border-width: 2px !important; */
|
||||
}
|
||||
|
||||
.offending .error {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.typeSignature {
|
||||
display: inline-block;
|
||||
/* z-index: 1; */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.typeSignature.gotDebug {
|
||||
background-color: gold;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.typeDebug {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.typeSignature:hover > .typeDebug {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
white-space-collapse: preserve;
|
||||
width: max-content;
|
||||
background-color: #d2ebf1e0;
|
||||
color: black;
|
||||
font-family: var(--my-monospace-font);
|
||||
padding: 4px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.editor:hover > .typeSignature {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.keyword {
|
||||
color: blue;
|
||||
font-weight: bold;
|
||||
}
|
||||
72
src/component/expr/ExprBlock.tsx
Normal file
72
src/component/expr/ExprBlock.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { useContext } from "react";
|
||||
|
||||
import { getType } from "dope2";
|
||||
|
||||
import { CallBlock, type CallBlockProps, type CallBlockState } from "./CallBlock";
|
||||
import { EnvContext } from "../../context/EnvContext";
|
||||
import { GlobalContext } from "../../context/GlobalContext";
|
||||
import { InputBlock, type InputBlockProps, type InputBlockState } from "./InputBlock";
|
||||
import { LambdaBlock, type LambdaBlockProps, type LambdaBlockState } from "./LambdaBlock";
|
||||
import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInBlock";
|
||||
// import { evalExprBlock, type ResolvedType } from "./eval";
|
||||
|
||||
import "./ExprBlock.css";
|
||||
import { Input } from "../other/Input";
|
||||
import { getActions } from "../app/actions";
|
||||
|
||||
export type ExprBlockState =
|
||||
InputBlockState
|
||||
| CallBlockState
|
||||
| LetInBlockState
|
||||
| LambdaBlockState;
|
||||
|
||||
export type SetStateFn<InType = ExprBlockState, OutType = InType> = (state: InType) => OutType;
|
||||
|
||||
export interface State2Props<InType, OutType = InType> {
|
||||
state: InType;
|
||||
setState: (callback: SetStateFn<InType, OutType>) => void;
|
||||
score: (suggestion: ExprBlockState) => number;
|
||||
}
|
||||
|
||||
interface ExprBlockProps extends State2Props<ExprBlockState> {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export function ExprBlock(props: ExprBlockProps) {
|
||||
const env = useContext(EnvContext);
|
||||
const globalContext = useContext(GlobalContext);
|
||||
|
||||
const renderBlock = {
|
||||
input: () => <InputBlock {...props as InputBlockProps} />,
|
||||
call: () => <CallBlock {...props as CallBlockProps} />,
|
||||
let: () => <LetInBlock {...props as LetInBlockProps} />,
|
||||
lambda: () => <LambdaBlock {...props as LambdaBlockProps} />,
|
||||
};
|
||||
|
||||
// const [resolved] = evalExprBlock(props.state, env);
|
||||
// const typeInfo = inferType(props.state, env);
|
||||
const actions = getActions(globalContext, props.setState);
|
||||
const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) =>
|
||||
[shortcut, (e) => { e.preventDefault(); action(); }]))
|
||||
|
||||
// return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
|
||||
|
||||
return <span className={"editor"}>
|
||||
{renderBlock[props.state.kind]()}
|
||||
{/* @ts-ignore */}
|
||||
{/* <div className={"typeSignature" + (resolved.__debug ? ' gotDebug' : '')}> */}
|
||||
{/* :: <Type type={typeInfo.type} /> */}
|
||||
{/* @ts-ignore */}
|
||||
{/* {resolved.__debug && <div className="typeDebug">{resolved.__debug}</div>} */}
|
||||
{/* </div> */}
|
||||
<Input
|
||||
placeholder="<c>"
|
||||
text=""
|
||||
suggestion=""
|
||||
onEnter={() => {}}
|
||||
onCancel={props.onCancel}
|
||||
onTextChange={() => {}}
|
||||
extraHandlers={extraHandlers}
|
||||
/>
|
||||
</span>;
|
||||
}
|
||||
47
src/component/expr/InputBlock.css
Normal file
47
src/component/expr/InputBlock.css
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
.inputBlock {
|
||||
position: relative;
|
||||
}
|
||||
.editable {
|
||||
position: relative;
|
||||
border: 0;
|
||||
font-size: 10pt;
|
||||
font-family: var(--my-monospace-font);
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
cursor: text;
|
||||
outline: 0;
|
||||
}
|
||||
.suggest {
|
||||
top: 2.4px;
|
||||
position: absolute;
|
||||
color: #aaa;
|
||||
}
|
||||
.suggestionsPlaceholder {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
.suggestions {
|
||||
display: block;
|
||||
color: black;
|
||||
text-align: left;
|
||||
position: absolute;
|
||||
border: solid 1px dodgerblue;
|
||||
cursor: pointer;
|
||||
max-height: calc(100vh - 64px);
|
||||
overflow: auto;
|
||||
z-index: 10;
|
||||
background-color: white;
|
||||
width: max-content;
|
||||
max-width: 500px;
|
||||
}
|
||||
.selected {
|
||||
background-color: dodgerblue;
|
||||
color: white;
|
||||
}
|
||||
.border-around-input {
|
||||
border: 1px solid black;
|
||||
padding: 1px;
|
||||
margin: 1px;
|
||||
}
|
||||
199
src/component/expr/InputBlock.tsx
Normal file
199
src/component/expr/InputBlock.tsx
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import { memo, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { trie } from "dope2";
|
||||
|
||||
import { EnvContext } from "../../context/EnvContext";
|
||||
// import type { Environment, ResolvedType } from "./eval";
|
||||
import "./InputBlock.css";
|
||||
import { Type } from "../other/Type";
|
||||
import type { ExprBlockState, State2Props } from "./ExprBlock";
|
||||
// import { attemptParseLiteral } from "./eval";
|
||||
import { Input } from "../other/Input";
|
||||
import { CallContext } from "../../context/CallContext";
|
||||
import { getActions } from "../app/actions";
|
||||
import { GlobalContext } from "../../context/GlobalContext";
|
||||
import { inferTypeInput } from "../../eval/infer_type";
|
||||
|
||||
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;
|
||||
value: InputValueType;
|
||||
focus: boolean
|
||||
}
|
||||
|
||||
// export type SuggestionType = ["literal"|"name", string, ResolvedType];
|
||||
// export type PrioritizedSuggestionType = [number, ...SuggestionType];
|
||||
|
||||
export interface InputBlockProps extends State2Props<InputBlockState,ExprBlockState> {
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
// const computeSuggestions = (
|
||||
// text: string,
|
||||
// env: Environment,
|
||||
// score: InputBlockProps['score'],
|
||||
// ): PrioritizedSuggestionType[] => {
|
||||
// const literals = attemptParseLiteral(text, env);
|
||||
// const ls: SuggestionType[] = [
|
||||
// // literals
|
||||
// ... literals.map((resolved) => ["literal", text, resolved]),
|
||||
|
||||
// // names
|
||||
// ... trie.suggest(env.names)(text)(Infinity)
|
||||
// .map(([name, resolved]) => ["name", name, resolved]),
|
||||
// ]
|
||||
// // return []; // <-- uncomment to disable suggestions (useful for debugging)
|
||||
// return ls
|
||||
// .map((suggestion: SuggestionType) =>
|
||||
// [score(suggestion[2]), ...suggestion] as PrioritizedSuggestionType)
|
||||
// .sort(([priorityA], [priorityB]) => priorityB - priorityA)
|
||||
// }
|
||||
|
||||
export function InputBlock({ state, setState, /*score,*/ onCancel }: InputBlockProps) {
|
||||
const {text, focus} = state;
|
||||
const globalContext = useContext(GlobalContext);
|
||||
const env = useContext(EnvContext);
|
||||
const callContext = useContext(CallContext);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [i, setI] = useState(0); // selected suggestion idx
|
||||
|
||||
const singleSuggestion = trie.growPrefix(env.names)(text);
|
||||
// const suggestions = useMemo(() => computeSuggestions(text, env, score), [text, score, env]);
|
||||
const suggestions = useMemo(() => [], []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (focus) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [focus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (suggestions.length >= i) {
|
||||
setI(0);
|
||||
}
|
||||
}, [suggestions.length]);
|
||||
|
||||
const onTextChange = newText => {
|
||||
setState(state => ({...state,
|
||||
text: newText,
|
||||
value: (trie.get(env.names)(newText) ? {
|
||||
kind: "name",
|
||||
} : state.value),
|
||||
}));
|
||||
}
|
||||
|
||||
const onSelectSuggestion = () => {
|
||||
// const [_priority, kind, name, dynamic] = suggestions[i];
|
||||
// if (kind === "literal") {
|
||||
// setState(state => ({
|
||||
// ...state,
|
||||
// text: name,
|
||||
// value: {kind, type: prettyT(getType(dynamic))},
|
||||
// }));
|
||||
// }
|
||||
// else {
|
||||
// setState(state => ({
|
||||
// ...state,
|
||||
// text: name,
|
||||
// value: {kind},
|
||||
// }))
|
||||
// }
|
||||
};
|
||||
|
||||
const extraHandlers = {
|
||||
ArrowDown: (e) => {
|
||||
setI((i + 1) % suggestions.length);
|
||||
e.preventDefault();
|
||||
},
|
||||
ArrowUp: (e) => {
|
||||
setI((suggestions.length + i - 1) % suggestions.length);
|
||||
e.preventDefault();
|
||||
},
|
||||
" ": (e) => {
|
||||
if (text.length > 0) {
|
||||
if (callContext.addParam) {
|
||||
callContext.addParam();
|
||||
}
|
||||
else {
|
||||
const actions = getActions(globalContext, setState);
|
||||
actions.c();
|
||||
}
|
||||
}
|
||||
e.preventDefault();
|
||||
},
|
||||
};
|
||||
|
||||
const typeInfo = inferTypeInput(state, env);
|
||||
|
||||
return <><Input
|
||||
placeholder="<name or literal>"
|
||||
onCancel={onCancel}
|
||||
onEnter={onSelectSuggestion}
|
||||
onTextChange={onTextChange}
|
||||
text={text}
|
||||
suggestion={singleSuggestion}
|
||||
extraHandlers={extraHandlers}
|
||||
>
|
||||
<span className="suggestionsPlaceholder">
|
||||
<Suggestions
|
||||
suggestions={suggestions}
|
||||
onSelect={onSelectSuggestion}
|
||||
i={i} setI={setI} />
|
||||
</span>
|
||||
</Input>
|
||||
::<Type type={typeInfo.type} />
|
||||
</>
|
||||
}
|
||||
|
||||
function Suggestions({ suggestions, onSelect, i, setI }) {
|
||||
return <>{(suggestions.length > 0) &&
|
||||
<div className={"suggestions"}>
|
||||
{suggestions.map((suggestion, j) =>
|
||||
<SuggestionMemo key={j}
|
||||
{...{setI, j,
|
||||
onSelect,
|
||||
highlighted: i===j,
|
||||
suggestion}}/>)}
|
||||
</div>
|
||||
}</>;
|
||||
}
|
||||
|
||||
interface SuggestionProps {
|
||||
setI: any;
|
||||
j: number;
|
||||
onSelect: any;
|
||||
highlighted: boolean;
|
||||
// suggestion: PrioritizedSuggestionType;
|
||||
}
|
||||
|
||||
function Suggestion({ setI, j, onSelect, highlighted, /*suggestion: [priority, kind, text, resolved]*/ }: SuggestionProps) {
|
||||
const onMouseEnter = j => () => {
|
||||
setI(j);
|
||||
};
|
||||
const onMouseDown = j => () => {
|
||||
setI(j);
|
||||
onSelect();
|
||||
};
|
||||
return <div
|
||||
key={`${j}_${name}`}
|
||||
className={(highlighted ? " selected" : "")}
|
||||
onMouseEnter={onMouseEnter(j)}
|
||||
onMouseDown={onMouseDown(j)}>
|
||||
{/* ({priority}) ({kind}) {text} :: <Type type={resolved.t} /> */}
|
||||
</div>
|
||||
}
|
||||
|
||||
const SuggestionMemo = memo<SuggestionProps>(Suggestion);
|
||||
9
src/component/expr/LambdaBlock.css
Normal file
9
src/component/expr/LambdaBlock.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.lambdaBlock {
|
||||
display: inline-block;
|
||||
border: solid 1px darkgrey;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.lambdaInner {
|
||||
display: inline-block;
|
||||
}
|
||||
82
src/component/expr/LambdaBlock.tsx
Normal file
82
src/component/expr/LambdaBlock.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { useContext } from "react";
|
||||
|
||||
// import { eqType, getSymbol, reduceUnification } from "dope2";
|
||||
|
||||
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
|
||||
import { EnvContext } from "../../context/EnvContext";
|
||||
// import { evalExprBlock, evalLambdaBlock, makeInnerEnv, makeTypeVar } from "./eval";
|
||||
|
||||
import "./LambdaBlock.css";
|
||||
import { Type } from "../other/Type";
|
||||
import { Input } from "../other/Input";
|
||||
import { inferTypeLambda } from "../../eval/infer_type";
|
||||
|
||||
export interface LambdaBlockState {
|
||||
kind: "lambda";
|
||||
paramName: string;
|
||||
focus: boolean;
|
||||
expr: ExprBlockState;
|
||||
}
|
||||
|
||||
export interface LambdaBlockProps<
|
||||
FnState=ExprBlockState,
|
||||
InputState=ExprBlockState,
|
||||
> extends State2Props<LambdaBlockState,ExprBlockState> {
|
||||
}
|
||||
|
||||
|
||||
export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
|
||||
const env = useContext(EnvContext);
|
||||
|
||||
const setParamName = paramName => setState(state => ({
|
||||
...state,
|
||||
paramName,
|
||||
}));
|
||||
const setExpr = callback => setState(state => ({
|
||||
...state,
|
||||
expr: callback(state.expr),
|
||||
}));
|
||||
|
||||
const {paramType, innerEnv} = inferTypeLambda(state, env);
|
||||
|
||||
// const [lambdaResolved, _, innerEnv] = evalLambdaBlock(state.paramName, state.expr, env);
|
||||
|
||||
// const inferredParamType = lambdaResolved.t.params[0](lambdaResolved.t);
|
||||
|
||||
// const innerEnv = env; // todo: change this
|
||||
|
||||
return <span className="lambdaBlock">
|
||||
<span className="keyword">λ</span>
|
||||
|
||||
<span className="lambdaInputParam">
|
||||
<Input
|
||||
placeholder="<name>"
|
||||
text={state.paramName}
|
||||
suggestion=""
|
||||
onEnter={() => {}}
|
||||
onCancel={() => {}}
|
||||
onTextChange={txt => setParamName(txt)}
|
||||
extraHandlers={{}}
|
||||
/>
|
||||
</span>
|
||||
<div className="typeSignature">
|
||||
:: <Type type={paramType} />
|
||||
</div>
|
||||
|
||||
<span className="keyword">:</span>
|
||||
|
||||
<div className="lambdaInner">
|
||||
<EnvContext value={innerEnv}>
|
||||
<ExprBlock
|
||||
state={state.expr}
|
||||
setState={setExpr}
|
||||
onCancel={() => setState(state => state.expr)}
|
||||
score={(s) => {
|
||||
// console.log('suggestionPriority of lambdaInner... just passing through');
|
||||
return score(s);
|
||||
}}
|
||||
/>
|
||||
</EnvContext>
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
23
src/component/expr/LetInBlock.css
Normal file
23
src/component/expr/LetInBlock.css
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
.letIn {
|
||||
display: inline-block;
|
||||
/* border: 1px solid darkgrey; */
|
||||
}
|
||||
|
||||
.inner {
|
||||
border: 1px solid darkgrey;
|
||||
margin-left: 4px
|
||||
}
|
||||
|
||||
.decl {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto auto;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.column.rightAlign {
|
||||
/* text-align: right; */
|
||||
}
|
||||
|
||||
.column {
|
||||
vertical-align: top;
|
||||
}
|
||||
117
src/component/expr/LetInBlock.tsx
Normal file
117
src/component/expr/LetInBlock.tsx
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import { useContext } from "react";
|
||||
|
||||
import { ExprBlock, type ExprBlockState } from "./ExprBlock";
|
||||
import { EnvContext } from "../../context/EnvContext";
|
||||
// import { evalExprBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval";
|
||||
import { type State2Props } from "./ExprBlock";
|
||||
import { GlobalContext } from "../../context/GlobalContext";
|
||||
|
||||
import "./LetInBlock.css";
|
||||
import { Input } from "../other/Input";
|
||||
import { inferTypeLet } from "../../eval/infer_type";
|
||||
import { Type } from "../other/Type";
|
||||
|
||||
export interface LetInBlockState {
|
||||
kind: "let";
|
||||
name: string;
|
||||
focus: boolean;
|
||||
value: ExprBlockState;
|
||||
inner: ExprBlockState;
|
||||
}
|
||||
|
||||
export interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> {
|
||||
}
|
||||
|
||||
export function LetInBlock(props: LetInBlockProps) {
|
||||
return <span className="letIn">
|
||||
<div className="decl">
|
||||
<DeclColumns {...props} />
|
||||
</div>
|
||||
<div className="inner">
|
||||
<InnerMost {...props} />
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
|
||||
function DeclColumns({state, setState, score}) {
|
||||
const env = useContext(EnvContext);
|
||||
const globalContext = useContext(GlobalContext);
|
||||
|
||||
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
||||
const setValue = callback => setState(state => ({...state, value: callback(state.value)}));
|
||||
|
||||
// const valueSuggestionPriority = (suggestion: ResolvedType) => {
|
||||
// const innerEnv = makeInnerEnv(env, name, suggestion);
|
||||
// const [resolved] = evalExprBlock(inner, innerEnv);
|
||||
// return scoreResolved(resolved, score);
|
||||
// };
|
||||
|
||||
// const [valueResolved] = evalExprBlock(value, env);
|
||||
// const innerEnv = makeInnerEnv(env, name, valueResolved);
|
||||
|
||||
const {paramType, innerEnv} = inferTypeLet(state, env);
|
||||
|
||||
return <>
|
||||
<span className="keyword column">let </span>
|
||||
<span className="column rightAlign">
|
||||
<Input
|
||||
placeholder="<name>"
|
||||
text={state.name}
|
||||
suggestion=""
|
||||
onEnter={() => {}}
|
||||
onCancel={() => {}}
|
||||
onTextChange={name => setState(state => ({...state, name}))}
|
||||
extraHandlers={{}}
|
||||
/>
|
||||
:: <Type type={paramType} />
|
||||
</span>
|
||||
<span className="keyword column"> = </span>
|
||||
<span className="column">
|
||||
<ExprBlock
|
||||
state={state.value}
|
||||
setState={setValue}
|
||||
score={() => 0}
|
||||
onCancel={() => setState(state => state.inner)} // keep inner
|
||||
/>
|
||||
</span>
|
||||
{state.inner.kind === "let" &&
|
||||
globalContext?.syntacticSugar &&
|
||||
<EnvContext value={innerEnv}>
|
||||
<DeclColumns
|
||||
state={state.inner}
|
||||
setState={setInner}
|
||||
score={score}
|
||||
/>
|
||||
</EnvContext>
|
||||
}
|
||||
</>;
|
||||
}
|
||||
|
||||
function InnerMost({state, setState, score}) {
|
||||
const env = useContext(EnvContext);
|
||||
const globalContext = useContext(GlobalContext);
|
||||
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
||||
// const [valueResolved] = evalExprBlock(state.value, env);
|
||||
// const innerEnv = makeInnerEnv(env, state.name, valueResolved);
|
||||
const {paramType, innerEnv} = inferTypeLet(state, env);
|
||||
const onCancel = () => setState(state => state.value);
|
||||
if (state.inner.kind === "let" && globalContext?.syntacticSugar) {
|
||||
return <EnvContext value={innerEnv}>
|
||||
<InnerMost
|
||||
state={state.inner}
|
||||
setState={setInner}
|
||||
score={score}
|
||||
/>
|
||||
</EnvContext>;
|
||||
}
|
||||
else {
|
||||
return <EnvContext value={innerEnv}>
|
||||
<ExprBlock
|
||||
state={state.inner}
|
||||
setState={setInner}
|
||||
score={score}
|
||||
onCancel={onCancel} // keep value
|
||||
/>
|
||||
</EnvContext>
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue