Skip to content

Commit

Permalink
Merge pull request #3324 from quantified-uncertainty/project2
Browse files Browse the repository at this point in the history
SqProject rewrite
  • Loading branch information
berekuk authored Aug 9, 2024
2 parents 008fc0e + 36282b5 commit f16cd06
Show file tree
Hide file tree
Showing 143 changed files with 4,942 additions and 2,602 deletions.
2 changes: 1 addition & 1 deletion packages/components/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @type {import('jest').Config} */
const jestConfig = {
testEnvironment: "jsdom",
testEnvironment: "./test/FixJSDOMEnvironment.ts",
extensionsToTreatAsEsm: [".ts", ".tsx"],
setupFiles: ["jest-canvas-mock"],
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"],
Expand Down
2 changes: 2 additions & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@codemirror/state": "^6.4.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@codemirror/view": "^6.26.3",
"@dagrejs/dagre": "^1.1.3",
"@floating-ui/react": "^0.26.16",
"@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^3.3.4",
Expand All @@ -38,6 +39,7 @@
"react-draggable": "^4.4.6",
"react-hook-form": "^7.50.0",
"react-markdown": "^9.0.1",
"reactflow": "^11.11.4",
"remark-gfm": "^4.0.0",
"shikiji": "^0.10.2",
"unist-util-visit": "^5.0.0",
Expand Down
13 changes: 11 additions & 2 deletions packages/components/src/components/CodeEditor/errorsExtension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { linter } from "@codemirror/lint";
import { Extension, Facet } from "@codemirror/state";

import { SqCompileError, SqError, SqRuntimeError } from "@quri/squiggle-lang";
import {
SqCompileError,
SqError,
SqImportError,
SqRuntimeError,
} from "@quri/squiggle-lang";

import { Simulation } from "../../lib/hooks/useSimulator.js";
import { simulationErrors } from "../../lib/utility.js";
Expand All @@ -28,7 +33,11 @@ export function errorsExtension(): Extension {
const diagnostics = errors
.map((err) => {
if (
!(err instanceof SqCompileError || err instanceof SqRuntimeError)
!(
err instanceof SqCompileError ||
err instanceof SqRuntimeError ||
err instanceof SqImportError
)
) {
return undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/components/CodeEditor/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const defaultReactProps: CodemirrorReactProps = {
onFocusByPath: null,
showGutter: false,
lineWrapping: true,
project: SqProject.create(),
project: new SqProject(),
sourceId: "fake",
onChange: () => {},
onSubmit: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,20 @@ export function getMarkers(
}
const simulation = state.facet(simulationFacet);

const sqResult = simulation?.output;
const sqResult = simulation?.output.result;
if (!sqResult?.ok) {
return;
}

if (
sqResult.value.result.context?.runContext.source !== state.doc.toString()
sqResult.value.result.context?.runContext.module.code !==
state.doc.toString()
) {
return; // autorun is off or the result is still rendering, can't show markers yet
}

// TODO - use AST for the current code state, and `sqResult` only for filtering
const ast = sqResult.value.bindings.context?.runContext.ast;
const ast = sqResult.value.bindings.context?.runContext.module.expectAst();
if (!ast) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { syntaxTree } from "@codemirror/language";
import { Facet } from "@codemirror/state";
import { SyntaxNode, Tree } from "@lezer/common";

import { SqProject } from "@quri/squiggle-lang";
import { getStdLib, SqProject } from "@quri/squiggle-lang";

import { FnDocumentationFromName } from "../../ui/FnDocumentation.js";
import { reactAsDom } from "../utils.js";
Expand Down Expand Up @@ -88,7 +88,7 @@ export const builtinCompletionsFacet = Facet.define<
return () => reactAsDom(<FnDocumentationFromName functionName={name} />);
};

for (const [name, value] of project.getStdLib().entrySeq()) {
for (const [name, value] of getStdLib().entrySeq()) {
stdlibCompletions.push({
label: name,
info: getInfoFunction(name),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ export function profilerExtension(): Extension {
return empty;
}

if (simulation.code !== state.doc.toString()) {
if (simulation.output.module.code !== state.doc.toString()) {
return empty;
}

const { output } = simulation;
if (!output.ok) {
const {
output: { result },
} = simulation;
if (!result.ok) {
return empty;
}

const profile = output.value.raw.runOutput.profile;
const profile = result.value.profile;
if (!profile) {
return empty;
}
Expand Down
18 changes: 8 additions & 10 deletions packages/components/src/components/CodeEditor/tooltips/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { getFunctionDocumentation } from "@quri/squiggle-lang";
import {
projectFacet,
renderImportTooltipFacet,
sourceIdFacet,
simulationFacet,
} from "../fields.js";
import { reactAsDom } from "../utils.js";
import { HoverTooltip } from "./HoverTooltip.js";
Expand Down Expand Up @@ -39,8 +39,8 @@ export function tooltipsExtension() {
// See also: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover
const hoverExtension = hoverTooltip((view, pos, side) => {
const renderImportTooltip = view.state.facet(renderImportTooltipFacet);
const simulation = view.state.facet(simulationFacet);
const project = view.state.facet(projectFacet);
const sourceId = view.state.facet(sourceIdFacet);

const { doc } = view.state;

Expand Down Expand Up @@ -97,11 +97,12 @@ export function tooltipsExtension() {
}

const name = getText(node);
if (cursor.type.is("Import")) {
const output = project.getOutput(sourceId);
if (!output.ok) return null;
if (!simulation) return null;
if (!simulation.output.result.ok) return null;
const output = simulation.output.result.value;

const value = output.value.imports.get(name);
if (cursor.type.is("Import")) {
const value = output.imports.get(name);
if (!value) return null;

return nodeTooltip(node, <ValueTooltip value={value} view={view} />);
Expand All @@ -114,10 +115,7 @@ export function tooltipsExtension() {
return null;
}

const bindings = project.getBindings(sourceId);
if (!bindings.ok) return null;

const value = bindings.value.get(name);
const value = output.bindings.get(name);
if (!value) return null;

// Should be a statement
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { keymap } from "@codemirror/view";

import { onFocusByPathFacet, projectFacet, sourceIdFacet } from "./fields.js";
import { onFocusByPathFacet, simulationFacet } from "./fields.js";

export function viewNodeExtension() {
return keymap.of([
{
key: "Alt-Shift-v",
run: (view) => {
const onFocusByPath = view.state.facet(onFocusByPathFacet);
const sourceId = view.state.facet(sourceIdFacet);
const project = view.state.facet(projectFacet);
const simulation = view.state.facet(simulationFacet);

if (!onFocusByPath) {
return true;
Expand All @@ -18,8 +17,9 @@ export function viewNodeExtension() {
if (offset === undefined) {
return true;
}
const valuePathResult = project.findValuePathByOffset(sourceId, offset);
if (valuePathResult.ok) {
const valuePathResult =
simulation?.output?.findValuePathByOffset(offset);
if (valuePathResult?.ok) {
onFocusByPath(valuePathResult.value);
}
return true;
Expand Down
55 changes: 18 additions & 37 deletions packages/components/src/components/PlaygroundSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { FC } from "react";
import { useWatch } from "react-hook-form";
import { z } from "zod";

import { defaultRunnerName, RunnerName, SqScale } from "@quri/squiggle-lang";
import { RunnerName, SqScale } from "@quri/squiggle-lang";
import {
CheckboxFormField,
NumberFormField,
RadioFormField,
SelectFormField,
TextFormField,
WrenchIcon,
} from "@quri/ui";

import { SAMPLE_COUNT_MAX, SAMPLE_COUNT_MIN } from "../lib/constants.js";
Expand All @@ -25,9 +24,8 @@ export const environmentSchema = z.object({
});

const runnerSchema = z.union([
z.literal("embedded" satisfies RunnerName),
z.literal("web-worker" satisfies RunnerName),
z.literal("embedded-with-serialization" satisfies RunnerName),
z.literal("embedded" satisfies RunnerName),
]);

export const functionSettingsSchema = z.object({
Expand Down Expand Up @@ -95,7 +93,7 @@ export const defaultPlaygroundSettings: PlaygroundSettings = {
seed: "default_seed",
profile: false,
},
runner: defaultRunnerName,
runner: "web-worker",
functionChartSettings: {
start: functionChartDefaults.min,
stop: functionChartDefaults.max,
Expand Down Expand Up @@ -149,19 +147,15 @@ const SelectRunnerField: FC = () => {
type Option = { value: RunnerName; label: string };

const options: Option[] = [
{ value: "embedded", label: "Embedded" },
{ value: "web-worker", label: "Web Worker (experimental)" },
{
value: "embedded-with-serialization",
label: "Embedded with serialization check (experimental)",
},
{ value: "web-worker", label: "Web Worker" },
{ value: "embedded", label: "Embedded (legacy)" },
];

return (
<div>
<SelectFormField<PlaygroundSettings, RunnerName, Option>
name="runner"
label="Squiggle Runner (Experimental)"
label="Squiggle Runner"
size="small"
options={options}
optionToFieldValue={(option) => option.value}
Expand All @@ -171,38 +165,25 @@ const SelectRunnerField: FC = () => {
renderOption={(option) => <div className="text-sm">{option.label}</div>}
required
/>
{runner === "embedded" && (
<FormComment>
<p>
This runner runs all Squiggle code in the main browser thread. This
can cause the UI to freeze during long-running simulations.
</p>
</FormComment>
)}
{runner === "web-worker" && (
<FormComment>
<p className="pb-2 pt-1">
<WrenchIcon className="mr-1 inline" size={14} />
This runner is <strong>experimental</strong>.
</p>
<p className="pb-2">
Risks involved: out-of-memory errors; responses arriving in wrong
order; bugs in serialization code when we return values from the
worker to the main thread.
<p className="pt-1">
The code will run in a separate Web Worker process. The main benefit
is that the UI won&apos;t be blocked during long-running
computations.
</p>
<p>
The main benefit is that the UI won&apos;t be blocked during
long-running computations.
<p className="pt-2">
Minor downsides are: we have to marshall data between the worker and
the main thread; the implementation is new and there could be bugs;
higher memory usage.
</p>
</FormComment>
)}
{runner === "embedded-with-serialization" && (
{runner === "embedded" && (
<FormComment>
<p>
<WrenchIcon className="mr-1 inline" size={14} />
This runner is only useful for debugging. It works in the same way
as the embedded runner, but it serializes and then deserializes each
result to check for bugs in serialization code.
<p className="pt-1">
This runner runs all Squiggle code in the main browser thread. This
can cause the UI to freeze during long-running simulations.
</p>
</FormComment>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { FC, useEffect, useRef, useState } from "react";

import {
ProjectAction,
ProjectEventListener,
SqProject,
} from "@quri/squiggle-lang";
import { Tooltip } from "@quri/ui";

const ActionDetails: FC<{ action: ProjectAction }> = ({ action }) => {
switch (action.type) {
case "loadImports":
return <div>{action.payload}</div>;
case "buildOutputIfPossible":
return <div>{action.payload.hash}</div>;
case "loadModule":
return <div>{action.payload.name}</div>;
default:
return null;
}
};

export const ActionLog: FC<{ project: SqProject }> = ({ project }) => {
const [actionLog, setActionLog] = useState<ProjectAction[]>([]);

useEffect(() => {
const listener: ProjectEventListener<"action"> = (event) => {
setActionLog((log) => [...log, event.data]);
};
project.addEventListener("action", listener);
return () => project.removeEventListener("action", listener);
});

const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
ref.current?.scrollTo(0, ref.current.scrollHeight);
}, [actionLog]);

return (
<details>
<summary className="cursor-pointer text-sm font-medium text-slate-700">
Action log
</summary>
<div className="max-h-24 overflow-y-auto p-2 text-xs" ref={ref}>
{actionLog.map((action, index) => (
<div key={index} className="flex">
<div className="cursor-pointer px-1 hover:bg-slate-100">
<Tooltip
render={() => (
<div className="max-w-64 whitespace-pre-wrap break-all rounded border bg-white px-2 py-1 text-xs shadow-md">
{JSON.stringify(action, null, 2)}
</div>
)}
>
<div className="flex gap-1">
<div className="text-slate-500">{action.type}</div>
<ActionDetails action={action} />
</div>
</Tooltip>
</div>
</div>
))}
</div>
</details>
);
};
Loading

0 comments on commit f16cd06

Please sign in to comment.