Add bindings to Squiggle Editor

This commit is contained in:
Sam Nolan 2022-04-29 13:50:57 +00:00
parent 5f78399760
commit 454ac0c252
8 changed files with 118 additions and 28 deletions

View File

@ -89,6 +89,7 @@
"@types/react": "17.0.43"
},
"source": "./src/index.ts",
"browser": "dist/bundle.js",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts"
}

View File

@ -3,10 +3,12 @@ import _ from "lodash";
import styled from "styled-components";
import {
run,
runPartial,
errorValueToString,
squiggleExpression,
bindings,
samplingParams,
} from "@quri/squiggle-lang";
import type { samplingParams } from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower";
import { DistributionChart } from "./DistributionChart";
import { ErrorBox } from "./ErrorBox";
@ -148,6 +150,8 @@ export interface SquiggleChartProps {
/** CSS width of the element */
width?: number;
height?: number;
/** Bindings of previous variables declared */
bindings?: bindings;
}
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
@ -156,6 +160,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
outputXYPoints = 1000,
onChange = () => {},
height = 60,
bindings = {},
width = NaN,
}: SquiggleChartProps) => {
const target = React.useRef(null);
@ -167,7 +172,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
sampleCount: sampleCount,
xyPointLength: outputXYPoints,
};
let expressionResult = run(squiggleString, samplingInputs);
let expressionResult = run(squiggleString, bindings, samplingInputs);
let internal: JSX.Element;
if (expressionResult.tag === "Ok") {
let expression = expressionResult.value;

View File

@ -3,7 +3,9 @@ import * as ReactDOM from "react-dom";
import { SquiggleChart } from "./SquiggleChart";
import { CodeEditor } from "./CodeEditor";
import styled from "styled-components";
import type { squiggleExpression } from "@quri/squiggle-lang";
import type { squiggleExpression, bindings } from "@quri/squiggle-lang";
import { runPartial, errorValueToString } from "@quri/squiggle-lang";
import { ErrorBox } from "./ErrorBox";
export interface SquiggleEditorProps {
/** The input string for squiggle */
@ -26,6 +28,8 @@ export interface SquiggleEditorProps {
onChange?(expr: squiggleExpression): void;
/** The width of the element */
width: number;
/** Previous variable declarations */
bindings: bindings;
}
const Input = styled.div`
@ -46,6 +50,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
diagramCount,
onChange,
environment,
bindings = {},
}: SquiggleEditorProps) => {
let [expression, setExpression] = React.useState(initialSquiggleString);
return (
@ -71,6 +76,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
diagramCount={diagramCount}
environment={environment}
onChange={onChange}
bindings={bindings}
/>
</div>
);
@ -107,3 +113,76 @@ export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
);
return parent;
}
export interface SquigglePartialProps {
/** The input string for squiggle */
initialSquiggleString?: string;
/** If the output requires monte carlo sampling, the amount of samples */
sampleCount?: number;
/** The amount of points returned to draw the distribution */
outputXYPoints?: number;
kernelWidth?: number;
pointDistLength?: number;
/** If the result is a function, where the function starts */
diagramStart?: number;
/** If the result is a function, where the function ends */
diagramStop?: number;
/** If the result is a function, how many points along the function it samples */
diagramCount?: number;
/** when the environment changes. Used again for notebook magic*/
onChange?(expr: bindings): void;
/** The width of the element */
width: number;
/** Previously declared variables */
bindings: bindings;
}
export let SquigglePartial: React.FC<SquigglePartialProps> = ({
initialSquiggleString = "",
onChange,
bindings,
}: SquigglePartialProps) => {
let [expression, setExpression] = React.useState(initialSquiggleString);
let squiggleResult = runPartial(expression, bindings);
if (squiggleResult.tag == "Ok") {
if (onChange) onChange(squiggleResult.value);
}
return (
<div>
<Input>
<CodeEditor
value={expression}
onChange={setExpression}
oneLine={true}
showGutter={false}
height={20}
/>
</Input>
{squiggleResult.tag == "Error" ? (
<ErrorBox heading="Error">
{errorValueToString(squiggleResult.value)}
</ErrorBox>
) : (
<></>
)}
</div>
);
};
export function renderSquigglePartialToDom(props: SquigglePartialProps) {
let parent = document.createElement("div");
ReactDOM.render(
<SquigglePartial
{...props}
onChange={(bindings) => {
// @ts-ignore
parent.value = bindings;
parent.dispatchEvent(new CustomEvent("input"));
if (props.onChange) props.onChange(bindings);
}}
/>,
parent
);
return parent;
}

View File

@ -1,7 +1,9 @@
export { SquiggleChart } from "./components/SquiggleChart";
export {
SquiggleEditor,
SquigglePartial,
renderSquiggleEditorToDom,
renderSquigglePartialToDom,
} from "./components/SquiggleEditor";
import SquigglePlayground, {
renderSquigglePlaygroundToDom,

View File

@ -1,23 +1,5 @@
import {
run,
Distribution,
resultMap,
squiggleExpression,
errorValueToString,
} from "../../src/js/index";
let testRun = (x: string): squiggleExpression => {
let result = run(x, { sampleCount: 100, xyPointLength: 100 });
expect(result.tag).toEqual("Ok");
if (result.tag === "Ok") {
return result.value;
} else {
throw Error(
"Expected squiggle expression to evaluate but got error: " +
errorValueToString(result.value)
);
}
};
import { Distribution, resultMap } from "../../src/js/index";
import { testRun } from "./TestHelpers";
function Ok<b>(x: b) {
return { tag: "Ok", value: x };

View File

@ -8,7 +8,7 @@ import {
} from "../../src/js/index";
export function testRun(x: string): squiggleExpression {
let squiggleResult = run(x, { sampleCount: 1000, xyPointLength: 100 });
let squiggleResult = run(x, {}, { sampleCount: 1000, xyPointLength: 100 });
// return squiggleResult.value
if (squiggleResult.tag === "Ok") {
return squiggleResult.value;

View File

@ -2,7 +2,9 @@ import * as _ from "lodash";
import {
genericDist,
samplingParams,
evaluate,
evaluateUsingExternalBindings,
evaluatePartialUsingExternalBindings,
externalBindings,
expressionValue,
errorValue,
distributionError,
@ -46,7 +48,7 @@ import {
Constructors_pointwiseLogarithm,
Constructors_pointwisePower,
} from "../rescript/Distributions/DistributionOperation/DistributionOperation.gen";
export type { samplingParams, errorValue };
export type { samplingParams, errorValue, externalBindings as bindings };
export let defaultSamplingInputs: samplingParams = {
sampleCount: 10000,
@ -96,15 +98,28 @@ export type squiggleExpression =
export function run(
squiggleString: string,
bindings?: externalBindings,
samplingInputs?: samplingParams
): result<squiggleExpression, errorValue> {
let b = bindings ? bindings : {};
let si: samplingParams = samplingInputs
? samplingInputs
: defaultSamplingInputs;
let result: result<expressionValue, errorValue> = evaluate(squiggleString);
let result: result<expressionValue, errorValue> =
evaluateUsingExternalBindings(squiggleString, b);
return resultMap(result, (x) => createTsExport(x, si));
}
// Run Partial. A partial is a block of code that doesn't return a value
export function runPartial(
squiggleString: string,
bindings: externalBindings,
_samplingInputs?: samplingParams
): result<externalBindings, errorValue> {
return evaluatePartialUsingExternalBindings(squiggleString, bindings);
}
function createTsExport(
x: expressionValue,
sampEnv: samplingParams
@ -166,7 +181,7 @@ function createTsExport(
}
}
// Helper functions to convert the recsript representations that genType doesn't
// Helper functions to convert the rescript representations that genType doesn't
// cover
function convertRawToTypescript(
result: rescriptExport,

View File

@ -40,6 +40,12 @@ let evaluate = Reducer.evaluate
@genType
let evaluateUsingExternalBindings = Reducer.evaluateUsingExternalBindings
@genType
let evaluatePartialUsingExternalBindings = Reducer.evaluatePartialUsingExternalBindings
@genType
type externalBindings = Reducer.externalBindings
@genType
type expressionValue = ReducerInterface_ExpressionValue.expressionValue