move everything

This commit is contained in:
Joeri Exelmans 2025-05-23 22:35:47 +02:00
parent 3ff7e76694
commit 9050581a10
25 changed files with 37 additions and 42 deletions

62
src/component/app/App.css Normal file
View file

@ -0,0 +1,62 @@
#root {
display: grid;
grid-template-areas:
"header header header"
"content content content"
"footer footer footer";
grid-template-columns: 100px 1fr 100px;
grid-template-rows: auto 1fr auto;
/* grid-gap: 10px; */
height: 100vh;
}
header {
grid-area: header;
}
nav {
grid-area: nav;
margin-left: 0.5rem;
}
main {
grid-area: content;
overflow: auto;
}
aside {
grid-area: side;
margin-right: 0.5rem;
}
footer {
text-align: right;
grid-area: footer;
background-color: dodgerblue;
color: white;
}
footer a {
color: white;
}
.command {
border: 1px solid black;
border-radius: 5px;
padding: 0 6px 0 6px;
margin: 0 4px 0 4px;
}
.command.highlighted {
background-color: dodgerblue;
color: white;
}
.factoryReset {
background-color: rgb(255, 0, 0);
color: black;
}

181
src/component/app/App.tsx Normal file
View file

@ -0,0 +1,181 @@
import { useEffect, useState } from 'react';
import './App.css';
import { ExprBlock, type ExprBlockState } from '../expr/ExprBlock';
import { GlobalContext } from '../../context/GlobalContext';
import { biggerExample, emptySet, factorial, higherOrder, higherOrder2Params, inc, initialEditorState, lambda2Params, nonEmptyEditorState, pushBool, tripleFunctionCallEditorState } from "./configurations";
import { actionShortcuts } from './actions';
// import { scoreResolved, type ResolvedType } from './eval';
const examples: [string, ExprBlockState][] = [
["empty editor" , initialEditorState ],
["push to list" , nonEmptyEditorState ],
["function w/ 4 params", tripleFunctionCallEditorState],
["bigger example" , biggerExample ],
["lambda 2 params" , lambda2Params ],
["higher order" , higherOrder ],
["higher order 2" , higherOrder2Params ],
["push Bool" , pushBool ],
["inc" , inc ],
["empty set" , emptySet ],
["factorial" , factorial ],
];
type AppState = {
history: ExprBlockState[],
future: ExprBlockState[],
}
const defaultState = {
history: [initialEditorState],
future: [],
};
function loadFromLocalStorage(): AppState {
if (localStorage["appState"]) {
// try {
const appState = JSON.parse(localStorage["appState"]); // may throw
// if our state is corrupt, discover it eagerly:
// evalEditorBlock(appState.history.at(-1), extendedEnv);
return appState; // all good
// }
// catch (e) {
// console.log('error recovering state from localStorage (resetting):', e);
// }
}
return defaultState;
}
export function App() {
// load from localStorage
const [appState, setAppState] = useState(loadFromLocalStorage());
const [syntacticSugar, setSyntacticSugar] = useState(true);
useEffect(() => {
// persist accross reloads
localStorage["appState"] = JSON.stringify(appState);
}, [appState]);
const factoryReset = () => {
setAppState(_ => defaultState);
}
// factoryReset();
const pushHistory = (callback: (p: ExprBlockState) => ExprBlockState) => {
setAppState(({history}) => {
const newState = callback(history.at(-1)!);
return {
history: history.concat([newState]),
future: [],
};
});
};
const onUndo = () => {
setAppState(({history, future}) => ({
history: history.slice(0,-1),
future: future.concat(history.at(-1)!),
}));
};
const onRedo = () => {
setAppState(({history, future}) => ({
history: history.concat(future.at(-1)!),
future: future.slice(0,-1),
}));
};
const onKeyDown = (e) => {
if (e.key === "Z" && e.ctrlKey) {
if (e.shiftKey) {
if (appState.future.length > 0) {
onRedo();
}
}
else {
if (appState.history.length > 1) {
onUndo();
}
}
e.preventDefault();
}
};
useEffect(() => {
window['APP_STATE'] = appState.history; // useful for debugging
}, [appState.history]);
useEffect(() => {
window.onkeydown = onKeyDown;
}, []);
const [highlighted, setHighlighted] = useState(
actionShortcuts.map(() => false));
const doHighlight = Object.fromEntries(actionShortcuts.map(([id], i) => {
return [id, () => {
setHighlighted(h => h.with(i, true));
setTimeout(() => setHighlighted(h => h.with(i, false)), 100);
}];
}));
const onSelectExample = (e: React.SyntheticEvent<HTMLSelectElement>) => {
// @ts-ignore
if (e.target.value >= 0) {
// @ts-ignore
// @ts-ignore
pushHistory(_ => examples[e.target.value][1]);
}
}
return (
<>
<header>
<button disabled={appState.history.length===1} onClick={onUndo}>Undo ({appState.history.length-1}) <kbd>Ctrl</kbd>+<kbd>Z</kbd></button>
<button disabled={appState.future.length===0} onClick={onRedo}>Redo ({appState.future.length}) <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd></button>
{
actionShortcuts.map(([_, keys, descr], i) =>
<span key={i} className={'command' + (highlighted[i] ? (' highlighted') : '')}>
{keys.map((key, j) => <kbd key={j}>{key}</kbd>)}
&nbsp;
{descr}
</span>)
}
<select onChange={onSelectExample} value={-1}>
<option value={-1}>load example...</option>
{
examples.map(([name], i) => {
return <option key={i} value={i}>{name}</option>;
})
}
</select>
<button className="factoryReset" onClick={factoryReset}>
FACTORY RESET
</button>
<label>
<input type="checkbox"
checked={syntacticSugar}
onChange={e => setSyntacticSugar(e.target.checked)}/>
syntactic sugar
</label>
</header>
<main onKeyDown={onKeyDown}>
<GlobalContext value={{undo: onUndo, redo: onRedo, doHighlight, syntacticSugar}}>
<ExprBlock
state={appState.history.at(-1)!}
setState={pushHistory}
onCancel={() => {}}
score={() => 0}
/>
</GlobalContext>
</main>
<footer>
<a href="https://deemz.org/git/joeri/dope2-webapp">Source code</a>
</footer>
</>
)
}

View file

@ -0,0 +1,62 @@
import { initialEditorState } from "./configurations";
// import { removeFocus } from "./eval";
const removeFocus = state => state;
export const actionShortcuts: [string, string[], string][] = [
["call" , ['c'], "expr ⌴" ],
["transform", ['t'], "⌴ expr" ],
["assign" , ['a'], "let (⌴ = expr) in ⌴"],
["declare" , ['d'], "let (⌴ = ⌴) in expr"],
["lambda" , ['l'], "λ⌴. expr" ],
];
export function getActions(globalContext, setState) {
return {
c: () => {
setState(state => ({
kind: "call",
fn: removeFocus(state),
input: initialEditorState,
}));
globalContext?.doHighlight.call();
},
t: () => {
setState(state => ({
kind: "call",
fn: initialEditorState,
input: removeFocus(state),
}));
globalContext?.doHighlight.transform();
},
a: () => {
setState(state => ({
kind: "let",
name: "",
focus: true,
value: removeFocus(state),
inner: removeFocus(initialEditorState),
}));
globalContext?.doHighlight.assign();
},
d: () => {
setState(state => ({
kind: "let",
name: "",
focus: true,
value: removeFocus(initialEditorState),
inner: removeFocus(state),
}));
globalContext?.doHighlight.declare();
},
l: () => {
setState(state => ({
kind: "lambda",
paramName: "",
focus: true,
expr: removeFocus(state),
}));
globalContext?.doHighlight.lambda();
},
};
}

View file

@ -0,0 +1,144 @@
import type { ExprBlockState } from "../expr/ExprBlock";
export const initialEditorState: ExprBlockState = {
kind: "input",
text: "",
value: { kind: "text" },
focus: true,
};
export const nonEmptyEditorState: ExprBlockState = {
kind: "call",
fn: {
kind: "call",
fn: {
kind: "input",
text: "list.push",
value: { kind: "name" },
focus: false,
},
input: {
kind: "input",
text: "list.emptyList",
value: { kind: "name" },
focus: false,
},
},
input: {
kind: "input",
text: "42",
value: { kind: "literal", type: "Int" },
focus: false,
},
};
export const tripleFunctionCallEditorState: ExprBlockState = {
kind: "call",
fn: {
kind: "call",
fn: {
kind: "call",
fn: {
kind: "call",
fn: {
kind: "input",
text: "functionWith4Params",
value: { kind: "name" },
focus: false,
},
input: {
kind: "input",
text: "42",
value: { kind: "literal", type: "Int" },
focus: false,
},
},
input: {
kind: "input",
text: "43",
value: { kind: "literal", type: "Int" },
focus: false,
},
},
input: {
kind: "input",
text: "44",
value: { kind: "literal", type: "Int" },
focus: false,
},
},
input: {
kind: "input",
text: "45",
value: { kind: "literal", type: "Int" },
focus: false,
},
};
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 lambda2Params: ExprBlockState = {
"kind": "let",
"inner": {
"kind": "input",
"text": "",
"value": {
"kind": "text"
},
"focus": false
},
"name": "myAddInt",
"focus": false,
"value": {
"kind": "lambda",
"paramName": "x",
"focus": false,
"expr": {
"kind": "lambda",
"paramName": "y",
"focus": false,
"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": "y",
"value": {
"kind": "name"
},
"focus": false
}
}
}
}
};
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 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}}}}}};
export const pushBool: ExprBlockState = {"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"list.push","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"list.emptyList","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"Bool","value":{"kind":"name"},"focus":true}};
export const inc: ExprBlockState = {"kind":"let","focus":false,"inner":{"kind":"input","text":"","value":{"kind":"name"},"focus":false},"name":"inc","value":{"kind":"lambda","focus":false,"paramName":"x","expr":{"kind":"call","fn":{"kind":"call","fn":{"kind":"input","text":"addInt","value":{"kind":"name"},"focus":false},"input":{"kind":"input","text":"x","value":{"kind":"name"},"focus":false}},"input":{"kind":"input","text":"1","value":{"kind":"literal","type":"Int"},"focus":true}}}};
export const 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 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}}}}}}}};

View file

@ -0,0 +1,112 @@
.functionBlock {
border: solid 1px darkgray;
display: inline-block;
margin: 2px;
color: black;
background-color: white;
vertical-align: top;
}
.functionName {
/* text-align: center; */
background-color: white;
}
.inputParam:after {
content: "";
position: absolute;
right: 0;
clip-path: polygon(1% 0%, 100% 50%, 0% 100%);
height: 100%;
width: var(--param-arrow-width);
right: calc(var(--param-arrow-width)*(-1) + .2px);
}
.inputParam {
display: inline-flex;
vertical-align: middle;
position: relative; /* to ensure the :after (which is absolute) is relative to ourselves */
flex-grow: 1;
--param-arrow-width: 8px;
margin-right: calc(var(--param-arrow-width)*2);
}
/* Count nested level AFTER .outputParam (resets the depth) */
.outputParam > .inputParam:after {
background-color: rgb(242, 253, 146);
}
.outputParam > .inputParam {
background-color: rgb(242, 253, 146);
}
.outputParam > .inputParam > .inputParam {
background-color: rgb(180, 248, 214);
}
.outputParam > .inputParam > .inputParam:after {
background-color: rgb(180, 248, 214);
}
.outputParam > .inputParam > .inputParam > .inputParam {
background-color: rgb(153, 212, 214);
}
.outputParam > .inputParam > .inputParam > .inputParam:after {
background-color: rgb(153, 212, 214);
}
.outputParam > .inputParam > .inputParam > .inputParam > .inputParam {
background-color: rgb(111, 186, 209);
}
.outputParam > .inputParam > .inputParam > .inputParam > .inputParam:after {
background-color: rgb(111, 186, 209);
}
.typeAnnot {
display: inline-block;
vertical-align: top;
}
.outputParam {
text-align: left;
vertical-align: top;
padding: 0px;
display: inline-block;
background-color: rgb(233, 224, 205);
width: 100%;
}
.functionBlock.unifyError > .functionParams > .outputParam {
background-color: pink;
}
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam {
background-color: pink;
color: black;
}
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam:after {
background-color: pink;
}
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam {
background-color: pink;
color: black;
}
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam:after {
background-color: pink;
}
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam > .inputParam {
background-color: pink;
color: black;
}
.functionBlock.unifyError > .functionParams > .outputParam > .inputParam > .inputParam > .inputParam:after {
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 {
background-color: pink;
}
.inputParam.offending {
background-color: darkred !important;
color: white !important;
}
.inputParam.offending:after {
background-color: darkred !important;
}

View file

@ -0,0 +1,135 @@
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";
import { getActions } from "../app/actions";
import "./CallBlock.css";
import { CallContext } from "../../context/CallContext";
import { inferTypeCall, type Environment } from "../../eval/infer_type";
import { Type } from "../other/Type";
export interface CallBlockState {
kind: "call";
fn: ExprBlockState;
input: ExprBlockState;
}
export interface CallBlockProps<
FnState=ExprBlockState,
InputState=ExprBlockState,
> extends State2Props<CallBlockState,ExprBlockState> {}
function nestedFnProperties({state, setState, score}: CallBlockProps, env: Environment) {
const setFn = (callback: SetStateFn) => {
setState(state => ({...state, fn: callback(state.fn)}));
};
const onFnCancel = () => {
setState(state => state.input); // we become our input
};
const scoreFn = (fnSuggestion: ExprBlockState) => {
return score({
kind: "call",
fn: fnSuggestion,
input: state.input,
});
};
return {state: state.fn, setState: setFn, onCancel: onFnCancel, score: scoreFn};
}
function nestedInputProperties({state, setState, score}: CallBlockProps, env: Environment) {
const setInput = (callback: SetStateFn) => {
setState(state => ({...state, input: callback(state.input)}));
};
const onInputCancel = () => {
setState(state => /*addFocusRightMost*/(state.fn)); // we become our function
};
const scoreInput = (inputSuggestion: ExprBlockState) => {
return score({
kind: "call",
fn: state.fn,
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);
const addParam = getActions(globalContext, props.setState).c;
// const [resolved] = evalExprBlock(props.state, env);
// return <span className={"functionBlock" + ((resolved.kind === "error") ? " unifyError" : "")}>
const typeInfo = inferTypeCall(props.state, env);
return <span className={"functionBlock"}>
<CallContext value={{addParam}}>
<FunctionHeader {...props} addParam={addParam} />
<div className="functionParams">
<div className="outputParam">
{/* 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()
|| (resolved.kind === "value") && <Value dynamic={resolved} />
|| "unknown" } */}
:: <Type type={typeInfo.type} />
</div>
</div>
</CallContext>
</span>;
}
function FunctionHeader(props) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
const nestedProperties = nestedFnProperties(props, env);
if (props.state.fn.kind === "call" && globalContext?.syntacticSugar) {
// if the function we're calling is itself the result of a function call,
// then we are anonymous, and so we don't draw a function name
return <FunctionHeader {...nestedProperties} />;
}
else {
// end of recursion - draw function name
return <span className="functionName">
&nbsp;&#119891;&#119899;&nbsp;
<ExprBlock {...nestedProperties} />
</span>;
}
}
function InputParams({ depth, errorDepth, ...rest }) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
const isOffending = depth === errorDepth;
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}>
<ExprBlock
{...nestedInputProperties(rest as CallBlockProps, env)}
/>
</EnvContext>
</div>;
}

View file

@ -0,0 +1,56 @@
.editor {
padding: 2px;;
}
.editor.error {
border: 1px solid red;
display: inline-block;
}
.editor.unknown {
border: 1px dashed dodgerblue;
display: inline-block;
}
*:hover:not(:has(> *:hover)) {
/* useful for debugging: */
/* border-width: 2px !important; */
}
.offending .error {
background-color: transparent;
}
.typeSignature {
display: inline-block;
/* z-index: 1; */
position: relative;
}
.typeSignature.gotDebug {
background-color: gold;
padding: 2px;
}
.typeDebug {
display: none;
}
.typeSignature:hover > .typeDebug {
display: inline-block;
position: absolute;
white-space-collapse: preserve;
width: max-content;
background-color: #d2ebf1e0;
color: black;
font-family: var(--my-monospace-font);
padding: 4px;
z-index: 1000;
}
.editor:hover > .typeSignature {
display: inline-block;
}
.keyword {
color: blue;
font-weight: bold;
}

View file

@ -0,0 +1,72 @@
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";
import { InputBlock, type InputBlockProps, type InputBlockState } from "./InputBlock";
import { LambdaBlock, type LambdaBlockProps, type LambdaBlockState } from "./LambdaBlock";
import { LetInBlock, type LetInBlockProps, type LetInBlockState } from "./LetInBlock";
// import { evalExprBlock, type ResolvedType } from "./eval";
import "./ExprBlock.css";
import { Input } from "../other/Input";
import { getActions } from "../app/actions";
export type ExprBlockState =
InputBlockState
| CallBlockState
| LetInBlockState
| LambdaBlockState;
export type SetStateFn<InType = ExprBlockState, OutType = InType> = (state: InType) => OutType;
export interface State2Props<InType, OutType = InType> {
state: InType;
setState: (callback: SetStateFn<InType, OutType>) => void;
score: (suggestion: ExprBlockState) => number;
}
interface ExprBlockProps extends State2Props<ExprBlockState> {
onCancel: () => void;
}
export function ExprBlock(props: ExprBlockProps) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
const renderBlock = {
input: () => <InputBlock {...props as InputBlockProps} />,
call: () => <CallBlock {...props as CallBlockProps} />,
let: () => <LetInBlock {...props as LetInBlockProps} />,
lambda: () => <LambdaBlock {...props as LambdaBlockProps} />,
};
// const [resolved] = evalExprBlock(props.state, env);
// const typeInfo = inferType(props.state, env);
const actions = getActions(globalContext, props.setState);
const extraHandlers = Object.fromEntries(Object.entries(actions).map(([shortcut, action]) =>
[shortcut, (e) => { e.preventDefault(); action(); }]))
// return <span className={"editor" + ((resolved.kind!=="value") ? " "+resolved.kind : "")}>
return <span className={"editor"}>
{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> */}
<Input
placeholder="<c>"
text=""
suggestion=""
onEnter={() => {}}
onCancel={props.onCancel}
onTextChange={() => {}}
extraHandlers={extraHandlers}
/>
</span>;
}

View file

@ -0,0 +1,47 @@
.inputBlock {
position: relative;
}
.editable {
position: relative;
border: 0;
font-size: 10pt;
font-family: var(--my-monospace-font);
background-color: transparent;
color: inherit;
padding: 0;
cursor: text;
outline: 0;
}
.suggest {
top: 2.4px;
position: absolute;
color: #aaa;
}
.suggestionsPlaceholder {
display: inline-block;
position: relative;
vertical-align: bottom;
}
.suggestions {
display: block;
color: black;
text-align: left;
position: absolute;
border: solid 1px dodgerblue;
cursor: pointer;
max-height: calc(100vh - 64px);
overflow: auto;
z-index: 10;
background-color: white;
width: max-content;
max-width: 500px;
}
.selected {
background-color: dodgerblue;
color: white;
}
.border-around-input {
border: 1px solid black;
padding: 1px;
margin: 1px;
}

View file

@ -0,0 +1,199 @@
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";
interface Literal {
kind: "literal";
type: string; // todo: store (and serialize) real type
};
interface Name {
kind: "name";
}
interface Text {
kind: "text";
}
export type InputValueType = Literal | Name | Text;
export interface InputBlockState {
kind: "input";
text: string;
value: InputValueType;
focus: boolean
}
// export type SuggestionType = ["literal"|"name", string, ResolvedType];
// export type PrioritizedSuggestionType = [number, ...SuggestionType];
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]),
// // 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)
// }
export function InputBlock({ state, setState, /*score,*/ onCancel }: InputBlockProps) {
const {text, focus} = state;
const globalContext = useContext(GlobalContext);
const env = useContext(EnvContext);
const callContext = useContext(CallContext);
const inputRef = useRef<HTMLInputElement>(null);
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(() => [], []);
useEffect(() => {
if (focus) {
inputRef.current?.focus();
}
}, [focus]);
useEffect(() => {
if (suggestions.length >= i) {
setI(0);
}
}, [suggestions.length]);
const onTextChange = newText => {
setState(state => ({...state,
text: newText,
value: (trie.get(env.names)(newText) ? {
kind: "name",
} : state.value),
}));
}
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 extraHandlers = {
ArrowDown: (e) => {
setI((i + 1) % suggestions.length);
e.preventDefault();
},
ArrowUp: (e) => {
setI((suggestions.length + i - 1) % suggestions.length);
e.preventDefault();
},
" ": (e) => {
if (text.length > 0) {
if (callContext.addParam) {
callContext.addParam();
}
else {
const actions = getActions(globalContext, setState);
actions.c();
}
}
e.preventDefault();
},
};
const typeInfo = inferTypeInput(state, env);
return <><Input
placeholder="<name or literal>"
onCancel={onCancel}
onEnter={onSelectSuggestion}
onTextChange={onTextChange}
text={text}
suggestion={singleSuggestion}
extraHandlers={extraHandlers}
>
<span className="suggestionsPlaceholder">
<Suggestions
suggestions={suggestions}
onSelect={onSelectSuggestion}
i={i} setI={setI} />
</span>
</Input>
::<Type type={typeInfo.type} />
</>
}
function Suggestions({ suggestions, onSelect, i, setI }) {
return <>{(suggestions.length > 0) &&
<div className={"suggestions"}>
{suggestions.map((suggestion, j) =>
<SuggestionMemo key={j}
{...{setI, j,
onSelect,
highlighted: i===j,
suggestion}}/>)}
</div>
}</>;
}
interface SuggestionProps {
setI: any;
j: number;
onSelect: any;
highlighted: boolean;
// suggestion: PrioritizedSuggestionType;
}
function Suggestion({ setI, j, onSelect, highlighted, /*suggestion: [priority, kind, text, resolved]*/ }: SuggestionProps) {
const onMouseEnter = j => () => {
setI(j);
};
const onMouseDown = j => () => {
setI(j);
onSelect();
};
return <div
key={`${j}_${name}`}
className={(highlighted ? " selected" : "")}
onMouseEnter={onMouseEnter(j)}
onMouseDown={onMouseDown(j)}>
{/* ({priority}) ({kind}) {text} :: <Type type={resolved.t} /> */}
</div>
}
const SuggestionMemo = memo<SuggestionProps>(Suggestion);

View file

@ -0,0 +1,9 @@
.lambdaBlock {
display: inline-block;
border: solid 1px darkgrey;
margin: 2px;
}
.lambdaInner {
display: inline-block;
}

View file

@ -0,0 +1,82 @@
import { useContext } from "react";
// import { eqType, getSymbol, reduceUnification } from "dope2";
import { ExprBlock, type ExprBlockState, type State2Props } from "./ExprBlock";
import { EnvContext } from "../../context/EnvContext";
// import { evalExprBlock, evalLambdaBlock, makeInnerEnv, makeTypeVar } from "./eval";
import "./LambdaBlock.css";
import { Type } from "../other/Type";
import { Input } from "../other/Input";
import { inferTypeLambda } from "../../eval/infer_type";
export interface LambdaBlockState {
kind: "lambda";
paramName: string;
focus: boolean;
expr: ExprBlockState;
}
export interface LambdaBlockProps<
FnState=ExprBlockState,
InputState=ExprBlockState,
> extends State2Props<LambdaBlockState,ExprBlockState> {
}
export function LambdaBlock({state, setState, score}: LambdaBlockProps) {
const env = useContext(EnvContext);
const setParamName = paramName => setState(state => ({
...state,
paramName,
}));
const setExpr = callback => setState(state => ({
...state,
expr: callback(state.expr),
}));
const {paramType, innerEnv} = inferTypeLambda(state, env);
// const [lambdaResolved, _, innerEnv] = evalLambdaBlock(state.paramName, state.expr, env);
// const inferredParamType = lambdaResolved.t.params[0](lambdaResolved.t);
// const innerEnv = env; // todo: change this
return <span className="lambdaBlock">
<span className="keyword">&#955;</span>
&nbsp;
<span className="lambdaInputParam">
<Input
placeholder="<name>"
text={state.paramName}
suggestion=""
onEnter={() => {}}
onCancel={() => {}}
onTextChange={txt => setParamName(txt)}
extraHandlers={{}}
/>
</span>
<div className="typeSignature">
&nbsp;::&nbsp;<Type type={paramType} />
</div>
&nbsp;
<span className="keyword">:</span>
&nbsp;
<div className="lambdaInner">
<EnvContext value={innerEnv}>
<ExprBlock
state={state.expr}
setState={setExpr}
onCancel={() => setState(state => state.expr)}
score={(s) => {
// console.log('suggestionPriority of lambdaInner... just passing through');
return score(s);
}}
/>
</EnvContext>
</div>
</span>
}

View file

@ -0,0 +1,23 @@
.letIn {
display: inline-block;
/* border: 1px solid darkgrey; */
}
.inner {
border: 1px solid darkgrey;
margin-left: 4px
}
.decl {
display: grid;
grid-template-columns: auto auto auto auto;
width: fit-content;
}
.column.rightAlign {
/* text-align: right; */
}
.column {
vertical-align: top;
}

View file

@ -0,0 +1,117 @@
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";
import "./LetInBlock.css";
import { Input } from "../other/Input";
import { inferTypeLet } from "../../eval/infer_type";
import { Type } from "../other/Type";
export interface LetInBlockState {
kind: "let";
name: string;
focus: boolean;
value: ExprBlockState;
inner: ExprBlockState;
}
export interface LetInBlockProps extends State2Props<LetInBlockState,ExprBlockState> {
}
export function LetInBlock(props: LetInBlockProps) {
return <span className="letIn">
<div className="decl">
<DeclColumns {...props} />
</div>
<div className="inner">
<InnerMost {...props} />
</div>
</span>
}
function DeclColumns({state, setState, score}) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
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);
return <>
<span className="keyword column">let&nbsp;</span>
<span className="column rightAlign">
<Input
placeholder="<name>"
text={state.name}
suggestion=""
onEnter={() => {}}
onCancel={() => {}}
onTextChange={name => setState(state => ({...state, name}))}
extraHandlers={{}}
/>
:: <Type type={paramType} />
</span>
<span className="keyword column">&nbsp;=&nbsp;</span>
<span className="column">
<ExprBlock
state={state.value}
setState={setValue}
score={() => 0}
onCancel={() => setState(state => state.inner)} // keep inner
/>
</span>
{state.inner.kind === "let" &&
globalContext?.syntacticSugar &&
<EnvContext value={innerEnv}>
<DeclColumns
state={state.inner}
setState={setInner}
score={score}
/>
</EnvContext>
}
</>;
}
function InnerMost({state, setState, score}) {
const env = useContext(EnvContext);
const globalContext = useContext(GlobalContext);
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 onCancel = () => setState(state => state.value);
if (state.inner.kind === "let" && globalContext?.syntacticSugar) {
return <EnvContext value={innerEnv}>
<InnerMost
state={state.inner}
setState={setInner}
score={score}
/>
</EnvContext>;
}
else {
return <EnvContext value={innerEnv}>
<ExprBlock
state={state.inner}
setState={setInner}
score={score}
onCancel={onCancel} // keep value
/>
</EnvContext>
}
}

View file

@ -0,0 +1,147 @@
import { useEffect, useRef, type ReactNode, type KeyboardEvent, useState } from "react";
interface InputProps {
placeholder: string;
text: string;
suggestion: string;
onTextChange: (text: string) => void;
onEnter: () => void;
onCancel: () => void;
extraHandlers: {[key:string]: (e: KeyboardEvent) => void}
children?: ReactNode | ReactNode[];
}
const autoInputWidth = (ref: React.RefObject<HTMLInputElement| null>, text, emptyWidth=150) => {
if (ref.current) {
ref.current.style.width = `${text.length === 0 ? emptyWidth : (text.length*8.0)}px`;
}
}
function getCaretPosition(ref: React.RefObject<HTMLInputElement| null>): number {
return ref.current?.selectionStart || 0;
}
// Move caret all the way to the right in the currently focused element
function setRightMostCaretPosition(elem) {
const range = document.createRange();
range.selectNode(elem);
if (elem.lastChild) { // if no text is entered, there is no lastChild
range.setStart(elem.lastChild, elem.textContent.length);
range.setEnd(elem.lastChild, elem.textContent.length);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
}
}
function focusNextElement() {
const editable = Array.from<any>(document.querySelectorAll('input.editable'));
const index = editable.indexOf(document.activeElement);
const nextElem = editable[index+1];
if (nextElem) {
nextElem.focus();
}
}
function focusPrevElement() {
const editable = Array.from<any>(document.querySelectorAll('input.editable'));
const index = editable.indexOf(document.activeElement);
const prevElem = editable[index-1]
if (prevElem) {
prevElem.focus();
setRightMostCaretPosition(prevElem);
}
}
export function Input({placeholder, text, suggestion, onTextChange, onEnter, onCancel, extraHandlers, children}: InputProps) {
const ref = useRef<HTMLInputElement>(null);
const [focus, setFocus] = useState<"yes"|"hide"|"no">("no");
useEffect(() => autoInputWidth(ref, (text+(focus?suggestion:'')) || placeholder), [ref, text, suggestion, focus]);
const onKeyDown = (e: KeyboardEvent) => {
setFocus("yes");
const keys = {
// auto-complete
Tab: () => {
if (e.shiftKey) {
focusPrevElement();
e.preventDefault();
}
else {
// not shift key
if (suggestion.length > 0) {
// complete with greyed out text
const newText = text + suggestion;
onTextChange(newText);
setRightMostCaretPosition(ref.current);
e.preventDefault();
}
else {
onEnter();
e.preventDefault();
}
}
},
Enter: () => {
onEnter();
e.preventDefault();
},
// cancel
Backspace: () => {
if (text.length === 0) {
onCancel();
focusPrevElement();
e.preventDefault();
}
},
// navigate with arrows
ArrowLeft: () => {
if (getCaretPosition(ref) <= 0) {
focusPrevElement();
e.preventDefault();
}
},
ArrowRight: () => {
if (getCaretPosition(ref) === text.length) {
focusNextElement();
e.preventDefault();
}
},
Escape: () => {
if (focus === "yes") {
setFocus("hide");
e.preventDefault();
}
},
...extraHandlers,
};
const handler = keys[e.key];
if (handler) {
handler(e);
}
};
return <span className="inputBlock">
{(focus === "yes") && children}
<span className="editable suggest">{text}{focus && suggestion}</span>
<input ref={ref}
placeholder={placeholder}
className="editable"
value={text}
onInput={(e) =>
// @ts-ignore
onTextChange(e.target.value)}
onKeyDown={onKeyDown}
onFocus={() => setFocus("yes")}
onBlur={() => setFocus("no")}
spellCheck={false}
/>
</span>;
}

View file

@ -0,0 +1,72 @@
.infix {
margin-left: 1px;
margin-right: 1px;
}
.type {
margin:1px;
display: inline-block;
font-family: "Roboto", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
font-variation-settings: "wdth" 100;
color: darkgrey;
}
.functionType {
border: solid 1px darkred;
background-color: bisque;
color:darkred;
}
.productType {
border: solid 1px darkblue;
background-color: aliceblue;
color:darkblue;
}
.sumType {
border: solid 1px #333;
background-color: lightyellow;
color:#333
}
.dictType {
border-radius: 5px;
border: solid 1px darkblue;
background-color: rgb(206, 232, 255);
color:darkblue
}
.setType {
border-radius: 5px;
padding-left: 2px;
padding-right: 2px;
border: solid 1px darkblue;
background-color: rgb(206, 232, 255);
color:darkblue
}
.listType {
padding-left: 2px;
padding-right: 2px;
border: solid 1px darkblue;
background-color: rgb(206, 232, 255);
color:darkblue
}
.iteratorType {
border-style: dashed;
/* animation: flickerAnimation 500ms steps(1) normal infinite; */
}
/* Animations */
@keyframes flickerAnimation {
0% { opacity:1; }
50% { opacity:0; }
100% { opacity:1; }
}

View file

@ -0,0 +1,46 @@
import { getHumanReadableName, getSymbol, symbolDict, symbolDictIterator, symbolFunction, symbolList, symbolProduct, symbolSet, symbolSetIterator, symbolSum } from "dope2";
import "./Type.css";
export function Type({type}) {
const symbol = getSymbol(type);
switch (symbol) {
case symbolFunction:
return <BinaryType type={type} cssClass="functionType" infix="&rarr;" prefix="" suffix=""/>;
case symbolProduct:
return <BinaryType type={type} cssClass="productType" infix="&#10799;" prefix="" suffix=""/>;
case symbolSum:
return <BinaryType type={type} cssClass="sumType" infix="+" prefix="" suffix=""/>;
case symbolDict:
return <BinaryType type={type} cssClass="dictType" infix="&rArr;" prefix="{" suffix="}"/>;
case symbolSet:
return <UnaryType type={type} cssClass="setType" prefix="{" suffix="}" />;
case symbolList:
return <UnaryType type={type} cssClass="listType" prefix="[" suffix="]" />;
case symbolSetIterator:
return <UnaryType type={type} cssClass="setType iteratorType" prefix="{*" suffix="}" />;
case symbolDictIterator:
return <BinaryType type={type} cssClass="dictType iteratorType" infix="*&rArr;" prefix="{" suffix="}"/>;
default:
return <div className="type">{getHumanReadableName(symbol)}</div>
}
}
function BinaryType({type, cssClass, infix, prefix, suffix}) {
return <div className={`type ${cssClass}`}>
{prefix}
<Type type={type.params[0](type)}/>
<span className="infix">{infix}</span>
<Type type={type.params[1](type)}/>
{suffix}
</div>
}
function UnaryType({type, cssClass, prefix, suffix}) {
return <div className={`type ${cssClass}`}>
{prefix}
<Type type={type.params[0](type)}/>
{suffix}
</div>
}

View file

@ -0,0 +1,19 @@
.valuePrimitive {
border: 1px solid black;
border-radius: 10px;
margin-left: 2px;
margin-right: 2px;
padding-left: 2px;
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

@ -0,0 +1,90 @@
import {getType, getInst, getSymbol, Double, Int, symbolFunction, symbolProduct, symbolSum, symbolDict, symbolSet, symbolList, eqType, match, getLeft, getRight, dict, Bool, set, Unit, symbolType, symbolUUID, getHumanReadableName, Ordering} from "dope2";
import "./Value.css";
import { Type } from "./Type";
export function Value({dynamic}) {
const type = getType(dynamic);
const inst = getInst(dynamic);
if (eqType(type)(Double)) {
return <ValueDouble val={inst}/>;
}
if (eqType(type)(Int)) {
return <ValueInt val={inst}/>;
}
if (eqType(type)(Bool)) {
return <ValueBool val={inst}/>;
}
if (eqType(type)(Unit)) {
return <ValueUnit/>;
}
if (eqType(type)(Ordering)) {
return <ValueOrdering val={inst}/>;
}
const symbol = getSymbol(type);
switch (symbol) {
case symbolFunction:
return <ValueFunction/>;
case symbolSum:
return <ValueSum val={inst} leftType={type.params[0](type)} rightType={type.params[1](type)}/>;
case symbolProduct:
return <ValueProduct val={inst} leftType={type.params[0](type)} rightType={type.params[1](type)}/>;
case symbolDict:
return <ValueDict val={inst} keyType={type.params[0](type)} valueType={type.params[1](type)}/>;
case symbolSet:
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:
console.log("don't know how to show value:", dynamic);
return <>don't know how to show value</>;
}
}
function ValueDouble({val}) {
return <span className="valuePrimitive">{val.toString()}</span>;
}
function ValueInt({val}) {
return <span className="valuePrimitive">{val.toString()}</span>;
}
function ValueFunction() {
return <>&#119891;&#119899;&nbsp;</>;
}
function ValueBool({val}) {
return <span className="valuePrimitive">{val.toString()}</span>;
}
function ValueList({val, elemType}) {
return <span className="listType">[{val.map((v, i) => <Value key={i} dynamic={{i:v, t:elemType}}/>)}]</span>;
}
function ValueSet({val, elemType}) {
return <span className="setType">{'{'}{set.fold(acc => elem => acc.concat([elem]))([])(val).map((v, i) => <Value key={i} dynamic={{i:v, t:elemType}}/>)}{'}'}</span>;
}
function ValueDict({val, keyType, valueType}) {
return <span className="dictType">{'{'}{dict.fold(acc => key => value => acc.concat([[key,value]]))([])(val).map(([key, value], i) => <span key={i}>
<Value dynamic={{i:key, t:keyType}}/>
&rArr;
<Value dynamic={{i:value, t:valueType}}/>
</span>)}{'}'}</span>;
}
function ValueSum({val, leftType, rightType}) {
return match(val)
(l => <span className="sumType">L <Value dynamic={{i:l, t:leftType}}/></span>)
(r => <span className="sumType">R <Value dynamic={{i:r, t:rightType}}/></span>);
}
function ValueProduct({val, leftType, rightType}) {
return <span className="productType">(<Value dynamic={{i:getLeft(val), t:leftType}}/>,&nbsp;<Value dynamic={{i:getRight(val), t:rightType}} />)</span>;
}
function ValueUnit() {
return <>{'()'}</>;
}
function ValueUUID({val}) {
return <span className="valueUUID">{getHumanReadableName(val)}</span>;
}
function ValueOrdering({val}) {
return <span className="valueOrdering">{{[-1]: "LessThan", [0]: "Equal", [1]: "GreaterThan" }[val]}</span>
}