Merge branch 'develop' into error-locations

This commit is contained in:
Vyacheslav Matyukhin 2022-10-08 04:29:57 +04:00
commit 76ea024342
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
11 changed files with 165 additions and 41 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ yarn-error.log
.vscode .vscode
todo.txt todo.txt
result result
shell.nix

View File

@ -10,6 +10,7 @@
"@hookform/resolvers": "^2.9.8", "@hookform/resolvers": "^2.9.8",
"@quri/squiggle-lang": "^0.5.0", "@quri/squiggle-lang": "^0.5.0",
"@react-hook/size": "^2.1.2", "@react-hook/size": "^2.1.2",
"@types/uuid": "^8.3.4",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"framer-motion": "^7.5.1", "framer-motion": "^7.5.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@ -18,6 +19,7 @@
"react-hook-form": "^7.36.1", "react-hook-form": "^7.36.1",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"react-vega": "^7.6.0", "react-vega": "^7.6.0",
"uuid": "^9.0.0",
"vega": "^5.22.1", "vega": "^5.22.1",
"vega-embed": "^6.21.0", "vega-embed": "^6.21.0",
"vega-lite": "^5.5.0", "vega-lite": "^5.5.0",
@ -42,6 +44,7 @@
"@types/node": "^18.8.0", "@types/node": "^18.8.0",
"@types/react": "^18.0.21", "@types/react": "^18.0.21",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@types/uuid": "^8.3.4",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"canvas": "^2.10.1", "canvas": "^2.10.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@ -82,7 +85,8 @@
"format": "prettier --write .", "format": "prettier --write .",
"prepack": "yarn run build:cjs && yarn run bundle", "prepack": "yarn run build:cjs && yarn run bundle",
"test": "jest", "test": "jest",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand" "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"test:profile": "node --cpu-prof node_modules/.bin/jest --runInBand"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [

View File

@ -1,25 +1,17 @@
import * as React from "react"; import * as React from "react";
import { import { SqValue, environment, SqProject } from "@quri/squiggle-lang";
SqValue,
environment,
defaultEnvironment,
resultMap,
SqValueTag,
} from "@quri/squiggle-lang";
import { useSquiggle } from "../lib/hooks"; import { useSquiggle } from "../lib/hooks";
import { SquiggleViewer } from "./SquiggleViewer"; import { SquiggleViewer } from "./SquiggleViewer";
import { JsImports } from "../lib/jsImports"; import { JsImports } from "../lib/jsImports";
import { getValueToRender } from "../lib/utility"; import { getValueToRender } from "../lib/utility";
export interface SquiggleChartProps { export type SquiggleChartProps = {
/** The input string for squiggle */ /** The input string for squiggle */
code?: string; code: string;
/** Allows to re-run the code if code hasn't changed */ /** Allows to re-run the code if code hasn't changed */
executionId?: number; executionId?: number;
/** If the output requires monte carlo sampling, the amount of samples */ /** If the output requires monte carlo sampling, the amount of samples */
sampleCount?: number; sampleCount?: number;
/** The amount of points returned to draw the distribution */
environment?: environment;
/** If the result is a function, where the function domain starts */ /** If the result is a function, where the function domain starts */
diagramStart?: number; diagramStart?: number;
/** If the result is a function, where the function domain ends */ /** If the result is a function, where the function domain ends */
@ -27,7 +19,7 @@ export interface SquiggleChartProps {
/** If the result is a function, the amount of stops sampled */ /** If the result is a function, the amount of stops sampled */
diagramCount?: number; diagramCount?: number;
/** When the squiggle code gets reevaluated */ /** When the squiggle code gets reevaluated */
onChange?(expr: SqValue | undefined): void; onChange?(expr: SqValue | undefined, sourceName: string): void;
/** CSS width of the element */ /** CSS width of the element */
width?: number; width?: number;
height?: number; height?: number;
@ -54,10 +46,27 @@ export interface SquiggleChartProps {
/** Whether to show vega actions to the user, so they can copy the chart spec */ /** Whether to show vega actions to the user, so they can copy the chart spec */
distributionChartActions?: boolean; distributionChartActions?: boolean;
enableLocalSettings?: boolean; enableLocalSettings?: boolean;
} } & (StandaloneExecutionProps | ProjectExecutionProps);
// Props needed for a standalone execution
type StandaloneExecutionProps = {
project?: undefined;
continues?: undefined;
/** The amount of points returned to draw the distribution, not needed if using a project */
environment?: environment;
};
// Props needed when executing inside a project.
type ProjectExecutionProps = {
environment?: undefined;
/** The project that this execution is part of */
project: SqProject;
/** What other squiggle sources from the project to continue. Default [] */
continues?: string[];
};
const defaultOnChange = () => {}; const defaultOnChange = () => {};
const defaultImports: JsImports = {}; const defaultImports: JsImports = {};
const defaultContinues: string[] = [];
export const splitSquiggleChartSettings = (props: SquiggleChartProps) => { export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
const { const {
@ -104,19 +113,32 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
splitSquiggleChartSettings(props); splitSquiggleChartSettings(props);
const { const {
code = "", code,
environment,
jsImports = defaultImports, jsImports = defaultImports,
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
executionId = 0, executionId = 0,
width, width,
height = 200, height = 200,
enableLocalSettings = false, enableLocalSettings = false,
continues = defaultContinues,
} = props; } = props;
const p = React.useMemo(() => {
if (props.project) {
return props.project;
} else {
const p = SqProject.create();
if (props.environment) {
p.setEnvironment(props.environment);
}
return p;
}
}, [props.project, props.environment]);
const resultAndBindings = useSquiggle({ const resultAndBindings = useSquiggle({
continues,
project: p,
code, code,
environment,
jsImports, jsImports,
onChange, onChange,
executionId, executionId,
@ -131,7 +153,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
height={height} height={height}
distributionPlotSettings={distributionPlotSettings} distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings} chartSettings={chartSettings}
environment={environment ?? defaultEnvironment} environment={p.getEnvironment()}
enableLocalSettings={enableLocalSettings} enableLocalSettings={enableLocalSettings}
/> />
); );

View File

@ -7,7 +7,7 @@ import {
} from "./SquiggleChart"; } from "./SquiggleChart";
import { useMaybeControlledValue, useSquiggle } from "../lib/hooks"; import { useMaybeControlledValue, useSquiggle } from "../lib/hooks";
import { JsImports } from "../lib/jsImports"; import { JsImports } from "../lib/jsImports";
import { defaultEnvironment, SqLocation } from "@quri/squiggle-lang"; import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang";
import { SquiggleViewer } from "./SquiggleViewer"; import { SquiggleViewer } from "./SquiggleViewer";
import { getErrorLocations, getValueToRender } from "../lib/utility"; import { getErrorLocations, getValueToRender } from "../lib/utility";
@ -56,9 +56,17 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
enableLocalSettings = false, enableLocalSettings = false,
} = props; } = props;
const project = React.useMemo(() => {
const p = SqProject.create();
if (environment) {
p.setEnvironment(environment);
}
return p;
}, [environment]);
const resultAndBindings = useSquiggle({ const resultAndBindings = useSquiggle({
code, code,
environment, project,
jsImports, jsImports,
onChange, onChange,
executionId, executionId,

View File

@ -28,7 +28,7 @@ import {
} from "@heroicons/react/solid"; } from "@heroicons/react/solid";
import clsx from "clsx"; import clsx from "clsx";
import { environment } from "@quri/squiggle-lang"; import { environment, SqProject } from "@quri/squiggle-lang";
import { SquiggleChartProps } from "./SquiggleChart"; import { SquiggleChartProps } from "./SquiggleChart";
import { CodeEditor } from "./CodeEditor"; import { CodeEditor } from "./CodeEditor";
@ -305,9 +305,17 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
executionId, executionId,
} = useRunnerState(code); } = useRunnerState(code);
const project = React.useMemo(() => {
const p = SqProject.create();
if (environment) {
p.setEnvironment(environment);
}
return p;
}, [environment]);
const resultAndBindings = useSquiggle({ const resultAndBindings = useSquiggle({
code, code,
environment, project,
jsImports: imports, jsImports: imports,
executionId, executionId,
}); });

View File

@ -2,7 +2,7 @@ import React, { useContext } from "react";
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang"; import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
import { NumberShower } from "../NumberShower"; import { NumberShower } from "../NumberShower";
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart"; import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; import { FunctionChart } from "../FunctionChart";
import clsx from "clsx"; import clsx from "clsx";
import { VariableBox } from "./VariableBox"; import { VariableBox } from "./VariableBox";
import { ItemSettingsMenu } from "./ItemSettingsMenu"; import { ItemSettingsMenu } from "./ItemSettingsMenu";

View File

@ -1,3 +1,4 @@
export { SqProject } from "@quri/squiggle-lang/";
export { SquiggleChart } from "./components/SquiggleChart"; export { SquiggleChart } from "./components/SquiggleChart";
export { SquiggleEditor } from "./components/SquiggleEditor"; export { SquiggleEditor } from "./components/SquiggleEditor";
export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquigglePlayground } from "./components/SquigglePlayground";

View File

@ -1,5 +1,4 @@
import { import {
environment,
result, result,
SqError, SqError,
SqProject, SqProject,
@ -8,13 +7,15 @@ import {
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
import * as uuid from "uuid";
type SquiggleArgs = { type SquiggleArgs = {
code: string; code: string;
executionId?: number; executionId?: number;
jsImports?: JsImports; jsImports?: JsImports;
environment?: environment; project: SqProject;
onChange?: (expr: SqValue | undefined) => void; continues?: string[];
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
}; };
export type ResultAndBindings = { export type ResultAndBindings = {
@ -22,33 +23,63 @@ export type ResultAndBindings = {
bindings: SqRecord; bindings: SqRecord;
}; };
const importSourceName = (sourceName: string) => "imports-" + sourceName;
const defaultContinues = [];
export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => { export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
const sourceName = useMemo(() => uuid.v4(), []);
const env = args.project.getEnvironment();
const continues = args.continues || defaultContinues;
const result = useMemo( const result = useMemo(
() => { () => {
const project = SqProject.create(); const project = args.project;
project.setSource("main", args.code);
if (args.environment) { project.setSource(sourceName, args.code);
project.setEnvironment(args.environment); let fullContinues = continues;
}
if (args.jsImports && Object.keys(args.jsImports).length) { if (args.jsImports && Object.keys(args.jsImports).length) {
const importsSource = jsImportsToSquiggleCode(args.jsImports); const importsSource = jsImportsToSquiggleCode(args.jsImports);
project.setSource("imports", importsSource); project.setSource(importSourceName(sourceName), importsSource);
project.setContinues("main", ["imports"]); fullContinues = continues.concat(importSourceName(sourceName));
} }
project.run("main"); project.setContinues(sourceName, fullContinues);
const result = project.getResult("main"); project.run(sourceName);
const bindings = project.getBindings("main"); const result = project.getResult(sourceName);
const bindings = project.getBindings(sourceName);
return { result, bindings }; return { result, bindings };
}, },
// This complains about executionId not being used inside the function body.
// This is on purpose, as executionId simply allows you to run the squiggle
// code again
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[args.code, args.environment, args.jsImports, args.executionId] [
args.code,
args.jsImports,
args.executionId,
sourceName,
continues,
args.project,
env,
]
); );
const { onChange } = args; const { onChange } = args;
useEffect(() => { useEffect(() => {
onChange?.(result.result.tag === "Ok" ? result.result.value : undefined); onChange?.(
}, [result, onChange]); result.result.tag === "Ok" ? result.result.value : undefined,
sourceName
);
}, [result, onChange, sourceName]);
useEffect(() => {
return () => {
args.project.removeSource(sourceName);
if (args.project.getSource(importSourceName(sourceName)))
args.project.removeSource(importSourceName(sourceName));
};
}, [args.project, sourceName]);
return result; return result;
}; };

View File

@ -3,11 +3,11 @@ import React from "react";
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import { SquiggleChart } from "../src/index"; import { SquiggleChart } from "../src/index";
test("Logs no warnings or errors", async () => { test("Logs nothing on render", async () => {
debugger;
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />); const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
unmount(); unmount();
expect(console.log).not.toBeCalled();
expect(console.warn).not.toBeCalled(); expect(console.warn).not.toBeCalled();
expect(console.error).not.toBeCalled(); expect(console.error).not.toBeCalled();
}); });

View File

@ -0,0 +1,39 @@
import { render } from "@testing-library/react";
import React from "react";
import "@testing-library/jest-dom";
import { SquiggleChart } from "../src/index";
import { SqProject } from "@quri/squiggle-lang";
test("Creates and cleans up source", async () => {
const project = SqProject.create();
const { unmount } = render(
<SquiggleChart code={"normal(0, 1)"} project={project} />
);
expect(project.getSourceIds().length).toBe(1);
const sourceId = project.getSourceIds()[0];
expect(project.getSource(sourceId)).toBe("normal(0, 1)");
unmount();
expect(project.getSourceIds().length).toBe(0);
expect(project.getSource(sourceId)).toBe(undefined);
});
test("Creates and cleans up source and imports", async () => {
const project = SqProject.create();
const { unmount } = render(
<SquiggleChart
code={"normal($x, 1)"}
project={project}
jsImports={{ x: 3 }}
/>
);
expect(project.getSourceIds().length).toBe(2);
unmount();
expect(project.getSourceIds()).toStrictEqual([]);
});

View File

@ -5186,6 +5186,11 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@types/uuid@^8.3.4":
version "8.3.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
"@types/vscode@^1.70.0": "@types/vscode@^1.70.0":
version "1.71.0" version "1.71.0"
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.71.0.tgz#a8d9bb7aca49b0455060e6eb978711b510bdd2e2" resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.71.0.tgz#a8d9bb7aca49b0455060e6eb978711b510bdd2e2"
@ -18723,6 +18728,11 @@ uuid@^8.0.0, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
v8-compile-cache-lib@^3.0.1: v8-compile-cache-lib@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"