simplify suggestions ordering
This commit is contained in:
parent
fdbf43a4e9
commit
bb6e742f5f
8 changed files with 109 additions and 110 deletions
10
src/App.tsx
10
src/App.tsx
|
|
@ -2,8 +2,10 @@ import { useEffect, useState } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { ExprBlock, type ExprBlockState } from './ExprBlock';
|
import { ExprBlock, type ExprBlockState } from './ExprBlock';
|
||||||
import { GlobalContext } from './GlobalContext';
|
import { GlobalContext } from './GlobalContext';
|
||||||
import { biggerExample, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
|
import { biggerExample, emptySet, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
|
||||||
import { actionShortcuts } from './actions';
|
import { actionShortcuts } from './actions';
|
||||||
|
import { scoreResolved, type ResolvedType } from './eval';
|
||||||
|
import type { InputBlockState } from './InputBlock';
|
||||||
|
|
||||||
|
|
||||||
const examples: [string, ExprBlockState][] = [
|
const examples: [string, ExprBlockState][] = [
|
||||||
|
|
@ -16,6 +18,7 @@ const examples: [string, ExprBlockState][] = [
|
||||||
["higher order 2" , higherOrder2Params ],
|
["higher order 2" , higherOrder2Params ],
|
||||||
["push Bool" , pushBool ],
|
["push Bool" , pushBool ],
|
||||||
["inc" , inc ],
|
["inc" , inc ],
|
||||||
|
["empty set" , emptySet ],
|
||||||
];
|
];
|
||||||
|
|
||||||
type AppState = {
|
type AppState = {
|
||||||
|
|
@ -163,9 +166,8 @@ export function App() {
|
||||||
state={appState.history.at(-1)!}
|
state={appState.history.at(-1)!}
|
||||||
setState={pushHistory}
|
setState={pushHistory}
|
||||||
onCancel={() => {}}
|
onCancel={() => {}}
|
||||||
suggestionPriority={() => {
|
score={(resolved: ResolvedType) => {
|
||||||
// console.log('suggestionPriority of App, always 0');
|
return scoreResolved(resolved, () => 0);
|
||||||
return 0;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</GlobalContext>
|
</GlobalContext>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
|
|
||||||
import { ExprBlock, type ExprBlockState, type SetStateFn, type State2Props } from "./ExprBlock";
|
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import { addFocusRightMost, evalCallBlock2, evalEditorBlock, removeFocus, scoreResolved, type ResolvedType } from "./eval";
|
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 "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
import { Value } from "./Value";
|
import { Value } from "./Value";
|
||||||
|
|
||||||
import "./CallBlock.css";
|
|
||||||
import { initialEditorState } from "./configurations";
|
|
||||||
import { CallContext } from "./CallContext";
|
|
||||||
import { getActions } from "./actions";
|
import { getActions } from "./actions";
|
||||||
|
import "./CallBlock.css";
|
||||||
|
import { CallContext } from "./CallContext";
|
||||||
|
|
||||||
export interface CallBlockState {
|
export interface CallBlockState {
|
||||||
kind: "call";
|
kind: "call";
|
||||||
|
|
@ -17,48 +16,61 @@ export interface CallBlockState {
|
||||||
input: ExprBlockState;
|
input: ExprBlockState;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CallBlockProps<
|
export interface CallBlockProps<
|
||||||
FnState=ExprBlockState,
|
FnState=ExprBlockState,
|
||||||
InputState=ExprBlockState,
|
InputState=ExprBlockState,
|
||||||
> extends State2Props<CallBlockState,ExprBlockState> {}
|
> extends State2Props<CallBlockState,ExprBlockState> {}
|
||||||
|
|
||||||
function nestedFnProperties({state, setState, suggestionPriority}: CallBlockProps, env) {
|
function nestedFnProperties({state, setState, score}: CallBlockProps, env) {
|
||||||
const setFn = (callback: SetStateFn) => {
|
const setFn = (callback: SetStateFn) => {
|
||||||
setState(state => ({...state, fn: callback(state.fn)}));
|
setState(state => ({...state, fn: callback(state.fn)}));
|
||||||
}
|
}
|
||||||
const onFnCancel = () => {
|
const onFnCancel = () => {
|
||||||
setState(state => state.input); // we become our input
|
setState(state => state.input); // we become our input
|
||||||
}
|
}
|
||||||
const fnSuggestionPriority = (fnSuggestion: ResolvedType) => computePriority(
|
const scoreFn = (fnSuggestion: ResolvedType) => {
|
||||||
|
return computePriority(
|
||||||
fnSuggestion,
|
fnSuggestion,
|
||||||
evalEditorBlock(state.input, env)[0],
|
evalExprBlock(state.input, env)[0],
|
||||||
suggestionPriority,
|
score,
|
||||||
env,
|
env,
|
||||||
);
|
);
|
||||||
return {state: state.fn, setState: setFn, onCancel: onFnCancel, suggestionPriority: fnSuggestionPriority};
|
};
|
||||||
|
return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn};
|
||||||
}
|
}
|
||||||
|
|
||||||
function nestedInputProperties({state, setState, suggestionPriority}: CallBlockProps, env) {
|
function nestedInputProperties({state, setState, score}: CallBlockProps, env: Environment) {
|
||||||
const setInput = (callback: SetStateFn) => {
|
const setInput = (callback: SetStateFn) => {
|
||||||
setState(state => ({...state, input: callback(state.input)}));
|
setState(state => ({...state, input: callback(state.input)}));
|
||||||
}
|
}
|
||||||
const onInputCancel = () => {
|
const onInputCancel = () => {
|
||||||
setState(state => addFocusRightMost(state.fn)); // we become our function
|
setState(state => addFocusRightMost(state.fn)); // we become our function
|
||||||
}
|
}
|
||||||
const inputSuggestionPriorirty = (inputSuggestion: ResolvedType) => computePriority(
|
const scoreInput = (inputSuggestion: ResolvedType) => {
|
||||||
evalEditorBlock(state.fn, env)[0], // fn *may* be set
|
return computePriority(
|
||||||
inputSuggestion, // suggestions will be for input
|
evalExprBlock(state.fn, env)[0], // fn *may* be set
|
||||||
suggestionPriority, // priority function we get from parent block
|
inputSuggestion, // suggestions will be for input
|
||||||
env,
|
score, // priority function we get from parent block
|
||||||
)
|
env,
|
||||||
return {state: state.input, setState: setInput, onCancel: onInputCancel, suggestionPriority: inputSuggestionPriorirty};
|
);
|
||||||
|
}
|
||||||
|
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) {
|
export function CallBlock(props: CallBlockProps) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
const addParam = getActions(globalContext, props.setState).c;
|
const addParam = getActions(globalContext, props.setState).c;
|
||||||
const [resolved] = evalEditorBlock(props.state, env);
|
const [resolved] = evalExprBlock(props.state, env);
|
||||||
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
|
return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
|
||||||
<CallContext value={{addParam}}>
|
<CallContext value={{addParam}}>
|
||||||
<FunctionHeader {...props} addParam={addParam} />
|
<FunctionHeader {...props} addParam={addParam} />
|
||||||
|
|
@ -80,12 +92,6 @@ export function CallBlock(props: CallBlockProps) {
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: ResolvedType) => number, env) {
|
|
||||||
const [resolved] = evalCallBlock2(fn, input, env);
|
|
||||||
const score = scoreResolved(resolved, outPriority);
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FunctionHeader(props) {
|
function FunctionHeader(props) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,14 @@ import { useContext } from "react";
|
||||||
|
|
||||||
import { getType } from "dope2";
|
import { getType } from "dope2";
|
||||||
|
|
||||||
import { CallBlock, type CallBlockState } from "./CallBlock";
|
import { CallBlock, type CallBlockProps, type CallBlockState } from "./CallBlock";
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import { GlobalContext } from "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
import { InputBlock, type InputBlockState } from "./InputBlock";
|
import { InputBlock, type InputBlockProps, type InputBlockState } from "./InputBlock";
|
||||||
import { LambdaBlock, type LambdaBlockState } from "./LambdaBlock";
|
import { LambdaBlock, type LambdaBlockProps, type LambdaBlockState } from "./LambdaBlock";
|
||||||
import { LetInBlock, type LetInBlockState } from "./LetInBlock";
|
import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInBlock";
|
||||||
import { Type } from "./Type";
|
import { Type } from "./Type";
|
||||||
import { evalEditorBlock, type ResolvedType } from "./eval";
|
import { evalExprBlock, type ResolvedType } from "./eval";
|
||||||
|
|
||||||
import "./ExprBlock.css";
|
import "./ExprBlock.css";
|
||||||
import { Input } from "./Input";
|
import { Input } from "./Input";
|
||||||
|
|
@ -26,48 +26,31 @@ export type SetStateFn<InType = ExprBlockState, OutType = InType> = (state: InTy
|
||||||
export interface State2Props<InType, OutType = InType> {
|
export interface State2Props<InType, OutType = InType> {
|
||||||
state: InType;
|
state: InType;
|
||||||
setState: (callback: SetStateFn<InType, OutType>) => void;
|
setState: (callback: SetStateFn<InType, OutType>) => void;
|
||||||
suggestionPriority: (suggestion: ResolvedType) => number;
|
score: (suggestion: ResolvedType) => number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExprBlockProps extends State2Props<ExprBlockState> {
|
interface ExprBlockProps extends State2Props<ExprBlockState> {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExprBlock({state, setState, suggestionPriority, onCancel}: ExprBlockProps) {
|
export function ExprBlock(props: ExprBlockProps) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
|
|
||||||
const renderBlock = {
|
const renderBlock = {
|
||||||
input: () => <InputBlock
|
input: () => <InputBlock {...props as InputBlockProps} />,
|
||||||
state={state as InputBlockState}
|
call: () => <CallBlock {...props as CallBlockProps} />,
|
||||||
setState={setState as (callback:(p:InputBlockState)=>ExprBlockState)=>void}
|
let: () => <LetInBlock {...props as LetInBlockProps} />,
|
||||||
suggestionPriority={suggestionPriority}
|
lambda: () => <LambdaBlock {...props as LambdaBlockProps} />,
|
||||||
onCancel={onCancel}
|
|
||||||
/>,
|
|
||||||
call: () => <CallBlock
|
|
||||||
state={state as CallBlockState}
|
|
||||||
setState={setState as (callback:(p:CallBlockState)=>ExprBlockState)=>void}
|
|
||||||
suggestionPriority={suggestionPriority}
|
|
||||||
/>,
|
|
||||||
let: () => <LetInBlock
|
|
||||||
state={state as LetInBlockState}
|
|
||||||
setState={setState as (callback:(p:LetInBlockState)=>ExprBlockState)=>void}
|
|
||||||
suggestionPriority={suggestionPriority}
|
|
||||||
/>,
|
|
||||||
lambda: () => <LambdaBlock
|
|
||||||
state={state as LambdaBlockState}
|
|
||||||
setState={setState as (callback:(p:LambdaBlockState)=>ExprBlockState)=>void}
|
|
||||||
suggestionPriority={suggestionPriority}
|
|
||||||
/>,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [resolved] = evalEditorBlock(state, env);
|
const [resolved] = evalExprBlock(props.state, env);
|
||||||
const actions = getActions(globalContext, setState);
|
const actions = getActions(globalContext, props.setState);
|
||||||
const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) =>
|
const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) =>
|
||||||
[shortcut, (e) => { e.preventDefault(); action(); }]))
|
[shortcut, (e) => { e.preventDefault(); action(); }]))
|
||||||
|
|
||||||
return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
|
return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
|
||||||
{renderBlock[state.kind]()}
|
{renderBlock[props.state.kind]()}
|
||||||
<div className="typeSignature">
|
<div className="typeSignature">
|
||||||
:: <Type type={getType(resolved)} />
|
:: <Type type={getType(resolved)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -76,7 +59,7 @@ export function ExprBlock({state, setState, suggestionPriority, onCancel}: ExprB
|
||||||
text=""
|
text=""
|
||||||
suggestion=""
|
suggestion=""
|
||||||
onEnter={() => {}}
|
onEnter={() => {}}
|
||||||
onCancel={onCancel}
|
onCancel={props.onCancel}
|
||||||
onTextChange={() => {}}
|
onTextChange={() => {}}
|
||||||
extraHandlers={extraHandlers}
|
extraHandlers={extraHandlers}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,12 @@ import { memo, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { getType, prettyT, trie } from "dope2";
|
import { getType, prettyT, trie } from "dope2";
|
||||||
|
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import type { Dynamic, ResolvedType } from "./eval";
|
import type { Environment, ResolvedType } from "./eval";
|
||||||
import "./InputBlock.css";
|
import "./InputBlock.css";
|
||||||
import { Type } from "./Type";
|
import { Type } from "./Type";
|
||||||
import type { ExprBlockState, State2Props } from "./ExprBlock";
|
import type { ExprBlockState, State2Props } from "./ExprBlock";
|
||||||
import { attemptParseLiteral } from "./eval";
|
import { attemptParseLiteral } from "./eval";
|
||||||
import { Input } from "./Input";
|
import { Input } from "./Input";
|
||||||
import { initialEditorState } from "./configurations";
|
|
||||||
import { CallContext } from "./CallContext";
|
import { CallContext } from "./CallContext";
|
||||||
import { getActions } from "./actions";
|
import { getActions } from "./actions";
|
||||||
import { GlobalContext } from "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
|
|
@ -33,37 +32,35 @@ export interface InputBlockState {
|
||||||
focus: boolean
|
focus: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SuggestionType = ['literal'|'name', string, Dynamic];
|
export type SuggestionType = ["literal"|"name", string, ResolvedType];
|
||||||
export type PrioritizedSuggestionType = [number, ...SuggestionType];
|
export type PrioritizedSuggestionType = [number, ...SuggestionType];
|
||||||
|
|
||||||
interface InputBlockProps extends State2Props<InputBlockState,ExprBlockState> {
|
export interface InputBlockProps extends State2Props<InputBlockState,ExprBlockState> {
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeSuggestions = (text, env, suggestionPriority: (s: ResolvedType) => number): PrioritizedSuggestionType[] => {
|
const computeSuggestions = (
|
||||||
|
text: string,
|
||||||
|
env: Environment,
|
||||||
|
score: InputBlockProps['score'],
|
||||||
|
): PrioritizedSuggestionType[] => {
|
||||||
const literals = attemptParseLiteral(text, env);
|
const literals = attemptParseLiteral(text, env);
|
||||||
|
|
||||||
const ls: SuggestionType[] = [
|
const ls: SuggestionType[] = [
|
||||||
// literals
|
// literals
|
||||||
... literals.map((lit) => ["literal", text, lit]),
|
... literals.map((resolved) => ["literal", text, resolved]),
|
||||||
|
|
||||||
// names
|
// names
|
||||||
... trie.suggest(env.names)(text)(Infinity)
|
... trie.suggest(env.names)(text)(Infinity)
|
||||||
.map(([name,type]) => [
|
.map(([name, resolved]) => ["name", name, resolved]),
|
||||||
"name",
|
|
||||||
name, {
|
|
||||||
...type,
|
|
||||||
substitutions: type.substitutions || new Map(),
|
|
||||||
kind: type.kind || "value",
|
|
||||||
}]),
|
|
||||||
]
|
]
|
||||||
// return []; // <-- uncomment to disable suggestions (useful for debugging)
|
// return []; // <-- uncomment to disable suggestions (useful for debugging)
|
||||||
return ls
|
return ls
|
||||||
.map((suggestion) => [suggestionPriority(suggestion[2]), ...suggestion] as PrioritizedSuggestionType)
|
.map((suggestion: SuggestionType) =>
|
||||||
|
[score(suggestion[2]), ...suggestion] as PrioritizedSuggestionType)
|
||||||
.sort(([priorityA], [priorityB]) => priorityB - priorityA)
|
.sort(([priorityA], [priorityB]) => priorityB - priorityA)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InputBlock({ state, setState, suggestionPriority, onCancel }: InputBlockProps) {
|
export function InputBlock({ state, setState, score, onCancel }: InputBlockProps) {
|
||||||
const {text, focus} = state;
|
const {text, focus} = state;
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
|
|
@ -72,7 +69,7 @@ export function InputBlock({ state, setState, suggestionPriority, onCancel }: In
|
||||||
const [i, setI] = useState(0); // selected suggestion idx
|
const [i, setI] = useState(0); // selected suggestion idx
|
||||||
|
|
||||||
const singleSuggestion = trie.growPrefix(env.names)(text);
|
const singleSuggestion = trie.growPrefix(env.names)(text);
|
||||||
const suggestions = useMemo(() => computeSuggestions(text, env, suggestionPriority), [text, suggestionPriority, env]);
|
const suggestions = useMemo(() => computeSuggestions(text, env, score), [text, score, env]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -176,20 +173,20 @@ interface SuggestionProps {
|
||||||
suggestion: PrioritizedSuggestionType;
|
suggestion: PrioritizedSuggestionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kind, name, dynamic] }: SuggestionProps) {
|
function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, kind, text, resolved] }: SuggestionProps) {
|
||||||
const onMouseEnter = j => () => {
|
const onMouseEnter = j => () => {
|
||||||
setI(j);
|
setI(j);
|
||||||
};
|
};
|
||||||
const onMouseDown = j => () => {
|
const onMouseDown = j => () => {
|
||||||
setI(j);
|
setI(j);
|
||||||
onSelect([priority, kind, name, dynamic]);
|
onSelect();
|
||||||
};
|
};
|
||||||
return <div
|
return <div
|
||||||
key={`${j}_${name}`}
|
key={`${j}_${name}`}
|
||||||
className={(highlighted ? " selected" : "")}
|
className={(highlighted ? " selected" : "")}
|
||||||
onMouseEnter={onMouseEnter(j)}
|
onMouseEnter={onMouseEnter(j)}
|
||||||
onMouseDown={onMouseDown(j)}>
|
onMouseDown={onMouseDown(j)}>
|
||||||
({priority}) ({kind}) {name} :: <Type type={getType(dynamic)} />
|
({priority}) ({kind}) {text} :: <Type type={resolved.t} />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { eqType, getSymbol, reduceUnification } from "dope2";
|
||||||
|
|
||||||
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
|
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import { evalEditorBlock, makeInnerEnv, makeTypeVar } from "./eval";
|
import { evalExprBlock, makeInnerEnv, makeTypeVar } from "./eval";
|
||||||
|
|
||||||
import "./LambdaBlock.css";
|
import "./LambdaBlock.css";
|
||||||
import { Type } from "./Type";
|
import { Type } from "./Type";
|
||||||
|
|
@ -17,14 +17,14 @@ export interface LambdaBlockState {
|
||||||
expr: ExprBlockState;
|
expr: ExprBlockState;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LambdaBlockProps<
|
export interface LambdaBlockProps<
|
||||||
FnState=ExprBlockState,
|
FnState=ExprBlockState,
|
||||||
InputState=ExprBlockState,
|
InputState=ExprBlockState,
|
||||||
> extends State2Props<LambdaBlockState,ExprBlockState> {
|
> extends State2Props<LambdaBlockState,ExprBlockState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockProps) {
|
export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
|
|
||||||
const setParamName = paramName => setState(state => ({
|
const setParamName = paramName => setState(state => ({
|
||||||
|
|
@ -38,7 +38,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
|
||||||
|
|
||||||
const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName);
|
const [paramType, staticInnerEnv] = makeTypeVar(env, state.paramName);
|
||||||
|
|
||||||
const [exprResolved] = evalEditorBlock(state.expr, staticInnerEnv);
|
const [exprResolved] = evalExprBlock(state.expr, staticInnerEnv);
|
||||||
|
|
||||||
const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType;
|
const inferredParamType = reduceUnification(exprResolved.unification).get(getSymbol(paramType)) || paramType;
|
||||||
|
|
||||||
|
|
@ -76,9 +76,9 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr
|
||||||
state={state.expr}
|
state={state.expr}
|
||||||
setState={setExpr}
|
setState={setExpr}
|
||||||
onCancel={() => setState(state => state.expr)}
|
onCancel={() => setState(state => state.expr)}
|
||||||
suggestionPriority={(s) => {
|
score={(s) => {
|
||||||
// console.log('suggestionPriority of lambdaInner... just passing through');
|
// console.log('suggestionPriority of lambdaInner... just passing through');
|
||||||
return suggestionPriority(s);
|
return score(s);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</EnvContext>
|
</EnvContext>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useContext } from "react";
|
||||||
|
|
||||||
import { ExprBlock, type ExprBlockState } from "./ExprBlock";
|
import { ExprBlock, type ExprBlockState } from "./ExprBlock";
|
||||||
import { EnvContext } from "./EnvContext";
|
import { EnvContext } from "./EnvContext";
|
||||||
import { evalEditorBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval";
|
import { evalExprBlock, makeInnerEnv, scoreResolved, type ResolvedType } from "./eval";
|
||||||
import { type State2Props } from "./ExprBlock";
|
import { type State2Props } from "./ExprBlock";
|
||||||
import { GlobalContext } from "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ export interface LetInBlockState {
|
||||||
inner: ExprBlockState;
|
inner: ExprBlockState;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> {
|
export interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LetInBlock(props: LetInBlockProps) {
|
export function LetInBlock(props: LetInBlockProps) {
|
||||||
|
|
@ -31,7 +31,7 @@ export function LetInBlock(props: LetInBlockProps) {
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPriority}) {
|
function DeclColumns({state: {name, value, inner, focus}, setState, score}) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
|
|
||||||
|
|
@ -40,11 +40,11 @@ function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPr
|
||||||
|
|
||||||
const valueSuggestionPriority = (suggestion: ResolvedType) => {
|
const valueSuggestionPriority = (suggestion: ResolvedType) => {
|
||||||
const innerEnv = makeInnerEnv(env, name, suggestion);
|
const innerEnv = makeInnerEnv(env, name, suggestion);
|
||||||
const [resolved] = evalEditorBlock(inner, innerEnv);
|
const [resolved] = evalExprBlock(inner, innerEnv);
|
||||||
return scoreResolved(resolved, suggestionPriority);
|
return scoreResolved(resolved, score);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [valueResolved] = evalEditorBlock(value, env);
|
const [valueResolved] = evalExprBlock(value, env);
|
||||||
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
|
@ -65,7 +65,7 @@ function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPr
|
||||||
<ExprBlock
|
<ExprBlock
|
||||||
state={value}
|
state={value}
|
||||||
setState={setValue}
|
setState={setValue}
|
||||||
suggestionPriority={valueSuggestionPriority}
|
score={valueSuggestionPriority}
|
||||||
onCancel={() => setState(state => state.inner)} // keep inner
|
onCancel={() => setState(state => state.inner)} // keep inner
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -75,18 +75,18 @@ function DeclColumns({state: {name, value, inner, focus}, setState, suggestionPr
|
||||||
<DeclColumns
|
<DeclColumns
|
||||||
state={inner}
|
state={inner}
|
||||||
setState={setInner}
|
setState={setInner}
|
||||||
suggestionPriority={suggestionPriority}
|
score={score}
|
||||||
/>
|
/>
|
||||||
</EnvContext>
|
</EnvContext>
|
||||||
}
|
}
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function InnerMost({state, setState, suggestionPriority}) {
|
function InnerMost({state, setState, score}) {
|
||||||
const env = useContext(EnvContext);
|
const env = useContext(EnvContext);
|
||||||
const globalContext = useContext(GlobalContext);
|
const globalContext = useContext(GlobalContext);
|
||||||
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
|
||||||
const [valueResolved] = evalEditorBlock(state.value, env);
|
const [valueResolved] = evalExprBlock(state.value, env);
|
||||||
const innerEnv = makeInnerEnv(env, state.name, valueResolved);
|
const innerEnv = makeInnerEnv(env, state.name, valueResolved);
|
||||||
const onCancel = () => setState(state => state.value);
|
const onCancel = () => setState(state => state.value);
|
||||||
if (state.inner.kind === "let" && globalContext?.syntacticSugar) {
|
if (state.inner.kind === "let" && globalContext?.syntacticSugar) {
|
||||||
|
|
@ -94,7 +94,7 @@ function InnerMost({state, setState, suggestionPriority}) {
|
||||||
<InnerMost
|
<InnerMost
|
||||||
state={state.inner}
|
state={state.inner}
|
||||||
setState={setInner}
|
setState={setInner}
|
||||||
suggestionPriority={suggestionPriority}
|
score={score}
|
||||||
/>
|
/>
|
||||||
</EnvContext>;
|
</EnvContext>;
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ function InnerMost({state, setState, suggestionPriority}) {
|
||||||
<ExprBlock
|
<ExprBlock
|
||||||
state={state.inner}
|
state={state.inner}
|
||||||
setState={setInner}
|
setState={setInner}
|
||||||
suggestionPriority={suggestionPriority}
|
score={score}
|
||||||
onCancel={onCancel} // keep value
|
onCancel={onCancel} // keep value
|
||||||
/>
|
/>
|
||||||
</EnvContext>
|
</EnvContext>
|
||||||
|
|
|
||||||
|
|
@ -137,4 +137,6 @@ export const higherOrder2Params: ExprBlockState = {"kind":"let","focus":false,"i
|
||||||
|
|
||||||
export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"Bool","value":{"kind":"name"},"focus":true}};
|
export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"Bool","value":{"kind":"name"},"focus":true}};
|
||||||
|
|
||||||
export const inc: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}};
|
export const inc: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}};
|
||||||
|
|
||||||
|
export const emptySet: ExprBlockState = {"kind":"call","fn":{"kind":"input","text":"set.emptySet","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"","value":{"kind":"text"},"focus":true}};
|
||||||
27
src/eval.ts
27
src/eval.ts
|
|
@ -4,7 +4,7 @@ import type { ExprBlockState } from "./ExprBlock";
|
||||||
import type { InputValueType } from "./InputBlock";
|
import type { InputValueType } from "./InputBlock";
|
||||||
|
|
||||||
const IS_DEV = (import.meta.env.MODE === "development");
|
const IS_DEV = (import.meta.env.MODE === "development");
|
||||||
const VERBOSE = false;
|
const VERBOSE = true;
|
||||||
|
|
||||||
export interface Environment {
|
export interface Environment {
|
||||||
names: any;
|
names: any;
|
||||||
|
|
@ -46,7 +46,7 @@ export type ResolvedType = Dynamic | DeepError | Unknown;
|
||||||
|
|
||||||
class NotFoundError extends Error {}
|
class NotFoundError extends Error {}
|
||||||
|
|
||||||
export const evalEditorBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => {
|
export const evalExprBlock = (s: ExprBlockState, env: Environment): [ResolvedType,Environment] => {
|
||||||
if (s.kind === "input") {
|
if (s.kind === "input") {
|
||||||
return evalInputBlock(s.text, s.value, env);
|
return evalInputBlock(s.text, s.value, env);
|
||||||
}
|
}
|
||||||
|
|
@ -88,8 +88,8 @@ export function evalInputBlock(text: string, value: InputValueType, env: Environ
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||||
const [fnResolved, env2] = evalEditorBlock(fn, env);
|
const [fnResolved, env2] = evalExprBlock(fn, env);
|
||||||
const [inputResolved, env3] = evalEditorBlock(input, env2);
|
const [inputResolved, env3] = evalExprBlock(input, env2);
|
||||||
if (VERBOSE) {
|
if (VERBOSE) {
|
||||||
console.log('==== evalCallBlock ====');
|
console.log('==== evalCallBlock ====');
|
||||||
console.log('env :', env);
|
console.log('env :', env);
|
||||||
|
|
@ -103,6 +103,9 @@ export function evalCallBlock(fn: ExprBlockState, input: ExprBlockState, env: En
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||||
|
if (occurring(fnResolved.t).intersection(occurring(inputResolved.t)).size > 0) {
|
||||||
|
throw new Error(`Precondition failed: function (${prettyT(fnResolved.t)}) and its input (${prettyT(inputResolved.t)}) have overlapping typevars!`);
|
||||||
|
}
|
||||||
if (getSymbol(fnResolved.t) !== symbolFunction) {
|
if (getSymbol(fnResolved.t) !== symbolFunction) {
|
||||||
// not a function...
|
// not a function...
|
||||||
if (isTypeVar(fnResolved.t)) {
|
if (isTypeVar(fnResolved.t)) {
|
||||||
|
|
@ -143,7 +146,7 @@ const inverseUnification = (uni, inverse) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
export function recomputeTypeVarsForEnv(name: string, resolved: ResolvedType, env: Environment): [ResolvedType,Environment] {
|
||||||
const [[newType], inverse] = recomputeTypeVarsWithInverse([resolved.t], env.nextFreeTypeVar);
|
const [[newType], inverse] = recomputeTypeVarsWithInverse([resolved.t], env.nextFreeTypeVar);
|
||||||
const newResolved: ResolvedType = {
|
const newResolved: ResolvedType = {
|
||||||
...resolved,
|
...resolved,
|
||||||
|
|
@ -281,9 +284,9 @@ function evalCallBlock3(fnResolved: ResolvedType, inputResolved: ResolvedType, e
|
||||||
}
|
}
|
||||||
|
|
||||||
export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
export function evalLetInBlock(value: ExprBlockState, name: string, inner: ExprBlockState, env: Environment): [ResolvedType,Environment] {
|
||||||
const [valueResolved] = evalEditorBlock(value, env);
|
const [valueResolved] = evalExprBlock(value, env);
|
||||||
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
const innerEnv = makeInnerEnv(env, name, valueResolved);
|
||||||
return evalEditorBlock(inner, innerEnv);
|
return evalExprBlock(inner, innerEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prettyRU = (rUni: Map<string, Type>) => {
|
const prettyRU = (rUni: Map<string, Type>) => {
|
||||||
|
|
@ -301,7 +304,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En
|
||||||
console.log('===================================')
|
console.log('===================================')
|
||||||
}
|
}
|
||||||
|
|
||||||
const [exprResolved] = evalEditorBlock(expr, staticInnerEnv);
|
const [exprResolved] = evalExprBlock(expr, staticInnerEnv);
|
||||||
const lambdaT = fnType(_ => paramType)(_ => exprResolved.t);
|
const lambdaT = fnType(_ => paramType)(_ => exprResolved.t);
|
||||||
// This is the only place in the code where we actually do something with the 'substitutions'. We compute the type of our lambda function:
|
// This is the only place in the code where we actually do something with the 'substitutions'. We compute the type of our lambda function:
|
||||||
const reduced = reduceUnification(exprResolved.unification);
|
const reduced = reduceUnification(exprResolved.unification);
|
||||||
|
|
@ -331,7 +334,7 @@ export function evalLambdaBlock(paramName: string, expr: ExprBlockState, env: En
|
||||||
t: paramTypeSubstituted,
|
t: paramTypeSubstituted,
|
||||||
unification: new Map(),
|
unification: new Map(),
|
||||||
});
|
});
|
||||||
const [result] = evalEditorBlock(expr, innerEnv);
|
const [result] = evalExprBlock(expr, innerEnv);
|
||||||
if (result.kind === "value") {
|
if (result.kind === "value") {
|
||||||
return result.i;
|
return result.i;
|
||||||
}
|
}
|
||||||
|
|
@ -410,17 +413,23 @@ export function attemptParseLiteral(text: string, env: Environment): Dynamic[] {
|
||||||
|
|
||||||
export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) {
|
export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) {
|
||||||
const bias = outPriority(resolved);
|
const bias = outPriority(resolved);
|
||||||
|
|
||||||
|
console.log('scoreResolved...', resolved);
|
||||||
|
|
||||||
if (resolved.kind === "value") {
|
if (resolved.kind === "value") {
|
||||||
|
console.log('best:', 2, bias);
|
||||||
return 2 + bias;
|
return 2 + bias;
|
||||||
}
|
}
|
||||||
else if (resolved.kind === "unknown") {
|
else if (resolved.kind === "unknown") {
|
||||||
|
console.log('ok:', 1, bias);
|
||||||
return 1 + bias;
|
return 1 + bias;
|
||||||
}
|
}
|
||||||
if (resolved.e instanceof UnifyError) {
|
if (resolved.e instanceof UnifyError) {
|
||||||
|
console.log('bad:', -1, bias);
|
||||||
return -1 + bias; // parameter doesn't match
|
return -1 + bias; // parameter doesn't match
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
console.log('worst:', -2, bias);
|
||||||
return -2 + bias; // even worse: fn is not a function!
|
return -2 + bias; // even worse: fn is not a function!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue