nicer looking
This commit is contained in:
parent
8abbac4bc9
commit
e850952738
14 changed files with 547 additions and 104 deletions
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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[],
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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; */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
:: <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>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
0
src/LambdaBlock.css
Normal 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">λ</span>
|
||||
|
||||
<span className="lambdaInputParam">
|
||||
<input
|
||||
ref={nameRef}
|
||||
className='editable'
|
||||
value={state.paramName}
|
||||
placeholder="<name>"
|
||||
onChange={e => setParamName(e.target.value)}
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span className="keyword">:</span>
|
||||
|
||||
<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>
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
return <>
|
||||
<span className="keyword column">let </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"> = </span>
|
||||
</span>
|
||||
<span className="keyword column"> = </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>
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
120
src/eval.ts
120
src/eval.ts
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue