Let ... in ... block autocomplete also sorted by best 'match', type-wise

This commit is contained in:
Joeri Exelmans 2025-05-16 08:51:58 +02:00
parent 2d81e42447
commit 8abbac4bc9
7 changed files with 66 additions and 43 deletions

View file

@ -47,6 +47,12 @@
.outputParam > .inputParam > .inputParam > .inputParam:after { .outputParam > .inputParam > .inputParam > .inputParam:after {
border-left-color: rgb(153, 212, 214); border-left-color: rgb(153, 212, 214);
} }
.outputParam > .inputParam > .inputParam > .inputParam > .inputParam {
background-color: rgb(111, 186, 209);
}
.outputParam > .inputParam > .inputParam > .inputParam > .inputParam:after {
border-left-color: rgb(111, 186, 209);
}
.typeAnnot { .typeAnnot {
display: inline-block; display: inline-block;

View file

@ -3,26 +3,23 @@ import { useContext, useInsertionEffect } from "react";
import { Editor, type EditorState } from "./Editor"; import { Editor, type EditorState } from "./Editor";
import { Value } from "./Value"; import { Value } from "./Value";
import { type SetStateFn, type State2Props } from "./Editor"; import { type SetStateFn, type State2Props } from "./Editor";
import { evalCallBlock, evalEditorBlock } from "./eval"; import { evalCallBlock, evalEditorBlock, scoreResolved } from "./eval";
import { type ResolvedType } from "./eval"; import { type ResolvedType } from "./eval";
import "./CallBlock.css"; import "./CallBlock.css";
import { EnvContext } from "./EnvContext"; import { EnvContext } from "./EnvContext";
import type { SuggestionType } from "./InputBlock"; import type { SuggestionType } from "./InputBlock";
import { UnifyError } from "dope2"; import { UnifyError } from "dope2";
export interface CallBlockState< export interface CallBlockState {
FnState=EditorState,
InputState=EditorState,
> {
kind: "call"; kind: "call";
fn: FnState; fn: EditorState;
input: InputState; input: EditorState;
} }
interface CallBlockProps< interface CallBlockProps<
FnState=EditorState, FnState=EditorState,
InputState=EditorState, InputState=EditorState,
> extends State2Props<CallBlockState<FnState,InputState>,EditorState> { > extends State2Props<CallBlockState,EditorState> {
suggestionPriority: (suggestion: SuggestionType) => number; suggestionPriority: (suggestion: SuggestionType) => number;
} }
@ -110,25 +107,7 @@ export function CallBlockNoSugar({ state, setState, suggestionPriority }: CallBl
function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: SuggestionType) => number) { function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: SuggestionType) => number) {
const resolved = evalCallBlock(fn, input); const resolved = evalCallBlock(fn, input);
return scoreResolved(resolved, outPriority);
const r = outPriority(['literal', '<computed>',
// @ts-ignore: // TODO fix this
{t: resolved.t}]);
if (resolved.kind === "value") {
// The computed output becomes an input for the surrounding block, which may also have a priority function defined, for instance our output is the input to another function
console.log('r:', r);
return 1 + r;
}
else if (resolved.kind === "unknown") {
return 0 + r;
}
if (resolved.e instanceof UnifyError) {
return -1 + r; // parameter doesn't match
}
else {
return -2 + r; // even worse: fn is not a function!
}
} }
function FunctionHeader({ fn, setFn, input, onFnCancel, suggestionPriority }) { function FunctionHeader({ fn, setFn, input, onFnCancel, suggestionPriority }) {

View file

@ -166,6 +166,7 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr
return <LetInBlock return <LetInBlock
state={state} state={state}
setState={setState as (callback:(p:LetInBlockState)=>EditorState)=>void} setState={setState as (callback:(p:LetInBlockState)=>EditorState)=>void}
suggestionPriority={suggestionPriority}
/>; />;
case "lambda": case "lambda":
return <></>; return <></>;

View file

@ -7,5 +7,8 @@ export interface LambdaBlockState {
kind: "lambda"; kind: "lambda";
paramName: string; paramName: string;
expr: EditorState; expr: EditorState;
resolved: ResolvedType; }
export function LambdaBlock {
} }

View file

@ -1,6 +1,6 @@
.keyword { .keyword {
color: blue; color: blue;
margin: 0 2px 0 2px; /* margin: 0 2px 0 2px; */
} }

View file

@ -4,11 +4,12 @@ import { growEnv } from "dope2";
import { Editor, type EditorState } from "./Editor"; import { Editor, type EditorState } from "./Editor";
import { EnvContext } from "./EnvContext"; import { EnvContext } from "./EnvContext";
import { evalEditorBlock, type ResolvedType } from "./eval"; import { evalEditorBlock, evalLetInBlock, scoreResolved, type ResolvedType } from "./eval";
import { type State2Props } from "./Editor"; import { type State2Props } from "./Editor";
import { autoInputWidth } from "./util/dom_trickery"; import { autoInputWidth } from "./util/dom_trickery";
import "./LetInBlock.css"; import "./LetInBlock.css";
import type { SuggestionType } from "./InputBlock";
export interface LetInBlockState { export interface LetInBlockState {
kind: "let"; kind: "let";
@ -17,17 +18,19 @@ export interface LetInBlockState {
inner: EditorState; inner: EditorState;
} }
interface LetInBlockProps extends State2Props<LetInBlockState> { interface LetInBlockProps extends State2Props<LetInBlockState,EditorState> {
suggestionPriority: (suggestion: SuggestionType) => number;
} }
export function makeInnerEnv(env, name: string, value: ResolvedType) { export function makeInnerEnv(env, name: string, value: ResolvedType) {
if (value.kind === "value") { if (value.kind === "value") {
return growEnv(env)(name)(value) return growEnv(env)(name)(value)
} }
return env; return env;
} }
export function LetInBlock({state, setState}: LetInBlockProps) { export function LetInBlock({state, setState, suggestionPriority}: LetInBlockProps) {
const {name, value, inner} = state; const {name, value, inner} = state;
const env = useContext(EnvContext); const env = useContext(EnvContext);
const valueResolved = evalEditorBlock(value, env); const valueResolved = evalEditorBlock(value, env);
@ -47,10 +50,12 @@ export function LetInBlock({state, setState}: LetInBlockProps) {
useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]); useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]);
console.log({innerEnv});
return <span className="letIn"> return <span className="letIn">
<div className="decl"> <div className="decl">
<span className="keyword">let</span> <span className="keyword">let</span>
&nbsp;
<input <input
ref={nameRef} ref={nameRef}
className='editable' className='editable'
@ -58,22 +63,28 @@ export function LetInBlock({state, setState}: LetInBlockProps) {
placeholder="<name>" placeholder="<name>"
onChange={onChangeName} onChange={onChangeName}
/> />
<span className="keyword">=</span> <span className="keyword">&nbsp;=&nbsp;</span>
<Editor <Editor
state={value} state={value}
setState={setValue} setState={setValue}
suggestionPriority={() => 0} suggestionPriority={(suggestion: SuggestionType) => {
onCancel={() => {}} const innerEnv = makeInnerEnv(env, name, suggestion[2]);
const resolved = evalEditorBlock(inner, innerEnv);
return scoreResolved(resolved, suggestionPriority);
}}
onCancel={() => setState(state => state.inner)} // keep inner
/> />
&nbsp;<span className="keyword">in</span> <span className="keyword">in</span>
</div> </div>
<div className="inner"> <div className="inner">
<EnvContext value={innerEnv}> <EnvContext value={innerEnv}>
<Editor <Editor
state={inner} state={inner}
setState={setInner} setState={setInner}
suggestionPriority={() => 0} suggestionPriority={(suggestion: SuggestionType) => {
onCancel={() => {}} return suggestionPriority(suggestion)
}}
onCancel={() => setState(state => state.value)} // keep value
/> />
</EnvContext> </EnvContext>
</div> </div>

View file

@ -38,9 +38,7 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
return evalCallBlock(fn, input); return evalCallBlock(fn, input);
} }
if (s.kind === "let") { if (s.kind === "let") {
const value = evalEditorBlock(s.value, env); return evalLetInBlock(s.value, s.name, s.inner, env);
const innerEnv = makeInnerEnv(env, s.name, value)
return evalEditorBlock(s.inner, innerEnv);
} }
if (s.kind === "lambda") { if (s.kind === "lambda") {
const expr = evalEditorBlock(s.expr, env); const expr = evalEditorBlock(s.expr, env);
@ -125,6 +123,12 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
} }
} }
export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, env) {
const valueResolved = evalEditorBlock(value, env);
const innerEnv = makeInnerEnv(env, name, valueResolved)
return evalEditorBlock(inner, innerEnv);
}
export function haveValue(resolved: ResolvedType) { export function haveValue(resolved: ResolvedType) {
// return resolved && !(resolved instanceof DeepError); // return resolved && !(resolved instanceof DeepError);
return resolved.kind === "value"; return resolved.kind === "value";
@ -166,3 +170,22 @@ export function attemptParseLiteral(text: string): Dynamic[] {
return literalParsers.map(parseFn => parseFn(text)) return literalParsers.map(parseFn => parseFn(text))
.filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[]; .filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
} }
export function scoreResolved(resolved: ResolvedType, outPriority) {
const bias = outPriority(['literal', '<computed>',
// @ts-ignore: // TODO fix this
{t: resolved.t}]);
if (resolved.kind === "value") {
return 1 + bias;
}
else if (resolved.kind === "unknown") {
return 0 + bias;
}
if (resolved.e instanceof UnifyError) {
return -1 + bias; // parameter doesn't match
}
else {
return -2 + bias; // even worse: fn is not a function!
}
}