diff --git a/src/CallBlock.css b/src/CallBlock.css index 5200efc..907e3e7 100644 --- a/src/CallBlock.css +++ b/src/CallBlock.css @@ -64,13 +64,9 @@ .outputParam { text-align: left; vertical-align: top; - /* margin-left: 28px; */ padding: 0px; - /* padding-left: 14px; */ display: inline-block; - /* border: solid 2px orange; */ background-color: rgb(233, 224, 205); - /* border-radius: 10px; */ width: 100%; } @@ -83,28 +79,28 @@ color: black; } .functionBlock.unifyError > .functionParams > .outputParam > .inputParam:after { - border-left-color: pink; + background-color: pink; } .functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam { background-color: pink; color: black; } .functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam:after { - border-left-color: pink; + background-color: pink; } .functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam > .inputParam { background-color: pink; color: black; } .functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam > .inputParam:after { - border-left-color: pink; + 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 { - border-left-color: pink; + background-color: pink; } .inputParam.offending { @@ -112,5 +108,5 @@ color: white !important; } .inputParam.offending:after { - border-left-color: darkred !important; + background-color: darkred !important; } diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index f63f58c..9ed749c 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -3,7 +3,7 @@ import { useContext, useInsertionEffect } from "react"; import { Editor, type EditorState } from "./Editor"; import { Value } from "./Value"; import { type SetStateFn, type State2Props } from "./Editor"; -import { evalCallBlock, evalEditorBlock, scoreResolved } from "./eval"; +import { evalCallBlock, evalCallBlock2, evalEditorBlock, scoreResolved } from "./eval"; import { type ResolvedType } from "./eval"; import "./CallBlock.css"; import { EnvContext } from "./EnvContext"; @@ -97,6 +97,7 @@ export function CallBlockNoSugar({ state, setState, suggestionPriority }: CallBl evalEditorBlock(state.fn, env), // fn *may* be set inputSuggestion[2], // suggestions will be for input suggestionPriority, // priority function we get from parent block + env, )} /> @@ -105,8 +106,8 @@ export function CallBlockNoSugar({ state, setState, suggestionPriority }: CallBl ; } -function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: SuggestionType) => number) { - const resolved = evalCallBlock(fn, input); +function computePriority(fn: ResolvedType, input: ResolvedType, outPriority: (s: SuggestionType) => number, env) { + const resolved = evalCallBlock2(fn, input, env); return scoreResolved(resolved, outPriority); } @@ -131,6 +132,7 @@ function FunctionHeader({ fn, setFn, input, onFnCancel, suggestionPriority }) { fnSuggestion[2], evalEditorBlock(fn.input, env), suggestionPriority, + env, )} />; } @@ -153,6 +155,7 @@ function FunctionName({fn, setFn, onFnCancel, suggestionPriority, input}) { fnSuggestion[2], // suggestions will be for function evalEditorBlock(input, env), // input *may* be set suggestionPriority, // priority function we get from parent block + env, )} /> ; @@ -180,6 +183,7 @@ function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDe evalEditorBlock(fn.fn, env), inputSuggestion[2], suggestionPriority, + env, )} />; } @@ -199,6 +203,7 @@ function InputParams({ fn, setFn, input, setInput, onInputCancel, depth, errorDe evalEditorBlock(fn, env), // fn *may* be set inputSuggestion[2], // suggestions will be for input suggestionPriority, // priority function we get from parent block + env, )} /> ; diff --git a/src/Editor.css b/src/Editor.css index f083c9a..5253c6f 100644 --- a/src/Editor.css +++ b/src/Editor.css @@ -3,10 +3,11 @@ } .typeSignature { - display: none; - position: absolute; + display: inline-block; + /* display: none; */ + /* position: absolute; */ z-index: 1; - background-color: white; + /* background-color: white; */ /* border: 1px solid black; */ } diff --git a/src/Editor.tsx b/src/Editor.tsx index e6319be..9abc86b 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -71,10 +71,6 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr const globalContext = useContext(CommandContext); const onCommand = (e: React.KeyboardEvent) => { - console.log(e); - - // const type = getType(state.resolved); - // const commands = getCommands(type); const commands = ['e', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', 'L', '=', '.', 'c', 'a']; if (!commands.includes(e.key)) { return; @@ -199,7 +195,7 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr return {renderBlock()}
- +  :: 
{ } const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) => number): PrioritizedSuggestionType[] => { - const literals = attemptParseLiteral(text); + const literals = attemptParseLiteral(text, env); const ls: SuggestionType[] = [ // literals diff --git a/src/LambdaBlock.css b/src/LambdaBlock.css index e69de29..208e0f1 100644 --- a/src/LambdaBlock.css +++ b/src/LambdaBlock.css @@ -0,0 +1,5 @@ +.lambdaExpr { + display: inline-block; + border: solid 1px darkgrey; + margin: 2px; +} \ No newline at end of file diff --git a/src/LambdaBlock.tsx b/src/LambdaBlock.tsx index 663e84b..abf684b 100644 --- a/src/LambdaBlock.tsx +++ b/src/LambdaBlock.tsx @@ -7,6 +7,7 @@ import { growEnv, TYPE_VARS } from "dope2"; import { autoInputWidth } from "./util/dom_trickery"; import "./LambdaBlock.css"; +import { getUnusedTypeVar } from "./eval"; export interface LambdaBlockState { @@ -31,12 +32,18 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr ...state, paramName, })); - const setExpr = callback => setState(state => ({ ...state, expr: callback(state.expr), })); + const onChangeName = (e) => { + if (state.paramName === "" && e.key === 'Backspace') { + setState(state => state.expr); + e.preventDefault(); + } + }; + useEffect(() => { nameRef.current?.focus(); }, []); @@ -46,7 +53,7 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr const innerEnv = growEnv(env)(state.paramName)({ kind: "unknown", i: undefined, - t: TYPE_VARS[0], + t: getUnusedTypeVar(env), }); return @@ -58,13 +65,14 @@ export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockPr className='editable' value={state.paramName} placeholder="" + onKeyDown={onChangeName} onChange={e => setParamName(e.target.value)} />   :   - +
- +
} diff --git a/src/eval.ts b/src/eval.ts index 5039e88..843b52e 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -1,4 +1,4 @@ -import { assignFnSubstitutions, Double, fnType, getSymbol, growEnv, Int, makeGeneric, NotAFunctionError, prettyT, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } from "dope2"; +import { assignFnSubstitutions, dict, Double, fnType, getSymbol, growEnv, Int, makeGeneric, NotAFunctionError, prettyT, set, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } from "dope2"; import type { EditorState } from "./Editor"; import type { InputValueType, SuggestionType } from "./InputBlock"; @@ -30,7 +30,11 @@ export interface Unknown { substitutions: Map; } -export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a), substitutions: new Map() }; +export const entirelyUnknown = env => ({ + kind: "unknown", + t: getUnusedTypeVar(env), + substitutions: new Map(), +} as Unknown); // the value of every block is either known (Dynamic), an error, or unknown export type ResolvedType = Dynamic | DeepError | Unknown; @@ -40,9 +44,7 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => { return evalInputBlock(s.text, s.value, env); } if (s.kind === "call") { - const fn = evalEditorBlock(s.fn, env); - const input = evalEditorBlock(s.input, env); - return evalCallBlock(fn, input); + return evalCallBlock(s.fn, s.input, env); } if (s.kind === "let") { return evalLetInBlock(s.value, s.name, s.inner, env); @@ -51,12 +53,12 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => { return evalLambdaBlock(s.paramName, s.expr, env); } - return entirelyUnknown; // todo + return entirelyUnknown(env); // todo }; export function evalInputBlock(text: string, value: InputValueType, env): ResolvedType { if (value.kind === "literal") { - return parseLiteral(text, value.type); + return parseLiteral(text, value.type, env); } else if (value.kind === "name") { const found = trie.get(env.name2dyn)(text); @@ -67,11 +69,11 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv substitutions: new Map(), }; } else { - return entirelyUnknown; + return entirelyUnknown(env); } } else { // kind === "text" -> unresolved - return entirelyUnknown; + return entirelyUnknown(env); } } @@ -79,48 +81,48 @@ const mergeMaps = (...maps: Map[]) => { return new Map(maps.flatMap(m => [...m])); } -export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedType { - if (getSymbol(fn.t) !== symbolFunction) { - if (fn.kind === "unknown") { - return entirelyUnknown; // don't flash everything red, giving the user a heart attack +export function evalCallBlock2(fnResolved: ResolvedType, inputResolved: ResolvedType, env): ResolvedType { + if (getSymbol(fnResolved.t) !== symbolFunction) { + if (fnResolved.kind === "unknown") { + return entirelyUnknown(env); // don't flash everything red, giving the user a heart attack } - // worst outcome: we know nothing about the result! + // worst outcome: we know nothing about the result! return { kind: "error", - e: new NotAFunctionError(`${prettyT(fn.t)} is not a function type!`), - t: entirelyUnknown.t, - substitutions: mergeMaps(fn.substitutions, input.substitutions), + e: new NotAFunctionError(`${prettyT(fnResolved.t)} is not a function type!`), + t: getUnusedTypeVar(env), + substitutions: mergeMaps(fnResolved.substitutions, inputResolved.substitutions), depth: 0, }; } try { // fn is a function... - const [outType, substitutions] = assignFnSubstitutions(fn.t, input.t); // may throw + const [outType, substitutions] = assignFnSubstitutions(fnResolved.t, inputResolved.t); // may throw - const mergedSubstitutions = mergeMaps(substitutions, fn.substitutions, input.substitutions); + const mergedSubstitutions = mergeMaps(substitutions, fnResolved.substitutions, inputResolved.substitutions); - if (input.kind === "error") { + if (inputResolved.kind === "error") { return { kind: "error", - e: input.e, // bubble up the error + e: inputResolved.e, // bubble up the error depth: 0, t: outType, substitutions: mergedSubstitutions, }; } - if (fn.kind === "error") { + if (fnResolved.kind === "error") { // also bubble up return { kind: "error", - e: fn.e, - depth: fn.depth+1, + e: fnResolved.e, + depth: fnResolved.depth+1, t: outType, substitutions: mergedSubstitutions, }; } // if the above statement did not throw => types are compatible... - if (input.kind === "value" && fn.kind === "value") { - const outValue = fn.i(input.i); + if (inputResolved.kind === "value" && fnResolved.kind === "value") { + const outValue = fnResolved.i(inputResolved.i); return { kind: "value", i: outValue, @@ -140,31 +142,46 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy catch (e) { if ((e instanceof UnifyError)) { // even though fn was incompatible with the given parameter, we can still suppose that our output-type will be that of fn...? - const outType = fn.t.params[1](fn.t); + const outType = fnResolved.t.params[1](fnResolved.t); return { kind: "error", e, depth: 0, t: outType, - substitutions: mergeMaps(fn.substitutions, input.substitutions), + substitutions: mergeMaps(fnResolved.substitutions, inputResolved.substitutions), }; } throw e; } } +export function evalCallBlock(fn: EditorState, input: EditorState, env): ResolvedType { + const fnResolved = evalEditorBlock(fn, env); + const inputResolved = evalEditorBlock(input, env); + return evalCallBlock2(fnResolved, inputResolved, env); +} + export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, env): ResolvedType { const valueResolved = evalEditorBlock(value, env); const innerEnv = makeInnerEnv(env, name, valueResolved) return evalEditorBlock(inner, innerEnv); } +export function getUnusedTypeVar(env) { + for (let i=0; ; i++) { + if (!dict.has(env.typeDict)(TYPE_VARS[i])) { + return TYPE_VARS[i]; + } + } +} + export function evalLambdaBlock(paramName: string, expr: EditorState, env): ResolvedType { + const paramType = getUnusedTypeVar(env); const fn = (x: any) => { const innerEnv = makeInnerEnv(env, paramName, { kind: "value", i: x, - t: TYPE_VARS[0], + t: paramType, substitutions: new Map(), }); const result = evalEditorBlock(expr, innerEnv); @@ -175,11 +192,11 @@ export function evalLambdaBlock(paramName: string, expr: EditorState, env): Reso // static env: we only know the name and the type const staticInnerEnv = makeInnerEnv(env, paramName, { kind: "unknown", // parameter value is not statically known - t: TYPE_VARS[0], + t: paramType, substitutions: new Map(), }); const abstractOutput = evalEditorBlock(expr, staticInnerEnv); - const t = fnType(_ => entirelyUnknown.t)(_ => abstractOutput.t); + const t = fnType(_ => paramType)(_ => abstractOutput.t); const T = substitute(t, abstractOutput.substitutions, []) return { kind: "value", @@ -194,18 +211,18 @@ export function haveValue(resolved: ResolvedType) { return resolved.kind === "value"; } -function parseLiteral(text: string, type: string): ResolvedType { +function parseLiteral(text: string, type: string, env): ResolvedType { // dirty if (type === "Int") { - return parseAsInt(text); + return parseAsInt(text, env); } if (type === "Double") { - return parseAsDouble(text); + return parseAsDouble(text, env); } - return entirelyUnknown; + return entirelyUnknown(env); } -function parseAsDouble(text: string): ResolvedType { +function parseAsDouble(text: string, env): ResolvedType { if (text !== '') { const num = Number(text); if (!Number.isNaN(num)) { @@ -217,9 +234,9 @@ function parseAsDouble(text: string): ResolvedType { }; } } - return entirelyUnknown; + return entirelyUnknown(env); } -function parseAsInt(text: string): ResolvedType { +function parseAsInt(text: string, env): ResolvedType { if (text !== '') { try { return { @@ -231,13 +248,13 @@ function parseAsInt(text: string): ResolvedType { } catch {} } - return entirelyUnknown; + return entirelyUnknown(env); } const literalParsers = [parseAsDouble, parseAsInt]; -export function attemptParseLiteral(text: string): Dynamic[] { - return literalParsers.map(parseFn => parseFn(text)) +export function attemptParseLiteral(text: string, env): Dynamic[] { + return literalParsers.map(parseFn => parseFn(text, env)) .filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[]; }