Let ... in ... block autocomplete also sorted by best 'match', type-wise
This commit is contained in:
parent
2d81e42447
commit
8abbac4bc9
7 changed files with 66 additions and 43 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 }) {
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,8 @@ 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 <></>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,8 @@ export interface LambdaBlockState {
|
||||||
kind: "lambda";
|
kind: "lambda";
|
||||||
paramName: string;
|
paramName: string;
|
||||||
expr: EditorState;
|
expr: EditorState;
|
||||||
resolved: ResolvedType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function LambdaBlock {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
.keyword {
|
.keyword {
|
||||||
color: blue;
|
color: blue;
|
||||||
margin: 0 2px 0 2px;
|
/* margin: 0 2px 0 2px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
<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"> = </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
|
||||||
/>
|
/>
|
||||||
<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>
|
||||||
|
|
|
||||||
29
src/eval.ts
29
src/eval.ts
|
|
@ -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!
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue