More improvements

This commit is contained in:
Ayaz Hafiz 2023-07-18 11:24:50 -05:00
parent 8388c93e62
commit 84165d21f4
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
10 changed files with 465 additions and 35 deletions

View file

@ -0,0 +1,4 @@
{
"tsserver.useLocalTsdk": true,
"tsserver.tsdk": "${workspaceFolder}/node_modules/typescript/lib"
}

View file

@ -1,29 +1,83 @@
import { ComponentProps } from "react";
import clsx from "clsx";
import { QuerySubs, TypeDescriptor } from "../../engine/subs";
import { Variable } from "../../schema";
import { ContentStyles } from "./../Content";
import DrawHeadConstructor from "../Content/HeadConstructor";
import { contentStyles } from "./../Content";
export function VariableElHelp({
variable,
styles,
onClick,
}: {
interface VariableElProps {
variable: Variable;
styles: ContentStyles;
onClick?: () => void;
}): JSX.Element {
const { name, bg } = styles;
subs: QuerySubs;
onClick?: (variable: Variable) => void;
nested?: boolean;
raw?: boolean;
}
export function VariableElPretty(props: VariableElProps): JSX.Element {
const { variable, subs } = props;
const desc = subs.get_root(variable);
const content = (
<DrawHeadConstructor
desc={desc}
drawVariablePretty={(variable) => (
<VariableElPretty {...props} variable={variable} nested />
)}
drawVariableRaw={(variable) => (
<VariableElRaw {...props} variable={variable} nested raw />
)}
/>
);
return (
<span className={clsx("py-0 pl-0 pr-1 rounded-md", bg)}>
<Helper {...props} desc={desc}>
{content}
</Helper>
);
}
function VariableElRaw(props: VariableElProps): JSX.Element {
const desc = props.subs.get_root(props.variable);
return <Helper {...props} desc={desc}></Helper>;
}
function Helper({
children,
variable,
desc,
onClick,
nested,
raw,
}: VariableElProps &
Pick<ComponentProps<"div">, "children"> & {
desc: TypeDescriptor | undefined;
}): JSX.Element {
const { bg } = contentStyles(desc);
const varHeader =
!nested || raw ? (
<span
className="ring-1 ring-inset ring-black-100 mr-1 px-1 bg-white rounded-md cursor"
className={clsx(
"ring-1 ring-inset ring-black-100 px-1 bg-white rounded-md cursor",
nested ? "text-md" : "p-0.5"
)}
onClick={(e) => {
e.stopPropagation();
onClick?.();
onClick?.(variable);
}}
>
{variable}
</span>
{name}
) : (
<></>
);
return (
<span
className={clsx(
"rounded-md whitespace-nowrap",
bg,
nested ? "text-sm" : "p-0.5 pl-0 text-base"
)}
>
{varHeader}
{children ? <span className="px-1">{children}</span> : <></>}
</span>
);
}

View file

@ -0,0 +1,344 @@
import { TypeDescriptor } from "../../engine/subs";
import {
ClosureType,
RecordFieldKind,
UnspecializedClosureType,
Variable,
} from "../../schema";
type DrawVariable = (variable: Variable) => JSX.Element;
export interface DrawHeadConstructorProps {
desc: TypeDescriptor | undefined;
drawVariablePretty: DrawVariable;
drawVariableRaw: DrawVariable;
}
export default function DrawHeadConstructor({
desc,
drawVariablePretty,
drawVariableRaw,
}: DrawHeadConstructorProps): JSX.Element {
if (!desc) {
return <>???</>;
}
const content = desc.content;
switch (content.type) {
case "Flex":
case "Rigid": {
const { name } = content;
return name ? <>{name}</> : <>_</>;
}
case "FlexAble":
case "RigidAble": {
const { name, abilities } = content;
const nameEl = name ? <>{name}</> : <>_</>;
return (
<>
{nameEl} has {abilities.join(", ")}
</>
);
}
case "Recursive": {
const { name, structure } = content;
const structureEl = drawVariableRaw(structure);
const nameEl = name ? <>{name} to </> : <></>;
return (
<>
&lt;{nameEl}
{structureEl}&gt;
</>
);
}
case "LambdaSet": {
const { ambient_function, solved, unspecialized, recursion_var } =
content;
const ambientFunctionEl = drawVariableRaw(ambient_function);
const solvedEl = (
<DrawSolved drawVariableRaw={drawVariableRaw} solved={solved} />
);
const unspecializedEl = (
<DrawUnspecialized
drawVariableRaw={drawVariableRaw}
unspecialized={unspecialized}
/>
);
const recursionVarEl = recursion_var ? (
<> as &lt;{drawVariableRaw(recursion_var)}&gt;</>
) : (
<></>
);
return (
<>
[{solvedEl}
{unspecializedEl}]{recursionVarEl} ^{ambientFunctionEl}
</>
);
}
case "ErasedLambda": {
return <>?</>;
}
case "Alias": {
const { kind, name, variables } = content;
const prefix = kind.type === "Opaque" ? "@" : "";
const variablesEl = (
<DrawVarArgumentsList
drawVariableRaw={drawVariableRaw}
variables={variables.type_variables}
/>
);
return (
<span>
{prefix}
{sym(name)}
{variablesEl}
</span>
);
}
case "Apply": {
const { symbol, variables } = content;
const variablesEl = (
<DrawVarArgumentsList
drawVariableRaw={drawVariableRaw}
variables={variables}
/>
);
return (
<>
{sym(symbol)}
{variablesEl}
</>
);
}
case "Function": {
const { arguments: args, lambda_type, ret } = content;
const argsEl = args.map((arg, i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
{drawVariablePretty(arg)}
</span>
));
const lambdaTypeEl = drawVariablePretty(lambda_type);
const retEl = drawVariablePretty(ret);
return (
<>
{argsEl}
{" -"}
{lambdaTypeEl}
{"-> "}
{retEl}
</>
);
}
case "Record": {
const { fields, extension } = content;
const fieldsEl = Object.entries(fields).map(([key, value], i) => {
const { field_type, kind } = value;
return (
<span key={i}>
{i !== 0 ? ", " : ""}
{key} {<DrawFieldKind kind={kind} />}{" "}
{drawVariablePretty(field_type)}
</span>
);
});
return (
<>
{"{"}
{fieldsEl}
{"}"}
{drawVariablePretty(extension)}
</>
);
}
case "Tuple": {
const { elements, extension } = content;
const elemsEl = Object.entries(elements).map(([key, value], i) => {
return (
<span key={i}>
{i !== 0 ? ", " : ""}
{key}: {drawVariablePretty(value)}
</span>
);
});
return (
<>
({elemsEl}){drawVariablePretty(extension)}
</>
);
}
case "TagUnion": {
const { tags, extension } = content;
return (
<>
<DrawTags tags={tags} drawVariableRaw={drawVariableRaw} />
{drawVariablePretty(extension.variable)}
</>
);
}
case "RecursiveTagUnion": {
const { tags, extension, recursion_var } = content;
return (
<>
(<DrawTags tags={tags} drawVariableRaw={drawVariableRaw} />
{drawVariablePretty(extension.variable)} as &lt;
{drawVariableRaw(recursion_var)}&gt;)
</>
);
}
case "FunctionOrTagUnion": {
const { functions, tags, extension } = content;
const functionsEl = functions.map((f, i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
{sym(f)}
</span>
));
const tagsEl = tags.map((t, i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
{t}
</span>
));
return (
<>
[{functionsEl} | {tagsEl}]{drawVariablePretty(extension.variable)}
</>
);
}
case "RangedNumber": {
const {
range: { kind, min_width, signed },
} = content;
switch (kind.type) {
case "AnyNum":
return <>{min_width}+</>;
case "Int":
return signed ? <>{min_width}+</> : <>{min_width}+</>;
}
break;
}
case "EmptyRecord": {
return <>{"{}"}</>;
}
case "EmptyTuple": {
return <>()</>;
}
case "EmptyTagUnion": {
return <>[]</>;
}
case "Error": {
return <></>;
}
}
}
function DrawVarArgumentsList({
variables,
drawVariableRaw,
}: {
variables: Variable[];
drawVariableRaw: DrawVariable;
}): JSX.Element {
return variables.length !== 0 ? (
<>
{" "}
{variables.map((v, i) => (
<span key={i}>{drawVariableRaw(v)}</span>
))}
</>
) : (
<></>
);
}
function DrawSolved({
solved,
drawVariableRaw,
}: {
solved: ClosureType[];
drawVariableRaw: DrawVariable;
}): JSX.Element {
const tags = solved.map(({ environment, function: fn }, i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
<DrawTag
tag={sym(fn)}
variables={environment}
drawVariableRaw={drawVariableRaw}
/>
</span>
));
return <>[{tags}]</>;
}
function DrawTags({
tags,
drawVariableRaw,
}: {
tags: Record<string, Variable[]>;
drawVariableRaw: DrawVariable;
}): JSX.Element {
const tagsEl = Object.entries(tags).map(([tag, vars], i) => (
<span key={i}>
{i !== 0 ? ", " : ""}
<DrawTag tag={tag} variables={vars} drawVariableRaw={drawVariableRaw} />
</span>
));
return <>[{tagsEl}]</>;
}
function DrawUnspecialized({
unspecialized,
drawVariableRaw,
}: {
unspecialized: UnspecializedClosureType[];
drawVariableRaw: DrawVariable;
}): JSX.Element {
const unspecs = unspecialized.map(
({ ability_member, lambda_set_region, specialization }, i) => (
<span key={i}>
{" + "}
{drawVariableRaw(specialization)}:{sym(ability_member)}:
{lambda_set_region}
</span>
)
);
return <>{unspecs}</>;
}
function DrawTag({
tag,
variables,
drawVariableRaw,
}: {
tag: string;
variables: Variable[];
drawVariableRaw: DrawVariable;
}): JSX.Element {
return (
<>
{tag}
<DrawVarArgumentsList
drawVariableRaw={drawVariableRaw}
variables={variables}
/>
</>
);
}
function DrawFieldKind({ kind }: { kind: RecordFieldKind }): JSX.Element {
switch (kind.type) {
case "Required":
case "Demanded":
return <>:</>;
case "Optional":
return <>?</>;
}
}
function sym(symbol: string): string {
if (symbol.startsWith("`")) symbol = symbol.slice(1);
if (symbol.endsWith("`")) symbol = symbol.slice(0, -1);
return symbol.split(".").at(-1)!;
}

View file

@ -1,7 +1,6 @@
import { EventIndex } from "../../engine/engine";
import { Variable } from "../../schema";
import { contentStyles } from "../Content";
import { VariableElHelp } from "../Common/Variable";
import { VariableElPretty } from "../Common/Variable";
import { CommonProps } from "./types";
interface VariableProps extends CommonProps {
@ -16,15 +15,13 @@ export function VariableEl({
variable,
}: VariableProps): JSX.Element {
engine.stepTo(index);
const desc = engine.subs.get_root(variable);
const styles = contentStyles(desc);
return (
<VariableElHelp
<VariableElPretty
variable={variable}
styles={styles}
onClick={() => {
subs={engine.subs}
onClick={(variable: Variable) => {
toggleVariableVis(variable);
}}
></VariableElHelp>
></VariableElPretty>
);
}

View file

@ -81,7 +81,10 @@ function Unification(props: UnificationProps): JSX.Element {
function getHeadline(index: EventIndex) {
return (
<button onClick={() => setIsOpen(!isOpen)} className="w-full text-left">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full text-left whitespace-nowrap"
>
<span
className={clsx("mr-2", isOpen ? "text-slate-500" : "text-slate-400")}
>
@ -103,7 +106,7 @@ function Unification(props: UnificationProps): JSX.Element {
const headlineBefore = getHeadline(beforeUnificationIndex);
const headlineAfter = (
<div className={MT}>
<div className={clsx(MT, "whitespace-nowrap")}>
{dropdownTransparent}
{resultHeadline} {leftVar(afterUnificationIndex)} {modeIcon}{" "}
{rightVar(afterUnificationIndex)}

View file

@ -1,3 +1,4 @@
import { Variable } from "../../schema";
export type ToggleVariableHandler = (variable: Variable) => void;
export type KeydownHandler = (key: string) => void;

View file

@ -3,7 +3,7 @@ import { Handle, Position } from "reactflow";
import { Variable } from "../../schema";
import { assertExhaustive } from "../../utils/exhaustive";
import { contentStyles } from "../Content";
import { VariableElHelp } from "../Common/Variable";
import { VariableElPretty } from "../Common/Variable";
import { SubsSnapshot, TypeDescriptor } from "../../engine/subs";
type AddSubVariableLink = (from: Variable, subVariable: Variable) => void;
@ -54,7 +54,7 @@ export default function VariableNode({ data }: VariableNodeProps): JSX.Element {
>
<Handle type="target" position={Position.Top} isConnectable={false} />
<div>
<VariableElHelp variable={variable} styles={styles} />
<VariableElPretty variable={variable} subs={subs} />
</div>
{expandedContent}
<Handle type="source" position={Position.Bottom} isConnectable={false} />
@ -185,12 +185,10 @@ function SubVariable({
}: {
variable: Variable;
} & BasisProps): JSX.Element {
const desc = subs.get_root(variable);
const styles = contentStyles(desc);
return (
<VariableElHelp
<VariableElPretty
variable={variable}
styles={styles}
subs={subs}
onClick={() => addSubVariableLink(origin, variable)}
/>
);

View file

@ -20,10 +20,12 @@ import "reactflow/dist/style.css";
import clsx from "clsx";
import VariableNode, { VariableNodeProps } from "./VariableNode";
import { SubsSnapshot } from "../../engine/subs";
import { KeydownHandler } from "../Events";
export interface VariablesGraphProps {
subs: SubsSnapshot;
onVariable: (handler: (variable: Variable) => void) => void;
onKeydown: (handler: KeydownHandler) => void;
}
type GraphDirection = "TB" | "BT" | "LR" | "RL";
@ -103,7 +105,11 @@ function addEdgeChange(edge: Edge, existingEdges: Edge[]): EdgeChange | null {
};
}
function Graph({ subs, onVariable }: VariablesGraphProps): JSX.Element {
function Graph({
subs,
onVariable,
onKeydown,
}: VariablesGraphProps): JSX.Element {
const instance = useReactFlow();
const initialNodes: Node[] = [];
const initialEdges: Edge[] = [];
@ -225,6 +231,13 @@ function Graph({ subs, onVariable }: VariablesGraphProps): JSX.Element {
);
onVariable(addNode);
onKeydown((key) => {
switch (key) {
case "c": {
onLayoutChange(direction);
}
}
});
return (
<ReactFlow

View file

@ -4,7 +4,7 @@ import { Engine } from "../engine/engine";
import EventList from "./EventList";
import VariablesGraph from "./Graph/VariablesGraph";
import { TypedEmitter } from "tiny-typed-emitter";
import { ToggleVariableHandler } from "./Events";
import { KeydownHandler, ToggleVariableHandler } from "./Events";
interface UiProps {
events: AllEvents;
@ -12,6 +12,7 @@ interface UiProps {
interface MessageEvents {
toggleVariable: ToggleVariableHandler;
keydown: KeydownHandler;
}
export default function Ui({ events }: UiProps): JSX.Element {
@ -19,15 +20,24 @@ export default function Ui({ events }: UiProps): JSX.Element {
const ee = new TypedEmitter<MessageEvents>();
const toggleVariableHandlers: ToggleVariableHandler[] = [];
const keydownHandlers: KeydownHandler[] = [];
ee.on("toggleVariable", (variable: Variable) => {
toggleVariableHandlers.forEach((handler) => handler(variable));
});
ee.on("keydown", (key: string) => {
keydownHandlers.forEach((handler) => handler(key));
});
engine.stepTo(engine.lastEventIndex());
const subs = engine.subs.snapshot();
return (
<div className="flex flex-col md:flex-row gap-0 w-full h-full">
<div
className="flex flex-col md:flex-row gap-0 w-full h-full"
onKeyDown={(e) => {
ee.emit("keydown", e.key);
}}
>
<div className="font-mono mt-2 text-lg md:flex-1 overflow-scroll">
<EventList
engine={engine}
@ -44,6 +54,9 @@ export default function Ui({ events }: UiProps): JSX.Element {
onVariable={(handler) => {
toggleVariableHandlers.push(handler);
}}
onKeydown={(handler) => {
keydownHandlers.push(handler);
}}
/>
</div>
</div>

View file

@ -58,7 +58,7 @@ export type ChangeEvent =
export type Change = ChangeEvent | RollbackChange;
export class Subs {
export class Subs implements QuerySubs {
#map: Map<Variable, VarType>;
private constructor(map: Map<Variable, VarType>) {
@ -165,9 +165,12 @@ export class Subs {
const SnapshotSymbol = Symbol("Snapshot");
export interface SubsSnapshot {
export interface QuerySubs {
get(variable: Variable): VarType | undefined;
get_root(variable: Variable): TypeDescriptor | undefined;
get_root_key(variable: Variable): Variable;
}
export interface SubsSnapshot extends QuerySubs {
__snapshot__: typeof SnapshotSymbol;
}