suggestions work again, improve error reporting

This commit is contained in:
Joeri Exelmans 2025-05-24 09:42:26 +02:00
parent 9050581a10
commit 69175c8cb1
12 changed files with 259 additions and 282 deletions

View file

@ -1,11 +1,14 @@
import { useEffect, useState } from 'react';
import './App.css';
import { ExprBlock, type ExprBlockState } from '../expr/ExprBlock';
import { extendedEnv } from '../../context/EnvContext';
import { GlobalContext } from '../../context/GlobalContext';
import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
import { inferType, scoreTypeInfo } from '../../eval/infer_type';
import { ExprBlock, type ExprBlockState } from '../expr/ExprBlock';
import { actionShortcuts } from './actions';
import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, setOfListOfBool, tripleFunctionCallEditorState } from "./configurations";
// import { scoreResolved, type ResolvedType } from './eval';
import './App.css';
const examples: [string, ExprBlockState][] = [
["empty editor" , initialEditorState ],
@ -19,6 +22,7 @@ const examples: [string, ExprBlockState][] = [
["inc" , inc ],
["empty set" , emptySet ],
["factorial" , factorial ],
["set of list of bool" , setOfListOfBool ],
];
type AppState = {
@ -124,7 +128,6 @@ export function App() {
const onSelectExample = (e: React.SyntheticEvent<HTMLSelectElement>) => {
// @ts-ignore
if (e.target.value >= 0) {
// @ts-ignore
// @ts-ignore
pushHistory(_ => examples[e.target.value][1]);
}
@ -168,7 +171,10 @@ export function App() {
state={appState.history.at(-1)!}
setState={pushHistory}
onCancel={() => {}}
score={() => 0}
score={(state: ExprBlockState) => {
const typeInfo = inferType(state, extendedEnv);
return scoreTypeInfo(typeInfo);
}}
/>
</GlobalContext>
</main>

View file

@ -3,7 +3,7 @@ import type { ExprBlockState } from "../expr/ExprBlock";
export const initialEditorState: ExprBlockState = {
kind: "input",
text: "",
value: { kind: "text" },
value: { kind: "gibberish" },
focus: true,
};
@ -75,7 +75,7 @@ export const tripleFunctionCallEditorState: ExprBlockState = {
},
};
export const biggerExample: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"text"},"focus":false},"name":"myListInc","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"myList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"inc","value":{"kind":"name"},"focus":false}}},"input":{"kind":"input","text":"id","value":{"kind":"name"},"focus":true}}},"name":"myList","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"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":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"3","value":{"kind":"literal","type":"Int"},"focus":false}}},"name":"id","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"input","text":"x","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 biggerExample: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"gibberish"},"focus":false},"name":"myListInc","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.map","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"myList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"inc","value":{"kind":"name"},"focus":false}}},"input":{"kind":"input","text":"id","value":{"kind":"name"},"focus":true}}},"name":"myList","value":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"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":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"input","text":"3","value":{"kind":"literal","type":"Int"},"focus":false}}},"name":"id","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"input","text":"x","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 lambda2Params: ExprBlockState = {
"kind": "let",
@ -83,7 +83,7 @@ export const lambda2Params: ExprBlockState = {
"kind": "input",
"text": "",
"value": {
"kind": "text"
"kind": "gibberish"
},
"focus": false
},
@ -131,7 +131,7 @@ export const lambda2Params: ExprBlockState = {
}
};
export const higherOrder: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"text"},"focus":false},"name":"myBinaryApply","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"lambda","focus":false,"paramName":"fn","expr":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}}}}};
export const higherOrder: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"gibberish"},"focus":false},"name":"myBinaryApply","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"lambda","focus":false,"paramName":"fn","expr":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}}}}};
export const higherOrder2Params: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"myBinaryApply","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"2","value":{"kind":"literal","type":"Int"},"focus":false}},"input":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":true}},"name":"myBinaryApply","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"lambda","focus":false,"paramName":"y","expr":{"kind":"lambda","focus":false,"paramName":"fn","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"fn","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"y","value":{"kind":"name"},"focus":true}}}}}};
@ -139,6 +139,8 @@ export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":
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}};
export const emptySet: ExprBlockState = {"kind":"call","fn":{"kind":"input","text":"set.emptySet","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"","value":{"kind":"gibberish"},"focus":true}};
export const factorial: ExprBlockState = {"kind":"lambda","paramName":"factorial","focus":true,"expr":{"kind":"lambda","paramName":"n","focus":true,"expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"leqZero","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":false}},"input":{"kind":"lambda","paramName":"_","focus":false,"expr":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":false}}},"input":{"kind":"lambda","paramName":"_","focus":false,"expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"mulInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":true}},"input":{"kind":"call","fn":{"kind":"input","text":"factorial","value":{"kind":"name"},"focus":true},"input":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"n","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"-1","value":{"kind":"literal","type":"Int"},"focus":false}}}}}}}};
export const setOfListOfBool: ExprBlockState = {"kind":"call","fn":{"kind":"input","text":"set.emptySet","value":{"kind":"name"},"focus":false},"input":{"kind":"call","fn":{"kind":"input","text":"compareLists","value":{"kind":"name"}},"input":{"kind":"input","text":"compareDoubles","value":{"kind":"name"}}}};

View file

@ -1,7 +1,6 @@
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";
@ -30,11 +29,7 @@ function nestedFnProperties({state, setState, score}: CallBlockProps, env: Envir
setState(state => state.input); // we become our input
};
const scoreFn = (fnSuggestion: ExprBlockState) => {
return score({
kind: "call",
fn: fnSuggestion,
input: state.input,
});
return score({ ...state, fn: fnSuggestion });
};
return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn};
}
@ -47,24 +42,11 @@ function nestedInputProperties({state, setState, score}: CallBlockProps, env: En
setState(state => /*addFocusRightMost*/(state.fn)); // we become our function
};
const scoreInput = (inputSuggestion: ExprBlockState) => {
return score({
kind: "call",
fn: state.fn,
input: inputSuggestion,
});
return score({ ...state, 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);
@ -80,9 +62,6 @@ export function CallBlock(props: CallBlockProps) {
{/* 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()
@ -113,20 +92,20 @@ function FunctionHeader(props) {
}
}
function InputParams({ depth, errorDepth, ...rest }) {
function InputParams({ ...rest }) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
const isOffending = depth === errorDepth;
const typeInfo = inferTypeCall(rest.state, env);
const inputEnv = typeInfo.fn.newEnv;
const isOffending = rest.state.err;
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}>
<EnvContext value={inputEnv}>
<ExprBlock
{...nestedInputProperties(rest as CallBlockProps, env)}
/>

View file

@ -1,7 +1,5 @@
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";
@ -13,6 +11,7 @@ import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInB
import "./ExprBlock.css";
import { Input } from "../other/Input";
import { getActions } from "../app/actions";
import { inferType, type Type } from "../../eval/infer_type";
export type ExprBlockState =
InputBlockState
@ -49,16 +48,14 @@ export function ExprBlock(props: ExprBlockProps) {
const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) =>
[shortcut, (e) => { e.preventDefault(); action(); }]))
// return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
const typeInfo = inferType(props.state, env);
return <span className={"editor"}>
return <span className={"editor" + (typeInfo.err ? " error" : "")}>
{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> */}
{typeInfo.err &&
<div>
{typeInfo.err.message.split('\n')[1]}
</div>}
<Input
placeholder="<c>"
text=""

View file

@ -3,16 +3,14 @@ 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";
import { inferType, inferTypeInput, type Environment, type Type } from "../../eval/infer_type";
import { Type as TypeBlock } from "../other/Type";
interface Literal {
kind: "literal";
@ -21,47 +19,60 @@ interface Literal {
interface Name {
kind: "name";
}
interface Text {
kind: "text";
interface Gibberish {
kind: "gibberish";
}
export type InputValueType = Literal | Name | Text;
export type InputValueType = Literal | Name | Gibberish;
export interface InputBlockState {
kind: "input";
text: string;
value: InputValueType;
focus: boolean
focus?: boolean;
}
// export type SuggestionType = ["literal"|"name", string, ResolvedType];
// export type PrioritizedSuggestionType = [number, ...SuggestionType];
export type PrioritizedSuggestionType = [number, Type, InputBlockState];
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]),
const attemptLiterals = [
["Double", text => (text !== '') && !Number.isNaN(Number(text))],
["Int" , text => /^-?[0-9]+$/.test(text)]
] as [string, (text: string) => boolean][];
// // 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)
// }
const computeSuggestions = (
text: string,
env: Environment,
score: InputBlockProps['score'],
): PrioritizedSuggestionType[] => {
const ls = [
...attemptLiterals
.filter(([_, test]) => test(text))
.map(([typename]) => ({
kind: "input",
text,
value: {
kind: "literal",
type: typename,
},
})),
...trie.suggest(env.names)(text)(Infinity)
.map(([name]) => ({
kind: "input",
text: name,
value: {
kind: "name",
},
})),
];
// return []; // <-- uncomment to disable suggestions (useful for debugging)
return ls.map((state) => [score(state), inferType(state, env).type, state] as PrioritizedSuggestionType)
.sort(([a],[b]) => b-a);
}
export function InputBlock({ state, setState, /*score,*/ onCancel }: InputBlockProps) {
export function InputBlock({ state, setState, score, onCancel }: InputBlockProps) {
const {text, focus} = state;
const globalContext = useContext(GlobalContext);
const env = useContext(EnvContext);
@ -70,8 +81,7 @@ export function InputBlock({ state, setState, /*score,*/ onCancel }: InputBlockP
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(() => [], []);
const suggestions = useMemo(() => computeSuggestions(text, env, score), [text, score, env]);
useEffect(() => {
@ -96,21 +106,8 @@ export function InputBlock({ state, setState, /*score,*/ onCancel }: InputBlockP
}
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 [_priority, _type, inputState] = suggestions[i];
setState(_ => inputState);
};
const extraHandlers = {
@ -154,7 +151,7 @@ export function InputBlock({ state, setState, /*score,*/ onCancel }: InputBlockP
i={i} setI={setI} />
</span>
</Input>
::<Type type={typeInfo.type} />
::<TypeBlock type={typeInfo.type} />
</>
}
@ -176,10 +173,10 @@ interface SuggestionProps {
j: number;
onSelect: any;
highlighted: boolean;
// suggestion: PrioritizedSuggestionType;
suggestion: PrioritizedSuggestionType;
}
function Suggestion({ setI, j, onSelect, highlighted, /*suggestion: [priority, kind, text, resolved]*/ }: SuggestionProps) {
function Suggestion({ setI, j, onSelect, highlighted, suggestion: [priority, type, {text, value: {kind} }] }: SuggestionProps) {
const onMouseEnter = j => () => {
setI(j);
};
@ -192,7 +189,7 @@ function Suggestion({ setI, j, onSelect, highlighted, /*suggestion: [priority, k
className={(highlighted ? " selected" : "")}
onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}>
{/* ({priority}) ({kind}) {text} :: <Type type={resolved.t} /> */}
({priority}) ({kind}) {text} :: <TypeBlock type={type} />
</div>
}

View file

@ -71,10 +71,7 @@ export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
state={state.expr}
setState={setExpr}
onCancel={() => setState(state => state.expr)}
score={(s) => {
// console.log('suggestionPriority of lambdaInner... just passing through');
return score(s);
}}
score={suggestion => score({...state, expr: suggestion})}
/>
</EnvContext>
</div>

View file

@ -2,7 +2,6 @@ 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";
@ -40,16 +39,7 @@ function DeclColumns({state, setState, score}) {
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);
const {value: valueTypeInfo, innerEnv} = inferTypeLet(state, env);
return <>
<span className="keyword column">let&nbsp;</span>
@ -63,14 +53,14 @@ function DeclColumns({state, setState, score}) {
onTextChange={name => setState(state => ({...state, name}))}
extraHandlers={{}}
/>
:: <Type type={paramType} />
:: <Type type={valueTypeInfo.type} />
</span>
<span className="keyword column">&nbsp;=&nbsp;</span>
<span className="column">
<ExprBlock
state={state.value}
setState={setValue}
score={() => 0}
score={suggestion => score({ ...state, value: suggestion })}
onCancel={() => setState(state => state.inner)} // keep inner
/>
</span>
@ -80,7 +70,7 @@ function DeclColumns({state, setState, score}) {
<DeclColumns
state={state.inner}
setState={setInner}
score={score}
score={suggestion => score({ ...state, inner: suggestion })}
/>
</EnvContext>
}
@ -93,14 +83,14 @@ function InnerMost({state, setState, score}) {
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 {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}
score={suggestion => score({ ...state, inner: suggestion })}
/>
</EnvContext>;
}
@ -109,7 +99,7 @@ function InnerMost({state, setState, score}) {
<ExprBlock
state={state.inner}
setState={setInner}
score={score}
score={suggestion => score({ ...state, inner: suggestion })}
onCancel={onCancel} // keep value
/>
</EnvContext>

View file

@ -1,7 +1,7 @@
import { createContext } from "react";
import { getDefaultTypeParser, module2Env, ModuleStd } from "dope2";
// import type { Dynamic, Environment } from "./eval";
import type { Environment } from "./infer_type";
import type { Environment } from "../eval/infer_type";
export const functionWith3Params = i => j => k => i+j+k;
export const functionWith4Params = i => j => k => l => i+j+k+l;

View file

@ -460,53 +460,6 @@
// }
// }
// function parseLiteral(text: string, type: string, env: Environment): [ResolvedType,Environment] {
// // dirty
// if (type === "Int") {
// return parseAsInt(text, env);
// }
// if (type === "Double") {
// return parseAsDouble(text, env);
// }
// return makeError(env, new Error("Failed to parse"));
// }
// function parseAsDouble(text: string, env: Environment): [ResolvedType,Environment] {
// if (text !== '') {
// const num = Number(text);
// if (!Number.isNaN(num)) {
// return [{
// kind: "value",
// i: num,
// t: Double,
// unification: new Map(),
// }, env];
// }
// }
// return makeError(env, new Error("Failed to parse as Double"));
// }
// function parseAsInt(text: string, env: Environment): [ResolvedType,Environment] {
// if (text !== '') {
// try {
// return [{
// kind: "value",
// i: BigInt(text),
// t: Int,
// unification: new Map(),
// }, env]; // may throw
// }
// catch {}
// }
// return makeError(env, new Error("Failed to parse as Int"));
// }
// const literalParsers = [parseAsDouble, parseAsInt];
// export function attemptParseLiteral(text: string, env: Environment): Dynamic[] {
// return literalParsers.map(parseFn => parseFn(text, env))
// .map(([resolved]) => resolved)
// .filter((resolved) => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
// }
// export function scoreResolved(resolved: ResolvedType, outPriority: (s:ResolvedType) => number) {
// const bias = outPriority(resolved);

View file

@ -1,4 +1,4 @@
import { Double, eqType, fnType, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, prettySS, recomputeTypeVars, substitute, trie, TYPE_VARS, UNBOUND_SYMBOLS, unify } from "dope2";
import { Double, eqType, fnType, IncompatibleTypesError, Int, mergeSubstitutionsN, occurring, prettyS, prettySS, prettyT, recomputeTypeVars, substitute, SubstitutionCycle, trie, TYPE_VARS, UNBOUND_SYMBOLS, unify } from "dope2";
import type { CallBlockState } from "../component/expr/CallBlock";
import type { ExprBlockState } from "../component/expr/ExprBlock";
@ -18,20 +18,35 @@ export interface Type {
export type Substitutions = Map<string, Type>;
export interface TypeInfo {
interface TypeInfoCommon {
type: Type;
subs: Substitutions;
newEnv: Environment;
err?: IncompatibleTypesError;
}
export interface TypeInfoLambda extends TypeInfo {
export interface TypeInfoInput extends TypeInfoCommon {
kind: "input";
}
export interface TypeInfoCall extends TypeInfoCommon {
kind: "call";
fn: TypeInfo;
input: TypeInfo;
}
export interface TypeInfoLet extends TypeInfoCommon {
kind: "let";
innerEnv: Environment;
value: TypeInfo;
inner: TypeInfo;
}
export interface TypeInfoLambda extends TypeInfoCommon {
kind: "lambda";
paramType: Type;
inner: TypeInfo;
innerEnv: Environment;
}
export interface TypeInfoCall extends TypeInfo {
inputEnv: Environment;
}
export type TypeInfo = TypeInfoInput | TypeInfoCall | TypeInfoLet | TypeInfoLambda;
export function inferType(s: ExprBlockState, env: Environment): TypeInfo {
if (s.kind === "input") {
@ -48,13 +63,14 @@ export function inferType(s: ExprBlockState, env: Environment): TypeInfo {
}
}
export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfo {
export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfoInput {
if (s.value.kind === "literal") {
const type = {
Int: Int,
Double: Double,
}[s.value.type] as Type;
return {
kind: "input",
type,
subs: new Map(),
newEnv: env,
@ -67,18 +83,21 @@ export function inferTypeInput(s: InputBlockState, env: Environment): TypeInfo {
? [found.t, env]
: rewriteType(found.t, env);
return {
kind: "input",
type,
subs: new Map(),
newEnv,
};
}
}
// kind === "text", or name not found
// kind === "gibberish", or name not found
const [type, newEnv] = typeUnknown(env);
return {
kind: "input",
type,
subs: new Map(),
newEnv,
err: new Error("Gibberish"),
}
}
@ -104,41 +123,59 @@ export function inferTypeCall(s: CallBlockState, env: Environment): TypeInfoCall
type = returnType;
newEnv = envWithReturn;
}
const mergedSubs = mergeSubstitutionsN([
fnTypeInfo.subs,
inputTypeInfo.subs,
subs,
]);
let mergedSubs;
try {
mergedSubs = mergeSubstitutionsN([
fnTypeInfo.subs,
inputTypeInfo.subs,
subs,
]);
} catch (e) {
if (e instanceof SubstitutionCycle) {
// wrap error
throw new IncompatibleTypesError(fnTypeInfo.type, fakeFnType, e);
}
throw e;
}
return {
kind: "call",
type,
subs: mergedSubs,
newEnv,
inputEnv,
fn: fnTypeInfo,
input: inputTypeInfo,
};
}
catch (e) {
if (e instanceof IncompatibleTypesError) {
const [type, newEnv] = typeUnknown(env);
return {
kind: "call",
type,
subs: new Map(),
newEnv,
err: e,
inputEnv,
fn: fnTypeInfo,
input: inputTypeInfo,
}
}
}
}
export function inferTypeLet(s: LetInBlockState, env: Environment): TypeInfoLambda {
export function inferTypeLet(s: LetInBlockState, env: Environment): TypeInfoLet {
const valTypeInfo = inferType(s.value, env);
const innerEnv = {
names: trie.insert(env.names)(s.name)({kind: "value", t: valTypeInfo.type}),
typevars: env.typevars,
};
const innerTypeInfo = inferType(s.inner, innerEnv);
return {
...inferType(s.inner, innerEnv),
paramType: valTypeInfo.type,
kind: "let",
type: innerTypeInfo.type,
subs: innerTypeInfo.subs,
newEnv: env,
value: valTypeInfo,
inner: innerTypeInfo,
innerEnv,
};
}
@ -154,21 +191,23 @@ export function inferTypeLambda(s: LambdaBlockState, env: Environment): TypeInfo
names: trie.insert(env.names)(s.paramName)({kind: "unknown", t: paramType}),
typevars: env.typevars.union(occurring(paramType) as Set<string>),
};
const typeInfo = inferType(s.expr, innerEnv);
const subsWithoutPType = new Map(typeInfo.subs);
const innerTypeInfo = inferType(s.expr, innerEnv);
const subsWithoutPType = new Map(innerTypeInfo.subs);
subsWithoutPType.delete(paramTypeVar);
const inferredPType = substitute(paramType, typeInfo.subs, []);
const inferredPType = substitute(paramType, innerTypeInfo.subs, []);
const inferredPType2 = rewriteInferredType(inferredPType, env);
if (eqType(inferredPType2)(paramType)) {
return {
type: fnType(_ => paramType)(_ => typeInfo.type),
kind: "lambda",
type: fnType(_ => paramType)(_ => innerTypeInfo.type),
subs: subsWithoutPType,
newEnv: env, // no change
paramType,
inner: innerTypeInfo,
innerEnv,
};
}
if ((iterations++) == 4) {
if ((iterations++) == 10) {
throw new Error("too many iterations!");
}
// console.log("-----------------", iterations);
@ -192,9 +231,10 @@ const highestTypeVar2 = (typevars: Iterable<string>) => {
}
function rewriteType(type: Type, env: Environment): [Type, Environment] {
const [recomputed] = recomputeTypeVars([type], highestTypeVar2(env.typevars)+1);
return [type, {
const newTypeVars = occurring(recomputed);
return [recomputed, {
names: env.names,
typevars: env.typevars.union(occurring(recomputed)),
typevars: env.typevars.union(newTypeVars),
}];
}
function typeUnknown(env: Environment): [Type, Environment] {
@ -218,3 +258,19 @@ function rewriteInferredType(type: Type, env: Environment) {
}
return substitute(type, substitutions, []);
}
export function scoreTypeInfo(typeInfo: TypeInfo): number {
const bias = typeInfo.err ? -1 : 0;
if (typeInfo.kind === "input") {
return bias;
}
else if (typeInfo.kind === "call") {
return bias + scoreTypeInfo(typeInfo.fn) + scoreTypeInfo(typeInfo.input);
}
else if (typeInfo.kind === "let") {
return bias + scoreTypeInfo(typeInfo.value) + scoreTypeInfo(typeInfo.inner);
}
else if (typeInfo.kind === "lambda") {
return bias + scoreTypeInfo(typeInfo.inner);
}
return 0; // shut up typescript
}