move everything

This commit is contained in:
Joeri Exelmans 2025-05-23 22:35:47 +02:00
parent 3ff7e76694
commit 9050581a10
25 changed files with 37 additions and 42 deletions

View 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;
}

View 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">
&nbsp;&#119891;&#119899;&nbsp;
<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>;
}

View 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;
}

View 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' : '')}> */}
{/* &nbsp;::&nbsp;<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>;
}

View 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;
}

View 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);

View file

@ -0,0 +1,9 @@
.lambdaBlock {
display: inline-block;
border: solid 1px darkgrey;
margin: 2px;
}
.lambdaInner {
display: inline-block;
}

View 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">&#955;</span>
&nbsp;
<span className="lambdaInputParam">
<Input
placeholder="<name>"
text={state.paramName}
suggestion=""
onEnter={() => {}}
onCancel={() => {}}
onTextChange={txt => setParamName(txt)}
extraHandlers={{}}
/>
</span>
<div className="typeSignature">
&nbsp;::&nbsp;<Type type={paramType} />
</div>
&nbsp;
<span className="keyword">:</span>
&nbsp;
<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>
}

View 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;
}

View 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&nbsp;</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">&nbsp;=&nbsp;</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>
}
}