nicer looking

This commit is contained in:
Joeri Exelmans 2025-05-17 09:25:13 +02:00
parent 8abbac4bc9
commit e850952738
14 changed files with 547 additions and 104 deletions

8
pnpm-lock.yaml generated
View file

@ -10,7 +10,7 @@ importers:
dependencies:
dope2:
specifier: git+https://deemz.org/git/joeri/dope2.git
version: git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381
version: git+https://deemz.org/git/joeri/dope2.git#0096bb5559224b4c9bbe74317e07dc71cfc09c70
react:
specifier: ^19.1.0
version: 19.1.0
@ -633,8 +633,8 @@ packages:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dope2@git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381:
resolution: {commit: d75bf9f0f200769a5248ace8e4e2ace04fd60381, repo: https://deemz.org/git/joeri/dope2.git, type: git}
dope2@git+https://deemz.org/git/joeri/dope2.git#0096bb5559224b4c9bbe74317e07dc71cfc09c70:
resolution: {commit: 0096bb5559224b4c9bbe74317e07dc71cfc09c70, repo: https://deemz.org/git/joeri/dope2.git, type: git}
version: 0.0.1
dunder-proto@1.0.1:
@ -1762,7 +1762,7 @@ snapshots:
depd@2.0.0: {}
dope2@git+https://deemz.org/git/joeri/dope2.git#d75bf9f0f200769a5248ace8e4e2ace04fd60381:
dope2@git+https://deemz.org/git/joeri/dope2.git#0096bb5559224b4c9bbe74317e07dc71cfc09c70:
dependencies:
functional-red-black-tree: 1.0.1

View file

@ -3,20 +3,23 @@ import './App.css';
import { CommandContext } from './CommandContext';
import { Editor, type EditorState } from './Editor';
import { extendedEnv } from './EnvContext';
import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations";
import { biggerExample, initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations";
import { evalEditorBlock } from "./eval";
const commands: [string, string[], string][] = [
["call" , ['c' ], "call" ],
["eval" , ['e','Tab','Enter'], "eval" ],
["transform", ['t', '.' ], "transform" ],
["let" , ['l', '=', 'a' ], "let ... in ..."],
["let" , ['l', '=' ], "let ... in ..."],
["lambda" , ['a' ], "lambda" ],
];
const examples: [string, EditorState][] = [
["empty editor", initialEditorState],
["push to list", nonEmptyEditorState],
["function w/ 4 params", tripleFunctionCallEditorState]];
["function w/ 4 params", tripleFunctionCallEditorState],
["bigger example", biggerExample],
];
type AppState = {
history: EditorState[],

View file

@ -1,11 +1,16 @@
.functionBlock {
border: solid 1px darkgray;
display: inline-block;
margin: 4px;
margin: 2px;
color: black;
background-color: white;
vertical-align: top;
}
/* * {
vertical-align: text-top;
} */
.functionName {
/* text-align: center; */
background-color: white;
@ -14,11 +19,11 @@
.inputParam:after {
content: "";
position: absolute;
border: solid transparent;
border-width: 10px;
right: -19px;
top: 0;
bottom:0;
right: 0;
clip-path: polygon(1% 0%, 100% 50%, 0% 100%);
height: 100%;
aspect-ratio: 0.25/1;
transform: translateX(99%);
}
.inputParam {
margin-right: 20px;
@ -30,7 +35,7 @@
/* Count nested level AFTER .outputParam (resets the depth) */
.outputParam > .inputParam:after {
border-left-color: rgb(242, 253, 146);
background-color: rgb(242, 253, 146);
}
.outputParam > .inputParam {
background-color: rgb(242, 253, 146);
@ -39,19 +44,19 @@
background-color: rgb(180, 248, 214);
}
.outputParam > .inputParam > .inputParam:after {
border-left-color: rgb(180, 248, 214);
background-color: rgb(180, 248, 214);
}
.outputParam > .inputParam > .inputParam > .inputParam {
background-color: rgb(153, 212, 214);
}
.outputParam > .inputParam > .inputParam > .inputParam:after {
border-left-color: rgb(153, 212, 214);
background-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);
background-color: rgb(111, 186, 209);
}
.typeAnnot {

View file

@ -1,8 +1,30 @@
.editor {
}
.typeSignature {
display: none;
position: absolute;
z-index: 1;
background-color: white;
/* border: 1px solid black; */
}
.editor:hover > .typeSignature {
display: inline-block;
}
.commandInput {
width: 90px;
width: 30px;
margin-left: 10px;
}
.keyword {
color: blue;
font-weight: bold;
/* vertical-align: top; */
}
* {
/* vertical-align: top; */
}

View file

@ -9,7 +9,7 @@ import { evalEditorBlock } from "./eval";
import { CommandContext } from "./CommandContext";
import "./Editor.css";
import { EnvContext } from "./EnvContext";
import type { LambdaBlockState } from "./LambdaBlock";
import { LambdaBlock, type LambdaBlockState } from "./LambdaBlock";
import { LetInBlock, type LetInBlockState } from "./LetInBlock";
import { initialEditorState } from "./configurations";
import { focusNextElement, focusPrevElement } from "./util/dom_trickery";
@ -71,9 +71,11 @@ 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', '=', '.', 'c'];
const commands = ['e', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', 'L', '=', '.', 'c', 'a'];
if (!commands.includes(e.key)) {
return;
}
@ -124,18 +126,35 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr
}
// l -> Let ... in ...
// = -> assign to name
if (e.key === 'l' || e.key === '=') {
if (e.key === 'l' || e.key === '=' && !e.shiftKey) {
// we become LetInBlock
setState(state => ({
kind: "let",
inner: removeFocus(initialEditorState),
name: "",
value: removeFocus(state),
resolved: undefined,
}));
globalContext?.doHighlight.let();
return;
}
if (e.key === 'L' || e.key === '=' && e.shiftKey) {
setState(state => ({
kind: "let",
inner: removeFocus(state),
name: "",
value: removeFocus(initialEditorState),
}));
}
// a -> lAmbdA
if (e.key === "a") {
setState(state => ({
kind: "lambda",
paramName: "",
expr: removeFocus(state),
}));
globalContext?.doHighlight.lambda();
return;
}
};
const renderBlock = () => {
@ -169,22 +188,26 @@ export function Editor({state, setState, onCancel, suggestionPriority}: EditorPr
suggestionPriority={suggestionPriority}
/>;
case "lambda":
return <></>;
return <LambdaBlock
state={state}
setState={setState as (callback:(p:LambdaBlockState)=>EditorState)=>void}
suggestionPriority={suggestionPriority}
/>;
}
}
const resolved = evalEditorBlock(state, env);
return <>
return <span className="editor">
{renderBlock()}
<div className="typeSignature">
&nbsp;::&nbsp;<Type type={getType(resolved)} />
<Type type={getType(resolved)} />
</div>
<input
ref={commandInputRef}
spellCheck={false}
className="editable commandInput"
placeholder={`<command>`}
placeholder={`<c>`}
onKeyDown={onCommand}
value={""}
onChange={() => {}} />
</>;
</span>;
}

View file

@ -40,13 +40,13 @@ interface InputBlockProps extends State2Props<InputBlockState> {
const computeSuggestions = (text, env, suggestionPriority: (s: SuggestionType) => number): PrioritizedSuggestionType[] => {
const literals = attemptParseLiteral(text);
const ls = [
const ls: SuggestionType[] = [
// literals
... literals.map((lit) => ["literal", text, lit]),
// names
... trie.suggest(env.name2dyn)(text)(Infinity)
.map(([name,type]) => ["name", name, type]),
.map(([name,type]) => ["name", name, {...type, substitutions: new Map()}]),
]
// return ls;
return ls

0
src/LambdaBlock.css Normal file
View file

View file

@ -1,6 +1,12 @@
import type { EditorState } from "./Editor";
import type { Dynamic } from "./eval";
import type { ResolvedType } from "./eval";
import { useContext, useEffect, useRef } from "react";
import { Editor, type EditorState, type State2Props } from "./Editor";
import type { SuggestionType } from "./InputBlock";
import { EnvContext } from "./EnvContext";
import { growEnv, TYPE_VARS } from "dope2";
import { autoInputWidth } from "./util/dom_trickery";
import "./LambdaBlock.css";
export interface LambdaBlockState {
@ -9,6 +15,66 @@ export interface LambdaBlockState {
expr: EditorState;
}
export function LambdaBlock {
interface LambdaBlockProps<
FnState=EditorState,
InputState=EditorState,
> extends State2Props<LambdaBlockState,EditorState> {
suggestionPriority: (suggestion: SuggestionType) => number;
}
export function LambdaBlock({state, setState, suggestionPriority}: LambdaBlockProps) {
const env = useContext(EnvContext);
const nameRef = useRef<HTMLInputElement>(null);
const setParamName = paramName => setState(state => ({
...state,
paramName,
}));
const setExpr = callback => setState(state => ({
...state,
expr: callback(state.expr),
}));
useEffect(() => {
nameRef.current?.focus();
}, []);
useEffect(() => autoInputWidth(nameRef, state.paramName, 60), [nameRef, state.paramName]);
const innerEnv = growEnv(env)(state.paramName)({
kind: "unknown",
i: undefined,
t: TYPE_VARS[0],
});
return <span>
<span className="keyword">&#955;</span>
&nbsp;
<span className="lambdaInputParam">
<input
ref={nameRef}
className='editable'
value={state.paramName}
placeholder="<name>"
onChange={e => setParamName(e.target.value)}
/>
</span>
&nbsp;
<span className="keyword">:</span>
&nbsp;
<span className="lambdaExpr">
<EnvContext value={innerEnv}>
<Editor
state={state.expr}
setState={setExpr}
onCancel={() => setState(state => state.expr)}
suggestionPriority={(suggestion: SuggestionType) => {
return suggestionPriority(suggestion);
}}
/>
</EnvContext>
</span>
</span>
}

View file

@ -1,6 +1,17 @@
.keyword {
color: blue;
/* margin: 0 2px 0 2px; */
.letIn {
display: inline-block;
border: 1px solid darkgrey;
}
.decl {
display: grid;
grid-template-columns: auto auto auto auto;
}
.column.rightAlign {
/* text-align: right; */
}
.column {
vertical-align: top;
}

View file

@ -1,10 +1,9 @@
import { useContext, useEffect, useRef } from "react";
import { growEnv } from "dope2";
import { Editor, type EditorState } from "./Editor";
import { EnvContext } from "./EnvContext";
import { evalEditorBlock, evalLetInBlock, scoreResolved, type ResolvedType } from "./eval";
import { evalEditorBlock, evalLetInBlock, makeInnerEnv, scoreResolved } from "./eval";
import { type State2Props } from "./Editor";
import { autoInputWidth } from "./util/dom_trickery";
@ -22,40 +21,54 @@ interface LetInBlockProps extends State2Props<LetInBlockState,EditorState> {
suggestionPriority: (suggestion: SuggestionType) => number;
}
export function makeInnerEnv(env, name: string, value: ResolvedType) {
if (value.kind === "value") {
return growEnv(env)(name)(value)
}
return env;
}
export function LetInBlock({state, setState, suggestionPriority}: LetInBlockProps) {
const {name, value, inner} = state;
return <span className="letIn">
<div className="decl">
<DeclColumns
state={state}
setState={setState}
suggestionPriority={suggestionPriority}
/>
</div>
<div className="inner">
<InnerMost
state={state}
setState={setState}
suggestionPriority={suggestionPriority}
/>
</div>
</span>
}
function DeclColumns({state, setState, suggestionPriority}) {
const env = useContext(EnvContext);
const valueResolved = evalEditorBlock(value, env);
const innerEnv = makeInnerEnv(env, name, valueResolved);
const nameRef = useRef<HTMLInputElement>(null);
const {name, value, inner} = state;
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
const setValue = callback => setState(state => ({...state, value: callback(state.value)}));
const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => {
setState(state => ({...state, name: e.target.value}));
}
const valueSuggestionPriority = (suggestion: SuggestionType) => {
const innerEnv = makeInnerEnv(env, name, suggestion[2]);
const resolved = evalEditorBlock(inner, innerEnv);
return scoreResolved(resolved, suggestionPriority);
};
const innerSuggestionPriority = suggestionPriority;
const nameRef = useRef<HTMLInputElement>(null);
useEffect(() => {
nameRef.current?.focus();
}, []);
useEffect(() => autoInputWidth(nameRef, name, 60), [nameRef, name]);
console.log({innerEnv});
const valueResolved = evalEditorBlock(state.value, env);
const innerEnv = makeInnerEnv(env, state.name, valueResolved);
return <span className="letIn">
<div className="decl">
<span className="keyword">let</span>
&nbsp;
return <>
<span className="keyword column">let&nbsp;</span>
<span className="column rightAlign">
<input
ref={nameRef}
className='editable'
@ -63,30 +76,52 @@ export function LetInBlock({state, setState, suggestionPriority}: LetInBlockProp
placeholder="<name>"
onChange={onChangeName}
/>
<span className="keyword">&nbsp;=&nbsp;</span>
</span>
<span className="keyword column">&nbsp;=&nbsp;</span>
<span className="column">
<Editor
state={value}
setState={setValue}
suggestionPriority={(suggestion: SuggestionType) => {
const innerEnv = makeInnerEnv(env, name, suggestion[2]);
const resolved = evalEditorBlock(inner, innerEnv);
return scoreResolved(resolved, suggestionPriority);
}}
suggestionPriority={valueSuggestionPriority}
onCancel={() => setState(state => state.inner)} // keep inner
/>
<span className="keyword">in</span>
</div>
<div className="inner">
</span>
{/* <span className="keyword column">in</span> */}
{inner.kind === "let" &&
<EnvContext value={innerEnv}>
<Editor
<DeclColumns
state={inner}
setState={setInner}
suggestionPriority={(suggestion: SuggestionType) => {
return suggestionPriority(suggestion)
}}
onCancel={() => setState(state => state.value)} // keep value
suggestionPriority={innerSuggestionPriority}
/>
</EnvContext>
</div>
</span>
}
</>;
}
function InnerMost({state, setState, suggestionPriority}) {
const env = useContext(EnvContext);
const setInner = callback => setState(state => ({...state, inner: callback(state.inner)}));
const valueResolved = evalEditorBlock(state.value, env);
const innerEnv = makeInnerEnv(env, state.name, valueResolved);
const onCancel = () => setState(state => state.value);
if (state.inner.kind === "let") {
return <EnvContext value={innerEnv}>
<InnerMost
state={state.inner}
setState={setInner}
suggestionPriority={suggestionPriority}
/>
</EnvContext>;
}
else {
return <EnvContext value={innerEnv}>
<Editor
state={state.inner}
setState={setInner}
suggestionPriority={suggestionPriority}
onCancel={onCancel} // keep value
/>
</EnvContext>
}
}

View file

@ -7,3 +7,13 @@
padding-right: 2px;
background-color: white;
}
.valueUUID {
border-radius: 10px;
background-color: lightyellow;
border: 1px solid black;
margin-left: 2px;
margin-right: 2px;
padding-left: 2px;
padding-right: 2px;
}

View file

@ -1,6 +1,7 @@
import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight, dict, Bool, set, Unit} from "dope2";
import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight, dict, Bool, set, Unit, symbolType, symbolUUID, getHumanReadableName} from "dope2";
import "./Value.css";
import { Type } from "./Type";
export function Value({dynamic}) {
const type = getType(dynamic);
@ -32,7 +33,10 @@ export function Value({dynamic}) {
return <ValueSet val={inst} elemType={type.params[0](type)} />;
case symbolList:
return <ValueList val={inst} elemType={type.params[0](type)} />;
case symbolType:
return <Type type={inst}/>;
case symbolUUID:
return <ValueUUID val={inst}/>
default:
return <>don't know how to show value</>;
}
@ -74,3 +78,6 @@ function ValueProduct({val, leftType, rightType}) {
function ValueUnit() {
return <>{'()'}</>;
}
function ValueUUID({val}) {
return <span className="valueUUID">{getHumanReadableName(val)}</span>;
}

View file

@ -74,3 +74,182 @@ export const tripleFunctionCallEditorState: EditorState = {
focus: false,
},
};
export const biggerExample: EditorState = {
"kind": "let",
"inner": {
"kind": "let",
"inner": {
"kind": "let",
"inner": {
"kind": "let",
"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": "input",
"text": "myList",
"value": {
"kind": "name"
},
"focus": false
}
},
"input": {
"kind": "input",
"text": "inc",
"value": {
"kind": "name"
},
"focus": false
}
}
},
"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",
"paramName": "x",
"expr": {
"kind": "input",
"text": "x",
"value": {
"kind": "name"
},
"focus": false
}
}
},
"name": "inc",
"value": {
"kind": "lambda",
"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
}
}
}
};

View file

@ -1,29 +1,36 @@
import { apply, assignFn, Double, getSymbol, Int, makeGeneric, NotAFunctionError, prettyT, symbolFunction, trie, UnifyError } from "dope2";
import { assignFnSubstitutions, Double, fnType, getSymbol, growEnv, Int, makeGeneric, NotAFunctionError, prettyT, substitute, symbolFunction, trie, TYPE_VARS, UnifyError } from "dope2";
import type { EditorState } from "./Editor";
import type { InputValueType } from "./InputBlock";
import { makeInnerEnv } from "./LetInBlock";
import type { InputValueType, SuggestionType } from "./InputBlock";
interface Type {
symbol: string;
params: any[];
};
export interface DeepError {
kind: "error";
e: Error;
depth: number;
t: any;
t: Type;
substitutions: Map<Type,Type>;
}
// a dynamically typed value = tuple (instance, type)
export interface Dynamic {
kind: "value",
i: any;
t: any;
t: Type;
substitutions: Map<Type,Type>;
};
export interface Unknown {
kind: "unknown";
t: any;
t: Type;
substitutions: Map<Type,Type>;
}
export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a) };
export const entirelyUnknown: Unknown = { kind: "unknown", t: makeGeneric(a => a), substitutions: new Map() };
// the value of every block is either known (Dynamic), an error, or unknown
export type ResolvedType = Dynamic | DeepError | Unknown;
@ -41,8 +48,8 @@ export const evalEditorBlock = (s: EditorState, env): ResolvedType => {
return evalLetInBlock(s.value, s.name, s.inner, env);
}
if (s.kind === "lambda") {
const expr = evalEditorBlock(s.expr, env);
// todo
return evalLambdaBlock(s.paramName, s.expr, env);
}
return entirelyUnknown; // todo
};
@ -54,7 +61,11 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv
else if (value.kind === "name") {
const found = trie.get(env.name2dyn)(text);
if (found) {
return { kind: "value", ...found };
return {
kind: found.kind || "value",
...found,
substitutions: new Map(),
};
} else {
return entirelyUnknown;
}
@ -64,6 +75,10 @@ export function evalInputBlock(text: string, value: InputValueType, env): Resolv
}
}
const mergeMaps = (...maps: Map<Type,Type>[]) => {
return new Map(maps.flatMap(m => [...m]));
}
export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedType {
if (getSymbol(fn.t) !== symbolFunction) {
if (fn.kind === "unknown") {
@ -74,12 +89,15 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
kind: "error",
e: new NotAFunctionError(`${prettyT(fn.t)} is not a function type!`),
t: entirelyUnknown.t,
substitutions: mergeMaps(fn.substitutions, input.substitutions),
depth: 0,
};
}
try {
// fn is a function...
const outType = assignFn(fn.t, input.t); // may throw
const [outType, substitutions] = assignFnSubstitutions(fn.t, input.t); // may throw
const mergedSubstitutions = mergeMaps(substitutions, fn.substitutions, input.substitutions);
if (input.kind === "error") {
return {
@ -87,6 +105,7 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
e: input.e, // bubble up the error
depth: 0,
t: outType,
substitutions: mergedSubstitutions,
};
}
if (fn.kind === "error") {
@ -96,16 +115,26 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
e: fn.e,
depth: fn.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);
return { kind: "value", i: outValue, t: outType };
return {
kind: "value",
i: outValue,
t: outType,
substitutions: mergedSubstitutions,
};
}
else {
// we don't know the value, but we do know the type:
return { kind: "unknown", t: outType };
return {
kind: "unknown",
t: outType,
substitutions: mergedSubstitutions,
};
}
}
catch (e) {
@ -117,18 +146,49 @@ export function evalCallBlock(fn: ResolvedType, input: ResolvedType): ResolvedTy
e,
depth: 0,
t: outType,
substitutions: mergeMaps(fn.substitutions, input.substitutions),
};
}
throw e;
}
}
export function evalLetInBlock(value: EditorState, name: string, inner: EditorState, 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 evalLambdaBlock(paramName: string, expr: EditorState, env): ResolvedType {
const fn = (x: any) => {
const innerEnv = makeInnerEnv(env, paramName, {
kind: "value",
i: x,
t: TYPE_VARS[0],
substitutions: new Map(),
});
const result = evalEditorBlock(expr, innerEnv);
if (result.kind === "value") {
return result.i;
}
}
// 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],
substitutions: new Map(),
});
const abstractOutput = evalEditorBlock(expr, staticInnerEnv);
const t = fnType(_ => entirelyUnknown.t)(_ => abstractOutput.t);
const T = substitute(t, abstractOutput.substitutions, [])
return {
kind: "value",
t: T,
i: fn,
substitutions: new Map(),
};
}
export function haveValue(resolved: ResolvedType) {
// return resolved && !(resolved instanceof DeepError);
return resolved.kind === "value";
@ -149,7 +209,12 @@ function parseAsDouble(text: string): ResolvedType {
if (text !== '') {
const num = Number(text);
if (!Number.isNaN(num)) {
return { kind: "value", i: num, t: Double };
return {
kind: "value",
i: num,
t: Double,
substitutions: new Map(),
};
}
}
return entirelyUnknown;
@ -157,7 +222,12 @@ function parseAsDouble(text: string): ResolvedType {
function parseAsInt(text: string): ResolvedType {
if (text !== '') {
try {
return { kind: "value", i: BigInt(text), t: Int }; // may throw
return {
kind: "value",
i: BigInt(text),
t: Int,
substitutions: new Map(),
}; // may throw
}
catch {}
}
@ -171,10 +241,14 @@ export function attemptParseLiteral(text: string): Dynamic[] {
.filter(resolved => (resolved.kind !== "unknown" && resolved.kind !== "error")) as unknown as Dynamic[];
}
export function scoreResolved(resolved: ResolvedType, outPriority) {
export function scoreResolved(resolved: ResolvedType, outPriority: (s:SuggestionType) => number) {
const bias = outPriority(['literal', '<computed>',
// @ts-ignore: // TODO fix this
{t: resolved.t}]);
{
// @ts-ignore
kind: "unknown",
t: resolved.t,
substitutions: new Map(),
}]);
if (resolved.kind === "value") {
return 1 + bias;
@ -189,3 +263,11 @@ export function scoreResolved(resolved: ResolvedType, outPriority) {
return -2 + bias; // even worse: fn is not a function!
}
}
export function makeInnerEnv(env, name: string, value: ResolvedType) {
if (name !== "" && value.kind === "value") {
return growEnv(env)(name)(value);
}
return env;
}