Merge remote-tracking branch 'origin/develop' into scoring-cleanup-three

This commit is contained in:
Quinn Dougherty 2022-06-22 22:06:16 -04:00
commit 13ba7d1a5e
23 changed files with 1092 additions and 879 deletions

View File

@ -18,6 +18,7 @@
"vega": "^5.22.1",
"vega-embed": "^6.21.0",
"vega-lite": "^5.2.0",
"vscode-uri": "^3.0.3",
"yup": "^0.32.11"
},
"devDependencies": {
@ -52,7 +53,7 @@
"tailwindcss": "^3.1.3",
"ts-loader": "^9.3.0",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.7.3",
"typescript": "^4.7.4",
"web-vitals": "^2.1.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",

View File

@ -1,4 +1,4 @@
import React, { FC, Fragment, useState } from "react";
import React, { FC, Fragment, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form";
import * as yup from "yup";
@ -35,6 +35,7 @@ interface PlaygroundProps {
/** If code is set, component becomes controlled */
code?: string;
onCodeChange?(expr: string): void;
onSettingsChange?(settings: any): void;
/** Should we show the editor? */
showEditor?: boolean;
}
@ -205,6 +206,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
showSummary = false,
code: controlledCode,
onCodeChange,
onSettingsChange,
showEditor = true,
}) => {
const [uncontrolledCode, setUncontrolledCode] = useState(
@ -233,6 +235,11 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
const vars = useWatch({
control,
});
useEffect(() => {
onSettingsChange?.(vars);
}, [vars, onSettingsChange]);
const chartSettings = {
start: Number(vars.diagramStart),
stop: Number(vars.diagramStop),

View File

@ -5,7 +5,7 @@ import { testRun } from "./TestHelpers";
describe("cumulative density function of a normal distribution", () => {
test("at 3 stdevs to the right of the mean is near 1", () => {
fc.assert(
fc.property(fc.float(), fc.float({ min: 1e-7 }), (mean, stdev) => {
fc.property(fc.integer(), fc.integer({ min: 1 }), (mean, stdev) => {
let threeStdevsAboveMean = mean + 3 * stdev;
let squiggleString = `cdf(normal(${mean}, ${stdev}), ${threeStdevsAboveMean})`;
let squiggleResult = testRun(squiggleString);
@ -16,7 +16,7 @@ describe("cumulative density function of a normal distribution", () => {
test("at 3 stdevs to the left of the mean is near 0", () => {
fc.assert(
fc.property(fc.float(), fc.float({ min: 1e-7 }), (mean, stdev) => {
fc.property(fc.integer(), fc.integer({ min: 1 }), (mean, stdev) => {
let threeStdevsBelowMean = mean - 3 * stdev;
let squiggleString = `cdf(normal(${mean}, ${stdev}), ${threeStdevsBelowMean})`;
let squiggleResult = testRun(squiggleString);

View File

@ -4,13 +4,16 @@ import * as fc from "fast-check";
// Beware: float64Array makes it appear in an infinite loop.
let arrayGen = () =>
fc.float32Array({
fc
.float32Array({
minLength: 10,
maxLength: 10000,
noDefaultInfinity: true,
noNaN: true,
});
})
.filter(
(xs_) => Math.min(...Array.from(xs_)) != Math.max(...Array.from(xs_))
);
describe("cumulative density function", () => {
let n = 10000;
@ -119,11 +122,7 @@ describe("cumulative density function", () => {
{ sampleCount: n, xyPointLength: 100 }
);
let cdfValue = dist.cdf(x).value;
if (x < Math.min(...xs)) {
expect(cdfValue).toEqual(0);
} else {
expect(cdfValue).toBeGreaterThan(0);
}
expect(cdfValue).toBeGreaterThanOrEqual(0);
})
);
});

View File

@ -5,15 +5,11 @@ import * as fc from "fast-check";
describe("Scalar manipulation is well-modeled by javascript math", () => {
test("in the case of natural logarithms", () => {
fc.assert(
fc.property(fc.float(), (x) => {
fc.property(fc.integer(), (x) => {
let squiggleString = `log(${x})`;
let squiggleResult = testRun(squiggleString);
if (x == 0) {
expect(squiggleResult.value).toEqual(-Infinity);
} else if (x < 0) {
expect(squiggleResult.value).toEqual(
"somemessage (confused why a test case hasn't pointed out to me that this message is bogus)"
);
} else {
expect(squiggleResult.value).toEqual(Math.log(x));
}
@ -23,7 +19,7 @@ describe("Scalar manipulation is well-modeled by javascript math", () => {
test("in the case of addition (with assignment)", () => {
fc.assert(
fc.property(fc.float(), fc.float(), fc.float(), (x, y, z) => {
fc.property(fc.integer(), fc.integer(), fc.integer(), (x, y, z) => {
let squiggleString = `x = ${x}; y = ${y}; z = ${z}; x + y + z`;
let squiggleResult = testRun(squiggleString);
expect(squiggleResult.value).toBeCloseTo(x + y + z);

View File

@ -1,11 +1,10 @@
import { errorValueToString } from "../../src/js/index";
import { testRun } from "./TestHelpers";
import * as fc from "fast-check";
describe("Symbolic mean", () => {
test("mean(triangular(x,y,z))", () => {
fc.assert(
fc.property(fc.float(), fc.float(), fc.float(), (x, y, z) => {
fc.property(fc.integer(), fc.integer(), fc.integer(), (x, y, z) => {
if (!(x < y && y < z)) {
try {
let squiggleResult = testRun(`mean(triangular(${x},${y},${z}))`);

View File

@ -19,7 +19,7 @@ do
fi
done
files=`ls src/rescript/**/**/*.resi src/rescript/**/*.resi` # src/rescript/*/resi
files=`ls src/rescript/**/*.resi` # src/rescript/*/resi
for file in $files
do
current=`cat $file`

View File

@ -51,7 +51,7 @@
"bisect_ppx": "^2.7.1",
"chalk": "^5.0.1",
"codecov": "^3.8.3",
"fast-check": "^2.25.0",
"fast-check": "^3.0.0",
"gentype": "^4.4.0",
"jest": "^27.5.1",
"moduleserve": "^0.9.1",
@ -62,7 +62,7 @@
"ts-jest": "^27.1.4",
"ts-loader": "^9.3.0",
"ts-node": "^10.8.1",
"typescript": "^4.7.3",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0"
},

View File

@ -1,3 +1,4 @@
/media/vendor
/out
/*.vsix
/syntaxes/*.json

View File

@ -4,6 +4,17 @@ _[marketplace](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-s
This extension provides support for [Squiggle](https://www.squiggle-language.com/) in VS Code.
Features:
- Preview `.squiggle` files in a preview pane
- Syntax highlighting for `.squiggle` and `.squiggleU` files
# Configuration
Some preview settings, e.g. whether to show the summary table or types of outputs, can be configurable on in the VS Code settings and persist between different preview sessions.
Check out the full list of Squiggle settings in the main VS Code settings.
# Build locally
We assume you ran `yarn` at the monorepo level for all dependencies.

View File

@ -0,0 +1,18 @@
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
{ "open": "\"", "close": "\"", "notIn": ["string", "comment"] }
]
}

View File

@ -0,0 +1,41 @@
(function () {
const vscode = acquireVsCodeApi();
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
function updateContent(text, showSettings) {
root.render(
React.createElement(squiggle_components.SquigglePlayground, {
code: text,
showEditor: false,
showTypes: Boolean(showSettings.showTypes),
showControls: Boolean(showSettings.showControls),
showSummary: Boolean(showSettings.showSummary),
})
);
}
// Handle messages sent from the extension to the webview
window.addEventListener("message", (event) => {
const message = event.data; // The json data that the extension sent
switch (message.type) {
case "update":
const { text, showSettings } = message;
// Update our webview's content
updateContent(text, showSettings);
// Then persist state information.
// This state is returned in the call to `vscode.getState` below when a webview is reloaded.
vscode.setState({ text, showSettings });
return;
}
});
const state = vscode.getState();
if (state) {
updateContent(state.text, state.showSettings);
}
})();

View File

@ -3,7 +3,7 @@
"displayName": "Squiggle",
"description": "Squiggle language support",
"license": "MIT",
"version": "0.0.4",
"version": "0.1.2",
"publisher": "QURI",
"repository": {
"type": "git",
@ -18,10 +18,45 @@
"Visualization"
],
"activationEvents": [
"onCustomEditor:squiggle.wysiwyg"
"onCustomEditor:squiggle.wysiwyg",
"onCommand:squiggle.preview"
],
"main": "./out/extension.js",
"contributes": {
"languages": [
{
"id": "squiggle",
"extensions": [
".squiggle"
],
"aliases": [
"Squiggle"
],
"configuration": "./language-configuration.json"
},
{
"id": "squiggleU",
"extensions": [
".squiggleU"
],
"aliases": [
"SquiggleU"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "squiggle",
"scopeName": "source.squiggle",
"path": "./syntaxes/squiggle.tmLanguage.json"
},
{
"language": "squiggleU",
"scopeName": "source.squiggle",
"path": "./syntaxes/squiggle.tmLanguage.json"
}
],
"customEditors": [
{
"viewType": "squiggle.wysiwyg",
@ -31,29 +66,82 @@
"filenamePattern": "*.squiggle"
}
],
"priority": "default"
"priority": "option"
}
],
"commands": []
"commands": [
{
"command": "squiggle.preview",
"title": "Open Preview",
"category": "Squiggle",
"when": "editorLangId == squiggle",
"icon": "$(open-preview)"
}
],
"menus": {
"editor/title": [
{
"command": "squiggle.preview",
"when": "editorLangId == squiggle",
"group": "navigation"
}
],
"commandPalette": [
{
"command": "squiggle.preview",
"when": "editorLangId == squiggle"
}
]
},
"keybindings": [
{
"command": "squiggle.preview",
"key": "ctrl+k v",
"mac": "cmd+k v",
"when": "editorLangId == squiggle"
}
],
"configuration": {
"title": "Squiggle",
"properties": {
"squiggle.playground.showTypes": {
"type": "boolean",
"default": false,
"description": "Whether to show the types of outputs in the playground"
},
"squiggle.playground.showControls": {
"type": "boolean",
"default": false,
"description": "Whether to show the log scale controls in the playground"
},
"squiggle.playground.showSummary": {
"type": "boolean",
"default": false,
"description": "Whether to show the summary table in the playground"
}
}
}
},
"scripts": {
"vscode:prepublish": "yarn run compile",
"compile:tsc": "tsc -p ./",
"compile:grammar": "js-yaml syntaxes/squiggle.tmLanguage.yaml >syntaxes/squiggle.tmLanguage.json",
"compile:vendor": "(cd ../squiggle-lang && yarn run build) && (cd ../components && yarn run bundle && yarn run build:css) && mkdir -p media/vendor && cp ../components/dist/bundle.js media/vendor/components.js && cp ../components/dist/main.css media/vendor/components.css && cp ../../node_modules/react/umd/react.production.min.js media/vendor/react.js && cp ../../node_modules/react-dom/umd/react-dom.production.min.js media/vendor/react-dom.js && cp ../website/static/img/quri-logo.png media/vendor/icon.png",
"compile": "yarn run compile:tsc && yarn run compile:vendor",
"compile": "yarn run compile:tsc && yarn run compile:grammar && yarn run compile:vendor",
"watch": "tsc -watch -p ./",
"pretest": "yarn run compile && yarn run lint",
"lint": "eslint src --ext ts",
"format": "eslint src --ext ts --fix"
},
"devDependencies": {
"@types/vscode": "^1.68.0",
"@types/glob": "^7.2.0",
"@types/node": "18.x",
"@types/vscode": "^1.68.0",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"eslint": "^8.18.0",
"glob": "^8.0.3",
"typescript": "^4.7.2"
"js-yaml": "^4.1.0",
"typescript": "^4.7.4"
}
}

View File

@ -1,14 +1,5 @@
import * as vscode from "vscode";
function getNonce() {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
import { getWebviewContent } from "./utils";
export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
public static register(context: vscode.ExtensionContext): vscode.Disposable {
@ -26,8 +17,6 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
/**
* Called when our custom editor is opened.
*
*
*/
public async resolveCustomTextEditor(
document: vscode.TextDocument,
@ -37,7 +26,12 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
webviewPanel.webview.options = {
enableScripts: true,
};
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
webviewPanel.webview.html = getWebviewContent({
webview: webviewPanel.webview,
script: "media/wysiwygWebview.js",
title: "Squiggle Editor",
context: this.context,
});
function updateWebview() {
webviewPanel.webview.postMessage({
@ -79,57 +73,6 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
updateWebview();
}
/**
* Get the static html used for the editor webviews.
*/
private getHtmlForWebview(webview: vscode.Webview): string {
// Local path to main script run in the webview
const styleUri = webview.asWebviewUri(
vscode.Uri.joinPath(
this.context.extensionUri,
"media/vendor/components.css"
)
);
const scriptUris = [
// vendor files are copied over by `yarn run compile`
"media/vendor/react.js",
"media/vendor/react-dom.js",
"media/vendor/components.js",
"media/wysiwyg.js",
].map((script) =>
webview.asWebviewUri(
vscode.Uri.joinPath(this.context.extensionUri, script)
)
);
// Use a nonce to whitelist which scripts can be run
const nonce = getNonce();
return /* html */ `
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--
Use a content security policy to only allow loading images from https or from our extension directory,
and only allow scripts that have a specific nonce.
-->
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-${nonce}' 'unsafe-eval';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleUri}" rel="stylesheet" />
<title>Squiggle Editor</title>
</head>
<body style="background-color: white; color: black; padding: 12px">
<div id="root"></div>
${scriptUris
.map((uri) => `<script nonce="${nonce}" src="${uri}"></script>`)
.join("")}
</body>
</html>`;
}
private updateTextDocument(document: vscode.TextDocument, text: string) {
const edit = new vscode.WorkspaceEdit();

View File

@ -2,12 +2,15 @@
// Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode";
import { SquiggleEditorProvider } from "./squiggleEditor";
import { SquiggleEditorProvider } from "./editor";
import { registerPreviewCommand } from "./preview";
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(SquiggleEditorProvider.register(context));
registerPreviewCommand(context);
}
// this method is called when your extension is deactivated

View File

@ -0,0 +1,53 @@
import * as vscode from "vscode";
import * as path from "path";
import { getWebviewContent } from "./utils";
export const registerPreviewCommand = (context: vscode.ExtensionContext) => {
context.subscriptions.push(
vscode.commands.registerTextEditorCommand("squiggle.preview", (editor) => {
// Create and show a new webview
const title = `Preview ${path.basename(editor.document.uri.path)}`;
const panel = vscode.window.createWebviewPanel(
"squigglePreview",
title,
vscode.ViewColumn.Beside,
{} // Webview options. More on these later.
);
panel.webview.options = {
enableScripts: true,
};
panel.webview.html = getWebviewContent({
context,
webview: panel.webview,
title,
script: "media/previewWebview.js",
});
const updateWebview = () => {
panel.webview.postMessage({
type: "update",
text: editor.document.getText(),
showSettings:
vscode.workspace.getConfiguration("squiggle").playground,
});
};
updateWebview();
const changeDocumentSubscription =
vscode.workspace.onDidChangeTextDocument((e) => {
if (e.document.uri.toString() === editor.document.uri.toString()) {
updateWebview();
}
});
// Make sure we get rid of the listener when our editor is closed.
panel.onDidDispose(() => {
changeDocumentSubscription.dispose();
});
})
);
};

View File

@ -0,0 +1,59 @@
import * as vscode from "vscode";
const getNonce = () => {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
export const getWebviewContent = ({
webview,
title,
script,
context,
}: {
webview: vscode.Webview;
title: string;
script: string;
context: vscode.ExtensionContext;
}) => {
const nonce = getNonce();
const styleUri = webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "media/vendor/components.css")
);
const scriptUris = [
// vendor files are copied over by `yarn run compile`
"media/vendor/react.js",
"media/vendor/react-dom.js",
"media/vendor/components.js",
script,
].map((script) =>
webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, script))
);
return `
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Use a content security policy to only allow scripts that have a specific nonce. -->
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-${nonce}' 'unsafe-eval';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleUri}" rel="stylesheet" />
<title>${title}</title>
</head>
<body style="background-color: white; color: black; padding: 8px 12px">
<div id="root"></div>
${scriptUris
.map((uri) => `<script nonce="${nonce}" src="${uri}"></script>`)
.join("")}
</body>
</html>
`;
};

View File

@ -0,0 +1,93 @@
scopeName: source.squiggle
patterns:
- include: "#statement"
- include: "#expression"
- include: "#comment-block"
- include: "#comment-line"
repository:
statement:
patterns:
- include: "#let"
- include: "#defun"
expression:
patterns:
- include: "#integer"
- include: "#float"
- include: "#string"
- include: "#block"
- include: "#function-call"
- include: "#keywords"
let:
match: ^\s*(\w+)\s*=
captures:
"1":
name: variable.other.squiggle
defun:
begin: ^\s*(\w+)\s*(\()
end: (\))\s*=
beginCaptures:
"1":
name: entity.name.function.squiggle
"2":
name: punctuation.definition.arguments.begin.squiggle
endCaptures:
"1":
name: punctuation.definition.arguments.end.squiggle
patterns:
- include: "#array-parameters"
array-parameters:
begin: \b([\$_a-z]+[\$_a-zA-Z0-9]*)
end: \s*(?:(,)|(?=\)))
beginCaptures:
"1":
name: variable.parameter.function.squiggle
function-call:
begin: (\w+)\s*(\()
end: (\))
beginCaptures:
"1":
name: entity.name.function.squiggle
"2":
name: punctuation.definition.arguments.begin.squiggle
endCaptures:
"1":
name: punctuation.definition.arguments.end.squiggle
patterns:
- include: "$self"
comment-block:
begin: /\*
end: \*/
name: comment.block.squiggle
comment-line:
patterns:
- include: "#comment-line-double-slash"
- include: "#comment-line-number-sign"
comment-line-double-slash:
match: //.*
name: comment.line.double-slash.squiggle
comment-line-number-sign:
match: "#.*"
name: comment.line.number-sign.squiggle
block:
begin: "{"
end: "}"
beginCaptures:
"0":
name: punctuation.definition.block.squiggle
endCaptures:
"0":
name: punctuation.definition.block.squiggle
patterns:
- include: "$self"
keywords:
match: \b(if|then|else|to)\b
name: keyword.control.squiggle
integer:
match: \b\d+([_a-zA-Z]+[_a-zA-Z0-9]*)?
name: constant.numeric.integer.squiggle
float:
match: \b(\d+\.\d*|\.?\d+)([eE]-?\d+)?([_a-zA-Z]+[_a-zA-Z0-9]*)?
name: constant.numeric.float.squiggle
string:
match: \".*?\"
name: string.quoted.double.squiggle

View File

@ -6,11 +6,13 @@ This website is built using [Docusaurus 2](https://docusaurus.io/), a modern sta
We assume you ran `yarn` at monorepo level.
The website depends on `squiggle-lang`, which you have to build manually.
The website depends on `squiggle-lang` and `components`, which you have to build manually.
```sh
cd ../squiggle-lang
yarn build
cd ../components
yarn build
```
Generate static content, to the `build` directory.

View File

@ -15,8 +15,10 @@
"@docusaurus/core": "2.0.0-beta.21",
"@docusaurus/preset-classic": "2.0.0-beta.21",
"@quri/squiggle-components": "^0.2.20",
"base64-js": "^1.5.1",
"clsx": "^1.1.1",
"hast-util-is-element": "2.1.2",
"pako": "^2.0.4",
"prism-react-renderer": "^1.3.3",
"react": "^18.1.0",
"react-dom": "^18.2.0",

View File

@ -1,8 +1,52 @@
import { deflate, inflate } from "pako";
import { toByteArray, fromByteArray } from "base64-js";
import React from "react";
import Layout from "@theme/Layout";
import { SquigglePlayground } from "../components/SquigglePlayground";
const HASH_PREFIX = "#code=";
function getHashData() {
if (typeof window === "undefined") {
return {};
}
const hash = window.location.hash;
if (!hash.startsWith(HASH_PREFIX)) {
return {};
}
try {
const compressed = toByteArray(
decodeURIComponent(hash.slice(HASH_PREFIX.length))
);
const text = inflate(compressed, { to: "string" });
return JSON.parse(text);
} catch (err) {
console.error(err);
return {};
}
}
function setHashData(data) {
const text = JSON.stringify({ ...getHashData(), ...data });
const compressed = deflate(text, { level: 9 });
window.history.replaceState(
undefined,
"",
HASH_PREFIX + encodeURIComponent(fromByteArray(compressed))
);
}
export default function PlaygroundPage() {
const playgroundProps = {
initialSquiggleString: "normal(0,1)",
height: 700,
showTypes: true,
...getHashData(),
onCodeChange: (code) => setHashData({ initialSquiggleString: code }),
onSettingsChange: (settings) => {
const { showTypes, showControls, showSummary, showEditor } = settings;
setHashData({ showTypes, showControls, showSummary, showEditor });
},
};
return (
<Layout title="Playground" description="Squiggle Playground">
<div
@ -10,11 +54,7 @@ export default function PlaygroundPage() {
maxWidth: 2000,
}}
>
<SquigglePlayground
initialSquiggleString="normal(0,1)"
height={700}
showTypes={true}
/>
<SquigglePlayground {...playgroundProps} />
</div>
</Layout>
);

1405
yarn.lock

File diff suppressed because it is too large Load Diff