store location in values; render both result and bindings

This commit is contained in:
Vyacheslav Matyukhin 2022-08-30 01:51:44 +04:00
parent ddfd4e0024
commit a7bbfad94b
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
22 changed files with 238 additions and 243 deletions

View File

@ -71,7 +71,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
distributionChartActions, distributionChartActions,
enableLocalSettings = false, enableLocalSettings = false,
}) => { }) => {
const result = useSquiggle({ const { result, bindings } = useSquiggle({
code, code,
environment, environment,
// jsImports, // jsImports,
@ -98,6 +98,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
}; };
return ( return (
<div>
<SquiggleViewer <SquiggleViewer
result={result} result={result}
width={width} width={width}
@ -107,6 +108,17 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
environment={environment ?? defaultEnvironment} environment={environment ?? defaultEnvironment}
enableLocalSettings={enableLocalSettings} enableLocalSettings={enableLocalSettings}
/> />
<hr className="my-4" />
<SquiggleViewer
result={{ tag: "Ok", value: bindings.asValue() }}
width={width}
height={height}
distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings}
environment={environment ?? defaultEnvironment}
enableLocalSettings={enableLocalSettings}
/>
</div>
); );
} }
); );

View File

@ -37,13 +37,18 @@ function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
*/ */
const VariableList: React.FC<{ const VariableList: React.FC<{
path: string[]; value: SqValue;
heading: string; heading: string;
children: (settings: MergedItemSettings) => React.ReactNode; children: (settings: MergedItemSettings) => React.ReactNode;
}> = ({ path, heading, children }) => ( }> = ({ value, heading, children }) => (
<VariableBox path={path} heading={heading}> <VariableBox value={value} heading={heading}>
{(settings) => ( {(settings) => (
<div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}> <div
className={clsx(
"space-y-3",
value.location.path.items.length ? "pt-1 mt-1" : null
)}
>
{children(settings)} {children(settings)}
</div> </div>
)} )}
@ -52,54 +57,41 @@ const VariableList: React.FC<{
export interface Props { export interface Props {
/** The output of squiggle's run */ /** The output of squiggle's run */
expression: SqValue; value: SqValue;
/** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */
path: string[];
width?: number; width?: number;
} }
export const ExpressionViewer: React.FC<Props> = ({ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
path,
expression,
width,
}) => {
const { getMergedSettings } = useContext(ViewerContext); const { getMergedSettings } = useContext(ViewerContext);
if (typeof expression !== "object") { switch (value.tag) {
return (
<VariableList path={path} heading="Error">
{() => `Unknown expression: ${expression}`}
</VariableList>
);
}
switch (expression.tag) {
case SqValueTag.Number: case SqValueTag.Number:
return ( return (
<VariableBox path={path} heading="Number"> <VariableBox value={value} heading="Number">
{() => ( {() => (
<div className="font-semibold text-slate-600"> <div className="font-semibold text-slate-600">
<NumberShower precision={3} number={expression.value} /> <NumberShower precision={3} number={value.value} />
</div> </div>
)} )}
</VariableBox> </VariableBox>
); );
case SqValueTag.Distribution: { case SqValueTag.Distribution: {
const distType = expression.value.tag; const distType = value.value.tag;
return ( return (
<VariableBox <VariableBox
path={path} value={value}
heading={`Distribution (${distType})\n${ heading={`Distribution (${distType})\n${
distType === SqDistributionTag.Symbolic distType === SqDistributionTag.Symbolic
? expression.value.toString() ? value.value.toString()
: "" : ""
}`} }`}
renderSettingsMenu={({ onChange }) => { renderSettingsMenu={({ onChange }) => {
const shape = expression.value.pointSet( const shape = value.value.pointSet(
getMergedSettings(path).environment getMergedSettings(value.location).environment
); );
return ( return (
<ItemSettingsMenu <ItemSettingsMenu
path={path} value={value}
onChange={onChange} onChange={onChange}
disableLogX={ disableLogX={
shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape()) shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape())
@ -112,7 +104,7 @@ export const ExpressionViewer: React.FC<Props> = ({
{(settings) => { {(settings) => {
return ( return (
<DistributionChart <DistributionChart
plot={defaultPlot(expression.value)} plot={defaultPlot(value.value)}
environment={settings.environment} environment={settings.environment}
{...settings.distributionPlotSettings} {...settings.distributionPlotSettings}
height={settings.height} height={settings.height}
@ -125,12 +117,12 @@ export const ExpressionViewer: React.FC<Props> = ({
} }
case SqValueTag.String: case SqValueTag.String:
return ( return (
<VariableBox path={path} heading="String"> <VariableBox value={value} heading="String">
{() => ( {() => (
<> <>
<span className="text-slate-400">"</span> <span className="text-slate-400">"</span>
<span className="text-slate-600 font-semibold font-mono"> <span className="text-slate-600 font-semibold font-mono">
{expression.value} {value.value}
</span> </span>
<span className="text-slate-400">"</span> <span className="text-slate-400">"</span>
</> </>
@ -139,61 +131,61 @@ export const ExpressionViewer: React.FC<Props> = ({
); );
case SqValueTag.Bool: case SqValueTag.Bool:
return ( return (
<VariableBox path={path} heading="Boolean"> <VariableBox value={value} heading="Boolean">
{() => expression.value.toString()} {() => value.value.toString()}
</VariableBox> </VariableBox>
); );
case SqValueTag.Symbol: case SqValueTag.Symbol:
return ( return (
<VariableBox path={path} heading="Symbol"> <VariableBox value={value} heading="Symbol">
{() => ( {() => (
<> <>
<span className="text-slate-500 mr-2">Undefined Symbol:</span> <span className="text-slate-500 mr-2">Undefined Symbol:</span>
<span className="text-slate-600">{expression.value}</span> <span className="text-slate-600">{value.value}</span>
</> </>
)} )}
</VariableBox> </VariableBox>
); );
case SqValueTag.Call: case SqValueTag.Call:
return ( return (
<VariableBox path={path} heading="Call"> <VariableBox value={value} heading="Call">
{() => expression.value} {() => value.value}
</VariableBox> </VariableBox>
); );
case SqValueTag.ArrayString: case SqValueTag.ArrayString:
return ( return (
<VariableBox path={path} heading="Array String"> <VariableBox value={value} heading="Array String">
{() => expression.value.map((r) => `"${r}"`).join(", ")} {() => value.value.map((r) => `"${r}"`).join(", ")}
</VariableBox> </VariableBox>
); );
case SqValueTag.Date: case SqValueTag.Date:
return ( return (
<VariableBox path={path} heading="Date"> <VariableBox value={value} heading="Date">
{() => expression.value.toDateString()} {() => value.value.toDateString()}
</VariableBox> </VariableBox>
); );
case SqValueTag.Void: case SqValueTag.Void:
return ( return (
<VariableBox path={path} heading="Void"> <VariableBox value={value} heading="Void">
{() => "Void"} {() => "Void"}
</VariableBox> </VariableBox>
); );
case SqValueTag.TimeDuration: { case SqValueTag.TimeDuration: {
return ( return (
<VariableBox path={path} heading="Time Duration"> <VariableBox value={value} heading="Time Duration">
{() => <NumberShower precision={3} number={expression.value} />} {() => <NumberShower precision={3} number={value.value} />}
</VariableBox> </VariableBox>
); );
} }
case SqValueTag.Lambda: case SqValueTag.Lambda:
return ( return (
<VariableBox <VariableBox
path={path} value={value}
heading="Function" heading="Function"
renderSettingsMenu={({ onChange }) => { renderSettingsMenu={({ onChange }) => {
return ( return (
<ItemSettingsMenu <ItemSettingsMenu
path={path} value={value}
onChange={onChange} onChange={onChange}
withFunctionSettings={true} withFunctionSettings={true}
/> />
@ -202,11 +194,11 @@ export const ExpressionViewer: React.FC<Props> = ({
> >
{(settings) => ( {(settings) => (
<> <>
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value <div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${value.value
.parameters() .parameters()
.join(",")})`}</div> .join(",")})`}</div>
<FunctionChart <FunctionChart
fn={expression.value} fn={value.value}
chartSettings={settings.chartSettings} chartSettings={settings.chartSettings}
distributionPlotSettings={settings.distributionPlotSettings} distributionPlotSettings={settings.distributionPlotSettings}
height={settings.height} height={settings.height}
@ -222,13 +214,13 @@ export const ExpressionViewer: React.FC<Props> = ({
case SqValueTag.Declaration: { case SqValueTag.Declaration: {
return ( return (
<VariableBox <VariableBox
path={path} value={value}
heading="Function Declaration" heading="Function Declaration"
renderSettingsMenu={({ onChange }) => { renderSettingsMenu={({ onChange }) => {
return ( return (
<ItemSettingsMenu <ItemSettingsMenu
onChange={onChange} onChange={onChange}
path={path} value={value}
withFunctionSettings={true} withFunctionSettings={true}
/> />
); );
@ -252,16 +244,15 @@ export const ExpressionViewer: React.FC<Props> = ({
} }
case SqValueTag.Module: { case SqValueTag.Module: {
return ( return (
<VariableList path={path} heading="Module"> <VariableList value={value} heading="Module">
{(_) => {(_) =>
expression.value value.value
.entries() .entries()
.filter(([key, _]) => !key.match(/^(Math|System)\./)) .filter(([key, _]) => !key.match(/^(Math|System)\./))
.map(([key, r]) => ( .map(([key, r]) => (
<ExpressionViewer <ExpressionViewer
key={key} key={key}
path={[...path, key]} value={r}
expression={r}
width={width !== undefined ? width - 20 : width} width={width !== undefined ? width - 20 : width}
/> />
)) ))
@ -270,16 +261,16 @@ export const ExpressionViewer: React.FC<Props> = ({
); );
} }
case SqValueTag.Record: case SqValueTag.Record:
const plot = makePlot(expression.value); const plot = makePlot(value.value);
if (plot) { if (plot) {
return ( return (
<VariableBox <VariableBox
path={path} value={value}
heading="Plot" heading="Plot"
renderSettingsMenu={({ onChange }) => { renderSettingsMenu={({ onChange }) => {
let disableLogX = plot.distributions.some((x) => { let disableLogX = plot.distributions.some((x) => {
let pointSet = x.distribution.pointSet( let pointSet = x.distribution.pointSet(
getMergedSettings(path).environment getMergedSettings(value.location).environment
); );
return ( return (
pointSet.tag === "Ok" && pointSet.tag === "Ok" &&
@ -288,7 +279,7 @@ export const ExpressionViewer: React.FC<Props> = ({
}); });
return ( return (
<ItemSettingsMenu <ItemSettingsMenu
path={path} value={value}
onChange={onChange} onChange={onChange}
disableLogX={disableLogX} disableLogX={disableLogX}
withFunctionSettings={false} withFunctionSettings={false}
@ -311,15 +302,14 @@ export const ExpressionViewer: React.FC<Props> = ({
); );
} else { } else {
return ( return (
<VariableList path={path} heading="Record"> <VariableList value={value} heading="Record">
{(_) => {(_) =>
expression.value value.value
.entries() .entries()
.map(([key, r]) => ( .map(([key, r]) => (
<ExpressionViewer <ExpressionViewer
key={key} key={key}
path={[...path, key]} value={r}
expression={r}
width={width !== undefined ? width - 20 : width} width={width !== undefined ? width - 20 : width}
/> />
)) ))
@ -329,15 +319,14 @@ export const ExpressionViewer: React.FC<Props> = ({
} }
case SqValueTag.Array: case SqValueTag.Array:
return ( return (
<VariableList path={path} heading="Array"> <VariableList value={value} heading="Array">
{(_) => {(_) =>
expression.value value.value
.getValues() .getValues()
.map((r, i) => ( .map((r, i) => (
<ExpressionViewer <ExpressionViewer
key={i} key={i}
path={[...path, String(i)]} value={r}
expression={r}
width={width !== undefined ? width - 20 : width} width={width !== undefined ? width - 20 : width}
/> />
)) ))
@ -346,13 +335,11 @@ export const ExpressionViewer: React.FC<Props> = ({
); );
default: { default: {
return ( return (
<VariableList path={path} heading="Error"> <VariableList value={value} heading="Error">
{() => ( {() => (
<div> <div>
<span>No display for type: </span>{" "} <span>No display for type: </span>{" "}
<span className="font-semibold text-slate-600"> <span className="font-semibold text-slate-600">{value.tag}</span>
{expression.tag}
</span>
</div> </div>
)} )}
</VariableList> </VariableList>

View File

@ -4,13 +4,14 @@ import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
import { Modal } from "../ui/Modal"; import { Modal } from "../ui/Modal";
import { ViewSettings, viewSettingsSchema } from "../ViewSettings"; import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
import { Path, pathAsString } from "./utils";
import { ViewerContext } from "./ViewerContext"; import { ViewerContext } from "./ViewerContext";
import { defaultTickFormat } from "../../lib/distributionSpecBuilder"; import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
import { PlaygroundContext } from "../SquigglePlayground"; import { PlaygroundContext } from "../SquigglePlayground";
import { SqValue } from "@quri/squiggle-lang";
import { locationAsString } from "./utils";
type Props = { type Props = {
path: Path; value: SqValue;
onChange: () => void; onChange: () => void;
disableLogX?: boolean; disableLogX?: boolean;
withFunctionSettings: boolean; withFunctionSettings: boolean;
@ -19,7 +20,7 @@ type Props = {
const ItemSettingsModal: React.FC< const ItemSettingsModal: React.FC<
Props & { close: () => void; resetScroll: () => void } Props & { close: () => void; resetScroll: () => void }
> = ({ > = ({
path, value,
onChange, onChange,
disableLogX, disableLogX,
withFunctionSettings, withFunctionSettings,
@ -29,7 +30,7 @@ const ItemSettingsModal: React.FC<
const { setSettings, getSettings, getMergedSettings } = const { setSettings, getSettings, getMergedSettings } =
useContext(ViewerContext); useContext(ViewerContext);
const mergedSettings = getMergedSettings(path); const mergedSettings = getMergedSettings(value.location);
const { register, watch } = useForm({ const { register, watch } = useForm({
resolver: yupResolver(viewSettingsSchema), resolver: yupResolver(viewSettingsSchema),
@ -53,8 +54,8 @@ const ItemSettingsModal: React.FC<
}); });
useEffect(() => { useEffect(() => {
const subscription = watch((vars) => { const subscription = watch((vars) => {
const settings = getSettings(path); // get the latest version const settings = getSettings(value.location); // get the latest version
setSettings(path, { setSettings(value.location, {
...settings, ...settings,
distributionPlotSettings: { distributionPlotSettings: {
showSummary: vars.showSummary, showSummary: vars.showSummary,
@ -75,7 +76,7 @@ const ItemSettingsModal: React.FC<
onChange(); onChange();
}); });
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
}, [getSettings, setSettings, onChange, path, watch]); }, [getSettings, setSettings, onChange, value.location, watch]);
const { getLeftPanelElement } = useContext(PlaygroundContext); const { getLeftPanelElement } = useContext(PlaygroundContext);
@ -83,7 +84,7 @@ const ItemSettingsModal: React.FC<
<Modal container={getLeftPanelElement()} close={close}> <Modal container={getLeftPanelElement()} close={close}>
<Modal.Header> <Modal.Header>
Chart settings Chart settings
{path.length ? ( {value.location.path.items.length ? (
<> <>
{" for "} {" for "}
<span <span
@ -91,7 +92,7 @@ const ItemSettingsModal: React.FC<
className="cursor-pointer" className="cursor-pointer"
onClick={resetScroll} onClick={resetScroll}
> >
{pathAsString(path)} {locationAsString(value.location)}
</span>{" "} </span>{" "}
</> </>
) : ( ) : (
@ -120,7 +121,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
if (!enableLocalSettings) { if (!enableLocalSettings) {
return null; return null;
} }
const settings = getSettings(props.path); const settings = getSettings(props.value.location);
const resetScroll = () => { const resetScroll = () => {
if (!ref.current) return; if (!ref.current) return;
@ -139,7 +140,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
{settings.distributionPlotSettings || settings.chartSettings ? ( {settings.distributionPlotSettings || settings.chartSettings ? (
<button <button
onClick={() => { onClick={() => {
setSettings(props.path, { setSettings(props.value.location, {
...settings, ...settings,
distributionPlotSettings: undefined, distributionPlotSettings: undefined,
chartSettings: undefined, chartSettings: undefined,

View File

@ -1,3 +1,4 @@
import { SqValue, SqValueLocation } from "@quri/squiggle-lang";
import React, { useContext, useReducer } from "react"; import React, { useContext, useReducer } from "react";
import { Tooltip } from "../ui/Tooltip"; import { Tooltip } from "../ui/Tooltip";
import { LocalItemSettings, MergedItemSettings } from "./utils"; import { LocalItemSettings, MergedItemSettings } from "./utils";
@ -8,14 +9,14 @@ type SettingsMenuParams = {
}; };
type VariableBoxProps = { type VariableBoxProps = {
path: string[]; value: SqValue;
heading: string; heading: string;
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode; renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
children: (settings: MergedItemSettings) => React.ReactNode; children: (settings: MergedItemSettings) => React.ReactNode;
}; };
export const VariableBox: React.FC<VariableBoxProps> = ({ export const VariableBox: React.FC<VariableBoxProps> = ({
path, value: { location },
heading = "Error", heading = "Error",
renderSettingsMenu, renderSettingsMenu,
children, children,
@ -27,10 +28,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
// So we use `forceUpdate` to force rerendering. // So we use `forceUpdate` to force rerendering.
const [_, forceUpdate] = useReducer((x) => x + 1, 0); const [_, forceUpdate] = useReducer((x) => x + 1, 0);
const settings = getSettings(path); const settings = getSettings(location);
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => { const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
setSettings(path, newSettings); setSettings(location, newSettings);
forceUpdate(); forceUpdate();
}; };
@ -38,8 +39,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed }); setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
}; };
const isTopLevel = path.length === 0; const isTopLevel = location.path.items.length === 0;
const name = isTopLevel ? "Result" : path[path.length - 1]; const name = isTopLevel
? { result: "Result", bindings: "Bindings" }[location.path.root]
: location.path.items[location.path.items.length - 1];
return ( return (
<div> <div>
@ -65,13 +68,13 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
</header> </header>
{settings.collapsed ? null : ( {settings.collapsed ? null : (
<div className="flex w-full"> <div className="flex w-full">
{path.length ? ( {location.path.items.length ? (
<div <div
className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer" className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
onClick={toggleCollapsed} onClick={toggleCollapsed}
></div> ></div>
) : null} ) : null}
<div className="grow">{children(getMergedSettings(path))}</div> <div className="grow">{children(getMergedSettings(location))}</div>
</div> </div>
)} )}
</div> </div>

View File

@ -1,14 +1,14 @@
import { defaultEnvironment } from "@quri/squiggle-lang"; import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
import React from "react"; import React from "react";
import { LocalItemSettings, MergedItemSettings, Path } from "./utils"; import { LocalItemSettings, MergedItemSettings } from "./utils";
type ViewerContextShape = { type ViewerContextShape = {
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update). // Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
// Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch. // Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
// See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this. // See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
getSettings(path: Path): LocalItemSettings; getSettings(location: SqValueLocation): LocalItemSettings;
getMergedSettings(path: Path): MergedItemSettings; getMergedSettings(location: SqValueLocation): MergedItemSettings;
setSettings(path: Path, value: LocalItemSettings): void; setSettings(location: SqValueLocation, value: LocalItemSettings): void;
enableLocalSettings: boolean; // show local settings icon in the UI enableLocalSettings: boolean; // show local settings icon in the UI
}; };

View File

@ -1,21 +1,20 @@
import React, { useCallback, useRef } from "react"; import React, { useCallback, useRef } from "react";
import { environment } from "@quri/squiggle-lang"; import { environment, SqValueLocation } from "@quri/squiggle-lang";
import { DistributionPlottingSettings } from "../DistributionChart"; import { DistributionPlottingSettings } from "../DistributionChart";
import { FunctionChartSettings } from "../FunctionChart"; import { FunctionChartSettings } from "../FunctionChart";
import { ExpressionViewer } from "./ExpressionViewer"; import { ExpressionViewer } from "./ExpressionViewer";
import { ViewerContext } from "./ViewerContext"; import { ViewerContext } from "./ViewerContext";
import { import {
LocalItemSettings, LocalItemSettings,
locationAsString,
MergedItemSettings, MergedItemSettings,
Path,
pathAsString,
} from "./utils"; } from "./utils";
import { useSquiggle } from "../../lib/hooks"; import { useSquiggle } from "../../lib/hooks";
import { SquiggleErrorAlert } from "../SquiggleErrorAlert"; import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
type Props = { type Props = {
/** The output of squiggle's run */ /** The output of squiggle's run */
result: ReturnType<typeof useSquiggle>; result: ReturnType<typeof useSquiggle>["result"];
width?: number; width?: number;
height: number; height: number;
distributionPlotSettings: DistributionPlottingSettings; distributionPlotSettings: DistributionPlottingSettings;
@ -45,22 +44,22 @@ export const SquiggleViewer: React.FC<Props> = ({
const settingsRef = useRef<Settings>({}); const settingsRef = useRef<Settings>({});
const getSettings = useCallback( const getSettings = useCallback(
(path: Path) => { (location: SqValueLocation) => {
return settingsRef.current[pathAsString(path)] || defaultSettings; return settingsRef.current[locationAsString(location)] || defaultSettings;
}, },
[settingsRef] [settingsRef]
); );
const setSettings = useCallback( const setSettings = useCallback(
(path: Path, value: LocalItemSettings) => { (location: SqValueLocation, value: LocalItemSettings) => {
settingsRef.current[pathAsString(path)] = value; settingsRef.current[locationAsString(location)] = value;
}, },
[settingsRef] [settingsRef]
); );
const getMergedSettings = useCallback( const getMergedSettings = useCallback(
(path: Path) => { (location: SqValueLocation) => {
const localSettings = getSettings(path); const localSettings = getSettings(location);
const result: MergedItemSettings = { const result: MergedItemSettings = {
distributionPlotSettings: { distributionPlotSettings: {
...distributionPlotSettings, ...distributionPlotSettings,
@ -91,7 +90,7 @@ export const SquiggleViewer: React.FC<Props> = ({
}} }}
> >
{result.tag === "Ok" ? ( {result.tag === "Ok" ? (
<ExpressionViewer path={[]} expression={result.value} width={width} /> <ExpressionViewer value={result.value} width={width} />
) : ( ) : (
<SquiggleErrorAlert error={result.value} /> <SquiggleErrorAlert error={result.value} />
)} )}

View File

@ -1,6 +1,6 @@
import { DistributionPlottingSettings } from "../DistributionChart"; import { DistributionPlottingSettings } from "../DistributionChart";
import { FunctionChartSettings } from "../FunctionChart"; import { FunctionChartSettings } from "../FunctionChart";
import { environment } from "@quri/squiggle-lang"; import { environment, SqValueLocation } from "@quri/squiggle-lang";
export type LocalItemSettings = { export type LocalItemSettings = {
collapsed: boolean; collapsed: boolean;
@ -17,6 +17,5 @@ export type MergedItemSettings = {
environment: environment; environment: environment;
}; };
export type Path = string[]; export const locationAsString = (location: SqValueLocation) =>
location.path.root + "/" + location.path.items.join(".");
export const pathAsString = (path: Path) => path.join(".");

View File

@ -18,12 +18,10 @@ type SquiggleArgs = {
export const useSquiggle = (args: SquiggleArgs) => { export const useSquiggle = (args: SquiggleArgs) => {
const result = useMemo( const result = useMemo(
() => { () => {
const { result, bindings } = run(args.code, { const result = run(args.code, {
environment: args.environment, environment: args.environment,
}); });
return resultMap(result, (v) => return result;
v.tag === SqValueTag.Void ? bindings.asValue() : v
);
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[ [
@ -37,10 +35,8 @@ export const useSquiggle = (args: SquiggleArgs) => {
const { onChange } = args; const { onChange } = args;
useEffect(() => { useEffect(() => {
onChange?.(result.tag === "Ok" ? result.value : undefined); onChange?.(result.result.tag === "Ok" ? result.result.value : undefined);
}, [result, onChange]); }, [result, onChange]);
console.log(result);
return result; return result;
}; };

View File

@ -1,16 +1,15 @@
import * as RSArray from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Array.gen"; import * as RSArray from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Array.gen";
import { wrapValue } from "./SqValue"; import { wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
type T = RSArray.squiggleValue_Array; type T = RSArray.squiggleValue_Array;
export class SqArray { export class SqArray {
_value: T; constructor(private _value: T, public location: SqValueLocation) {}
constructor(_value: T) {
this._value = _value;
}
getValues() { getValues() {
return RSArray.getValues(this._value).map(wrapValue); return RSArray.getValues(this._value).map((v, i) =>
wrapValue(v, this.location.extend(i))
);
} }
} }

View File

@ -16,11 +16,14 @@ export const wrapDistribution = (value: T): SqDistribution => {
abstract class SqAbstractDistribution { abstract class SqAbstractDistribution {
abstract tag: Tag; abstract tag: Tag;
_value: T;
constructor(value: T) { constructor(private _value: T) {}
this._value = value;
} protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
const value = rsMethod(this._value);
if (!value) throw new Error("Internal casting error");
return value;
};
pointSet(env: environment) { pointSet(env: environment) {
const innerResult = RSDistribution.toPointSet(this._value, env); const innerResult = RSDistribution.toPointSet(this._value, env);
@ -76,20 +79,11 @@ abstract class SqAbstractDistribution {
} }
} }
const valueMethod = <IR>(
_this: SqAbstractDistribution,
rsMethod: (v: T) => IR | null | undefined
) => {
const value = rsMethod(_this._value);
if (!value) throw new Error("Internal casting error");
return value;
};
export class SqPointSetDistribution extends SqAbstractDistribution { export class SqPointSetDistribution extends SqAbstractDistribution {
tag = Tag.PointSet; tag = Tag.PointSet;
value() { value() {
return valueMethod(this, RSDistribution.getPointSet); return this.valueMethod(RSDistribution.getPointSet);
} }
} }
@ -97,7 +91,7 @@ export class SqSampleSetDistribution extends SqAbstractDistribution {
tag = Tag.SampleSet; tag = Tag.SampleSet;
value() { value() {
return valueMethod(this, RSDistribution.getSampleSet); return this.valueMethod(RSDistribution.getSampleSet);
} }
} }
@ -105,7 +99,7 @@ export class SqSymbolicDistribution extends SqAbstractDistribution {
tag = Tag.Symbolic; tag = Tag.Symbolic;
value() { value() {
return valueMethod(this, RSDistribution.getSymbolic); return this.valueMethod(RSDistribution.getSymbolic);
} }
} }

View File

@ -3,11 +3,7 @@ import * as RSDistributionError from "../rescript/ForTS/ForTS_Distribution/ForTS
type T = RSDistributionError.distributionError; type T = RSDistributionError.distributionError;
export class SqDistributionError { export class SqDistributionError {
_value: T; constructor(private _value: T) {}
constructor(_value: T) {
this._value = _value;
}
toString() { toString() {
return RSDistributionError.toString(this._value); return RSDistributionError.toString(this._value);

View File

@ -1,11 +1,7 @@
import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen"; import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen";
export class SqError { export class SqError {
_value: RSErrorValue.reducerErrorValue; constructor(private _value: RSErrorValue.reducerErrorValue) {}
constructor(_value: RSErrorValue.reducerErrorValue) {
this._value = _value;
}
toString() { toString() {
return RSErrorValue.toString(this._value); return RSErrorValue.toString(this._value);

View File

@ -1,13 +1,10 @@
import * as RSLambda from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.gen"; import * as RSLambda from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.gen";
import { SqValueLocation } from "./SqValueLocation";
type T = RSLambda.squiggleValue_Lambda; type T = RSLambda.squiggleValue_Lambda;
export class SqLambda { export class SqLambda {
_value: T; constructor(private _value: T, public location: SqValueLocation) {}
constructor(_value: T) {
this._value = _value;
}
parameters() { parameters() {
return RSLambda.parameters(this._value); return RSLambda.parameters(this._value);

View File

@ -3,9 +3,5 @@ import * as RSDeclaration from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_Squi
type T = RSDeclaration.squiggleValue_Declaration; type T = RSDeclaration.squiggleValue_Declaration;
export class SqLambdaDeclaration { export class SqLambdaDeclaration {
_value: T; constructor(private _value: T) {}
constructor(_value: T) {
this._value = _value;
}
} }

View File

@ -1,20 +1,23 @@
import * as RSModuleValue from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Module.gen"; import * as RSModuleValue from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Module.gen";
import { SqModuleValue, wrapValue } from "./SqValue"; import { SqModuleValue, wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
export class SqModule { export class SqModule {
_value: RSModuleValue.squiggleValue_Module; constructor(
private _value: RSModuleValue.squiggleValue_Module,
constructor(_value: RSModuleValue.squiggleValue_Module) { public location: SqValueLocation
this._value = _value; ) {}
}
entries() { entries() {
return RSModuleValue.getKeyValuePairs(this._value).map( return RSModuleValue.getKeyValuePairs(this._value).map(
([k, v]) => [k, wrapValue(v)] as const ([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
); );
} }
asValue() { asValue() {
return new SqModuleValue(RSModuleValue.toSquiggleValue(this._value)); return new SqModuleValue(
RSModuleValue.toSquiggleValue(this._value),
this.location
);
} }
} }

View File

@ -25,29 +25,22 @@ export const wrapPointSetDist = (value: T) => {
}; };
abstract class SqAbstractPointSetDist { abstract class SqAbstractPointSetDist {
_value: T; constructor(private _value: T) {}
constructor(_value: T) {
this._value = _value;
}
abstract asShape(): SqShape; abstract asShape(): SqShape;
}
const valueMethod = <IR>( protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
_this: SqAbstractPointSetDist, const value = rsMethod(this._value);
rsMethod: (v: T) => IR | null | undefined
) => {
const value = rsMethod(_this._value);
if (!value) throw new Error("Internal casting error"); if (!value) throw new Error("Internal casting error");
return value; return value;
}; };
}
export class SqMixedPointSetDist extends SqAbstractPointSetDist { export class SqMixedPointSetDist extends SqAbstractPointSetDist {
tag = Tag.Mixed as const; tag = Tag.Mixed as const;
get value(): RSPointSetDist.mixedShape { get value(): RSPointSetDist.mixedShape {
return valueMethod(this, RSPointSetDist.getMixed); return this.valueMethod(RSPointSetDist.getMixed);
} }
asShape() { asShape() {
@ -63,7 +56,7 @@ export class SqDiscretePointSetDist extends SqAbstractPointSetDist {
tag = Tag.Discrete as const; tag = Tag.Discrete as const;
get value(): RSPointSetDist.discreteShape { get value(): RSPointSetDist.discreteShape {
return valueMethod(this, RSPointSetDist.getDiscrete); return this.valueMethod(RSPointSetDist.getDiscrete);
} }
asShape() { asShape() {
@ -79,7 +72,7 @@ export class SqContinuousPointSetDist extends SqAbstractPointSetDist {
tag = Tag.Continuous as const; tag = Tag.Continuous as const;
get value(): RSPointSetDist.continuousShape { get value(): RSPointSetDist.continuousShape {
return valueMethod(this, RSPointSetDist.getContinues); return this.valueMethod(RSPointSetDist.getContinues);
} }
asShape() { asShape() {

View File

@ -5,13 +5,10 @@ import { SqError } from "./SqError";
import { SqModule } from "./SqModule"; import { SqModule } from "./SqModule";
import { wrapValue } from "./SqValue"; import { wrapValue } from "./SqValue";
import { resultMap2 } from "./types"; import { resultMap2 } from "./types";
import { SqValueLocation } from "./SqValueLocation";
export class SqProject { export class SqProject {
_value: RSProject.reducerProject; constructor(private _value: RSProject.reducerProject) {}
constructor(_value: RSProject.reducerProject) {
this._value = _value;
}
static create() { static create() {
return new SqProject(RSProject.createProject()); return new SqProject(RSProject.createProject());
@ -94,14 +91,27 @@ export class SqProject {
} }
getBindings(sourceId: string) { getBindings(sourceId: string) {
return new SqModule(RSProject.getBindings(this._value, sourceId)); return new SqModule(
RSProject.getBindings(this._value, sourceId),
new SqValueLocation(this, sourceId, {
root: "bindings",
items: [],
})
);
} }
getResult(sourceId: string) { getResult(sourceId: string) {
const innerResult = RSProject.getResult(this._value, sourceId); const innerResult = RSProject.getResult(this._value, sourceId);
return resultMap2( return resultMap2(
innerResult, innerResult,
wrapValue, (v) =>
wrapValue(
v,
new SqValueLocation(this, sourceId, {
root: "result",
items: [],
})
),
(v: reducerErrorValue) => new SqError(v) (v: reducerErrorValue) => new SqError(v)
); );
} }

View File

@ -1,18 +1,15 @@
import * as RSRecord from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Record.gen"; import * as RSRecord from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Record.gen";
import { wrapValue } from "./SqValue"; import { wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
type T = RSRecord.squiggleValue_Record; type T = RSRecord.squiggleValue_Record;
export class SqRecord { export class SqRecord {
_value: T; constructor(private _value: T, public location: SqValueLocation) {}
constructor(_value: T) {
this._value = _value;
}
entries() { entries() {
return RSRecord.getKeyValuePairs(this._value).map( return RSRecord.getKeyValuePairs(this._value).map(
([k, v]) => [k, wrapValue(v)] as const ([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
); );
} }
} }

View File

@ -3,9 +3,5 @@ import * as RSType from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleVal
type T = RSType.squiggleValue_Type; type T = RSType.squiggleValue_Type;
export class SqType { export class SqType {
_value: T; constructor(private _value: T) {}
constructor(_value: T) {
this._value = _value;
}
} }

View File

@ -7,42 +7,38 @@ import { SqModule } from "./SqModule";
import { SqRecord } from "./SqRecord"; import { SqRecord } from "./SqRecord";
import { SqArray } from "./SqArray"; import { SqArray } from "./SqArray";
import { SqType } from "./SqType"; import { SqType } from "./SqType";
import { SqProject } from "./SqProject";
import { SqValueLocation } from "./SqValueLocation";
export { Tag as SqValueTag }; export { Tag as SqValueTag };
type T = RSValue.squiggleValue; type T = RSValue.squiggleValue;
export const wrapValue = (value: T): SqValue => { export const wrapValue = (value: T, location: SqValueLocation): SqValue => {
const tag = RSValue.getTag(value); const tag = RSValue.getTag(value);
return new tagToClass[tag](value); return new tagToClass[tag](value, location);
}; };
export abstract class SqAbstractValue { export abstract class SqAbstractValue {
abstract tag: Tag; abstract tag: Tag;
_value: T;
constructor(value: T) { constructor(private _value: T, public location: SqValueLocation) {}
this._value = value;
}
}
const valueMethod = <IR>( protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
_this: SqAbstractValue, const value = rsMethod(this._value);
rsMethod: (v: T) => IR | null | undefined
) => {
const value = rsMethod(_this._value);
if (value === undefined || value === null) { if (value === undefined || value === null) {
throw new Error("Internal casting error"); throw new Error("Internal casting error");
} }
return value; return value;
}; };
}
export class SqArrayValue extends SqAbstractValue { export class SqArrayValue extends SqAbstractValue {
tag = Tag.Array as const; tag = Tag.Array as const;
get value() { get value() {
return new SqArray(valueMethod(this, RSValue.getArray)); return new SqArray(this.valueMethod(RSValue.getArray), this.location);
} }
} }
@ -50,7 +46,7 @@ export class SqArrayStringValue extends SqAbstractValue {
tag = Tag.ArrayString as const; tag = Tag.ArrayString as const;
get value() { get value() {
return valueMethod(this, RSValue.getArrayString); return this.valueMethod(RSValue.getArrayString);
} }
} }
@ -58,7 +54,7 @@ export class SqBoolValue extends SqAbstractValue {
tag = Tag.Bool as const; tag = Tag.Bool as const;
get value() { get value() {
return valueMethod(this, RSValue.getBool); return this.valueMethod(RSValue.getBool);
} }
} }
@ -66,7 +62,7 @@ export class SqCallValue extends SqAbstractValue {
tag = Tag.Call as const; tag = Tag.Call as const;
get value() { get value() {
return valueMethod(this, RSValue.getCall); return this.valueMethod(RSValue.getCall);
} }
} }
@ -74,7 +70,7 @@ export class SqDateValue extends SqAbstractValue {
tag = Tag.Date as const; tag = Tag.Date as const;
get value() { get value() {
return valueMethod(this, RSValue.getDate); return this.valueMethod(RSValue.getDate);
} }
} }
@ -82,7 +78,7 @@ export class SqDeclarationValue extends SqAbstractValue {
tag = Tag.Declaration as const; tag = Tag.Declaration as const;
get value() { get value() {
return new SqLambdaDeclaration(valueMethod(this, RSValue.getDeclaration)); return new SqLambdaDeclaration(this.valueMethod(RSValue.getDeclaration));
} }
} }
@ -90,7 +86,7 @@ export class SqDistributionValue extends SqAbstractValue {
tag = Tag.Distribution as const; tag = Tag.Distribution as const;
get value() { get value() {
return wrapDistribution(valueMethod(this, RSValue.getDistribution)); return wrapDistribution(this.valueMethod(RSValue.getDistribution));
} }
} }
@ -98,7 +94,7 @@ export class SqLambdaValue extends SqAbstractValue {
tag = Tag.Lambda as const; tag = Tag.Lambda as const;
get value() { get value() {
return new SqLambda(valueMethod(this, RSValue.getLambda)); return new SqLambda(this.valueMethod(RSValue.getLambda), this.location);
} }
} }
@ -106,7 +102,7 @@ export class SqModuleValue extends SqAbstractValue {
tag = Tag.Module as const; tag = Tag.Module as const;
get value() { get value() {
return new SqModule(valueMethod(this, RSValue.getModule)); return new SqModule(this.valueMethod(RSValue.getModule), this.location);
} }
} }
@ -114,7 +110,7 @@ export class SqNumberValue extends SqAbstractValue {
tag = Tag.Number as const; tag = Tag.Number as const;
get value() { get value() {
return valueMethod(this, RSValue.getNumber); return this.valueMethod(RSValue.getNumber);
} }
} }
@ -122,7 +118,7 @@ export class SqRecordValue extends SqAbstractValue {
tag = Tag.Record as const; tag = Tag.Record as const;
get value() { get value() {
return new SqRecord(valueMethod(this, RSValue.getRecord)); return new SqRecord(this.valueMethod(RSValue.getRecord), this.location);
} }
} }
@ -130,7 +126,7 @@ export class SqStringValue extends SqAbstractValue {
tag = Tag.String as const; tag = Tag.String as const;
get value(): string { get value(): string {
return valueMethod(this, RSValue.getString); return this.valueMethod(RSValue.getString);
} }
} }
@ -138,7 +134,7 @@ export class SqSymbolValue extends SqAbstractValue {
tag = Tag.Symbol as const; tag = Tag.Symbol as const;
get value(): string { get value(): string {
return valueMethod(this, RSValue.getSymbol); return this.valueMethod(RSValue.getSymbol);
} }
} }
@ -146,7 +142,7 @@ export class SqTimeDurationValue extends SqAbstractValue {
tag = Tag.TimeDuration as const; tag = Tag.TimeDuration as const;
get value() { get value() {
return valueMethod(this, RSValue.getTimeDuration); return this.valueMethod(RSValue.getTimeDuration);
} }
} }
@ -154,7 +150,7 @@ export class SqTypeValue extends SqAbstractValue {
tag = Tag.Type as const; tag = Tag.Type as const;
get value() { get value() {
return new SqType(valueMethod(this, RSValue.getType)); return new SqType(this.valueMethod(RSValue.getType));
} }
} }
@ -162,7 +158,7 @@ export class SqTypeIdentifierValue extends SqAbstractValue {
tag = Tag.TypeIdentifier as const; tag = Tag.TypeIdentifier as const;
get value() { get value() {
return valueMethod(this, RSValue.getTypeIdentifier); return this.valueMethod(RSValue.getTypeIdentifier);
} }
} }

View File

@ -0,0 +1,24 @@
import { isParenthesisNode } from "mathjs";
import { SqProject } from "./SqProject";
type PathItem = string | number;
type SqValuePath = {
root: "result" | "bindings";
items: PathItem[];
};
export class SqValueLocation {
constructor(
public project: SqProject,
public sourceId: string,
public path: SqValuePath
) {}
extend(item: PathItem) {
return new SqValueLocation(this.project, this.sourceId, {
root: this.path.root,
items: [...this.path.items, item],
});
}
}

View File

@ -1,6 +1,7 @@
import { environment } from "../rescript/ForTS/ForTS_ReducerProject.gen"; import { environment } from "../rescript/ForTS/ForTS_ReducerProject.gen";
import { SqProject } from "./SqProject"; import { SqProject } from "./SqProject";
import { SqValue, SqValueTag } from "./SqValue"; import { SqValue, SqValueTag } from "./SqValue";
export { SqValueLocation } from "./SqValueLocation";
export { result } from "../rescript/ForTS/ForTS_Result_tag"; export { result } from "../rescript/ForTS/ForTS_Result_tag";
export { SqDistribution, SqDistributionTag } from "./SqDistribution"; export { SqDistribution, SqDistributionTag } from "./SqDistribution";
export { SqDistributionError } from "./SqDistributionError"; export { SqDistributionError } from "./SqDistributionError";