Merge branch 'develop' into reducer-typescript-wall

packages/squiggle-lang/src/rescript/ReducerInterface/ReducerInterface_GenericDistribution.res
packages/squiggle-lang/src/rescript/SquiggleLibrary/SquiggleLibrary_Math.res
This commit is contained in:
Umur Ozkul 2022-06-23 04:29:13 +02:00
commit e23f8b011b
83 changed files with 4941 additions and 2528 deletions

View File

@ -19,6 +19,8 @@ jobs:
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }}
should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }}
steps:
- id: skip_lang_check
name: Check if the changes are about squiggle-lang src files
@ -35,6 +37,16 @@ jobs:
uses: fkirc/skip-duplicate-actions@v3.4.1
with:
paths: '["packages/website/**"]'
- id: skip_vscodeext_check
name: Check if the changes are about vscode extension src files
uses: fkirc/skip-duplicate-actions@v3.4.1
with:
paths: '["packages/vscode-ext/**"]'
- id: skip_cli_check
name: Check if the changes are about cli src files
uses: fkirc/skip-duplicate-actions@v3.4.1
with:
paths: '["packages/cli/**"]'
lang-lint:
name: Language lint
@ -158,3 +170,52 @@ jobs:
run: cd ../components && yarn build
- name: Build website assets
run: yarn build
vscode-ext-lint:
name: VS Code extension lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/vscode-ext
steps:
- uses: actions/checkout@v2
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Lint the VSCode Extension source code
run: yarn lint
vscode-ext-build:
name: VS Code extension build
runs-on: ubuntu-latest
needs: pre_check
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }} || (needs.pre_check.outputs.should_skip_vscodeext != 'true') }}
defaults:
run:
shell: bash
working-directory: packages/vscode-ext
steps:
- uses: actions/checkout@v2
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build
run: yarn compile
cli-lint:
name: CLI lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/cli
steps:
- uses: actions/checkout@v2
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
dry: true
prettier_options: --check packages/cli

View File

@ -12,3 +12,4 @@ packages/squiggle-lang/coverage/
packages/squiggle-lang/.cache/
packages/website/build/
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
packages/vscode-ext/media/vendor/

View File

@ -17,6 +17,7 @@ _An estimation language_.
- [Known bugs](https://www.squiggle-language.com/docs/Discussions/Bugs)
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)
- [Use squiggle in VS Code](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle)
## Our deployments
@ -39,6 +40,8 @@ the packages can be found in `packages`.
of the calculation.
- `packages/website` is the main descriptive website for squiggle,
it is hosted at `squiggle-language.com`.
- `packages/vscode-ext` is the VS Code extension for writing estimation functions.
- `packages/cli` is an experimental way of using imports in squiggle, which is also on [npm](https://www.npmjs.com/package/squiggle-cli-experimental).
# Develop

View File

@ -7,7 +7,7 @@
"lint:all": "prettier --check . && cd packages/squiggle-lang && yarn lint:rescript"
},
"devDependencies": {
"prettier": "^2.6.2"
"prettier": "^2.7.1"
},
"workspaces": [
"packages/*"

4
packages/cli/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
## Artifacts
*.swp
/node_modules/
yarn-error.log

22
packages/cli/README.md Normal file
View File

@ -0,0 +1,22 @@
## Squiggle CLI
This package can be used to incorporate a very simple `import` system into Squiggle.
To use, write special files with a `.squiggleU` file type. In these files, you can write lines like,
```
@import(models/gdp_over_time.squiggle, gdpOverTime)
gdpOverTime(2.5)
```
The imports will be replaced with the contents of the file in `models/gdp_over_time.squiggle` upon compilation. The `.squiggleU` file will be converted into a `.squiggle` file with the `import` statement having this replacement.
## Running
### `npx squiggle-cli-experimental compile`
Runs compilation in the current directory and all of its subdirectories.
### `npx squiggle-cli-experimental watch`
Watches `.squiggleU` files in the current directory (and subdirectories) and rebuilds them when they are saved. Note that this will _not_ rebuild files when their dependencies are changed, just when they are changed directly.

96
packages/cli/index.js Executable file
View File

@ -0,0 +1,96 @@
#!/usr/bin/env node
import fs from "fs";
import path from "path";
import indentString from "indent-string";
import chokidar from "chokidar";
import chalk from "chalk";
import { Command } from "commander";
import glob from "glob";
const processFile = (fileName, seen = []) => {
const normalizedFileName = path.resolve(fileName);
if (seen.includes(normalizedFileName)) {
throw new Error(`Recursive dependency for file ${fileName}`);
}
const fileContents = fs.readFileSync(fileName, "utf-8");
if (!fileName.endsWith(".squiggleU")) {
return fileContents;
}
const regex = /\@import\(\s*([^)]+?)\s*\)/g;
const matches = Array.from(fileContents.matchAll(regex)).map((r) =>
r[1].split(/\s*,\s*/)
);
const newContent = fileContents.replaceAll(regex, "");
const appendings = [];
matches.forEach((r) => {
const importFileName = r[0];
const rename = r[1];
const item = fs.statSync(importFileName);
if (item.isFile()) {
const data = processFile(importFileName, [...seen, normalizedFileName]);
if (data) {
const importString = `${rename} = {\n${indentString(data, 2)}\n}\n`;
appendings.push(importString);
}
} else {
console.log(
chalk.red(`Import Error`) +
`: ` +
chalk.cyan(importFileName) +
` not found in file ` +
chalk.cyan(fileName) +
`. Make sure the @import file names all exist in this repo.`
);
}
});
const imports = appendings.join("\n");
const newerContent = imports.concat(newContent);
return newerContent;
};
const run = (fileName) => {
const content = processFile(fileName);
const parsedPath = path.parse(path.resolve(fileName));
const newFilename = `${parsedPath.dir}/${parsedPath.name}.squiggle`;
fs.writeFileSync(newFilename, content);
console.log(chalk.cyan(`Updated ${fileName} -> ${newFilename}`));
};
const compile = () => {
glob("**/*.squiggleU", (_err, files) => {
files.forEach(run);
});
};
const watch = () => {
chokidar
.watch("**.squiggleU")
.on("ready", () => console.log(chalk.green("Ready!")))
.on("change", (event, _) => {
run(event);
});
};
const program = new Command();
program
.name("squiggle-utils")
.description("CLI to transform squiggle files with @imports")
.version("0.0.1");
program
.command("watch")
.description("watch files and compile on the fly")
.action(watch);
program
.command("compile")
.description("compile all .squiggleU files into .squiggle files")
.action(compile);
program.parse();

21
packages/cli/package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "squiggle-cli-experimental",
"version": "0.0.3",
"main": "index.js",
"homepage": "https://squiggle-language.com",
"author": "Quantified Uncertainty Research Institute",
"bin": "index.js",
"type": "module",
"scripts": {
"start": "node ."
},
"license": "MIT",
"dependencies": {
"chalk": "^5.0.1",
"chokidar": "^3.5.3",
"commander": "^9.3.0",
"fs": "^0.0.1-security",
"glob": "^8.0.3",
"indent-string": "^5.0.0"
}
}

View File

@ -1,3 +1,4 @@
dist/
storybook-static
src/styles/base.css
src/styles/forms.css

View File

@ -25,6 +25,43 @@ import { SquiggleEditor } from "@quri/squiggle-components";
/>;
```
# Usage in a Nextjs project
For now, `squiggle-components` requires the `window` property, so using the package in nextjs requires dynamic loading:
```
import React from "react";
import { SquiggleChart } from "@quri/squiggle-components";
import dynamic from "next/dynamic";
const SquiggleChart = dynamic(
() => import("@quri/squiggle-components").then((mod) => mod.SquiggleChart),
{
loading: () => <p>Loading...</p>,
ssr: false,
}
);
export function DynamicSquiggleChart({ squiggleString }) {
if (squiggleString == "") {
return null;
} else {
return (
<SquiggleChart
squiggleString={squiggleString}
width={445}
height={200}
showSummary={true}
showTypes={true}
/>
);
}
}
```
# Build storybook for development
We assume that you had run `yarn` at monorepo level, installing dependencies.

View File

@ -3,63 +3,72 @@
"version": "0.2.20",
"license": "MIT",
"dependencies": {
"@headlessui/react": "^1.6.4",
"@headlessui/react": "^1.6.5",
"@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^2.9.1",
"@quri/squiggle-lang": "^0.2.8",
"@react-hook/size": "^2.1.2",
"clsx": "^1.1.1",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-ace": "^10.1.0",
"react-dom": "^18.1.0",
"react-hook-form": "^7.32.0",
"react-hook-form": "^7.32.2",
"react-use": "^17.4.0",
"react-vega": "^7.5.1",
"vega": "^5.22.1",
"vega-embed": "^6.20.6",
"vega-embed": "^6.21.0",
"vega-lite": "^5.2.0",
"vscode-uri": "^3.0.3",
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.17.12",
"@storybook/addon-actions": "^6.5.8",
"@storybook/addon-essentials": "^6.5.8",
"@storybook/addon-links": "^6.5.8",
"@storybook/builder-webpack5": "^6.5.7",
"@storybook/manager-webpack5": "^6.5.8",
"@storybook/node-logger": "^6.5.6",
"@storybook/addon-actions": "^6.5.9",
"@storybook/addon-essentials": "^6.5.9",
"@storybook/addon-links": "^6.5.9",
"@storybook/builder-webpack5": "^6.5.9",
"@storybook/manager-webpack5": "^6.5.9",
"@storybook/node-logger": "^6.5.9",
"@storybook/preset-create-react-app": "^4.1.2",
"@storybook/react": "^6.5.8",
"@tailwindcss/forms": "^0.5.2",
"@storybook/react": "^6.5.9",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.0",
"@testing-library/user-event": "^14.2.1",
"@types/jest": "^27.5.0",
"@types/lodash": "^4.14.182",
"@types/node": "^17.0.42",
"@types/node": "^18.0.0",
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.5",
"@types/styled-components": "^5.1.24",
"@types/webpack": "^5.28.0",
"cross-env": "^7.0.3",
"mini-css-extract-plugin": "^2.6.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss-cli": "^9.1.0",
"postcss-import": "^14.1.0",
"postcss-loader": "^7.0.0",
"react": "^18.1.0",
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1",
"style-loader": "^3.3.1",
"tailwindcss": "^3.1.2",
"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",
"webpack-dev-server": "^4.9.2"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18",
"react-dom": "^16.8.0 || ^17 || ^18"
},
"scripts": {
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
"build": "tsc -b && postcss ./src/styles/main.css -o ./dist/main.css && build-storybook -s public",
"build:cjs": "tsc -b",
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
"build:storybook": "build-storybook -s public",
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
"bundle": "webpack",
"all": "yarn bundle && yarn build",
"lint": "prettier --check .",

View File

@ -1,5 +1,5 @@
import _ from "lodash";
import React, { FC } from "react";
import React, { FC, useMemo } from "react";
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-golang";
@ -21,14 +21,15 @@ export const CodeEditor: FC<CodeEditorProps> = ({
showGutter = false,
height,
}) => {
let lineCount = value.split("\n").length;
let id = _.uniqueId();
const lineCount = value.split("\n").length;
const id = useMemo(() => _.uniqueId(), []);
return (
<AceEditor
value={value}
mode="golang"
theme="github"
width={"100%"}
width="100%"
fontSize={14}
height={String(height) + "px"}
minLines={oneLine ? lineCount : undefined}

View File

@ -129,6 +129,7 @@ export const CheckBox: React.FC<CheckBoxProps> = ({
value={value + ""}
onChange={() => onChange(!value)}
disabled={disabled}
className="form-checkbox"
/>
<label className={clsx(disabled && "text-slate-400")}> {label}</label>
</span>

View File

@ -1,7 +1,5 @@
import * as React from "react";
import {
run,
errorValueToString,
squiggleExpression,
bindings,
environment,
@ -9,253 +7,11 @@ import {
defaultImports,
defaultBindings,
defaultEnvironment,
declaration,
} from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower";
import { DistributionChart } from "./DistributionChart";
import { ErrorAlert } from "./Alert";
import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
function getRange<a>(x: declaration<a>) {
let first = x.args[0];
switch (first.tag) {
case "Float": {
return { floats: { min: first.value.min, max: first.value.max } };
}
case "Date": {
return { time: { min: first.value.min, max: first.value.max } };
}
}
}
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
let range = getRange(x);
let min = range.floats ? range.floats.min : 0;
let max = range.floats ? range.floats.max : 10;
return {
start: min,
stop: max,
count: 20,
};
}
interface VariableBoxProps {
heading: string;
children: React.ReactNode;
showTypes: boolean;
}
export const VariableBox: React.FC<VariableBoxProps> = ({
heading = "Error",
children,
showTypes = false,
}) => {
if (showTypes) {
return (
<div className="bg-white border border-grey-200 m-2">
<div className="border-b border-grey-200 p-3">
<header className="font-mono">{heading}</header>
</div>
<div className="p-3">{children}</div>
</div>
);
} else {
return <div>{children}</div>;
}
};
export interface SquiggleItemProps {
/** The input string for squiggle */
expression: squiggleExpression;
width?: number;
height: number;
/** Whether to show a summary of statistics for distributions */
showSummary: boolean;
/** Whether to show type information */
showTypes: boolean;
/** Whether to show users graph controls (scale etc) */
showControls: boolean;
/** Settings for displaying functions */
chartSettings: FunctionChartSettings;
/** Environment for further function executions */
environment: environment;
}
const SquiggleItem: React.FC<SquiggleItemProps> = ({
expression,
width,
height,
showSummary,
showTypes = false,
showControls = false,
chartSettings,
environment,
}) => {
switch (expression.tag) {
case "number":
return (
<VariableBox heading="Number" showTypes={showTypes}>
<div className="font-semibold text-slate-600">
<NumberShower precision={3} number={expression.value} />
</div>
</VariableBox>
);
case "distribution": {
let distType = expression.value.type();
return (
<VariableBox
heading={`Distribution (${distType})`}
showTypes={showTypes}
>
{distType === "Symbolic" && showTypes ? (
<div>{expression.value.toString()}</div>
) : null}
<DistributionChart
distribution={expression.value}
height={height}
width={width}
showSummary={showSummary}
showControls={showControls}
/>
</VariableBox>
);
}
case "string":
return (
<VariableBox heading="String" showTypes={showTypes}>
<span className="text-slate-400">"</span>
<span className="text-slate-600 font-semibold">
{expression.value}
</span>
<span className="text-slate-400">"</span>
</VariableBox>
);
case "boolean":
return (
<VariableBox heading="Boolean" showTypes={showTypes}>
{expression.value.toString()}
</VariableBox>
);
case "symbol":
return (
<VariableBox heading="Symbol" showTypes={showTypes}>
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
<span className="text-slate-600">{expression.value}</span>
</VariableBox>
);
case "call":
return (
<VariableBox heading="Call" showTypes={showTypes}>
{expression.value}
</VariableBox>
);
case "array":
return (
<VariableBox heading="Array" showTypes={showTypes}>
{expression.value.map((r, i) => (
<div key={i} className="flex pt-1">
<div className="flex-none bg-slate-100 rounded-sm px-1">
<header className="text-slate-400 font-mono">{i}</header>
</div>
<div className="px-2 mb-2 grow">
<SquiggleItem
key={i}
expression={r}
width={width !== undefined ? width - 20 : width}
height={50}
showTypes={showTypes}
showControls={showControls}
chartSettings={chartSettings}
environment={environment}
showSummary={showSummary}
/>
</div>
</div>
))}
</VariableBox>
);
case "record":
return (
<VariableBox heading="Record" showTypes={showTypes}>
<div className="space-y-3">
{Object.entries(expression.value).map(([key, r]) => (
<div key={key} className="flex space-x-2">
<div className="flex-none">
<header className="text-slate-500 font-mono">{key}:</header>
</div>
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
<SquiggleItem
expression={r}
width={width !== undefined ? width - 20 : width}
height={height / 3}
showTypes={showTypes}
showSummary={showSummary}
showControls={showControls}
chartSettings={chartSettings}
environment={environment}
/>
</div>
</div>
))}
</div>
</VariableBox>
);
case "arraystring":
return (
<VariableBox heading="Array String" showTypes={showTypes}>
{expression.value.map((r) => `"${r}"`).join(", ")}
</VariableBox>
);
case "date":
return (
<VariableBox heading="Date" showTypes={showTypes}>
{expression.value.toDateString()}
</VariableBox>
);
case "timeDuration": {
return (
<VariableBox heading="Time Duration" showTypes={showTypes}>
<NumberShower precision={3} number={expression.value} />
</VariableBox>
);
}
case "lambda":
return (
<VariableBox heading="Function" showTypes={showTypes}>
<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.parameters.join(
","
)})`}</div>
<FunctionChart
fn={expression.value}
chartSettings={chartSettings}
height={height}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
</VariableBox>
);
case "lambdaDeclaration": {
return (
<VariableBox heading="Function Declaration" showTypes={showTypes}>
<FunctionChart
fn={expression.value.fn}
chartSettings={getChartSettings(expression.value)}
height={height}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
</VariableBox>
);
}
default: {
return <>Should be unreachable</>;
}
}
};
import { FunctionChartSettings } from "./FunctionChart";
import { useSquiggle } from "../lib/hooks";
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
import { SquiggleItem } from "./SquiggleItem";
export interface SquiggleChartProps {
/** The input string for squiggle */
@ -266,8 +22,8 @@ export interface SquiggleChartProps {
environment?: environment;
/** If the result is a function, where the function starts, ends and the amount of stops */
chartSettings?: FunctionChartSettings;
/** When the environment changes */
onChange?(expr: squiggleExpression): void;
/** When the squiggle code gets reevaluated */
onChange?(expr: squiggleExpression | undefined): void;
/** CSS width of the element */
width?: number;
height?: number;
@ -275,7 +31,7 @@ export interface SquiggleChartProps {
bindings?: bindings;
/** JS imported parameters */
jsImports?: jsImports;
/** Whether to show a summary of the distirbution */
/** Whether to show a summary of the distribution */
showSummary?: boolean;
/** Whether to show type information about returns, default false */
showTypes?: boolean;
@ -283,12 +39,13 @@ export interface SquiggleChartProps {
showControls?: boolean;
}
const defaultOnChange = () => {};
const defaultChartSettings = { start: 0, stop: 10, count: 20 };
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
squiggleString = "",
environment,
onChange = () => {},
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
height = 200,
bindings = defaultBindings,
jsImports = defaultImports,
@ -298,28 +55,28 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
showControls = false,
chartSettings = defaultChartSettings,
}) => {
let expressionResult = run(squiggleString, bindings, environment, jsImports);
if (expressionResult.tag !== "Ok") {
return (
<ErrorAlert heading={"Parse Error"}>
{errorValueToString(expressionResult.value)}
</ErrorAlert>
);
const { result } = useSquiggle({
code: squiggleString,
bindings,
environment,
jsImports,
onChange,
});
if (result.tag !== "Ok") {
return <SquiggleErrorAlert error={result.value} />;
}
let e = environment ?? defaultEnvironment;
let expression = expressionResult.value;
onChange(expression);
return (
<SquiggleItem
expression={expression}
expression={result.value}
width={width}
height={height}
showSummary={showSummary}
showTypes={showTypes}
showControls={showControls}
chartSettings={chartSettings}
environment={e}
environment={environment ?? defaultEnvironment}
/>
);
};

View File

@ -1,39 +1,51 @@
import * as React from "react";
import React, { useState } from "react";
import * as ReactDOM from "react-dom";
import { SquiggleChart } from "./SquiggleChart";
import { CodeEditor } from "./CodeEditor";
import type {
import {
squiggleExpression,
environment,
bindings,
jsImports,
defaultEnvironment,
} from "@quri/squiggle-lang";
import {
runPartial,
errorValueToString,
defaultImports,
defaultBindings,
} from "@quri/squiggle-lang";
import { ErrorAlert } from "./Alert";
import { defaultImports, defaultBindings } from "@quri/squiggle-lang";
import { SquiggleContainer } from "./SquiggleContainer";
import { useSquiggle, useSquigglePartial } from "../lib/hooks";
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
import { SquiggleItem } from "./SquiggleItem";
const WrappedCodeEditor: React.FC<{
code: string;
setCode: (code: string) => void;
}> = ({ code, setCode }) => (
<div className="border border-grey-200 p-2 m-4">
<CodeEditor
value={code}
onChange={setCode}
oneLine={true}
showGutter={false}
height={20}
/>
</div>
);
export interface SquiggleEditorProps {
/** The input string for squiggle */
initialSquiggleString?: string;
/** If the output requires monte carlo sampling, the amount of samples */
environment?: environment;
/** The width of the element */
width?: 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: squiggleExpression): void;
/** The width of the element */
width?: number;
/** When the environment changes. Used again for notebook magic */
onChange?(expr: squiggleExpression | undefined): void;
/** Previous variable declarations */
bindings?: bindings;
/** If the output requires monte carlo sampling, the amount of samples */
environment?: environment;
/** JS Imports */
jsImports?: jsImports;
/** Whether to show detail about types of the returns, default false */
@ -44,169 +56,109 @@ export interface SquiggleEditorProps {
showSummary?: boolean;
}
export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
export const SquiggleEditor: React.FC<SquiggleEditorProps> = ({
initialSquiggleString = "",
width,
environment,
diagramStart = 0,
diagramStop = 10,
diagramCount = 20,
onChange,
bindings = defaultBindings,
environment,
jsImports = defaultImports,
showTypes = false,
showControls = false,
showSummary = false,
}: SquiggleEditorProps) => {
const [expression, setExpression] = React.useState(initialSquiggleString);
const [code, setCode] = useState(initialSquiggleString);
const { result, observableRef } = useSquiggle({
code,
bindings,
environment,
jsImports,
onChange,
});
const chartSettings = {
start: diagramStart,
stop: diagramStop,
count: diagramCount,
};
return (
<div ref={observableRef}>
<SquiggleContainer>
<div>
<div className="border border-grey-200 p-2 m-4">
<CodeEditor
value={expression}
onChange={setExpression}
oneLine={true}
showGutter={false}
height={20}
/>
</div>
<SquiggleChart
<WrappedCodeEditor code={code} setCode={setCode} />
{result.tag === "Ok" ? (
<SquiggleItem
expression={result.value}
width={width}
environment={environment}
squiggleString={expression}
chartSettings={chartSettings}
onChange={onChange}
bindings={bindings}
jsImports={jsImports}
height={200}
showSummary={showSummary}
showTypes={showTypes}
showControls={showControls}
showSummary={showSummary}
chartSettings={chartSettings}
environment={environment ?? defaultEnvironment}
/>
</div>
) : (
<SquiggleErrorAlert error={result.value} />
)}
</SquiggleContainer>
</div>
);
};
export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
let parent = document.createElement("div");
ReactDOM.render(
<SquiggleEditor
{...props}
onChange={(expr) => {
// Typescript complains on two levels here.
// - Div elements don't have a value property
// - Even if it did (like it was an input element), it would have to
// be a string
//
// Which are reasonable in most web contexts.
//
// However we're using observable, neither of those things have to be
// true there. div elements can contain the value property, and can have
// the value be any datatype they wish.
//
// This is here to get the 'viewof' part of:
// viewof env = cell('normal(0,1)')
// to work
// @ts-ignore
parent.value = expr;
parent.dispatchEvent(new CustomEvent("input"));
if (props.onChange) props.onChange(expr);
}}
/>,
parent
);
const parent = document.createElement("div");
ReactDOM.render(<SquiggleEditor {...props} />, parent);
return parent;
}
export interface SquigglePartialProps {
/** The input string for squiggle */
initialSquiggleString?: string;
/** If the output requires monte carlo sampling, the amount of samples */
environment?: environment;
/** 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;
onChange?(expr: bindings | undefined): void;
/** Previously declared variables */
bindings?: bindings;
/** If the output requires monte carlo sampling, the amount of samples */
environment?: environment;
/** Variables imported from js */
jsImports?: jsImports;
/** Whether to give users access to graph controls */
showControls?: boolean;
}
export let SquigglePartial: React.FC<SquigglePartialProps> = ({
export const SquigglePartial: React.FC<SquigglePartialProps> = ({
initialSquiggleString = "",
onChange,
bindings = defaultBindings,
environment,
jsImports = defaultImports,
}: SquigglePartialProps) => {
const [expression, setExpression] = React.useState(initialSquiggleString);
const [error, setError] = React.useState<string | null>(null);
const [code, setCode] = useState(initialSquiggleString);
const runSquiggleAndUpdateBindings = () => {
const squiggleResult = runPartial(
expression,
const { result, observableRef } = useSquigglePartial({
code,
bindings,
environment,
jsImports
);
if (squiggleResult.tag === "Ok") {
if (onChange) onChange(squiggleResult.value);
setError(null);
} else {
setError(errorValueToString(squiggleResult.value));
}
};
React.useEffect(runSquiggleAndUpdateBindings, [expression]);
jsImports,
onChange,
});
return (
<div ref={observableRef}>
<SquiggleContainer>
<div>
<div className="border border-grey-200 p-2 m-4">
<CodeEditor
value={expression}
onChange={setExpression}
oneLine={true}
showGutter={false}
height={20}
/>
</div>
{error !== null ? (
<ErrorAlert heading="Error">{error}</ErrorAlert>
<WrappedCodeEditor code={code} setCode={setCode} />
{result.tag !== "Ok" ? (
<SquiggleErrorAlert error={result.value} />
) : null}
</div>
</SquiggleContainer>
</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
);
const parent = document.createElement("div");
ReactDOM.render(<SquigglePartial {...props} />, parent);
return parent;
}

View File

@ -0,0 +1,11 @@
import { errorValue, errorValueToString } from "@quri/squiggle-lang";
import React from "react";
import { ErrorAlert } from "./Alert";
type Props = {
error: errorValue;
};
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
return <ErrorAlert heading="Error">{errorValueToString(error)}</ErrorAlert>;
};

View File

@ -0,0 +1,262 @@
import * as React from "react";
import {
squiggleExpression,
environment,
declaration,
} from "@quri/squiggle-lang";
import { NumberShower } from "./NumberShower";
import { DistributionChart } from "./DistributionChart";
import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
function getRange<a>(x: declaration<a>) {
const first = x.args[0];
switch (first.tag) {
case "Float": {
return { floats: { min: first.value.min, max: first.value.max } };
}
case "Date": {
return { time: { min: first.value.min, max: first.value.max } };
}
}
}
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
const range = getRange(x);
const min = range.floats ? range.floats.min : 0;
const max = range.floats ? range.floats.max : 10;
return {
start: min,
stop: max,
count: 20,
};
}
interface VariableBoxProps {
heading: string;
children: React.ReactNode;
showTypes: boolean;
}
export const VariableBox: React.FC<VariableBoxProps> = ({
heading = "Error",
children,
showTypes = false,
}) => {
if (showTypes) {
return (
<div className="bg-white border border-grey-200 m-2">
<div className="border-b border-grey-200 p-3">
<header className="font-mono">{heading}</header>
</div>
<div className="p-3">{children}</div>
</div>
);
} else {
return <div>{children}</div>;
}
};
export interface SquiggleItemProps {
/** The input string for squiggle */
expression: squiggleExpression;
width?: number;
height: number;
/** Whether to show a summary of statistics for distributions */
showSummary: boolean;
/** Whether to show type information */
showTypes: boolean;
/** Whether to show users graph controls (scale etc) */
showControls: boolean;
/** Settings for displaying functions */
chartSettings: FunctionChartSettings;
/** Environment for further function executions */
environment: environment;
}
export const SquiggleItem: React.FC<SquiggleItemProps> = ({
expression,
width,
height,
showSummary,
showTypes = false,
showControls = false,
chartSettings,
environment,
}) => {
switch (expression.tag) {
case "number":
return (
<VariableBox heading="Number" showTypes={showTypes}>
<div className="font-semibold text-slate-600">
<NumberShower precision={3} number={expression.value} />
</div>
</VariableBox>
);
case "distribution": {
const distType = expression.value.type();
return (
<VariableBox
heading={`Distribution (${distType})`}
showTypes={showTypes}
>
{distType === "Symbolic" && showTypes ? (
<div>{expression.value.toString()}</div>
) : null}
<DistributionChart
distribution={expression.value}
height={height}
width={width}
showSummary={showSummary}
showControls={showControls}
/>
</VariableBox>
);
}
case "string":
return (
<VariableBox heading="String" showTypes={showTypes}>
<span className="text-slate-400">"</span>
<span className="text-slate-600 font-semibold">
{expression.value}
</span>
<span className="text-slate-400">"</span>
</VariableBox>
);
case "boolean":
return (
<VariableBox heading="Boolean" showTypes={showTypes}>
{expression.value.toString()}
</VariableBox>
);
case "symbol":
return (
<VariableBox heading="Symbol" showTypes={showTypes}>
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
<span className="text-slate-600">{expression.value}</span>
</VariableBox>
);
case "call":
return (
<VariableBox heading="Call" showTypes={showTypes}>
{expression.value}
</VariableBox>
);
case "array":
return (
<VariableBox heading="Array" showTypes={showTypes}>
{expression.value.map((r, i) => (
<div key={i} className="flex pt-1">
<div className="flex-none bg-slate-100 rounded-sm px-1">
<header className="text-slate-400 font-mono">{i}</header>
</div>
<div className="px-2 mb-2 grow">
<SquiggleItem
key={i}
expression={r}
width={width !== undefined ? width - 20 : width}
height={50}
showTypes={showTypes}
showControls={showControls}
chartSettings={chartSettings}
environment={environment}
showSummary={showSummary}
/>
</div>
</div>
))}
</VariableBox>
);
case "record":
return (
<VariableBox heading="Record" showTypes={showTypes}>
<div className="space-y-3">
{Object.entries(expression.value).map(([key, r]) => (
<div key={key} className="flex space-x-2">
<div className="flex-none">
<header className="text-slate-500 font-mono">{key}:</header>
</div>
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
<SquiggleItem
expression={r}
width={width !== undefined ? width - 20 : width}
height={height / 3}
showTypes={showTypes}
showSummary={showSummary}
showControls={showControls}
chartSettings={chartSettings}
environment={environment}
/>
</div>
</div>
))}
</div>
</VariableBox>
);
case "arraystring":
return (
<VariableBox heading="Array String" showTypes={showTypes}>
{expression.value.map((r) => `"${r}"`).join(", ")}
</VariableBox>
);
case "date":
return (
<VariableBox heading="Date" showTypes={showTypes}>
{expression.value.toDateString()}
</VariableBox>
);
case "timeDuration": {
return (
<VariableBox heading="Time Duration" showTypes={showTypes}>
<NumberShower precision={3} number={expression.value} />
</VariableBox>
);
}
case "lambda":
return (
<VariableBox heading="Function" showTypes={showTypes}>
<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.parameters.join(
","
)})`}</div>
<FunctionChart
fn={expression.value}
chartSettings={chartSettings}
height={height}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
</VariableBox>
);
case "lambdaDeclaration": {
return (
<VariableBox heading="Function Declaration" showTypes={showTypes}>
<FunctionChart
fn={expression.value.fn}
chartSettings={getChartSettings(expression.value)}
height={height}
environment={{
sampleCount: environment.sampleCount / 10,
xyPointLength: environment.xyPointLength / 10,
}}
/>
</VariableBox>
);
}
case "module": {
return (
<VariableBox heading="Module" showTypes={showTypes}>
<span className="text-slate-600 font-semibold">Internal Module</span>
</VariableBox>
);
}
default: {
return (
<div>
<span>No display for type: </span>{" "}
<span className="font-semibold text-slate-600">{expression.tag}</span>
</div>
);
}
}
};

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";
@ -9,6 +9,7 @@ import {
CodeIcon,
CogIcon,
CurrencyDollarIcon,
EyeIcon,
} from "@heroicons/react/solid";
import clsx from "clsx";
@ -31,6 +32,12 @@ interface PlaygroundProps {
showControls?: boolean;
/** Whether to show the summary table in the playground */
showSummary?: boolean;
/** If code is set, component becomes controlled */
code?: string;
onCodeChange?(expr: string): void;
onSettingsChange?(settings: any): void;
/** Should we show the editor? */
showEditor?: boolean;
}
const schema = yup
@ -64,6 +71,7 @@ const schema = yup
showTypes: yup.boolean(),
showControls: yup.boolean(),
showSummary: yup.boolean(),
showEditor: yup.boolean(),
showSettingsPage: yup.boolean().default(false),
diagramStart: yup
.number()
@ -162,7 +170,7 @@ function InputItem<T>({
<input
type={type}
{...register(name)}
className="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
className="form-input max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
/>
</label>
);
@ -182,7 +190,7 @@ function Checkbox<T>({
<input
type="checkbox"
{...register(name)}
className="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
<div className="ml-3 text-sm font-medium text-gray-700">{label}</div>
@ -190,14 +198,20 @@ function Checkbox<T>({
);
}
const SquigglePlayground: FC<PlaygroundProps> = ({
export const SquigglePlayground: FC<PlaygroundProps> = ({
initialSquiggleString = "",
height = 500,
showTypes = false,
showControls = false,
showSummary = false,
code: controlledCode,
onCodeChange,
onSettingsChange,
showEditor = true,
}) => {
const [squiggleString, setSquiggleString] = useState(initialSquiggleString);
const [uncontrolledCode, setUncontrolledCode] = useState(
initialSquiggleString
);
const [importString, setImportString] = useState("{}");
const [imports, setImports] = useState({});
const [importsAreValid, setImportsAreValid] = useState(true);
@ -210,6 +224,7 @@ const SquigglePlayground: FC<PlaygroundProps> = ({
showTypes: showTypes,
showControls: showControls,
showSummary: showSummary,
showEditor: showEditor,
leftSizePercent: 50,
showSettingsPage: false,
diagramStart: 0,
@ -220,6 +235,11 @@ const SquigglePlayground: FC<PlaygroundProps> = ({
const vars = useWatch({
control,
});
useEffect(() => {
onSettingsChange?.(vars);
}, [vars, onSettingsChange]);
const chartSettings = {
start: Number(vars.diagramStart),
stop: Number(vars.diagramStop),
@ -239,6 +259,8 @@ const SquigglePlayground: FC<PlaygroundProps> = ({
}
};
const code = controlledCode ?? uncontrolledCode;
const samplingSettings = (
<div className="space-y-6 p-3 max-w-xl">
<div>
@ -276,6 +298,11 @@ const SquigglePlayground: FC<PlaygroundProps> = ({
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
<HeadedSection title="General Display Settings">
<div className="space-y-4">
<Checkbox
name="showEditor"
register={register}
label="Show code editor on left"
/>
<InputItem
name="chartHeight"
type="number"
@ -374,59 +401,78 @@ const SquigglePlayground: FC<PlaygroundProps> = ({
</div>
);
return (
<SquiggleContainer>
<Tab.Group>
<div className="pb-4">
<Tab.List className="flex w-fit p-0.5 rounded-md bg-slate-100 hover:bg-slate-200">
<StyledTab name="Code" icon={CodeIcon} />
<StyledTab name="Sampling Settings" icon={CogIcon} />
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
</Tab.List>
</div>
<div className="flex" style={{ height }}>
<div className="w-1/2">
<Tab.Panels>
<Tab.Panel>
<div className="border border-slate-200">
<CodeEditor
value={squiggleString}
onChange={setSquiggleString}
oneLine={false}
showGutter={true}
height={height - 1}
/>
</div>
</Tab.Panel>
<Tab.Panel>{samplingSettings}</Tab.Panel>
<Tab.Panel>{viewSettings}</Tab.Panel>
<Tab.Panel>{inputVariableSettings}</Tab.Panel>
</Tab.Panels>
</div>
<div className="w-1/2 p-2 pl-4">
<div style={{ maxHeight: height }}>
const squiggleChart = (
<SquiggleChart
squiggleString={squiggleString}
squiggleString={code}
environment={env}
chartSettings={chartSettings}
height={vars.chartHeight}
showTypes={vars.showTypes}
showControls={vars.showControls}
showSummary={vars.showSummary}
bindings={defaultBindings}
jsImports={imports}
showSummary={vars.showSummary}
/>
);
const firstTab = vars.showEditor ? (
<div className="border border-slate-200">
<CodeEditor
value={code}
onChange={(newCode) => {
if (controlledCode === undefined) {
// uncontrolled mode
setUncontrolledCode(newCode);
}
onCodeChange?.(newCode);
}}
oneLine={false}
showGutter={true}
height={height - 1}
/>
</div>
) : (
squiggleChart
);
const tabs = (
<Tab.Panels>
<Tab.Panel>{firstTab}</Tab.Panel>
<Tab.Panel>{samplingSettings}</Tab.Panel>
<Tab.Panel>{viewSettings}</Tab.Panel>
<Tab.Panel>{inputVariableSettings}</Tab.Panel>
</Tab.Panels>
);
const withEditor = (
<div className="flex mt-1">
<div className="w-1/2">{tabs}</div>
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
</div>
);
const withoutEditor = <div className="mt-3">{tabs}</div>;
return (
<SquiggleContainer>
<Tab.Group>
<div className="pb-4">
<Tab.List className="flex w-fit p-0.5 mt-2 rounded-md bg-slate-100 hover:bg-slate-200">
<StyledTab
name={vars.showEditor ? "Code" : "Display"}
icon={vars.showEditor ? CodeIcon : EyeIcon}
/>
<StyledTab name="Sampling Settings" icon={CogIcon} />
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
</Tab.List>
{vars.showEditor ? withEditor : withoutEditor}
</div>
</Tab.Group>
</SquiggleContainer>
);
};
export default SquigglePlayground;
export function renderSquigglePlaygroundToDom(props: PlaygroundProps) {
const parent = document.createElement("div");
ReactDOM.render(<SquigglePlayground {...props} />, parent);

View File

@ -6,7 +6,7 @@ export {
renderSquigglePartialToDom,
} from "./components/SquiggleEditor";
export {
default as SquigglePlayground,
SquigglePlayground,
renderSquigglePlaygroundToDom,
} from "./components/SquigglePlayground";
export { SquiggleContainer } from "./components/SquiggleContainer";

View File

@ -0,0 +1,63 @@
import {
bindings,
environment,
jsImports,
run,
runPartial,
} from "@quri/squiggle-lang";
import { useEffect, useMemo, useRef } from "react";
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = {
code: string;
bindings?: bindings;
jsImports?: jsImports;
environment?: environment;
onChange?: (expr: Extract<T, { tag: "Ok" }>["value"] | undefined) => void;
};
const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>(
args: SquiggleArgs<T>,
f: (...args: Parameters<typeof run>) => T
) => {
// We're using observable, where div elements can have a `value` property:
// https://observablehq.com/@observablehq/introduction-to-views
//
// This is here to get the 'viewof' part of:
// viewof env = cell('normal(0,1)')
// to work
const ref = useRef<
HTMLDivElement & { value?: Extract<T, { tag: "Ok" }>["value"] }
>(null);
const result: T = useMemo<T>(
() => f(args.code, args.bindings, args.environment, args.jsImports),
[f, args.code, args.bindings, args.environment, args.jsImports]
);
useEffect(() => {
if (!ref.current) return;
ref.current.value = result.tag === "Ok" ? result.value : undefined;
ref.current.dispatchEvent(new CustomEvent("input"));
}, [result]);
const { onChange } = args;
useEffect(() => {
onChange?.(result.tag === "Ok" ? result.value : undefined);
}, [result, onChange]);
return {
result, // squiggleExpression or externalBindings
observableRef: ref, // can be passed to outermost <div> if you want to use your component as an observablehq's view
};
};
export const useSquigglePartial = (
args: SquiggleArgs<ReturnType<typeof runPartial>>
) => {
return useSquiggleAny(args, runPartial);
};
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
return useSquiggleAny(args, run);
};

View File

@ -1,4 +1,4 @@
import SquigglePlayground from "../components/SquigglePlayground";
import { SquigglePlayground } from "../components/SquigglePlayground";
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />

View File

@ -1,5 +1,5 @@
.squiggle {
/*
/*
This file contains:
1) Base Tailwind preflight styles
2) Base https://github.com/tailwindlabs/tailwindcss-forms styles
@ -7,167 +7,192 @@ This file contains:
(Both are wrapped in .squiggle)
*/
/*
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
*/
/* html { */
/* html { */
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
tab-size: 4; /* 3 */
font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
/* } */
font-family: theme(
"fontFamily.sans",
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji"
); /* 4 */
/* } */
/*
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
/* body { */
/* body { */
margin: 0; /* 1 */
line-height: inherit; /* 2 */
/* } */
/* } */
/*
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
}
border-color: theme("borderColor.DEFAULT", currentColor); /* 2 */
}
::before,
::after {
--tw-content: '';
}
::before,
::after {
--tw-content: "";
}
/*
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
}
/*
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
abbr:where([title]) {
text-decoration: underline dotted;
}
}
/*
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
}
/*
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
a {
color: inherit;
text-decoration: inherit;
}
}
/*
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
b,
strong {
font-weight: bolder;
}
}
/*
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */
code,
kbd,
samp,
pre {
font-family: theme(
"fontFamily.mono",
ui-monospace,
SFMono-Regular,
Menlo,
Monaco,
Consolas,
"Liberation Mono",
"Courier New",
monospace
); /* 1 */
font-size: 1em; /* 2 */
}
}
/*
/*
Add the correct font size in all browsers.
*/
small {
small {
font-size: 80%;
}
}
/*
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
}
sub {
sub {
bottom: -0.25em;
}
}
sup {
sup {
top: -0.5em;
}
}
/*
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
}
}
/*
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
@ -175,336 +200,197 @@ textarea {
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
}
/*
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
button,
select {
text-transform: none;
}
}
/*
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button; /* 1 */
background-color: transparent; /* 2 */
background-image: none; /* 2 */
}
}
/*
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
:-moz-focusring {
outline: auto;
}
}
/*
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
:-moz-ui-invalid {
box-shadow: none;
}
}
/*
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
progress {
vertical-align: baseline;
}
}
/*
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
}
/*
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
}
/*
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
::-webkit-search-decoration {
-webkit-appearance: none;
}
}
/*
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
}
/*
/*
Add the correct display in Chrome and Safari.
*/
summary {
summary {
display: list-item;
}
}
/*
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
}
fieldset {
fieldset {
margin: 0;
padding: 0;
}
}
legend {
legend {
padding: 0;
}
}
ol,
ul,
menu {
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
}
/*
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
textarea {
resize: vertical;
}
}
/*
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::placeholder,
textarea::placeholder {
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
color: theme('colors.gray.400', #9ca3af); /* 2 */
}
color: theme("colors.gray.400", #9ca3af); /* 2 */
}
/*
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
button,
[role="button"] {
cursor: pointer;
}
}
/*
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
:disabled {
cursor: default;
}
}
/*
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
}
/*
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
img,
video {
max-width: 100%;
height: auto;
}
/* these styles were generated by tailwindcss-forms */
[type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select {
appearance: none;
background-color: #fff;
border-color: #6b7280;
border-width: 1px;
border-radius: 0px;
padding-top: 0.5rem;
padding-right: 0.75rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
font-size: 1rem;
line-height: 1.5rem;
--tw-shadow: 0 0 #0000;
}
[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus {
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: #2563eb;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
border-color: #2563eb;
}
input::placeholder,textarea::placeholder {
color: #6b7280;
opacity: 1;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-date-and-time-value {
min-height: 1.5em;
}
::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field {
padding-top: 0;
padding-bottom: 0;
}
select {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
[multiple] {
background-image: initial;
background-position: initial;
background-repeat: unset;
background-size: initial;
padding-right: 0.75rem;
-webkit-print-color-adjust: unset;
print-color-adjust: unset;
}
[type='checkbox'],[type='radio'] {
appearance: none;
padding: 0;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
display: inline-block;
vertical-align: middle;
background-origin: border-box;
-webkit-user-select: none;
user-select: none;
flex-shrink: 0;
height: 1rem;
width: 1rem;
color: #2563eb;
background-color: #fff;
border-color: #6b7280;
border-width: 1px;
--tw-shadow: 0 0 #0000;
}
[type='checkbox'] {
border-radius: 0px;
}
[type='radio'] {
border-radius: 100%;
}
[type='checkbox']:focus,[type='radio']:focus {
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
--tw-ring-offset-width: 2px;
--tw-ring-offset-color: #fff;
--tw-ring-color: #2563eb;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
[type='checkbox']:checked,[type='radio']:checked {
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
[type='checkbox']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
[type='radio']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
}
[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus {
border-color: transparent;
background-color: currentColor;
}
[type='checkbox']:indeterminate {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus {
border-color: transparent;
background-color: currentColor;
}
[type='file'] {
background: unset;
border-color: inherit;
border-width: 0;
border-radius: 0;
padding: 0;
font-size: unset;
line-height: inherit;
}
[type='file']:focus {
outline: 1px solid ButtonText;
outline: 1px auto -webkit-focus-ring-color;
}
}
}

View File

@ -0,0 +1,130 @@
/* Fork of https://github.com/tailwindlabs/tailwindcss-forms styles, see the comment in main.css for details. */
.squiggle {
.form-input,
.form-textarea,
.form-select,
.form-multiselect {
appearance: none;
background-color: #fff;
border-color: #6b7280;
border-width: 1px;
border-radius: 0px;
padding-top: 0.5rem;
padding-right: 0.75rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
font-size: 1rem;
line-height: 1.5rem;
--tw-shadow: 0 0 #0000;
}
.form-input:focus,
.form-textarea:focus,
.form-select:focus,
.form-multiselect:focus {
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: #2563eb;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0
calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
border-color: #2563eb;
}
.form-input::placeholder,
.form-textarea::placeholder {
color: #6b7280;
opacity: 1;
}
.form-input::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
.form-input::-webkit-date-and-time-value {
min-height: 1.5em;
}
.form-input::-webkit-datetime-edit,
.form-input::-webkit-datetime-edit-year-field,
.form-input::-webkit-datetime-edit-month-field,
.form-input::-webkit-datetime-edit-day-field,
.form-input::-webkit-datetime-edit-hour-field,
.form-input::-webkit-datetime-edit-minute-field,
.form-input::-webkit-datetime-edit-second-field,
.form-input::-webkit-datetime-edit-millisecond-field,
.form-input::-webkit-datetime-edit-meridiem-field {
padding-top: 0;
padding-bottom: 0;
}
.form-checkbox,
.form-radio {
appearance: none;
padding: 0;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
display: inline-block;
vertical-align: middle;
background-origin: border-box;
-webkit-user-select: none;
user-select: none;
flex-shrink: 0;
height: 1rem;
width: 1rem;
color: #2563eb;
background-color: #fff;
border-color: #6b7280;
border-width: 1px;
--tw-shadow: 0 0 #0000;
}
.form-checkbox {
border-radius: 0px;
}
.form-checkbox:focus,
.form-radio:focus {
outline: 2px solid transparent;
outline-offset: 2px;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 2px;
--tw-ring-offset-color: #fff;
--tw-ring-color: #2563eb;
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0
var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0
calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow),
var(--tw-shadow);
}
.form-checkbox:checked,
.form-radio:checked {
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.form-checkbox:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
}
.form-checkbox:checked:hover,
.form-checkbox:checked:focus,
.form-radio:checked:hover,
.form-radio:checked:focus {
border-color: transparent;
background-color: currentColor;
}
.form-checkbox:indeterminate {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
border-color: transparent;
background-color: currentColor;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.form-checkbox:indeterminate:hover,
.form-checkbox:indeterminate:focus {
border-color: transparent;
background-color: currentColor;
}
}

View File

@ -1,8 +1,24 @@
/*
Fork of tailwind's preflight with `.squiggle` scoping.
*/
@import "./base.css";
/*
Fork of https://github.com/tailwindlabs/tailwindcss-forms styles (with strategy: "class"), but with `.squiggle` scoping.
This is necessary because tailwindcss-forms's css specificity is lower than preflight's specificity (`padding: 0` and so on),
so unscoped forms css with scoped preflight css, and there's no setting for scoping.
*/
@import "./forms.css";
/*
This doesn't include tailwind's original preflight (it's disabled in tailwind.config.js),
but this line is still necessary for proper initialization of `--tw-*` variables.
*/
@tailwind base;
@tailwind components;
@tailwind utilities;
/* necessary hack because scoped preflight in ./base.css has higher specificity */
/* Necessary hack because scoped preflight in ./base.css has higher specificity. */
.ace_cursor {
border-left: 2px solid !important;
}

View File

@ -1,8 +1,10 @@
module.exports = {
content: ["./src/**/*.{html,tsx,ts,js,jsx}"],
corePlugins: {
preflight: false,
},
important: ".squiggle",
theme: {
extend: {},
},
important: ".squiggle",
plugins: [require("@tailwindcss/forms")],
};

View File

@ -1,11 +1,10 @@
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
mode: "production",
devtool: "source-map",
profile: true,
entry: ["./src/index.ts", "./src/styles/main.css"],
entry: "./src/index.ts",
module: {
rules: [
{
@ -14,13 +13,8 @@ module.exports = {
options: { projectReferences: true },
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
},
],
},
plugins: [new MiniCssExtractPlugin()],
resolve: {
extensions: [".js", ".tsx", ".ts"],
alias: {
@ -42,4 +36,18 @@ module.exports = {
compress: true,
port: 9000,
},
externals: {
react: {
commonjs: "react",
commonjs2: "react",
amd: "react",
root: "React",
},
"react-dom": {
commonjs: "react-dom",
commonjs2: "react-dom",
amd: "react-dom",
root: "ReactDOM",
},
},
};

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

@ -1,6 +1,6 @@
{
"name": "@quri/squiggle-lang",
"version": "0.2.9",
"version": "0.2.11",
"homepage": "https://squiggle-language.com",
"license": "MIT",
"scripts": {
@ -39,6 +39,7 @@
"dependencies": {
"@stdlib/stats": "^0.0.13",
"jstat": "^1.9.5",
"lodash": "^4.17.21",
"mathjs": "^10.6.0",
"pdfast": "^0.2.0",
"rescript": "^9.1.4"
@ -51,10 +52,9 @@
"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",
"lodash": "^4.17.21",
"moduleserve": "^0.9.1",
"nyc": "^15.1.0",
"peggy": "^2.0.1",
@ -63,7 +63,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

@ -182,6 +182,11 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
)
->E.R2.fmap(r => Dist(r))
->OutputLocal.fromResult
| ToDist(Scale(#Multiply, f)) =>
dist
->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~algebraicCombination=#Multiply, ~f)
->E.R2.fmap(r => Dist(r))
->OutputLocal.fromResult
| ToDist(Scale(#Logarithm, f)) =>
dist
->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~algebraicCombination=#Logarithm, ~f)
@ -298,6 +303,7 @@ module Constructors = {
let algebraicLogarithm = (~env, dist1, dist2) =>
C.algebraicLogarithm(dist1, dist2)->run(~env)->toDistR
let algebraicPower = (~env, dist1, dist2) => C.algebraicPower(dist1, dist2)->run(~env)->toDistR
let scaleMultiply = (~env, dist, n) => C.scaleMultiply(dist, n)->run(~env)->toDistR
let scalePower = (~env, dist, n) => C.scalePower(dist, n)->run(~env)->toDistR
let scaleLogarithm = (~env, dist, n) => C.scaleLogarithm(dist, n)->run(~env)->toDistR
let pointwiseAdd = (~env, dist1, dist2) => C.pointwiseAdd(dist1, dist2)->run(~env)->toDistR

View File

@ -102,6 +102,8 @@ module Constructors: {
@genType
let scaleLogarithm: (~env: env, genericDist, float) => result<genericDist, error>
@genType
let scaleMultiply: (~env: env, genericDist, float) => result<genericDist, error>
@genType
let scalePower: (~env: env, genericDist, float) => result<genericDist, error>
@genType
let pointwiseAdd: (~env: env, genericDist, genericDist) => result<genericDist, error>

View File

@ -76,6 +76,7 @@ module DistributionOperation = {
]
type toScaleFn = [
| #Multiply
| #Power
| #Logarithm
| #LogarithmWithThreshold(float)
@ -138,11 +139,12 @@ module DistributionOperation = {
| ToDist(Truncate(_, _)) => `truncate`
| ToDist(Inspect) => `inspect`
| ToDist(Scale(#Power, r)) => `scalePower(${E.Float.toFixed(r)})`
| ToDist(Scale(#Multiply, r)) => `scaleMultiply(${E.Float.toFixed(r)})`
| ToDist(Scale(#Logarithm, r)) => `scaleLog(${E.Float.toFixed(r)})`
| ToDist(Scale(#LogarithmWithThreshold(eps), r)) =>
`scaleLogWithThreshold(${E.Float.toFixed(r)}, epsilon=${E.Float.toFixed(eps)})`
| ToString(ToString) => `toString`
| ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})`
| ToString(ToSparkline(n)) => `sparkline(${E.I.toString(n)})`
| ToBool(IsNormalized) => `isNormalized`
| ToDistCombination(Algebraic(_), _, _) => `algebraic`
| ToDistCombination(Pointwise, _, _) => `pointwise`
@ -179,6 +181,7 @@ module Constructors = {
ToScore(LogScore(answer, prior)),
prediction,
)
let scaleMultiply = (dist, n): t => FromDist(ToDist(Scale(#Multiply, n)), dist)
let scalePower = (dist, n): t => FromDist(ToDist(Scale(#Power, n)), dist)
let scaleLogarithm = (dist, n): t => FromDist(ToDist(Scale(#Logarithm, n)), dist)
let scaleLogarithmWithThreshold = (dist, n, eps): t => FromDist(

View File

@ -214,7 +214,9 @@ module Truncate = {
| Some(r) => Ok(r)
| None =>
toPointSetFn(t)->E.R2.fmap(t => {
DistributionTypes.PointSet(PointSetDist.T.truncate(leftCutoff, rightCutoff, t))
DistributionTypes.PointSet(
PointSetDist.T.truncate(leftCutoff, rightCutoff, t)->PointSetDist.T.normalize,
)
})
}
}

View File

@ -295,7 +295,7 @@ module Float = {
let inv = (p, t: t) => p < t ? 0.0 : 1.0
let mean = (t: t) => Ok(t)
let sample = (t: t) => t
let toString = (t: t) => j`Delta($t)`
let toString = (t: t) => j`PointMass($t)`
let toPointSetDist = (t: t): PointSetTypes.pointSetDist => Discrete(
Discrete.make(~integralSumCache=Some(1.0), {xs: [t], ys: [1.0]}),
)

View File

@ -221,9 +221,9 @@ let dispatchToGenericOutput = (
}
| ("integralSum", [IevDistribution(dist)]) => Helpers.toFloatFn(#IntegralSum, dist, ~env)
| ("toString", [IevDistribution(dist)]) => Helpers.toStringFn(ToString, dist, ~env)
| ("toSparkline", [IevDistribution(dist)]) =>
| ("sparkline", [IevDistribution(dist)]) =>
Helpers.toStringFn(ToSparkline(MagicNumbers.Environment.sparklineLength), dist, ~env)
| ("toSparkline", [IevDistribution(dist), IevNumber(n)]) =>
| ("sparkline", [IevDistribution(dist), IevNumber(n)]) =>
Helpers.toStringFn(ToSparkline(Belt.Float.toInt(n)), dist, ~env)
| ("exp", [IevDistribution(a)]) =>
// https://mathjs.org/docs/reference/functions/exp.html
@ -273,6 +273,8 @@ let dispatchToGenericOutput = (
Helpers.toDistFn(Scale(#Logarithm, float), dist, ~env)
| ("scaleLogWithThreshold", [IevDistribution(dist), IevNumber(base), IevNumber(eps)]) =>
Helpers.toDistFn(Scale(#LogarithmWithThreshold(eps), base), dist, ~env)
| ("scaleMultiply", [IevDistribution(dist), IevNumber(float)]) =>
Helpers.toDistFn(Scale(#Multiply, float), dist, ~env)
| ("scalePow", [IevDistribution(dist), IevNumber(float)]) =>
Helpers.toDistFn(Scale(#Power, float), dist, ~env)
| ("scaleExp", [IevDistribution(dist)]) =>
@ -280,6 +282,8 @@ let dispatchToGenericOutput = (
| ("cdf", [IevDistribution(dist), IevNumber(float)]) => Helpers.toFloatFn(#Cdf(float), dist, ~env)
| ("pdf", [IevDistribution(dist), IevNumber(float)]) => Helpers.toFloatFn(#Pdf(float), dist, ~env)
| ("inv", [IevDistribution(dist), IevNumber(float)]) => Helpers.toFloatFn(#Inv(float), dist, ~env)
| ("quantile", [IevDistribution(dist), IevNumber(float)]) =>
Helpers.toFloatFn(#Inv(float), dist, ~env)
| ("toSampleSet", [IevDistribution(dist), IevNumber(float)]) =>
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist, ~env)
| ("toSampleSet", [IevDistribution(dist)]) =>

View File

@ -0,0 +1,18 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/naming-convention": "warn",
"@typescript-eslint/semi": "warn",
"curly": "warn",
"eqeqeq": "warn",
"no-throw-literal": "warn",
"semi": "off"
},
"ignorePatterns": ["out", "dist", "**/*.d.ts"]
}

4
packages/vscode-ext/.gitignore vendored Normal file
View File

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

View File

@ -0,0 +1,5 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": ["dbaeumer.vscode-eslint"]
}

28
packages/vscode-ext/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,28 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "${defaultBuildTask}"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
],
"outFiles": ["${workspaceFolder}/out/test/**/*.js"],
"preLaunchTask": "${defaultBuildTask}"
}
]
}

View File

@ -0,0 +1,11 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}

20
packages/vscode-ext/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,10 @@
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts

View File

@ -0,0 +1 @@
--ignore-engines true

View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2020 Foretold
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,22 @@
# Squiggle For VS Code
_[marketplace](https://marketplace.visualstudio.com/items?itemName=QURI.vscode-squiggle)_
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.
Then, simply `yarn compile` at the `vscode-ext` package level. It will build `squiggle-lang`, `squiggle-components`, and the VS Code extension source code.

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

@ -0,0 +1,41 @@
// based on https://github.com/microsoft/vscode-extension-samples/blob/main/custom-editor-sample/media/catScratch.js
(function () {
const vscode = acquireVsCodeApi();
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
function updateContent(text) {
root.render(
React.createElement(squiggle_components.SquigglePlayground, {
code: text,
onCodeChange: (code) => {
vscode.postMessage({ type: "edit", text: code });
},
})
);
}
// 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 = message.text;
// Update our webview's content
updateContent(text);
// Then persist state information.
// This state is returned in the call to `vscode.getState` below when a webview is reloaded.
vscode.setState({ text });
return;
}
});
const state = vscode.getState();
if (state) {
updateContent(state.text);
}
})();

View File

View File

@ -0,0 +1,147 @@
{
"name": "vscode-squiggle",
"displayName": "Squiggle",
"description": "Squiggle language support",
"license": "MIT",
"version": "0.1.2",
"publisher": "QURI",
"repository": {
"type": "git",
"url": "git+https://github.com/quantified-uncertainty/squiggle.git"
},
"icon": "media/vendor/icon.png",
"engines": {
"vscode": "^1.68.0"
},
"categories": [
"Programming Languages",
"Visualization"
],
"activationEvents": [
"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",
"displayName": "Squiggle WYSIWYG",
"selector": [
{
"filenamePattern": "*.squiggle"
}
],
"priority": "option"
}
],
"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: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/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",
"js-yaml": "^4.1.0",
"typescript": "^4.7.4"
}
}

View File

@ -0,0 +1,88 @@
import * as vscode from "vscode";
import { getWebviewContent } from "./utils";
export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
public static register(context: vscode.ExtensionContext): vscode.Disposable {
const provider = new SquiggleEditorProvider(context);
const providerRegistration = vscode.window.registerCustomEditorProvider(
SquiggleEditorProvider.viewType,
provider
);
return providerRegistration;
}
private static readonly viewType = "squiggle.wysiwyg";
constructor(private readonly context: vscode.ExtensionContext) {}
/**
* Called when our custom editor is opened.
*/
public async resolveCustomTextEditor(
document: vscode.TextDocument,
webviewPanel: vscode.WebviewPanel
): Promise<void> {
// Setup initial content for the webview
webviewPanel.webview.options = {
enableScripts: true,
};
webviewPanel.webview.html = getWebviewContent({
webview: webviewPanel.webview,
script: "media/wysiwygWebview.js",
title: "Squiggle Editor",
context: this.context,
});
function updateWebview() {
webviewPanel.webview.postMessage({
type: "update",
text: document.getText(),
});
}
// Hook up event handlers so that we can synchronize the webview with the text document.
//
// The text document acts as our model, so we have to sync change in the document to our
// editor and sync changes in the editor back to the document.
//
// Remember that a single text document can also be shared between multiple custom
// editors (this happens for example when you split a custom editor)
const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(
(e) => {
if (e.document.uri.toString() === document.uri.toString()) {
updateWebview();
}
}
);
// Make sure we get rid of the listener when our editor is closed.
webviewPanel.onDidDispose(() => {
changeDocumentSubscription.dispose();
});
// Receive message from the webview.
webviewPanel.webview.onDidReceiveMessage((e) => {
switch (e.type) {
case "edit":
this.updateTextDocument(document, e.text);
return;
}
});
updateWebview();
}
private updateTextDocument(document: vscode.TextDocument, text: string) {
const edit = new vscode.WorkspaceEdit();
// Just replace the entire document every time.
edit.replace(
document.uri,
new vscode.Range(0, 0, document.lineCount, 0),
text
);
return vscode.workspace.applyEdit(edit);
}
}

View File

@ -0,0 +1,17 @@
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode";
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
export function deactivate() {}

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

@ -0,0 +1,15 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "out",
"lib": ["ES2020", "dom"],
"sourceMap": true,
"rootDir": "src",
"strict": true /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,3 @@
.docusaurus
build
docs/Api/.*

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

@ -3,8 +3,12 @@ sidebar_position: 1
title: Date
---
Squiggle date types are a very simple implementation on [Javascript's Date type](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date). It's mainly here for early experimentation. There are more relevant functions for the [Duration](/docs/Api/Duration) type.
### makeFromYear
(Now `makeDateFromYear`)
```
Date.makeFromYear: (number) => date
```
@ -19,6 +23,16 @@ makeFromYear(2022.32);
toString: (date) => string
```
### add
```
add: (date, duration) => date
```
```js
makeFromYear(2022.32) + years(5);
```
### subtract
```
@ -30,13 +44,3 @@ subtract: (date, duration) => date
makeFromYear(2040) - makeFromYear(2020); // 20 years
makeFromYear(2040) - years(20); // 2020
```
### add
```
add: (date, duration) => date
```
```js
makeFromYear(2022.32) + years(5);
```

View File

@ -3,6 +3,34 @@ sidebar_position: 2
title: Dictionary
---
Squiggle dictionaries work similar to Python dictionaries. The syntax is similar to objects in Javascript.
Dictionaries are unordered and duplicates are not allowed. They are meant to be immutable, like most types in Squiggle.
**Example**
```javascript
valueFromOfficeItems = {
keyboard: 1,
chair: 0.01 to 0.5,
headphones: "ToDo"
}
valueFromHomeItems = {
monitor: 1,
bed: 0.2 to 0.6,
lights: 0.02 to 0.2,
coffee: 5 to 20
}
homeToItemsConversion = 0.1 to 0.4
conversionFn(i) = [i[0], i[1] * homeToItemsConversion]
updatedValueFromHomeItems = valueFromHomeItems |> Dict.toList |> map(conversionFn) |> Dict.fromList
allItems = merge(valueFromOfficeItems, updatedValueFromHomeItems)
```
### toList
```

View File

@ -3,50 +3,47 @@ sidebar_position: 3
title: Distribution
---
import Admonition from "@theme/Admonition";
import TOCInline from "@theme/TOCInline";
Distributions are the flagship data type in Squiggle. The distribution type is a generic data type that contains one of three different formats of distributions.
These subtypes are [point set](/docs/Api/DistPointSet), [sample set](/docs/Api/DistSampleSet), and symbolic. The first two of these have a few custom functions that only work on them. You can read more about the differences between these formats [here](/docs/Discussions/Three-Formats-Of-Distributions).
Several functions below only can work on particular distribution formats.
For example, scoring and pointwise math requires the point set format. When this happens, the types are automatically converted to the correct format. These conversions are lossy.
<TOCInline toc={toc} />
## Distribution Creation
### Normal Distribution
These are functions for creating primative distributions. Many of these could optionally take in distributions as inputs. In these cases, Monte Carlo Sampling will be used to generate the greater distribution. This can be used for simple hierarchical models.
**Definitions**
See a longer tutorial on creating distributions [here](/docs/Guides/DistributionCreation).
### normal
```javascript
normal: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
```
```javascript
normal: (dict<{p5: frValueDistOrNumber, p95: frValueDistOrNumber}>) => distribution
```
```javascript
normal: (dict<{mean: frValueDistOrNumber, stdev: frValueDistOrNumber}>) => distribution
normal: (distribution|number, distribution|number) => distribution
normal: (dict<{p5: distribution|number, p95: distribution|number}>) => distribution
normal: (dict<{mean: distribution|number, stdev: distribution|number}>) => distribution
```
**Examples**
```js
normal(5, 1);
normal({ p5: 4, p95: 10 });
normal({ mean: 5, stdev: 2 });
normal(5, 1)
normal({ p5: 4, p95: 10 })
normal({ mean: 5, stdev: 2 })
normal(5 to 10, normal(3, 2))
normal({ mean: uniform(5, 9), stdev: 3 })
```
### Lognormal Distribution
### lognormal
**Definitions**
```javascript
lognormal: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
```
```javascript
lognormal: (dict<{p5: frValueDistOrNumber, p95: frValueDistOrNumber}>) => distribution
```
```javascript
lognormal: (dict<{mean: frValueDistOrNumber, stdev: frValueDistOrNumber}>) => distribution
lognormal: (distribution|number, distribution|number) => distribution
lognormal: (dict<{p5: distribution|number, p95: distribution|number}>) => distribution
lognormal: (dict<{mean: distribution|number, stdev: distribution|number}>) => distribution
```
**Examples**
@ -57,12 +54,10 @@ lognormal({ p5: 4, p95: 10 });
lognormal({ mean: 5, stdev: 2 });
```
### Uniform Distribution
### uniform
**Definitions**
```javascript
uniform: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
```
uniform: (distribution|number, distribution|number) => distribution
```
**Examples**
@ -71,12 +66,10 @@ uniform: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
uniform(10, 12);
```
### Beta Distribution
### beta
**Definitions**
```javascript
beta: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
```
beta: (distribution|number, distribution|number) => distribution
```
**Examples**
@ -85,12 +78,10 @@ beta: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
beta(20, 25);
```
### Cauchy Distribution
### cauchy
**Definitions**
```javascript
cauchy: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
```
cauchy: (distribution|number, distribution|number) => distribution
```
**Examples**
@ -99,12 +90,22 @@ cauchy: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
cauchy(5, 1);
```
### Gamma Distribution
**Definitions**
### gamma
```javascript
gamma: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
gamma: (distribution|number, distribution|number) => distribution
```
**Examples**
```js
gamma(5, 1);
```
### logistic
```
logistic: (distribution|number, distribution|number) => distribution
```
**Examples**
@ -113,30 +114,53 @@ gamma: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
gamma(5, 1);
```
### Logistic Distribution
### exponential
**Definitions**
```javascript
logistic: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
```
exponential: (distribution|number) => distribution
```
**Examples**
```javascript
gamma(5, 1);
exponential(2);
```
### To (Distribution)
### bernoulli
**Definitions**
```javascript
to: (frValueDistOrNumber, frValueDistOrNumber) => distribution;
```
bernoulli: (distribution|number) => distribution
```
**Examples**
```javascript
credibleIntervalToDistribution(frValueDistOrNumber, frValueDistOrNumber) => distribution;
bernoulli(0.5);
```
### triangular
```javascript
triangular: (number, number, number) => distribution;
```
**Examples**
```javascript
triangular(5, 10, 20);
```
### to / credibleIntervalToDistribution
The `to` function is an easy way to generate simple distributions using predicted _5th_ and _95th_ percentiles.
If both values are above zero, a `lognormal` distribution is used. If not, a `normal` distribution is used.
`To` is an alias for `credibleIntervalToDistribution`. However, because of its frequent use, it is recommended to use the shorter name.
```
to: (distribution|number, distribution|number) => distribution
credibleIntervalToDistribution(distribution|number, distribution|number) => distribution
```
**Examples**
@ -147,97 +171,29 @@ to(5,10)
-5 to 5
```
### Exponential
### mixture
**Definitions**
```javascript
exponential: (frValueDistOrNumber) => distribution;
```
mixture: (...distributionLike, weights?:list<float>) => distribution
mixture: (list<distributionLike>, weights?:list<float>) => distribution
```
**Examples**
```javascript
exponential(2);
```
### Bernoulli
**Definitions**
```javascript
bernoulli: (frValueDistOrNumber) => distribution;
```
**Examples**
```javascript
bernoulli(0.5);
```
### toContinuousPointSet
Converts a set of points to a continuous distribution
**Definitions**
```javascript
toContinuousPointSet: (array<dict<{x: numeric, y: numeric}>>) => distribution
```
**Examples**
```javascript
toContinuousPointSet([
{ x: 0, y: 0.1 },
{ x: 1, y: 0.2 },
{ x: 2, y: 0.15 },
{ x: 3, y: 0.1 },
]);
```
### toDiscretePointSet
Converts a set of points to a discrete distribution
**Definitions**
```javascript
toDiscretePointSet: (array<dict<{x: numeric, y: numeric}>>) => distribution
```
**Examples**
```javascript
toDiscretePointSet([
{ x: 0, y: 0.1 },
{ x: 1, y: 0.2 },
{ x: 2, y: 0.15 },
{ x: 3, y: 0.1 },
]);
mixture(normal(5, 1), normal(10, 1), 8);
mx(normal(5, 1), normal(10, 1), [0.3, 0.7]);
mx([normal(5, 1), normal(10, 1)], [0.3, 0.7]);
```
## Functions
### mixture
```javascript
mixture: (...distributionLike, weights:list<float>) => distribution
```
**Examples**
```javascript
mixture(normal(5, 1), normal(10, 1));
mx(normal(5, 1), normal(10, 1), [0.3, 0.7]);
```
### sample
Get one random sample from the distribution
One random sample from the distribution
```javascript
sample(distribution) => number
```
sample: (distribution) => number
```
**Examples**
@ -248,66 +204,70 @@ sample(normal(5, 2));
### sampleN
Get n random samples from the distribution
N random samples from the distribution
```javascript
```
sampleN: (distribution, number) => list<number>
```
**Examples**
```javascript
sample: normal(5, 2), 100;
sampleN(normal(5, 2), 100);
```
### mean
Get the distribution mean
The distribution mean
```javascript
mean: (distribution) => number;
```
mean: (distribution) => number
```
**Examples**
```javascript
mean: normal(5, 2);
mean(normal(5, 2));
```
### stdev
```javascript
stdev: (distribution) => number;
Standard deviation. Only works now on sample set distributions (so converts other distributions into sample set in order to calculate.)
```
stdev: (distribution) => number
```
### variance
```javascript
variance: (distribution) => number;
Variance. Similar to stdev, only works now on sample set distributions.
```
variance: (distribution) => number
```
### mode
```javascript
mode: (distribution) => number;
```
mode: (distribution) => number
```
### cdf
```javascript
cdf: (distribution, number) => number;
```
cdf: (distribution, number) => number
```
**Examples**
```javascript
cdf: normal(5, 2), 3;
cdf(normal(5, 2), 3);
```
### pdf
```javascript
pdf: (distribution, number) => number;
```
pdf: (distribution, number) => number
```
**Examples**
@ -316,24 +276,26 @@ pdf: (distribution, number) => number;
pdf(normal(5, 2), 3);
```
### inv
### quantile
```javascript
inv: (distribution, number) => number;
```
quantile: (distribution, number) => number
```
**Examples**
```javascript
inv(normal(5, 2), 0.5);
quantile(normal(5, 2), 0.5);
```
### toPointSet
Converts a distribution to the pointSet format
**TODO: Will soon be called "PointSet.make"**
```javascript
toPointSet: (distribution) => pointSetDistribution;
Converts a distribution to the pointSet format.
```
toPointSet: (distribution) => pointSetDistribution
```
**Examples**
@ -344,10 +306,12 @@ toPointSet(normal(5, 2));
### toSampleSet
Converts a distribution to the sampleSet format, with n samples
**TODO: Will soon be called "SampleSet.make"**
```javascript
toSampleSet: (distribution, number) => sampleSetDistribution;
Converts a distribution to the sampleSet format, with n samples.
```
toSampleSet: (distribution, number) => sampleSetDistribution
```
**Examples**
@ -360,7 +324,7 @@ toSampleSet(normal(5, 2), 1000);
Truncates the left side of a distribution. Returns either a pointSet distribution or a symbolic distribution.
```javascript
```
truncateLeft: (distribution, l => number) => distribution
```
@ -374,7 +338,7 @@ truncateLeft(normal(5, 2), 3);
Truncates the right side of a distribution. Returns either a pointSet distribution or a symbolic distribution.
```javascript
```
truncateRight: (distribution, r => number) => distribution
```
@ -384,14 +348,12 @@ truncateRight: (distribution, r => number) => distribution
truncateLeft(normal(5, 2), 6);
```
## Scoring
### klDivergence
KullbackLeibler divergence between two distributions
[KullbackLeibler divergence](https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence) between two distributions.
```javascript
klDivergence: (distribution, distribution) => number;
```
klDivergence: (distribution, distribution) => number
```
**Examples**
@ -404,8 +366,8 @@ klDivergence(normal(5, 2), normal(5, 4)); // returns 0.57
### toString
```javascript
toString: (distribution) => string;
```
toString: (distribution) => string
```
**Examples**
@ -414,42 +376,46 @@ toString: (distribution) => string;
toString(normal(5, 2));
```
### toSparkline
### sparkline
Produce a sparkline of length n
Produce a sparkline of length n. For example, `▁▁▁▁▁▂▄▆▇██▇▆▄▂▁▁▁▁▁`. These can be useful for testing or quick text visualizations.
```javascript
toSparkline: (distribution, n = 20) => string;
```
sparkline: (distribution, n = 20) => string
```
**Examples**
```javascript
toSparkline(normal(5, 2), 10);
toSparkline(truncateLeft(normal(5, 2), 3), 20); // produces ▁▇█████▇▅▄▃▂▂▁▁▁▁▁▁▁
```
### inspect
Prints the value of the distribution to the Javascript console, then returns the distribution.
Prints the value of the distribution to the Javascript console, then returns the distribution. Useful for debugging.
```javascript
inspect: (distribution) => distribution;
```
inspect: (distribution) => distribution
```
**Examples**
```javascript
inspect(normal(5, 2));
inspect(normal(5, 2)); // logs "normal(5, 2)" to the javascript console and returns the distribution.
```
## Normalization
There are some situations where computation will return unnormalized distributions. This means that their cumulative sums are not equal to 1.0. Unnormalized distributions are not valid for many relevant functions; for example, klDivergence and scoring.
The only functions that do not return normalized distributions are the pointwise arithmetic operations and the scalewise arithmetic operations. If you use these functions, it is recommended that you consider normalizing the resulting distributions.
### normalize
Normalize a distribution. This means scaling it appropriately so that it's cumulative sum is equal to 1.
```javascript
normalize: (distribution) => distribution;
```
normalize: (distribution) => distribution
```
**Examples**
@ -462,8 +428,8 @@ normalize(normal(5, 2));
Check of a distribution is normalized. Most distributions are typically normalized, but there are some commands that could produce non-normalized distributions.
```javascript
isNormalized: (distribution) => bool;
```
isNormalized: (distribution) => bool
```
**Examples**
@ -474,10 +440,12 @@ isNormalized(normal(5, 2)); // returns true
### integralSum
Get the sum of the integral of a distribution. If the distribution is normalized, this will be 1.
**Note: If you have suggestions for better names for this, please let us know.**
```javascript
integralSum: (distribution) => number;
Get the sum of the integral of a distribution. If the distribution is normalized, this will be 1.0. This is useful for understanding unnormalized distributions.
```
integralSum: (distribution) => number
```
**Examples**
@ -486,151 +454,214 @@ integralSum: (distribution) => number;
integralSum(normal(5, 2));
```
## Algebraic Operations
## Regular Arithmetic Operations
Regular arithmetic operations cover the basic mathematical operations on distributions. They work much like their equivalent operations on numbers.
The infixes `+`,`-`, `*`, `/`, `^` are supported for addition, subtraction, multiplication, division, power, and unaryMinus.
```javascript
pointMass(5 + 10) == pointMass(5) + pointMass(10);
```
### add
```
add: (distributionLike, distributionLike) => distribution
```
**Examples**
```javascript
add: (distributionLike, distributionLike) => distribution;
normal(0, 1) + normal(1, 3); // returns normal(1, 3.16...)
add(normal(0, 1), normal(1, 3)); // returns normal(1, 3.16...)
```
### sum
```javascript
**Todo: Not yet implemented**
```
sum: (list<distributionLike>) => distribution
```
**Examples**
```javascript
sum([normal(0, 1), normal(1, 3), uniform(10, 1)]);
```
### multiply
```javascript
multiply: (distributionLike, distributionLike) => distribution;
```
multiply: (distributionLike, distributionLike) => distribution
```
### product
```javascript
```
product: (list<distributionLike>) => distribution
```
### subtract
```javascript
subtract: (distributionLike, distributionLike) => distribution;
```
subtract: (distributionLike, distributionLike) => distribution
```
### divide
```javascript
divide: (distributionLike, distributionLike) => distribution;
```
divide: (distributionLike, distributionLike) => distribution
```
### pow
```javascript
pow: (distributionLike, distributionLike) => distribution;
```
pow: (distributionLike, distributionLike) => distribution
```
### exp
```javascript
exp: (distributionLike, distributionLike) => distribution;
```
exp: (distributionLike, distributionLike) => distribution
```
### log
```javascript
log: (distributionLike, distributionLike) => distribution;
```
log: (distributionLike, distributionLike) => distribution
```
### log10
```javascript
log10: (distributionLike, distributionLike) => distribution;
```
log10: (distributionLike, distributionLike) => distribution
```
### unaryMinus
```javascript
unaryMinus: (distribution) => distribution;
```
unaryMinus: (distribution) => distribution
```
## Pointwise Operations
**Examples**
```javascript
-normal(5, 2); // same as normal(-5, 2)
unaryMinus(normal(5, 2)); // same as normal(-5, 2)
```
## Pointwise Arithmetic Operations
<Admonition type="caution" title="Unnormalized Results">
<p>
Pointwise arithmetic operations typically return unnormalized or completely
invalid distributions. For example, the operation{" "}
<code>normal(5,2) .- uniform(10,12)</code> results in a distribution-like
object with negative probability mass.
</p>
</Admonition>
Pointwise arithmetic operations cover the standard arithmetic operations, but work in a different way than the regular operations. These operate on the y-values of the distributions instead of the x-values. A pointwise addition would add the y-values of two distributions.
The infixes `.+`,`.-`, `.*`, `./`, `.^` are supported for their respective operations.
The `mixture` methods works with pointwise addition.
### dotAdd
```javascript
dotAdd: (distributionLike, distributionLike) => distribution;
```
dotAdd: (distributionLike, distributionLike) => distribution
```
### dotMultiply
```javascript
dotMultiply: (distributionLike, distributionLike) => distribution;
```
dotMultiply: (distributionLike, distributionLike) => distribution
```
### dotSubtract
```javascript
dotSubtract: (distributionLike, distributionLike) => distribution;
```
dotSubtract: (distributionLike, distributionLike) => distribution
```
### dotDivide
```javascript
dotDivide: (distributionLike, distributionLike) => distribution;
```
dotDivide: (distributionLike, distributionLike) => distribution
```
### dotPow
```javascript
dotPow: (distributionLike, distributionLike) => distribution;
```
dotPow: (distributionLike, distributionLike) => distribution
```
### dotExp
```javascript
dotExp: (distributionLike, distributionLike) => distribution;
```
dotExp: (distributionLike, distributionLike) => distribution
```
## Scale Operations
## Scale Arithmetic Operations
<Admonition type="caution" title="Likely to change">
<p>
We're planning on removing scale operations in favor of more general
functions soon.
</p>
</Admonition>
Scale operations are similar to pointwise operations, but operate on a constant y-value instead of y-values coming from a distribution. You can think about this as scaling a distribution vertically by a constant.
The following items would be equivalent.
```js
scalePow(normal(5,2), 2)
mapY(normal(5,2), {|y| y ^ 2}) // Not yet available
```
### scaleMultiply
```javascript
scaleMultiply: (distributionLike, number) => distribution;
```
scaleMultiply: (distributionLike, number) => distribution
```
### scalePow
```javascript
scalePow: (distributionLike, number) => distribution;
```
scalePow: (distributionLike, number) => distribution
```
### scaleExp
```javascript
scaleExp: (distributionLike, number) => distribution;
```
scaleExp: (distributionLike, number) => distribution
```
### scaleLog
```javascript
scaleLog: (distributionLike, number) => distribution;
```
scaleLog: (distributionLike, number) => distribution
```
### scaleLog10
```javascript
scaleLog10: (distributionLike, number) => distribution;
```
scaleLog10: (distributionLike, number) => distribution
```
## Special
### Declaration (Continuous Function)
### Declaration (Continuous Functions)
Adds metadata to a function of the input ranges. Works now for numeric and date inputs. This is useful when making predictions. It allows you to limit the domain that your prediction will be used and scored within.
Adds metadata to a function of the input ranges. Works now for numeric and date inputs. This is useful when making formal predictions. It allows you to limit the domain that your prediction will be used and scored within.
```javascript
Declarations are currently experimental and will likely be removed or changed in the future.
```
declareFn: (dict<{fn: lambda, inputs: array<dict<{min: number, max: number}>>}>) => declaration
```

View File

@ -3,20 +3,56 @@ sidebar_position: 4
title: Point Set Distribution
---
:::danger
These functions aren't yet implemented with these specific names. This should be changed soon
:::
Point set distributions are one of the three distribution formats. They are stored as a list of x-y coordinates representing both discrete and continuous distributions.
One complication is that it's possible to represent invalid probability distributions in the point set format. For example, you can represent shapes with negative values, or shapes that are not normalized.
### make
Converts the distribution in question into a point set distribution. If the distribution is symbolic, then it does this by taking the quantiles. If the distribution is a sample set, then it uses a version of kernel density estimation to approximate the point set format. One complication of this latter process is that if there is a high proportion of overlapping samples (samples that are exactly the same as each other), it will convert these samples into discrete point masses. Eventually we'd like to add further methods to help adjust this process.
```
PointSet.make: (distribution) => pointSetDist
```
### makeContinuous
**TODO: Now called "toContinuousPointSet"**
Converts a set of x-y coordinates directly into a continuous distribution.
```
PointSet.makeContinuous: (list<{x: number, y: number}>) => pointSetDist
```
```javascript
PointSet.makeContinuous([
{ x: 0, y: 0.1 },
{ x: 1, y: 0.2 },
{ x: 2, y: 0.15 },
{ x: 3, y: 0.1 },
]);
```
### makeDiscrete
**TODO: Now called "toDiscretePointSet"**
Converts a set of x-y coordinates directly into a discrete distribution.
```
PointSet.makeDiscrete: (list<{x: number, y: number}>) => pointSetDist
```
```javascript
PointSet.makeDiscrete([
{ x: 0, y: 0.1 },
{ x: 1, y: 0.2 },
{ x: 2, y: 0.15 },
{ x: 3, y: 0.1 },
]);
```

View File

@ -3,12 +3,22 @@ sidebar_position: 5
title: Sample Set Distribution
---
:::danger
These functions aren't yet implemented with these specific names. This should be added soon.
:::
Sample set distributions are one of the three distribution formats. Internally, they are stored as a list of numbers. It's useful to distinguish point set distributions from arbitrary lists of numbers to make it clear which functions are applicable.
Monte Carlo calculations typically result in sample set distributions.
All regular distribution function work on sample set distributions. In addition, there are several functions that only work on sample set distributions.
### make
```
SampleSet.make: (distribution) => sampleSet
SampleSet.make: (() => number) => sampleSet
SampleSet.make: (list<number>) => sampleSet
SampleSet.make: (() => number) => sampleSet // not yet implemented
```
### map
@ -35,7 +45,7 @@ SampleSet.map3: (sampleSet, sampleSet, sampleSet, ((number, number, number) => n
SampleSet.toList: (sampleSet) => list<number>
```
Gets the internal samples of a sampleSet distribution. This is separate from the sampleN() function, which would shuffle the samples. toList() maintains order and length. Gets the internal samples of a sampleSet distribution. This is separate from the sampleN() function, which would shuffle the samples. toList() maintains order and length.
Gets the internal samples of a sampleSet distribution. This is separate from the sampleN() function, which would shuffle the samples. toList() maintains order and length.
**Examples**

View File

@ -3,6 +3,10 @@ sidebar_position: 6
title: Duration
---
Duration works with the [Date](/docs/Api/Date) type. Similar to the Date implementation, the Duration functions are early and experimental. There is no support yet for date or duration probability distributions.
Durations are stored in Unix milliseconds.
import TOCInline from "@theme/TOCInline";
<TOCInline toc={toc} />

View File

@ -3,13 +3,21 @@ sidebar_position: 7
title: List
---
Squiggle lists are a lot like Python lists or Ruby arrays. They accept all types.
```javascript
myList = [3, normal(5, 2), "random"];
```
### make
**Note: currently just called `makeList`, without the preix**
```
List.make: (number, 'a) => list<'a>
```
Returns an array of size `n` filled with value `e`.
Returns an array of size `n` filled with the value.
```js
List.make(4, 1); // creates the list [1, 1, 1, 1]
@ -31,6 +39,8 @@ length: (list<'a>) => number
### up to
**Note: currently just called `upTo`, without the preix**
```
List.upTo: (low:number, high:number) => list<number>
```

View File

@ -6,7 +6,7 @@ title: Math
### E
```
Math.E:
Math.e:
```
Euler's number; ≈ 2.718281828459045
@ -14,7 +14,7 @@ Euler's number; ≈ 2.718281828459045
### LN2
```
Math.LN2:
Math.ln2:
```
Natural logarithm of 2; ≈ 0.6931471805599453
@ -22,7 +22,7 @@ Natural logarithm of 2; ≈ 0.6931471805599453
### LN10
```
Math.LN10:
Math.ln10:
```
Natural logarithm of 10; ≈ 2.302585092994046
@ -30,7 +30,7 @@ Natural logarithm of 10; ≈ 2.302585092994046
### LOG2E
```
Math.LOG2E:
Math.log2e:
```
Base 2 logarithm of E; ≈ 1.4426950408889634Base 2 logarithm of E; ≈ 1.4426950408889634
@ -38,7 +38,7 @@ Base 2 logarithm of E; ≈ 1.4426950408889634Base 2 logarithm of E; ≈ 1.442695
### LOG10E
```
Math.LOG10E:
Math.log10e:
```
Base 10 logarithm of E; ≈ 0.4342944819032518
@ -46,7 +46,7 @@ Base 10 logarithm of E; ≈ 0.4342944819032518
### PI
```
Math.PI:
Math.pi:
```
Pi - ratio of the circumference to the diameter of a circle; ≈ 3.141592653589793
@ -54,7 +54,7 @@ Pi - ratio of the circumference to the diameter of a circle; ≈ 3.1415926535897
### SQRT1_2
```
Math.SQRT1_2:
Math.sqrt1_2:
```
Square root of 1/2; ≈ 0.7071067811865476
@ -62,7 +62,7 @@ Square root of 1/2; ≈ 0.7071067811865476
### SQRT2
```
Math.SQRT2:
Math.sqrt2:
```
Square root of 2; ≈ 1.4142135623730951
@ -70,7 +70,7 @@ Square root of 2; ≈ 1.4142135623730951
### PHI
```
Math.PHI:
Math.phi:
```
Phi is the golden ratio. 1.618033988749895
@ -78,7 +78,7 @@ Phi is the golden ratio. 1.618033988749895
### TAU
```
Math.TAU:
Math.tau:
```
Tau is the ratio constant of a circle's circumference to radius, equal to 2 \* pi. 6.283185307179586

View File

@ -3,63 +3,67 @@ sidebar_position: 9
title: Number
---
Squiggle `numbers` are Javascript floats.
Many of the functions below work on lists or pairs of numbers.
import TOCInline from "@theme/TOCInline";
<TOCInline toc={toc} />
### ceil
```javascript
ceil: (number) => number;
```
ceil: (number) => number
```
### floor
```javascript
floor: (number) => number;
```
floor: (number) => number
```
### abs
```javascript
abs: (number) => number;
```
abs: (number) => number
```
### round
```javascript
round: (number) => number;
```
round: (number) => number
```
## Statistics
### max
```javascript
```
max: (list<number>) => number
```
### min
```javascript
```
min: (list<number>) => number
```
### mean
```javascript
```
mean: (list<number>) => number
```
### stdev
```javascript
```
stdev: (list<number>) => number
```
### variance
```javascript
```
variance: (list<number>) => number
```
@ -67,25 +71,25 @@ variance: (list<number>) => number
### unaryMinus
```javascript
unaryMinus: (number) => number;
```
unaryMinus: (number) => number
```
### equal
```javascript
equal: (number, number) => boolean;
```
equal: (number, number) => boolean
```
### add
```javascript
add: (number, number) => number;
```
add: (number, number) => number
```
### sum
```javascript
```
sum: (list<number>) => number
```
@ -97,13 +101,13 @@ cumsum: (list<number>) => list<number>
### multiply
```javascript
multiply: (number, number) => number;
```
multiply: (number, number) => number
```
### product
```javascript
```
product: (list<number>) => number
```
@ -115,30 +119,30 @@ cumprod: (list<number>) => list<number>
### subtract
```javascript
subtract: (number, number) => number;
```
subtract: (number, number) => number
```
### divide
```javascript
divide: (number, number) => number;
```
divide: (number, number) => number
```
### pow
```javascript
pow: (number, number) => number;
```
pow: (number, number) => number
```
### exp
```javascript
exp: (number) => number;
```
exp: (number) => number
```
### log
```javascript
log: (number) => number;
```
log: (number) => number
```

View File

@ -1,6 +1,6 @@
---
title: "Distribution Creation"
sidebar_position: 20
sidebar_position: 2
---
import { SquiggleEditor } from "../../src/components/SquiggleEditor";
@ -91,7 +91,7 @@ The `mixture` mixes combines multiple distributions to create a mixture. You can
### Arguments
- `distributions`: A set of distributions or numbers, each passed as a paramater. Numbers will be converted into Delta distributions.
- `distributions`: A set of distributions or numbers, each passed as a paramater. Numbers will be converted into point mass distributions.
- `weights`: An optional array of numbers, each representing the weight of its corresponding distribution. The weights will be re-scaled to add to `1.0`. If a weights array is provided, it must be the same length as the distribution paramaters.
### Aliases
@ -221,22 +221,22 @@ Creates a [uniform distribution](<https://en.wikipedia.org/wiki/Uniform_distribu
</p>
</Admonition>
## Delta
## Point Mass
`delta(value:number)`
`pointMass(value:number)`
Creates a discrete distribution with all of its probability mass at point `value`.
Few Squiggle users call the function `delta()` directly. Numbers are converted into delta distributions automatically, when it is appropriate.
Few Squiggle users call the function `pointMass()` directly. Numbers are converted into point mass distributions automatically, when it is appropriate.
For example, in the function `mixture(1,2,normal(5,2))`, the first two arguments will get converted into delta distributions
with values at 1 and 2. Therefore, this is the same as `mixture(delta(1),delta(2),normal(5,2))`.
For example, in the function `mixture(1,2,normal(5,2))`, the first two arguments will get converted into point mass distributions
with values at 1 and 2. Therefore, this is the same as `mixture(pointMass(1),pointMass(2),pointMass(5,2))`.
`Delta()` distributions are currently the only discrete distributions accessible in Squiggle.
`pointMass()` distributions are currently the only discrete distributions accessible in Squiggle.
<Tabs>
<TabItem value="ex1" label="delta(3)" default>
<SquiggleEditor initialSquiggleString="delta(3)" />
<TabItem value="ex1" label="pointMass(3)" default>
<SquiggleEditor initialSquiggleString="pointMass(3)" />
</TabItem>
<TabItem value="ex3" label="mixture(1,3,5)">
<SquiggleEditor initialSquiggleString="mixture(1,3,5)" />

View File

@ -1,5 +1,5 @@
---
title: "Functions Reference"
title: "Distribution Functions"
sidebar_position: 3
---
@ -170,7 +170,7 @@ given point x.
### Cumulative density function
The `cdf(dist, x)` gives the cumulative probability of the distribution
or all values lower than x. It is the inverse of `inv`.
or all values lower than x. It is the inverse of `quantile`.
<SquiggleEditor initialSquiggleString="cdf(normal(0,1),0)" />
@ -179,13 +179,13 @@ or all values lower than x. It is the inverse of `inv`.
- `x` must be a scalar
- `dist` must be a distribution
### Inverse CDF
### Quantile
The `inv(dist, prob)` gives the value x or which the probability for all values
The `quantile(dist, prob)` gives the value x or which the probability for all values
lower than x is equal to prob. It is the inverse of `cdf`. In the literature, it
is also known as the quantiles function.
<SquiggleEditor initialSquiggleString="inv(normal(0,1),0.5)" />
<SquiggleEditor initialSquiggleString="quantile(normal(0,1),0.5)" />
#### Validity

View File

@ -49,6 +49,6 @@ ozzie_estimate(1) * nuno_estimate(1, 1)`}
## See more
- [Distribution creation](./Distributions)
- [Distribution creation](./DistributionCreation)
- [Functions reference](./Functions)
- [Gallery](../Discussions/Gallery)

View File

@ -122,14 +122,14 @@ TODO
TODO
## `pdf`, `cdf`, and `inv`
## `pdf`, `cdf`, and `quantile`
With $\forall dist, pdf := x \mapsto \texttt{pdf}(dist, x) \land cdf := x \mapsto \texttt{cdf}(dist, x) \land inv := p \mapsto \texttt{inv}(dist, p)$,
With $\forall dist, pdf := x \mapsto \texttt{pdf}(dist, x) \land cdf := x \mapsto \texttt{cdf}(dist, x) \land quantile := p \mapsto \texttt{quantile}(dist, p)$,
### `cdf` and `inv` are inverses
### `cdf` and `quantile` are inverses
$$
\forall x \in (0,1), cdf(inv(x)) = x \land \forall x \in \texttt{dom}(cdf), x = inv(cdf(x))
\forall x \in (0,1), cdf(quantile(x)) = x \land \forall x \in \texttt{dom}(cdf), x = quantile(cdf(x))
$$
### The codomain of `cdf` equals the open interval `(0,1)` equals the codomain of `pdf`

View File

@ -25,7 +25,7 @@ $$
a \cdot Normal(\mu, \sigma) = Normal(a \cdot \mu, |a| \cdot \sigma)
$$
We can now look at the inverse cdf of a $Normal(0,1)$. We find that the 95% point is reached at $1.6448536269514722$. ([source](https://stackoverflow.com/questions/20626994/how-to-calculate-the-inverse-of-the-normal-cumulative-distribution-function-in-p)) This means that the 90% confidence interval is $[-1.6448536269514722, 1.6448536269514722]$, which has a width of $2 \cdot 1.6448536269514722$.
We can now look at the quantile of a $Normal(0,1)$. We find that the 95% point is reached at $1.6448536269514722$. ([source](https://stackoverflow.com/questions/20626994/how-to-calculate-the-inverse-of-the-normal-cumulative-distribution-function-in-p)) This means that the 90% confidence interval is $[-1.6448536269514722, 1.6448536269514722]$, which has a width of $2 \cdot 1.6448536269514722$.
So then, if we take a $Normal(0,1)$ and we multiply it by $\frac{(high -. low)}{(2. *. 1.6448536269514722)}$, it's 90% confidence interval will be multiplied by the same amount. Then we just have to shift it by the mean to get our target normal.

View File

@ -66,7 +66,7 @@ const config = {
},
{
type: "doc",
docId: "Api/Dictionary",
docId: "Api/DistGeneric",
position: "left",
label: "API",
},

View File

@ -15,11 +15,13 @@
"@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.1.0",
"react-dom": "^18.2.0",
"rehype-katex": "^5",
"remark-math": "^3"
},

View File

@ -26,6 +26,11 @@ const sidebars = {
id: "Introduction",
label: "Introduction",
},
{
type: "doc",
id: "Node-Packages",
label: "Node Packages",
},
{
type: "category",
label: "Guides",

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>
);

View File

@ -3,5 +3,4 @@ module.exports = {
theme: {
extend: {},
},
plugins: [require("@tailwindcss/forms")],
};

2415
yarn.lock

File diff suppressed because it is too large Load Diff