Merge remote-tracking branch 'origin/develop' into scoring-cleanup-three
This commit is contained in:
commit
f5366540f7
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -100,7 +100,7 @@ jobs:
|
||||||
uses: creyD/prettier_action@v4.2
|
uses: creyD/prettier_action@v4.2
|
||||||
with:
|
with:
|
||||||
dry: true
|
dry: true
|
||||||
prettier_options: --check packages/components
|
prettier_options: --check packages/components --ignore-path packages/components/.prettierignore
|
||||||
|
|
||||||
components-bundle-build:
|
components-bundle-build:
|
||||||
name: Components bundle and build
|
name: Components bundle and build
|
||||||
|
@ -154,5 +154,7 @@ jobs:
|
||||||
run: cd ../../ && yarn
|
run: cd ../../ && yarn
|
||||||
- name: Build rescript in squiggle-lang
|
- name: Build rescript in squiggle-lang
|
||||||
run: cd ../squiggle-lang && yarn build
|
run: cd ../squiggle-lang && yarn build
|
||||||
|
- name: Build components
|
||||||
|
run: cd ../components && yarn build
|
||||||
- name: Build website assets
|
- name: Build website assets
|
||||||
run: yarn build
|
run: yarn build
|
||||||
|
|
4
packages/cli/.gitignore
vendored
Normal file
4
packages/cli/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
## Artifacts
|
||||||
|
*.swp
|
||||||
|
/node_modules/
|
||||||
|
yarn-error.log
|
20
packages/cli/README.md
Normal file
20
packages/cli/README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
## 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
Normal file
96
packages/cli/index.js
Normal 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();
|
22
packages/cli/package.json
Normal file
22
packages/cli/package.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
dist/
|
dist/
|
||||||
storybook-static
|
storybook-static
|
||||||
|
src/styles/base.css
|
||||||
|
src/styles/forms.css
|
||||||
|
|
|
@ -1,3 +1,15 @@
|
||||||
|
import "../src/styles/main.css";
|
||||||
|
import "!style-loader!css-loader!postcss-loader!../src/styles/main.css";
|
||||||
|
import { SquiggleContainer } from "../src/components/SquiggleContainer";
|
||||||
|
|
||||||
|
export const decorators = [
|
||||||
|
(Story) => (
|
||||||
|
<SquiggleContainer>
|
||||||
|
<Story />
|
||||||
|
</SquiggleContainer>
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
controls: {
|
controls: {
|
||||||
|
|
|
@ -3,53 +3,71 @@
|
||||||
"version": "0.2.20",
|
"version": "0.2.20",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@headlessui/react": "^1.6.4",
|
||||||
|
"@heroicons/react": "^1.0.6",
|
||||||
|
"@hookform/resolvers": "^2.9.1",
|
||||||
"@quri/squiggle-lang": "^0.2.8",
|
"@quri/squiggle-lang": "^0.2.8",
|
||||||
"@react-hook/size": "^2.1.2",
|
"@react-hook/size": "^2.1.2",
|
||||||
|
"clsx": "^1.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-ace": "^10.1.0",
|
"react-ace": "^10.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-hook-form": "^7.32.0",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"react-vega": "^7.5.1",
|
"react-vega": "^7.5.1",
|
||||||
"styled-components": "^5.3.5",
|
|
||||||
"vega": "^5.22.1",
|
"vega": "^5.22.1",
|
||||||
"vega-embed": "^6.20.6",
|
"vega-embed": "^6.20.6",
|
||||||
"vega-lite": "^5.2.0"
|
"vega-lite": "^5.2.0",
|
||||||
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.17.12",
|
"@babel/plugin-proposal-private-property-in-object": "^7.17.12",
|
||||||
"@storybook/addon-actions": "^6.5.3",
|
"@storybook/addon-actions": "^6.5.8",
|
||||||
"@storybook/addon-essentials": "^6.5.4",
|
"@storybook/addon-essentials": "^6.5.8",
|
||||||
"@storybook/addon-links": "^6.5.4",
|
"@storybook/addon-links": "^6.5.8",
|
||||||
"@storybook/builder-webpack5": "^6.5.4",
|
"@storybook/builder-webpack5": "^6.5.8",
|
||||||
"@storybook/manager-webpack5": "^6.5.4",
|
"@storybook/manager-webpack5": "^6.5.8",
|
||||||
"@storybook/node-logger": "^6.5.4",
|
"@storybook/node-logger": "^6.5.6",
|
||||||
"@storybook/preset-create-react-app": "^4.1.1",
|
"@storybook/preset-create-react-app": "^4.1.2",
|
||||||
"@storybook/react": "^6.5.4",
|
"@storybook/react": "^6.5.8",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.2.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^14.2.0",
|
"@testing-library/user-event": "^14.2.0",
|
||||||
"@types/jest": "^27.5.0",
|
"@types/jest": "^27.5.0",
|
||||||
"@types/lodash": "^4.14.182",
|
"@types/lodash": "^4.14.182",
|
||||||
"@types/node": "^17.0.35",
|
"@types/node": "^17.0.42",
|
||||||
"@types/react": "^18.0.9",
|
"@types/react": "^18.0.9",
|
||||||
"@types/react-dom": "^18.0.4",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@types/styled-components": "^5.1.24",
|
"@types/styled-components": "^5.1.24",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"mini-css-extract-plugin": "^2.6.0",
|
||||||
|
"postcss-cli": "^9.1.0",
|
||||||
|
"postcss-import": "^14.1.0",
|
||||||
|
"postcss-loader": "^7.0.0",
|
||||||
|
"react": "^18.1.0",
|
||||||
|
"react-dom": "^18.1.0",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
|
"tailwindcss": "^3.1.2",
|
||||||
"ts-loader": "^9.3.0",
|
"ts-loader": "^9.3.0",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||||
"typescript": "^4.6.3",
|
"typescript": "^4.7.3",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"webpack": "^5.72.1",
|
"webpack": "^5.73.0",
|
||||||
"webpack-cli": "^4.9.2",
|
"webpack-cli": "^4.10.0",
|
||||||
"webpack-dev-server": "^4.9.0"
|
"webpack-dev-server": "^4.9.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18",
|
||||||
|
"react-dom": "^16.8.0 || ^17 || ^18"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
||||||
"build": "tsc -b && 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",
|
"bundle": "webpack",
|
||||||
"all": "yarn bundle && yarn build",
|
"all": "yarn bundle && yarn build",
|
||||||
"lint": "prettier --check .",
|
"lint": "prettier --check .",
|
||||||
|
|
9
packages/components/postcss.config.js
Normal file
9
packages/components/postcss.config.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
"postcss-import": {},
|
||||||
|
"tailwindcss/nesting": {},
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
cssnano: {},
|
||||||
|
},
|
||||||
|
};
|
86
packages/components/src/components/Alert.tsx
Normal file
86
packages/components/src/components/Alert.tsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
XCircleIcon,
|
||||||
|
InformationCircleIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
|
} from "@heroicons/react/solid";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
export const Alert: React.FC<{
|
||||||
|
heading: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
headingColor: string;
|
||||||
|
bodyColor: string;
|
||||||
|
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||||
|
iconColor: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}> = ({
|
||||||
|
heading = "Error",
|
||||||
|
backgroundColor,
|
||||||
|
headingColor,
|
||||||
|
bodyColor,
|
||||||
|
icon: Icon,
|
||||||
|
iconColor,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={clsx("rounded-md p-4", backgroundColor)}>
|
||||||
|
<div className="flex">
|
||||||
|
<Icon
|
||||||
|
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<div className="ml-3">
|
||||||
|
<header className={clsx("text-sm font-medium", headingColor)}>
|
||||||
|
{heading}
|
||||||
|
</header>
|
||||||
|
{children && React.Children.count(children) ? (
|
||||||
|
<div className={clsx("mt-2 text-sm", bodyColor)}>{children}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ErrorAlert: React.FC<{
|
||||||
|
heading: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}> = (props) => (
|
||||||
|
<Alert
|
||||||
|
{...props}
|
||||||
|
backgroundColor="bg-red-100"
|
||||||
|
headingColor="text-red-800"
|
||||||
|
bodyColor="text-red-700"
|
||||||
|
icon={XCircleIcon}
|
||||||
|
iconColor="text-red-400"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const MessageAlert: React.FC<{
|
||||||
|
heading: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}> = (props) => (
|
||||||
|
<Alert
|
||||||
|
{...props}
|
||||||
|
backgroundColor="bg-slate-100"
|
||||||
|
headingColor="text-slate-700"
|
||||||
|
bodyColor="text-slate-700"
|
||||||
|
icon={InformationCircleIcon}
|
||||||
|
iconColor="text-slate-400"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SuccessAlert: React.FC<{
|
||||||
|
heading: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}> = (props) => (
|
||||||
|
<Alert
|
||||||
|
{...props}
|
||||||
|
backgroundColor="bg-green-50"
|
||||||
|
headingColor="text-green-800"
|
||||||
|
bodyColor="text-green-700"
|
||||||
|
icon={CheckCircleIcon}
|
||||||
|
iconColor="text-green-400"
|
||||||
|
/>
|
||||||
|
);
|
|
@ -1,5 +1,5 @@
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { FC } from "react";
|
import React, { FC, useMemo } from "react";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
|
|
||||||
import "ace-builds/src-noconflict/mode-golang";
|
import "ace-builds/src-noconflict/mode-golang";
|
||||||
|
@ -14,21 +14,23 @@ interface CodeEditorProps {
|
||||||
showGutter?: boolean;
|
showGutter?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let CodeEditor: FC<CodeEditorProps> = ({
|
export const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
oneLine = false,
|
oneLine = false,
|
||||||
showGutter = false,
|
showGutter = false,
|
||||||
height,
|
height,
|
||||||
}: CodeEditorProps) => {
|
}) => {
|
||||||
let lineCount = value.split("\n").length;
|
const lineCount = value.split("\n").length;
|
||||||
let id = _.uniqueId();
|
const id = useMemo(() => _.uniqueId(), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AceEditor
|
<AceEditor
|
||||||
value={value}
|
value={value}
|
||||||
mode="golang"
|
mode="golang"
|
||||||
theme="github"
|
theme="github"
|
||||||
width={"100%"}
|
width="100%"
|
||||||
|
fontSize={14}
|
||||||
height={String(height) + "px"}
|
height={String(height) + "px"}
|
||||||
minLines={oneLine ? lineCount : undefined}
|
minLines={oneLine ? lineCount : undefined}
|
||||||
maxLines={oneLine ? lineCount : undefined}
|
maxLines={oneLine ? lineCount : undefined}
|
||||||
|
@ -47,4 +49,3 @@ export let CodeEditor: FC<CodeEditorProps> = ({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CodeEditor;
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import _ from "lodash";
|
|
||||||
import {
|
import {
|
||||||
Distribution,
|
Distribution,
|
||||||
result,
|
result,
|
||||||
|
@ -8,15 +7,16 @@ import {
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { Vega, VisualizationSpec } from "react-vega";
|
import { Vega, VisualizationSpec } from "react-vega";
|
||||||
import * as chartSpecification from "../vega-specs/spec-distributions.json";
|
import * as chartSpecification from "../vega-specs/spec-distributions.json";
|
||||||
import { ErrorBox } from "./ErrorBox";
|
import { ErrorAlert } from "./Alert";
|
||||||
import { useSize } from "react-use";
|
import { useSize } from "react-use";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
linearXScale,
|
linearXScale,
|
||||||
logXScale,
|
logXScale,
|
||||||
linearYScale,
|
linearYScale,
|
||||||
expYScale,
|
expYScale,
|
||||||
} from "./DistributionVegaScales";
|
} from "./DistributionVegaScales";
|
||||||
import styled from "styled-components";
|
|
||||||
import { NumberShower } from "./NumberShower";
|
import { NumberShower } from "./NumberShower";
|
||||||
|
|
||||||
type DistributionChartProps = {
|
type DistributionChartProps = {
|
||||||
|
@ -35,38 +35,34 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
|
||||||
showSummary,
|
showSummary,
|
||||||
width,
|
width,
|
||||||
showControls = false,
|
showControls = false,
|
||||||
}: DistributionChartProps) => {
|
}) => {
|
||||||
let [isLogX, setLogX] = React.useState(false);
|
const [isLogX, setLogX] = React.useState(false);
|
||||||
let [isExpY, setExpY] = React.useState(false);
|
const [isExpY, setExpY] = React.useState(false);
|
||||||
let shape = distribution.pointSet();
|
const shape = distribution.pointSet();
|
||||||
const [sized, _] = useSize((size) => {
|
const [sized] = useSize((size) => {
|
||||||
if (shape.tag === "Ok") {
|
if (shape.tag === "Error") {
|
||||||
let massBelow0 =
|
return (
|
||||||
|
<ErrorAlert heading="Distribution Error">
|
||||||
|
{distributionErrorToString(shape.value)}
|
||||||
|
</ErrorAlert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const massBelow0 =
|
||||||
shape.value.continuous.some((x) => x.x <= 0) ||
|
shape.value.continuous.some((x) => x.x <= 0) ||
|
||||||
shape.value.discrete.some((x) => x.x <= 0);
|
shape.value.discrete.some((x) => x.x <= 0);
|
||||||
let spec = buildVegaSpec(isLogX, isExpY);
|
const spec = buildVegaSpec(isLogX, isExpY);
|
||||||
|
|
||||||
let widthProp = width ? width : size.width;
|
let widthProp = width ? width : size.width;
|
||||||
|
if (widthProp < 20) {
|
||||||
// Check whether we should disable the checkbox
|
console.warn(
|
||||||
var logCheckbox = (
|
`Width of Distribution is set to ${widthProp}, which is too small`
|
||||||
<CheckBox label="Log X scale" value={isLogX} onChange={setLogX} />
|
|
||||||
);
|
|
||||||
if (massBelow0) {
|
|
||||||
logCheckbox = (
|
|
||||||
<CheckBox
|
|
||||||
label="Log X scale"
|
|
||||||
value={isLogX}
|
|
||||||
onChange={setLogX}
|
|
||||||
disabled={true}
|
|
||||||
tooltip={
|
|
||||||
"Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
widthProp = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = (
|
return (
|
||||||
<ChartContainer width={widthProp + "px"}>
|
<div style={{ width: widthProp }}>
|
||||||
<Vega
|
<Vega
|
||||||
spec={spec}
|
spec={spec}
|
||||||
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
|
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
|
||||||
|
@ -74,34 +70,33 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
|
||||||
height={height}
|
height={height}
|
||||||
actions={false}
|
actions={false}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex justify-center">
|
||||||
{showSummary && <SummaryTable distribution={distribution} />}
|
{showSummary && <SummaryTable distribution={distribution} />}
|
||||||
|
</div>
|
||||||
{showControls && (
|
{showControls && (
|
||||||
<div>
|
<div>
|
||||||
{logCheckbox}
|
<CheckBox
|
||||||
|
label="Log X scale"
|
||||||
|
value={isLogX}
|
||||||
|
onChange={setLogX}
|
||||||
|
// Check whether we should disable the checkbox
|
||||||
|
{...(massBelow0
|
||||||
|
? {
|
||||||
|
disabled: true,
|
||||||
|
tooltip:
|
||||||
|
"Your distribution has mass lower than or equal to 0. Log only works on strictly positive values.",
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
/>
|
||||||
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
|
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ChartContainer>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
var result = (
|
|
||||||
<ErrorBox heading="Distribution Error">
|
|
||||||
{distributionErrorToString(shape.value)}
|
|
||||||
</ErrorBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
});
|
||||||
return sized;
|
return sized;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ChartContainerProps = { width: string };
|
|
||||||
|
|
||||||
let ChartContainer = styled.div<ChartContainerProps>`
|
|
||||||
width: ${(props) => props.width};
|
|
||||||
`;
|
|
||||||
|
|
||||||
function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec {
|
function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec {
|
||||||
return {
|
return {
|
||||||
...chartSpecification,
|
...chartSpecification,
|
||||||
|
@ -120,17 +115,13 @@ interface CheckBoxProps {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Label = styled.label<{ disabled: boolean }>`
|
export const CheckBox: React.FC<CheckBoxProps> = ({
|
||||||
${(props) => props.disabled && "color: #999;"}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const CheckBox = ({
|
|
||||||
label,
|
label,
|
||||||
onChange,
|
onChange,
|
||||||
value,
|
value,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
tooltip,
|
tooltip,
|
||||||
}: CheckBoxProps) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<span title={tooltip}>
|
<span title={tooltip}>
|
||||||
<input
|
<input
|
||||||
|
@ -138,74 +129,65 @@ export const CheckBox = ({
|
||||||
value={value + ""}
|
value={value + ""}
|
||||||
onChange={() => onChange(!value)}
|
onChange={() => onChange(!value)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
className="form-checkbox"
|
||||||
/>
|
/>
|
||||||
<Label disabled={disabled}>{label}</Label>
|
<label className={clsx(disabled && "text-slate-400")}> {label}</label>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<th className="border border-slate-200 bg-slate-50 py-1 px-2 text-slate-500 font-semibold">
|
||||||
|
{children}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<td className="border border-slate-200 py-1 px-2 text-slate-900">
|
||||||
|
{children}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
|
||||||
type SummaryTableProps = {
|
type SummaryTableProps = {
|
||||||
distribution: Distribution;
|
distribution: Distribution;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Table = styled.table`
|
const SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => {
|
||||||
margin-left: auto;
|
const mean = distribution.mean();
|
||||||
margin-right: auto;
|
const stdev = distribution.stdev();
|
||||||
border-collapse: collapse;
|
const p5 = distribution.inv(0.05);
|
||||||
text-align: center;
|
const p10 = distribution.inv(0.1);
|
||||||
border-style: hidden;
|
const p25 = distribution.inv(0.25);
|
||||||
`;
|
const p50 = distribution.inv(0.5);
|
||||||
|
const p75 = distribution.inv(0.75);
|
||||||
|
const p90 = distribution.inv(0.9);
|
||||||
|
const p95 = distribution.inv(0.95);
|
||||||
|
|
||||||
const TableHead = styled.thead`
|
const hasResult = (x: result<number, distributionError>): boolean =>
|
||||||
border-bottom: 1px solid rgb(141 149 167);
|
x.tag === "Ok";
|
||||||
`;
|
|
||||||
|
|
||||||
const TableHeadCell = styled.th`
|
const unwrapResult = (
|
||||||
border-right: 1px solid rgb(141 149 167);
|
|
||||||
border-left: 1px solid rgb(141 149 167);
|
|
||||||
padding: 0.3em;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TableBody = styled.tbody``;
|
|
||||||
|
|
||||||
const Row = styled.tr``;
|
|
||||||
|
|
||||||
const Cell = styled.td`
|
|
||||||
padding: 0.3em;
|
|
||||||
border-right: 1px solid rgb(141 149 167);
|
|
||||||
border-left: 1px solid rgb(141 149 167);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SummaryTable: React.FC<SummaryTableProps> = ({
|
|
||||||
distribution,
|
|
||||||
}: SummaryTableProps) => {
|
|
||||||
let mean = distribution.mean();
|
|
||||||
let p5 = distribution.inv(0.05);
|
|
||||||
let p10 = distribution.inv(0.1);
|
|
||||||
let p25 = distribution.inv(0.25);
|
|
||||||
let p50 = distribution.inv(0.5);
|
|
||||||
let p75 = distribution.inv(0.75);
|
|
||||||
let p90 = distribution.inv(0.9);
|
|
||||||
let p95 = distribution.inv(0.95);
|
|
||||||
let unwrapResult = (
|
|
||||||
x: result<number, distributionError>
|
x: result<number, distributionError>
|
||||||
): React.ReactNode => {
|
): React.ReactNode => {
|
||||||
if (x.tag === "Ok") {
|
if (x.tag === "Ok") {
|
||||||
return <NumberShower number={x.value} />;
|
return <NumberShower number={x.value} />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<ErrorBox heading="Distribution Error">
|
<ErrorAlert heading="Distribution Error">
|
||||||
{distributionErrorToString(x.value)}
|
{distributionErrorToString(x.value)}
|
||||||
</ErrorBox>
|
</ErrorAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<table className="border border-collapse border-slate-400">
|
||||||
<TableHead>
|
<thead className="bg-slate-50">
|
||||||
<Row>
|
<tr>
|
||||||
<TableHeadCell>{"Mean"}</TableHeadCell>
|
<TableHeadCell>{"Mean"}</TableHeadCell>
|
||||||
|
{hasResult(stdev) && <TableHeadCell>{"Stdev"}</TableHeadCell>}
|
||||||
<TableHeadCell>{"5%"}</TableHeadCell>
|
<TableHeadCell>{"5%"}</TableHeadCell>
|
||||||
<TableHeadCell>{"10%"}</TableHeadCell>
|
<TableHeadCell>{"10%"}</TableHeadCell>
|
||||||
<TableHeadCell>{"25%"}</TableHeadCell>
|
<TableHeadCell>{"25%"}</TableHeadCell>
|
||||||
|
@ -213,11 +195,12 @@ const SummaryTable: React.FC<SummaryTableProps> = ({
|
||||||
<TableHeadCell>{"75%"}</TableHeadCell>
|
<TableHeadCell>{"75%"}</TableHeadCell>
|
||||||
<TableHeadCell>{"90%"}</TableHeadCell>
|
<TableHeadCell>{"90%"}</TableHeadCell>
|
||||||
<TableHeadCell>{"95%"}</TableHeadCell>
|
<TableHeadCell>{"95%"}</TableHeadCell>
|
||||||
</Row>
|
</tr>
|
||||||
</TableHead>
|
</thead>
|
||||||
<TableBody>
|
<tbody>
|
||||||
<Row>
|
<tr>
|
||||||
<Cell>{unwrapResult(mean)}</Cell>
|
<Cell>{unwrapResult(mean)}</Cell>
|
||||||
|
{hasResult(stdev) && <Cell>{unwrapResult(stdev)}</Cell>}
|
||||||
<Cell>{unwrapResult(p5)}</Cell>
|
<Cell>{unwrapResult(p5)}</Cell>
|
||||||
<Cell>{unwrapResult(p10)}</Cell>
|
<Cell>{unwrapResult(p10)}</Cell>
|
||||||
<Cell>{unwrapResult(p25)}</Cell>
|
<Cell>{unwrapResult(p25)}</Cell>
|
||||||
|
@ -225,8 +208,8 @@ const SummaryTable: React.FC<SummaryTableProps> = ({
|
||||||
<Cell>{unwrapResult(p75)}</Cell>
|
<Cell>{unwrapResult(p75)}</Cell>
|
||||||
<Cell>{unwrapResult(p90)}</Cell>
|
<Cell>{unwrapResult(p90)}</Cell>
|
||||||
<Cell>{unwrapResult(p95)}</Cell>
|
<Cell>{unwrapResult(p95)}</Cell>
|
||||||
</Row>
|
</tr>
|
||||||
</TableBody>
|
</tbody>
|
||||||
</Table>
|
</table>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import styled from "styled-components";
|
|
||||||
|
|
||||||
const ShowError = styled.div`
|
|
||||||
border: 1px solid #792e2e;
|
|
||||||
background: #eee2e2;
|
|
||||||
padding: 0.4em 0.8em;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const ErrorBox: React.FC<{
|
|
||||||
heading: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}> = ({ heading = "Error", children }) => {
|
|
||||||
return (
|
|
||||||
<ShowError>
|
|
||||||
<h3>{heading}</h3>
|
|
||||||
{children}
|
|
||||||
</ShowError>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,40 +1,9 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import _ from "lodash";
|
import { lambdaValue, environment, runForeign } from "@quri/squiggle-lang";
|
||||||
import type { Spec } from "vega";
|
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
||||||
import {
|
import { FunctionChart1Number } from "./FunctionChart1Number";
|
||||||
Distribution,
|
import { ErrorAlert, MessageAlert } from "./Alert";
|
||||||
result,
|
|
||||||
lambdaValue,
|
|
||||||
environment,
|
|
||||||
runForeign,
|
|
||||||
squiggleExpression,
|
|
||||||
errorValue,
|
|
||||||
errorValueToString,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { createClassFromSpec } from "react-vega";
|
|
||||||
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
|
||||||
import { DistributionChart } from "./DistributionChart";
|
|
||||||
import { NumberShower } from "./NumberShower";
|
|
||||||
import { ErrorBox } from "./ErrorBox";
|
|
||||||
|
|
||||||
let SquigglePercentilesChart = createClassFromSpec({
|
|
||||||
spec: percentilesSpec as Spec,
|
|
||||||
});
|
|
||||||
|
|
||||||
const _rangeByCount = (start: number, stop: number, count: number) => {
|
|
||||||
const step = (stop - start) / (count - 1);
|
|
||||||
const items = _.range(start, stop, step);
|
|
||||||
const result = items.concat([stop]);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
function unwrap<a, b>(x: result<a, b>): a {
|
|
||||||
if (x.tag === "Ok") {
|
|
||||||
return x.value;
|
|
||||||
} else {
|
|
||||||
throw Error("FAILURE TO UNWRAP");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export type FunctionChartSettings = {
|
export type FunctionChartSettings = {
|
||||||
start: number;
|
start: number;
|
||||||
stop: number;
|
stop: number;
|
||||||
|
@ -45,167 +14,66 @@ interface FunctionChartProps {
|
||||||
fn: lambdaValue;
|
fn: lambdaValue;
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
environment: environment;
|
environment: environment;
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type percentiles = {
|
|
||||||
x: number;
|
|
||||||
p1: number;
|
|
||||||
p5: number;
|
|
||||||
p10: number;
|
|
||||||
p20: number;
|
|
||||||
p30: number;
|
|
||||||
p40: number;
|
|
||||||
p50: number;
|
|
||||||
p60: number;
|
|
||||||
p70: number;
|
|
||||||
p80: number;
|
|
||||||
p90: number;
|
|
||||||
p95: number;
|
|
||||||
p99: number;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
type errors = _.Dictionary<
|
|
||||||
{
|
|
||||||
x: number;
|
|
||||||
value: string;
|
|
||||||
}[]
|
|
||||||
>;
|
|
||||||
|
|
||||||
type point = { x: number; value: result<Distribution, string> };
|
|
||||||
|
|
||||||
let getPercentiles = ({ chartSettings, fn, environment }) => {
|
|
||||||
let chartPointsToRender = _rangeByCount(
|
|
||||||
chartSettings.start,
|
|
||||||
chartSettings.stop,
|
|
||||||
chartSettings.count
|
|
||||||
);
|
|
||||||
|
|
||||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
|
||||||
let result = runForeign(fn, [x], environment);
|
|
||||||
if (result.tag === "Ok") {
|
|
||||||
if (result.value.tag == "distribution") {
|
|
||||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
value: {
|
|
||||||
tag: "Error",
|
|
||||||
value:
|
|
||||||
"Cannot currently render functions that don't return distributions",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
value: { tag: "Error", value: errorValueToString(result.value) },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let initialPartition: [
|
|
||||||
{ x: number; value: Distribution }[],
|
|
||||||
{ x: number; value: string }[]
|
|
||||||
] = [[], []];
|
|
||||||
|
|
||||||
let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
|
|
||||||
if (current.value.tag === "Ok") {
|
|
||||||
acc[0].push({ x: current.x, value: current.value.value });
|
|
||||||
} else {
|
|
||||||
acc[1].push({ x: current.x, value: current.value.value });
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, initialPartition);
|
|
||||||
|
|
||||||
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
|
|
||||||
|
|
||||||
let percentiles: percentiles = functionImage.map(({ x, value }) => {
|
|
||||||
// We convert it to to a pointSet distribution first, so that in case its a sample set
|
|
||||||
// distribution, it doesn't internally convert it to a pointSet distribution for every
|
|
||||||
// single inv() call.
|
|
||||||
let toPointSet: Distribution = unwrap(value.toPointSet());
|
|
||||||
return {
|
|
||||||
x: x,
|
|
||||||
p1: unwrap(toPointSet.inv(0.01)),
|
|
||||||
p5: unwrap(toPointSet.inv(0.05)),
|
|
||||||
p10: unwrap(toPointSet.inv(0.1)),
|
|
||||||
p20: unwrap(toPointSet.inv(0.2)),
|
|
||||||
p30: unwrap(toPointSet.inv(0.3)),
|
|
||||||
p40: unwrap(toPointSet.inv(0.4)),
|
|
||||||
p50: unwrap(toPointSet.inv(0.5)),
|
|
||||||
p60: unwrap(toPointSet.inv(0.6)),
|
|
||||||
p70: unwrap(toPointSet.inv(0.7)),
|
|
||||||
p80: unwrap(toPointSet.inv(0.8)),
|
|
||||||
p90: unwrap(toPointSet.inv(0.9)),
|
|
||||||
p95: unwrap(toPointSet.inv(0.95)),
|
|
||||||
p99: unwrap(toPointSet.inv(0.99)),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { percentiles, errors: groupedErrors };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FunctionChart: React.FC<FunctionChartProps> = ({
|
export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
fn,
|
fn,
|
||||||
chartSettings,
|
chartSettings,
|
||||||
environment,
|
environment,
|
||||||
}: FunctionChartProps) => {
|
height,
|
||||||
let [mouseOverlay, setMouseOverlay] = React.useState(0);
|
}) => {
|
||||||
function handleHover(_name: string, value: unknown) {
|
if (fn.parameters.length > 1) {
|
||||||
setMouseOverlay(value as number);
|
|
||||||
}
|
|
||||||
function handleOut() {
|
|
||||||
setMouseOverlay(NaN);
|
|
||||||
}
|
|
||||||
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
|
||||||
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
|
|
||||||
? runForeign(fn, [mouseOverlay], environment)
|
|
||||||
: {
|
|
||||||
tag: "Error",
|
|
||||||
value: {
|
|
||||||
tag: "REExpectedType",
|
|
||||||
value: "Hover x-coordinate returned NaN. Expected a number.",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let showChart =
|
|
||||||
mouseItem.tag === "Ok" && mouseItem.value.tag == "distribution" ? (
|
|
||||||
<DistributionChart
|
|
||||||
distribution={mouseItem.value.value}
|
|
||||||
width={400}
|
|
||||||
height={140}
|
|
||||||
showSummary={false}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
);
|
|
||||||
|
|
||||||
let getPercentilesMemoized = React.useMemo(
|
|
||||||
() => getPercentiles({ chartSettings, fn, environment }),
|
|
||||||
[environment, fn]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<MessageAlert heading="Function Display Not Supported">
|
||||||
<SquigglePercentilesChart
|
Only functions with one parameter are displayed.
|
||||||
data={{ facet: getPercentilesMemoized.percentiles }}
|
</MessageAlert>
|
||||||
actions={false}
|
|
||||||
signalListeners={signalListeners}
|
|
||||||
/>
|
|
||||||
{showChart}
|
|
||||||
{_.entries(getPercentilesMemoized.errors).map(
|
|
||||||
([errorName, errorPoints]) => (
|
|
||||||
<ErrorBox key={errorName} heading={errorName}>
|
|
||||||
Values:{" "}
|
|
||||||
{errorPoints
|
|
||||||
.map((r, i) => <NumberShower key={i} number={r.x} />)
|
|
||||||
.reduce((a, b) => (
|
|
||||||
<>
|
|
||||||
{a}, {b}
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</ErrorBox>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
const result1 = runForeign(fn, [chartSettings.start], environment);
|
||||||
|
const result2 = runForeign(fn, [chartSettings.stop], environment);
|
||||||
|
const getValidResult = () => {
|
||||||
|
if (result1.tag === "Ok") {
|
||||||
|
return result1;
|
||||||
|
} else if (result2.tag === "Ok") {
|
||||||
|
return result2;
|
||||||
|
} else {
|
||||||
|
return result1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const validResult = getValidResult();
|
||||||
|
const resultType =
|
||||||
|
validResult.tag === "Ok" ? validResult.value.tag : ("Error" as const);
|
||||||
|
|
||||||
|
switch (resultType) {
|
||||||
|
case "distribution":
|
||||||
|
return (
|
||||||
|
<FunctionChart1Dist
|
||||||
|
fn={fn}
|
||||||
|
chartSettings={chartSettings}
|
||||||
|
environment={environment}
|
||||||
|
height={height}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "number":
|
||||||
|
return (
|
||||||
|
<FunctionChart1Number
|
||||||
|
fn={fn}
|
||||||
|
chartSettings={chartSettings}
|
||||||
|
environment={environment}
|
||||||
|
height={height}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "Error":
|
||||||
|
return (
|
||||||
|
<ErrorAlert heading="Error">The function failed to be run</ErrorAlert>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<MessageAlert heading="Function Display Not Supported">
|
||||||
|
There is no function visualization for this type of output:{" "}
|
||||||
|
<span className="font-bold">{resultType}</span>
|
||||||
|
</MessageAlert>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
212
packages/components/src/components/FunctionChart1Dist.tsx
Normal file
212
packages/components/src/components/FunctionChart1Dist.tsx
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
import type { Spec } from "vega";
|
||||||
|
import {
|
||||||
|
Distribution,
|
||||||
|
result,
|
||||||
|
lambdaValue,
|
||||||
|
environment,
|
||||||
|
runForeign,
|
||||||
|
squiggleExpression,
|
||||||
|
errorValue,
|
||||||
|
errorValueToString,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
|
import { createClassFromSpec } from "react-vega";
|
||||||
|
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
||||||
|
import { DistributionChart } from "./DistributionChart";
|
||||||
|
import { NumberShower } from "./NumberShower";
|
||||||
|
import { ErrorAlert } from "./Alert";
|
||||||
|
|
||||||
|
let SquigglePercentilesChart = createClassFromSpec({
|
||||||
|
spec: percentilesSpec as Spec,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _rangeByCount = (start: number, stop: number, count: number) => {
|
||||||
|
const step = (stop - start) / (count - 1);
|
||||||
|
const items = _.range(start, stop, step);
|
||||||
|
const result = items.concat([stop]);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function unwrap<a, b>(x: result<a, b>): a {
|
||||||
|
if (x.tag === "Ok") {
|
||||||
|
return x.value;
|
||||||
|
} else {
|
||||||
|
throw Error("FAILURE TO UNWRAP");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export type FunctionChartSettings = {
|
||||||
|
start: number;
|
||||||
|
stop: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FunctionChart1DistProps {
|
||||||
|
fn: lambdaValue;
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
environment: environment;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type percentiles = {
|
||||||
|
x: number;
|
||||||
|
p1: number;
|
||||||
|
p5: number;
|
||||||
|
p10: number;
|
||||||
|
p20: number;
|
||||||
|
p30: number;
|
||||||
|
p40: number;
|
||||||
|
p50: number;
|
||||||
|
p60: number;
|
||||||
|
p70: number;
|
||||||
|
p80: number;
|
||||||
|
p90: number;
|
||||||
|
p95: number;
|
||||||
|
p99: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
type errors = _.Dictionary<
|
||||||
|
{
|
||||||
|
x: number;
|
||||||
|
value: string;
|
||||||
|
}[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
type point = { x: number; value: result<Distribution, string> };
|
||||||
|
|
||||||
|
let getPercentiles = ({ chartSettings, fn, environment }) => {
|
||||||
|
let chartPointsToRender = _rangeByCount(
|
||||||
|
chartSettings.start,
|
||||||
|
chartSettings.stop,
|
||||||
|
chartSettings.count
|
||||||
|
);
|
||||||
|
|
||||||
|
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||||
|
let result = runForeign(fn, [x], environment);
|
||||||
|
if (result.tag === "Ok") {
|
||||||
|
if (result.value.tag == "distribution") {
|
||||||
|
return { x, value: { tag: "Ok", value: result.value.value } };
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
value: {
|
||||||
|
tag: "Error",
|
||||||
|
value:
|
||||||
|
"Cannot currently render functions that don't return distributions",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
value: { tag: "Error", value: errorValueToString(result.value) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let initialPartition: [
|
||||||
|
{ x: number; value: Distribution }[],
|
||||||
|
{ x: number; value: string }[]
|
||||||
|
] = [[], []];
|
||||||
|
|
||||||
|
let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
|
||||||
|
if (current.value.tag === "Ok") {
|
||||||
|
acc[0].push({ x: current.x, value: current.value.value });
|
||||||
|
} else {
|
||||||
|
acc[1].push({ x: current.x, value: current.value.value });
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, initialPartition);
|
||||||
|
|
||||||
|
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
|
||||||
|
|
||||||
|
let percentiles: percentiles = functionImage.map(({ x, value }) => {
|
||||||
|
// We convert it to to a pointSet distribution first, so that in case its a sample set
|
||||||
|
// distribution, it doesn't internally convert it to a pointSet distribution for every
|
||||||
|
// single inv() call.
|
||||||
|
let toPointSet: Distribution = unwrap(value.toPointSet());
|
||||||
|
return {
|
||||||
|
x: x,
|
||||||
|
p1: unwrap(toPointSet.inv(0.01)),
|
||||||
|
p5: unwrap(toPointSet.inv(0.05)),
|
||||||
|
p10: unwrap(toPointSet.inv(0.1)),
|
||||||
|
p20: unwrap(toPointSet.inv(0.2)),
|
||||||
|
p30: unwrap(toPointSet.inv(0.3)),
|
||||||
|
p40: unwrap(toPointSet.inv(0.4)),
|
||||||
|
p50: unwrap(toPointSet.inv(0.5)),
|
||||||
|
p60: unwrap(toPointSet.inv(0.6)),
|
||||||
|
p70: unwrap(toPointSet.inv(0.7)),
|
||||||
|
p80: unwrap(toPointSet.inv(0.8)),
|
||||||
|
p90: unwrap(toPointSet.inv(0.9)),
|
||||||
|
p95: unwrap(toPointSet.inv(0.95)),
|
||||||
|
p99: unwrap(toPointSet.inv(0.99)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { percentiles, errors: groupedErrors };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
||||||
|
fn,
|
||||||
|
chartSettings,
|
||||||
|
environment,
|
||||||
|
height,
|
||||||
|
}) => {
|
||||||
|
let [mouseOverlay, setMouseOverlay] = React.useState(0);
|
||||||
|
function handleHover(_name: string, value: unknown) {
|
||||||
|
setMouseOverlay(value as number);
|
||||||
|
}
|
||||||
|
function handleOut() {
|
||||||
|
setMouseOverlay(NaN);
|
||||||
|
}
|
||||||
|
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
||||||
|
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
|
||||||
|
? runForeign(fn, [mouseOverlay], environment)
|
||||||
|
: {
|
||||||
|
tag: "Error",
|
||||||
|
value: {
|
||||||
|
tag: "REExpectedType",
|
||||||
|
value: "Hover x-coordinate returned NaN. Expected a number.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let showChart =
|
||||||
|
mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? (
|
||||||
|
<DistributionChart
|
||||||
|
distribution={mouseItem.value.value}
|
||||||
|
width={400}
|
||||||
|
height={50}
|
||||||
|
showSummary={false}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
let getPercentilesMemoized = React.useMemo(
|
||||||
|
() => getPercentiles({ chartSettings, fn, environment }),
|
||||||
|
[environment, fn]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SquigglePercentilesChart
|
||||||
|
data={{ facet: getPercentilesMemoized.percentiles }}
|
||||||
|
height={height}
|
||||||
|
actions={false}
|
||||||
|
signalListeners={signalListeners}
|
||||||
|
/>
|
||||||
|
{showChart}
|
||||||
|
{_.entries(getPercentilesMemoized.errors).map(
|
||||||
|
([errorName, errorPoints]) => (
|
||||||
|
<ErrorAlert key={errorName} heading={errorName}>
|
||||||
|
Values:{" "}
|
||||||
|
{errorPoints
|
||||||
|
.map((r, i) => <NumberShower key={i} number={r.x} />)
|
||||||
|
.reduce((a, b) => (
|
||||||
|
<>
|
||||||
|
{a}, {b}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</ErrorAlert>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
119
packages/components/src/components/FunctionChart1Number.tsx
Normal file
119
packages/components/src/components/FunctionChart1Number.tsx
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import _ from "lodash";
|
||||||
|
import type { Spec } from "vega";
|
||||||
|
import {
|
||||||
|
result,
|
||||||
|
lambdaValue,
|
||||||
|
environment,
|
||||||
|
runForeign,
|
||||||
|
errorValueToString,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
|
import { createClassFromSpec } from "react-vega";
|
||||||
|
import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
|
||||||
|
import { ErrorAlert } from "./Alert";
|
||||||
|
|
||||||
|
let SquiggleLineChart = createClassFromSpec({
|
||||||
|
spec: lineChartSpec as Spec,
|
||||||
|
});
|
||||||
|
|
||||||
|
const _rangeByCount = (start: number, stop: number, count: number) => {
|
||||||
|
const step = (stop - start) / (count - 1);
|
||||||
|
const items = _.range(start, stop, step);
|
||||||
|
const result = items.concat([stop]);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FunctionChartSettings = {
|
||||||
|
start: number;
|
||||||
|
stop: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FunctionChart1NumberProps {
|
||||||
|
fn: lambdaValue;
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
environment: environment;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type point = { x: number; value: result<number, string> };
|
||||||
|
|
||||||
|
let getFunctionImage = ({ chartSettings, fn, environment }) => {
|
||||||
|
//We adjust the count, because the count is made for distributions, which are much more expensive to estimate
|
||||||
|
let adjustedCount = chartSettings.count * 20;
|
||||||
|
|
||||||
|
let chartPointsToRender = _rangeByCount(
|
||||||
|
chartSettings.start,
|
||||||
|
chartSettings.stop,
|
||||||
|
adjustedCount
|
||||||
|
);
|
||||||
|
|
||||||
|
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||||
|
let result = runForeign(fn, [x], environment);
|
||||||
|
if (result.tag === "Ok") {
|
||||||
|
if (result.value.tag == "number") {
|
||||||
|
return { x, value: { tag: "Ok", value: result.value.value } };
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
value: {
|
||||||
|
tag: "Error",
|
||||||
|
value: "This component expected number outputs",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
value: { tag: "Error", value: errorValueToString(result.value) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let initialPartition: [
|
||||||
|
{ x: number; value: number }[],
|
||||||
|
{ x: number; value: string }[]
|
||||||
|
] = [[], []];
|
||||||
|
|
||||||
|
let [functionImage, errors] = chartPointsData.reduce((acc, current) => {
|
||||||
|
if (current.value.tag === "Ok") {
|
||||||
|
acc[0].push({ x: current.x, value: current.value.value });
|
||||||
|
} else {
|
||||||
|
acc[1].push({ x: current.x, value: current.value.value });
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, initialPartition);
|
||||||
|
|
||||||
|
return { errors, functionImage };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FunctionChart1Number: React.FC<FunctionChart1NumberProps> = ({
|
||||||
|
fn,
|
||||||
|
chartSettings,
|
||||||
|
environment,
|
||||||
|
height,
|
||||||
|
}: FunctionChart1NumberProps) => {
|
||||||
|
let getFunctionImageMemoized = React.useMemo(
|
||||||
|
() => getFunctionImage({ chartSettings, fn, environment }),
|
||||||
|
[environment, fn]
|
||||||
|
);
|
||||||
|
|
||||||
|
let data = getFunctionImageMemoized.functionImage.map(({ x, value }) => ({
|
||||||
|
x,
|
||||||
|
y: value,
|
||||||
|
}));
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SquiggleLineChart
|
||||||
|
data={{ facet: data }}
|
||||||
|
height={height}
|
||||||
|
actions={false}
|
||||||
|
/>
|
||||||
|
{getFunctionImageMemoized.errors.map(({ x, value }) => (
|
||||||
|
<ErrorAlert key={x} heading={value}>
|
||||||
|
Error at point ${x}
|
||||||
|
</ErrorAlert>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
49
packages/components/src/components/JsonEditor.tsx
Normal file
49
packages/components/src/components/JsonEditor.tsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import _ from "lodash";
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import AceEditor from "react-ace";
|
||||||
|
|
||||||
|
import "ace-builds/src-noconflict/mode-json";
|
||||||
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
|
|
||||||
|
interface CodeEditorProps {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
oneLine?: boolean;
|
||||||
|
width?: number;
|
||||||
|
height: number;
|
||||||
|
showGutter?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JsonEditor: FC<CodeEditorProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
oneLine = false,
|
||||||
|
showGutter = false,
|
||||||
|
height,
|
||||||
|
}) => {
|
||||||
|
const lineCount = value.split("\n").length;
|
||||||
|
const id = _.uniqueId();
|
||||||
|
return (
|
||||||
|
<AceEditor
|
||||||
|
value={value}
|
||||||
|
mode="json"
|
||||||
|
theme="github"
|
||||||
|
width={"100%"}
|
||||||
|
height={String(height) + "px"}
|
||||||
|
minLines={oneLine ? lineCount : undefined}
|
||||||
|
maxLines={oneLine ? lineCount : undefined}
|
||||||
|
showGutter={showGutter}
|
||||||
|
highlightActiveLine={false}
|
||||||
|
showPrintMargin={false}
|
||||||
|
onChange={onChange}
|
||||||
|
name={id}
|
||||||
|
editorProps={{
|
||||||
|
$blockScrolling: true,
|
||||||
|
}}
|
||||||
|
setOptions={{
|
||||||
|
enableBasicAutocompletion: false,
|
||||||
|
enableLiveAutocompletion: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,5 +1,4 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
const orderOfMagnitudeNum = (n: number) => {
|
const orderOfMagnitudeNum = (n: number) => {
|
||||||
return Math.pow(10, n);
|
return Math.pow(10, n);
|
||||||
|
@ -74,25 +73,23 @@ export interface NumberShowerProps {
|
||||||
precision?: number;
|
precision?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let NumberShower: React.FC<NumberShowerProps> = ({
|
export const NumberShower: React.FC<NumberShowerProps> = ({
|
||||||
number,
|
number,
|
||||||
precision = 2,
|
precision = 2,
|
||||||
}: NumberShowerProps) => {
|
}) => {
|
||||||
let numberWithPresentation = numberShow(number, precision);
|
const numberWithPresentation = numberShow(number, precision);
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{numberWithPresentation.value}
|
{numberWithPresentation.value}
|
||||||
{numberWithPresentation.symbol}
|
{numberWithPresentation.symbol}
|
||||||
{numberWithPresentation.power ? (
|
{numberWithPresentation.power ? (
|
||||||
<span>
|
<span>
|
||||||
{"\u00b710"}
|
{"\u00b7" /* dot symbol */}10
|
||||||
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
|
<span style={{ fontSize: "0.6em", verticalAlign: "super" }}>
|
||||||
{numberWithPresentation.power}
|
{numberWithPresentation.power}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : null}
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import _ from "lodash";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import {
|
import {
|
||||||
run,
|
|
||||||
errorValueToString,
|
|
||||||
squiggleExpression,
|
squiggleExpression,
|
||||||
bindings,
|
bindings,
|
||||||
environment,
|
environment,
|
||||||
|
@ -12,209 +8,10 @@ import {
|
||||||
defaultBindings,
|
defaultBindings,
|
||||||
defaultEnvironment,
|
defaultEnvironment,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { NumberShower } from "./NumberShower";
|
import { FunctionChartSettings } from "./FunctionChart";
|
||||||
import { DistributionChart } from "./DistributionChart";
|
import { useSquiggle } from "../lib/hooks";
|
||||||
import { ErrorBox } from "./ErrorBox";
|
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
||||||
import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
|
import { SquiggleItem } from "./SquiggleItem";
|
||||||
|
|
||||||
const variableBox = {
|
|
||||||
Component: styled.div`
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 2px;
|
|
||||||
margin-bottom: 0.4em;
|
|
||||||
`,
|
|
||||||
Heading: styled.div`
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
padding-left: 0.8em;
|
|
||||||
padding-right: 0.8em;
|
|
||||||
padding-top: 0.1em;
|
|
||||||
`,
|
|
||||||
Body: styled.div`
|
|
||||||
padding: 0.4em 0.8em;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface VariableBoxProps {
|
|
||||||
heading: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
showTypes: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VariableBox: React.FC<VariableBoxProps> = ({
|
|
||||||
heading = "Error",
|
|
||||||
children,
|
|
||||||
showTypes = false,
|
|
||||||
}: VariableBoxProps) => {
|
|
||||||
if (showTypes) {
|
|
||||||
return (
|
|
||||||
<variableBox.Component>
|
|
||||||
<variableBox.Heading>
|
|
||||||
<h3>{heading}</h3>
|
|
||||||
</variableBox.Heading>
|
|
||||||
<variableBox.Body>{children}</variableBox.Body>
|
|
||||||
</variableBox.Component>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <div>{children}</div>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let RecordKeyHeader = styled.h3``;
|
|
||||||
|
|
||||||
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,
|
|
||||||
}: SquiggleItemProps) => {
|
|
||||||
switch (expression.tag) {
|
|
||||||
case "number":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Number" showTypes={showTypes}>
|
|
||||||
<NumberShower precision={3} number={expression.value} />
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "distribution": {
|
|
||||||
let distType = expression.value.type();
|
|
||||||
return (
|
|
||||||
<VariableBox
|
|
||||||
heading={`Distribution (${distType})`}
|
|
||||||
showTypes={showTypes}
|
|
||||||
>
|
|
||||||
{distType === "Symbolic" && showTypes ? (
|
|
||||||
<>
|
|
||||||
<div>{expression.value.toString()}</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
<DistributionChart
|
|
||||||
distribution={expression.value}
|
|
||||||
height={height}
|
|
||||||
width={width}
|
|
||||||
showSummary={showSummary}
|
|
||||||
showControls={showControls}
|
|
||||||
/>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "string":
|
|
||||||
return (
|
|
||||||
<VariableBox
|
|
||||||
heading="String"
|
|
||||||
showTypes={showTypes}
|
|
||||||
>{`"${expression.value}"`}</VariableBox>
|
|
||||||
);
|
|
||||||
case "boolean":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Boolean" showTypes={showTypes}>
|
|
||||||
{expression.value.toString()}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "symbol":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Symbol" showTypes={showTypes}>
|
|
||||||
{expression.value}
|
|
||||||
</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) => (
|
|
||||||
<SquiggleItem
|
|
||||||
key={i}
|
|
||||||
expression={r}
|
|
||||||
width={width !== undefined ? width - 20 : width}
|
|
||||||
height={50}
|
|
||||||
showTypes={showTypes}
|
|
||||||
showControls={showControls}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment}
|
|
||||||
showSummary={showSummary}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "record":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Record" showTypes={showTypes}>
|
|
||||||
{Object.entries(expression.value).map(([key, r]) => (
|
|
||||||
<div key={key}>
|
|
||||||
<RecordKeyHeader>{key}</RecordKeyHeader>
|
|
||||||
<SquiggleItem
|
|
||||||
expression={r}
|
|
||||||
width={width !== undefined ? width - 20 : width}
|
|
||||||
height={50}
|
|
||||||
showTypes={showTypes}
|
|
||||||
showSummary={showSummary}
|
|
||||||
showControls={showControls}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment}
|
|
||||||
/>
|
|
||||||
</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 (
|
|
||||||
<FunctionChart
|
|
||||||
fn={expression.value}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={{
|
|
||||||
sampleCount: environment.sampleCount / 10,
|
|
||||||
xyPointLength: environment.xyPointLength / 10,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface SquiggleChartProps {
|
export interface SquiggleChartProps {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
|
@ -225,8 +22,8 @@ export interface SquiggleChartProps {
|
||||||
environment?: environment;
|
environment?: environment;
|
||||||
/** If the result is a function, where the function starts, ends and the amount of stops */
|
/** If the result is a function, where the function starts, ends and the amount of stops */
|
||||||
chartSettings?: FunctionChartSettings;
|
chartSettings?: FunctionChartSettings;
|
||||||
/** When the environment changes */
|
/** When the squiggle code gets reevaluated */
|
||||||
onChange?(expr: squiggleExpression): void;
|
onChange?(expr: squiggleExpression | undefined): void;
|
||||||
/** CSS width of the element */
|
/** CSS width of the element */
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
@ -234,7 +31,7 @@ export interface SquiggleChartProps {
|
||||||
bindings?: bindings;
|
bindings?: bindings;
|
||||||
/** JS imported parameters */
|
/** JS imported parameters */
|
||||||
jsImports?: jsImports;
|
jsImports?: jsImports;
|
||||||
/** Whether to show a summary of the distirbution */
|
/** Whether to show a summary of the distribution */
|
||||||
showSummary?: boolean;
|
showSummary?: boolean;
|
||||||
/** Whether to show type information about returns, default false */
|
/** Whether to show type information about returns, default false */
|
||||||
showTypes?: boolean;
|
showTypes?: boolean;
|
||||||
|
@ -242,19 +39,14 @@ export interface SquiggleChartProps {
|
||||||
showControls?: boolean;
|
showControls?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChartWrapper = styled.div`
|
const defaultOnChange = () => {};
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
const defaultChartSettings = { start: 0, stop: 10, count: 20 };
|
||||||
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
|
|
||||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
`;
|
|
||||||
|
|
||||||
let defaultChartSettings = { start: 0, stop: 10, count: 20 };
|
|
||||||
|
|
||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
||||||
squiggleString = "",
|
squiggleString = "",
|
||||||
environment,
|
environment,
|
||||||
onChange = () => {},
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
height = 60,
|
height = 200,
|
||||||
bindings = defaultBindings,
|
bindings = defaultBindings,
|
||||||
jsImports = defaultImports,
|
jsImports = defaultImports,
|
||||||
showSummary = false,
|
showSummary = false,
|
||||||
|
@ -262,31 +54,29 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
||||||
showTypes = false,
|
showTypes = false,
|
||||||
showControls = false,
|
showControls = false,
|
||||||
chartSettings = defaultChartSettings,
|
chartSettings = defaultChartSettings,
|
||||||
}: SquiggleChartProps) => {
|
}) => {
|
||||||
let expressionResult = run(squiggleString, bindings, environment, jsImports);
|
const { result } = useSquiggle({
|
||||||
let e = environment ? environment : defaultEnvironment;
|
code: squiggleString,
|
||||||
let internal: JSX.Element;
|
bindings,
|
||||||
if (expressionResult.tag === "Ok") {
|
environment,
|
||||||
let expression = expressionResult.value;
|
jsImports,
|
||||||
onChange(expression);
|
onChange,
|
||||||
internal = (
|
});
|
||||||
|
|
||||||
|
if (result.tag !== "Ok") {
|
||||||
|
return <SquiggleErrorAlert error={result.value} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<SquiggleItem
|
<SquiggleItem
|
||||||
expression={expression}
|
expression={result.value}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
showSummary={showSummary}
|
showSummary={showSummary}
|
||||||
showTypes={showTypes}
|
showTypes={showTypes}
|
||||||
showControls={showControls}
|
showControls={showControls}
|
||||||
chartSettings={chartSettings}
|
chartSettings={chartSettings}
|
||||||
environment={e}
|
environment={environment ?? defaultEnvironment}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
internal = (
|
|
||||||
<ErrorBox heading={"Parse Error"}>
|
|
||||||
{errorValueToString(expressionResult.value)}
|
|
||||||
</ErrorBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <ChartWrapper>{internal}</ChartWrapper>;
|
|
||||||
};
|
};
|
||||||
|
|
25
packages/components/src/components/SquiggleContainer.tsx
Normal file
25
packages/components/src/components/SquiggleContainer.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { useContext } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SquiggleContextShape = {
|
||||||
|
containerized: boolean;
|
||||||
|
};
|
||||||
|
const SquiggleContext = React.createContext<SquiggleContextShape>({
|
||||||
|
containerized: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
||||||
|
const context = useContext(SquiggleContext);
|
||||||
|
if (context.containerized) {
|
||||||
|
return <>{children}</>;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<SquiggleContext.Provider value={{ containerized: true }}>
|
||||||
|
<div className="squiggle">{children}</div>
|
||||||
|
</SquiggleContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,39 +1,51 @@
|
||||||
import * as React from "react";
|
import React, { useState } from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import { SquiggleChart } from "./SquiggleChart";
|
|
||||||
import { CodeEditor } from "./CodeEditor";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import styled from "styled-components";
|
import {
|
||||||
import type {
|
|
||||||
squiggleExpression,
|
squiggleExpression,
|
||||||
environment,
|
environment,
|
||||||
bindings,
|
bindings,
|
||||||
jsImports,
|
jsImports,
|
||||||
|
defaultEnvironment,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import {
|
import { defaultImports, defaultBindings } from "@quri/squiggle-lang";
|
||||||
runPartial,
|
import { SquiggleContainer } from "./SquiggleContainer";
|
||||||
errorValueToString,
|
import { useSquiggle, useSquigglePartial } from "../lib/hooks";
|
||||||
defaultImports,
|
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
||||||
defaultBindings,
|
import { SquiggleItem } from "./SquiggleItem";
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { ErrorBox } from "./ErrorBox";
|
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 {
|
export interface SquiggleEditorProps {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
initialSquiggleString?: string;
|
initialSquiggleString?: string;
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
/** The width of the element */
|
||||||
environment?: environment;
|
width?: number;
|
||||||
/** If the result is a function, where the function starts */
|
/** If the result is a function, where the function starts */
|
||||||
diagramStart?: number;
|
diagramStart?: number;
|
||||||
/** If the result is a function, where the function ends */
|
/** If the result is a function, where the function ends */
|
||||||
diagramStop?: number;
|
diagramStop?: number;
|
||||||
/** If the result is a function, how many points along the function it samples */
|
/** If the result is a function, how many points along the function it samples */
|
||||||
diagramCount?: number;
|
diagramCount?: number;
|
||||||
/** when the environment changes. Used again for notebook magic*/
|
/** When the environment changes. Used again for notebook magic */
|
||||||
onChange?(expr: squiggleExpression): void;
|
onChange?(expr: squiggleExpression | undefined): void;
|
||||||
/** The width of the element */
|
|
||||||
width?: number;
|
|
||||||
/** Previous variable declarations */
|
/** Previous variable declarations */
|
||||||
bindings?: bindings;
|
bindings?: bindings;
|
||||||
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
|
environment?: environment;
|
||||||
/** JS Imports */
|
/** JS Imports */
|
||||||
jsImports?: jsImports;
|
jsImports?: jsImports;
|
||||||
/** Whether to show detail about types of the returns, default false */
|
/** Whether to show detail about types of the returns, default false */
|
||||||
|
@ -44,169 +56,109 @@ export interface SquiggleEditorProps {
|
||||||
showSummary?: boolean;
|
showSummary?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Input = styled.div`
|
export const SquiggleEditor: React.FC<SquiggleEditorProps> = ({
|
||||||
border: 1px solid #ddd;
|
|
||||||
padding: 0.3em 0.3em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
|
|
||||||
initialSquiggleString = "",
|
initialSquiggleString = "",
|
||||||
width,
|
width,
|
||||||
environment,
|
|
||||||
diagramStart = 0,
|
diagramStart = 0,
|
||||||
diagramStop = 10,
|
diagramStop = 10,
|
||||||
diagramCount = 20,
|
diagramCount = 20,
|
||||||
onChange,
|
onChange,
|
||||||
bindings = defaultBindings,
|
bindings = defaultBindings,
|
||||||
|
environment,
|
||||||
jsImports = defaultImports,
|
jsImports = defaultImports,
|
||||||
showTypes = false,
|
showTypes = false,
|
||||||
showControls = false,
|
showControls = false,
|
||||||
showSummary = false,
|
showSummary = false,
|
||||||
}: SquiggleEditorProps) => {
|
}: SquiggleEditorProps) => {
|
||||||
let [expression, setExpression] = React.useState(initialSquiggleString);
|
const [code, setCode] = useState(initialSquiggleString);
|
||||||
let chartSettings = {
|
|
||||||
|
const { result, observableRef } = useSquiggle({
|
||||||
|
code,
|
||||||
|
bindings,
|
||||||
|
environment,
|
||||||
|
jsImports,
|
||||||
|
onChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
const chartSettings = {
|
||||||
start: diagramStart,
|
start: diagramStart,
|
||||||
stop: diagramStop,
|
stop: diagramStop,
|
||||||
count: diagramCount,
|
count: diagramCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div ref={observableRef}>
|
||||||
<Input>
|
<SquiggleContainer>
|
||||||
<CodeEditor
|
<WrappedCodeEditor code={code} setCode={setCode} />
|
||||||
value={expression}
|
{result.tag === "Ok" ? (
|
||||||
onChange={setExpression}
|
<SquiggleItem
|
||||||
oneLine={true}
|
expression={result.value}
|
||||||
showGutter={false}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
</Input>
|
|
||||||
<SquiggleChart
|
|
||||||
width={width}
|
width={width}
|
||||||
environment={environment}
|
height={200}
|
||||||
squiggleString={expression}
|
showSummary={showSummary}
|
||||||
chartSettings={chartSettings}
|
|
||||||
onChange={onChange}
|
|
||||||
bindings={bindings}
|
|
||||||
jsImports={jsImports}
|
|
||||||
showTypes={showTypes}
|
showTypes={showTypes}
|
||||||
showControls={showControls}
|
showControls={showControls}
|
||||||
showSummary={showSummary}
|
chartSettings={chartSettings}
|
||||||
|
environment={environment ?? defaultEnvironment}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<SquiggleErrorAlert error={result.value} />
|
||||||
|
)}
|
||||||
|
</SquiggleContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
|
export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
|
||||||
let parent = document.createElement("div");
|
const parent = document.createElement("div");
|
||||||
ReactDOM.render(
|
ReactDOM.render(<SquiggleEditor {...props} />, parent);
|
||||||
<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
|
|
||||||
);
|
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SquigglePartialProps {
|
export interface SquigglePartialProps {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
initialSquiggleString?: string;
|
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*/
|
/** when the environment changes. Used again for notebook magic*/
|
||||||
onChange?(expr: bindings): void;
|
onChange?(expr: bindings | undefined): void;
|
||||||
/** Previously declared variables */
|
/** Previously declared variables */
|
||||||
bindings?: bindings;
|
bindings?: bindings;
|
||||||
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
|
environment?: environment;
|
||||||
/** Variables imported from js */
|
/** Variables imported from js */
|
||||||
jsImports?: jsImports;
|
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 = "",
|
initialSquiggleString = "",
|
||||||
onChange,
|
onChange,
|
||||||
bindings = defaultBindings,
|
bindings = defaultBindings,
|
||||||
environment,
|
environment,
|
||||||
jsImports = defaultImports,
|
jsImports = defaultImports,
|
||||||
}: SquigglePartialProps) => {
|
}: SquigglePartialProps) => {
|
||||||
let [expression, setExpression] = React.useState(initialSquiggleString);
|
const [code, setCode] = useState(initialSquiggleString);
|
||||||
let [error, setError] = React.useState<string | null>(null);
|
|
||||||
|
|
||||||
let runSquiggleAndUpdateBindings = () => {
|
const { result, observableRef } = useSquigglePartial({
|
||||||
let squiggleResult = runPartial(
|
code,
|
||||||
expression,
|
|
||||||
bindings,
|
bindings,
|
||||||
environment,
|
environment,
|
||||||
jsImports
|
jsImports,
|
||||||
);
|
onChange,
|
||||||
if (squiggleResult.tag == "Ok") {
|
});
|
||||||
if (onChange) onChange(squiggleResult.value);
|
|
||||||
setError(null);
|
|
||||||
} else {
|
|
||||||
setError(errorValueToString(squiggleResult.value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
React.useEffect(runSquiggleAndUpdateBindings, [expression]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div ref={observableRef}>
|
||||||
<Input>
|
<SquiggleContainer>
|
||||||
<CodeEditor
|
<WrappedCodeEditor code={code} setCode={setCode} />
|
||||||
value={expression}
|
{result.tag !== "Ok" ? (
|
||||||
onChange={setExpression}
|
<SquiggleErrorAlert error={result.value} />
|
||||||
oneLine={true}
|
) : null}
|
||||||
showGutter={false}
|
</SquiggleContainer>
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
</Input>
|
|
||||||
{error !== null ? <ErrorBox heading="Error">{error}</ErrorBox> : <></>}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function renderSquigglePartialToDom(props: SquigglePartialProps) {
|
export function renderSquigglePartialToDom(props: SquigglePartialProps) {
|
||||||
let parent = document.createElement("div");
|
const parent = document.createElement("div");
|
||||||
ReactDOM.render(
|
ReactDOM.render(<SquigglePartial {...props} />, parent);
|
||||||
<SquigglePartial
|
|
||||||
{...props}
|
|
||||||
onChange={(bindings) => {
|
|
||||||
// @ts-ignore
|
|
||||||
parent.value = bindings;
|
|
||||||
|
|
||||||
parent.dispatchEvent(new CustomEvent("input"));
|
|
||||||
if (props.onChange) props.onChange(bindings);
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
parent
|
|
||||||
);
|
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
11
packages/components/src/components/SquiggleErrorAlert.tsx
Normal file
11
packages/components/src/components/SquiggleErrorAlert.tsx
Normal 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>;
|
||||||
|
};
|
262
packages/components/src/components/SquiggleItem.tsx
Normal file
262
packages/components/src/components/SquiggleItem.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,78 +1,25 @@
|
||||||
import _ from "lodash";
|
import React, { FC, Fragment, useState } from "react";
|
||||||
import React, { FC, ReactElement, useState } from "react";
|
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { SquiggleChart } from "./SquiggleChart";
|
import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||||
import CodeEditor from "./CodeEditor";
|
import * as yup from "yup";
|
||||||
import styled from "styled-components";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { Tab } from "@headlessui/react";
|
||||||
import {
|
import {
|
||||||
defaultBindings,
|
ChartSquareBarIcon,
|
||||||
environment,
|
CodeIcon,
|
||||||
defaultImports,
|
CogIcon,
|
||||||
} from "@quri/squiggle-lang";
|
CurrencyDollarIcon,
|
||||||
|
EyeIcon,
|
||||||
|
} from "@heroicons/react/solid";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
interface FieldFloatProps {
|
import { defaultBindings, environment } from "@quri/squiggle-lang";
|
||||||
label: string;
|
|
||||||
className?: string;
|
|
||||||
value: number;
|
|
||||||
onChange: (value: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Input = styled.input``;
|
import { SquiggleChart } from "./SquiggleChart";
|
||||||
|
import { CodeEditor } from "./CodeEditor";
|
||||||
const FormItem = (props: { label: string; children: ReactElement }) => (
|
import { JsonEditor } from "./JsonEditor";
|
||||||
<div>
|
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||||
<label>{props.label}</label>
|
import { SquiggleContainer } from "./SquiggleContainer";
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
function FieldFloat(Props: FieldFloatProps) {
|
|
||||||
let [contents, setContents] = useState(Props.value + "");
|
|
||||||
return (
|
|
||||||
<FormItem label={Props.label}>
|
|
||||||
<Input
|
|
||||||
value={contents}
|
|
||||||
className={Props.className ? Props.className : ""}
|
|
||||||
onChange={(e) => {
|
|
||||||
setContents(e.target.value);
|
|
||||||
let result = parseFloat(contents);
|
|
||||||
if (_.isFinite(result)) {
|
|
||||||
Props.onChange(result);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShowBoxProps {
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ShowBox = styled.div<ShowBoxProps>`
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 2px;
|
|
||||||
height: ${(props) => props.height};
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface TitleProps {
|
|
||||||
readonly maxHeight: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Display = styled.div<TitleProps>`
|
|
||||||
background: #f6f6f6;
|
|
||||||
border-left: 1px solid #eee;
|
|
||||||
height: 100vh;
|
|
||||||
padding: 3px;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: ${(props) => props.maxHeight}px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Row = styled.div`
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 50% 50%;
|
|
||||||
`;
|
|
||||||
const Col = styled.div``;
|
|
||||||
|
|
||||||
interface PlaygroundProps {
|
interface PlaygroundProps {
|
||||||
/** The initial squiggle string to put in the playground */
|
/** The initial squiggle string to put in the playground */
|
||||||
|
@ -85,65 +32,442 @@ interface PlaygroundProps {
|
||||||
showControls?: boolean;
|
showControls?: boolean;
|
||||||
/** Whether to show the summary table in the playground */
|
/** Whether to show the summary table in the playground */
|
||||||
showSummary?: boolean;
|
showSummary?: boolean;
|
||||||
|
/** If code is set, component becomes controlled */
|
||||||
|
code?: string;
|
||||||
|
onCodeChange?(expr: string): void;
|
||||||
|
/** Should we show the editor? */
|
||||||
|
showEditor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let SquigglePlayground: FC<PlaygroundProps> = ({
|
const schema = yup
|
||||||
|
.object()
|
||||||
|
.shape({
|
||||||
|
sampleCount: yup
|
||||||
|
.number()
|
||||||
|
.required()
|
||||||
|
.positive()
|
||||||
|
.integer()
|
||||||
|
.default(1000)
|
||||||
|
.min(10)
|
||||||
|
.max(1000000),
|
||||||
|
xyPointLength: yup
|
||||||
|
.number()
|
||||||
|
.required()
|
||||||
|
.positive()
|
||||||
|
.integer()
|
||||||
|
.default(1000)
|
||||||
|
.min(10)
|
||||||
|
.max(10000),
|
||||||
|
chartHeight: yup.number().required().positive().integer().default(350),
|
||||||
|
leftSizePercent: yup
|
||||||
|
.number()
|
||||||
|
.required()
|
||||||
|
.positive()
|
||||||
|
.integer()
|
||||||
|
.min(10)
|
||||||
|
.max(100)
|
||||||
|
.default(50),
|
||||||
|
showTypes: yup.boolean(),
|
||||||
|
showControls: yup.boolean(),
|
||||||
|
showSummary: yup.boolean(),
|
||||||
|
showEditor: yup.boolean(),
|
||||||
|
showSettingsPage: yup.boolean().default(false),
|
||||||
|
diagramStart: yup
|
||||||
|
.number()
|
||||||
|
.required()
|
||||||
|
.positive()
|
||||||
|
.integer()
|
||||||
|
.default(0)
|
||||||
|
.min(0),
|
||||||
|
diagramStop: yup
|
||||||
|
.number()
|
||||||
|
.required()
|
||||||
|
.positive()
|
||||||
|
.integer()
|
||||||
|
.default(10)
|
||||||
|
.min(0),
|
||||||
|
diagramCount: yup
|
||||||
|
.number()
|
||||||
|
.required()
|
||||||
|
.positive()
|
||||||
|
.integer()
|
||||||
|
.default(20)
|
||||||
|
.min(2),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
type StyledTabProps = {
|
||||||
|
name: string;
|
||||||
|
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledTab: React.FC<StyledTabProps> = ({ name, icon: Icon }) => {
|
||||||
|
return (
|
||||||
|
<Tab key={name} as={Fragment}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<button className="group flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100">
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium",
|
||||||
|
selected && "bg-white shadow-sm ring-1 ring-black ring-opacity-5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className={clsx(
|
||||||
|
"-ml-0.5 mr-2 h-4 w-4",
|
||||||
|
selected
|
||||||
|
? "text-slate-500"
|
||||||
|
: "text-gray-400 group-hover:text-gray-900"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
selected
|
||||||
|
? "text-gray-900"
|
||||||
|
: "text-gray-600 group-hover:text-gray-900"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeadedSection: FC<{ title: string; children: React.ReactNode }> = ({
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
}) => (
|
||||||
|
<div>
|
||||||
|
<header className="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{title}
|
||||||
|
</header>
|
||||||
|
<div className="mt-4">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Text: FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<p className="text-sm text-gray-500">{children}</p>
|
||||||
|
);
|
||||||
|
|
||||||
|
function InputItem<T>({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
type,
|
||||||
|
register,
|
||||||
|
}: {
|
||||||
|
name: Path<T>;
|
||||||
|
label: string;
|
||||||
|
type: "number";
|
||||||
|
register: UseFormRegister<T>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<label className="block">
|
||||||
|
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
{...register(name)}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Checkbox<T>({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
register,
|
||||||
|
}: {
|
||||||
|
name: Path<T>;
|
||||||
|
label: string;
|
||||||
|
register: UseFormRegister<T>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<label className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
{...register(name)}
|
||||||
|
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>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
initialSquiggleString = "",
|
initialSquiggleString = "",
|
||||||
height = 300,
|
height = 500,
|
||||||
showTypes = false,
|
showTypes = false,
|
||||||
showControls = false,
|
showControls = false,
|
||||||
showSummary = false,
|
showSummary = false,
|
||||||
}: PlaygroundProps) => {
|
code: controlledCode,
|
||||||
let [squiggleString, setSquiggleString] = useState(initialSquiggleString);
|
onCodeChange,
|
||||||
let [sampleCount, setSampleCount] = useState(1000);
|
showEditor = true,
|
||||||
let [outputXYPoints, setOutputXYPoints] = useState(1000);
|
}) => {
|
||||||
let [pointDistLength, setPointDistLength] = useState(1000);
|
const [uncontrolledCode, setUncontrolledCode] = useState(
|
||||||
let [diagramStart, setDiagramStart] = useState(0);
|
initialSquiggleString
|
||||||
let [diagramStop, setDiagramStop] = useState(10);
|
);
|
||||||
let [diagramCount, setDiagramCount] = useState(20);
|
const [importString, setImportString] = useState("{}");
|
||||||
let chartSettings = {
|
const [imports, setImports] = useState({});
|
||||||
start: diagramStart,
|
const [importsAreValid, setImportsAreValid] = useState(true);
|
||||||
stop: diagramStop,
|
const { register, control } = useForm({
|
||||||
count: diagramCount,
|
resolver: yupResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
sampleCount: 1000,
|
||||||
|
xyPointLength: 1000,
|
||||||
|
chartHeight: 150,
|
||||||
|
showTypes: showTypes,
|
||||||
|
showControls: showControls,
|
||||||
|
showSummary: showSummary,
|
||||||
|
showEditor: showEditor,
|
||||||
|
leftSizePercent: 50,
|
||||||
|
showSettingsPage: false,
|
||||||
|
diagramStart: 0,
|
||||||
|
diagramStop: 10,
|
||||||
|
diagramCount: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const vars = useWatch({
|
||||||
|
control,
|
||||||
|
});
|
||||||
|
const chartSettings = {
|
||||||
|
start: Number(vars.diagramStart),
|
||||||
|
stop: Number(vars.diagramStop),
|
||||||
|
count: Number(vars.diagramCount),
|
||||||
};
|
};
|
||||||
let env: environment = {
|
const env: environment = {
|
||||||
sampleCount: sampleCount,
|
sampleCount: Number(vars.sampleCount),
|
||||||
xyPointLength: outputXYPoints,
|
xyPointLength: Number(vars.xyPointLength),
|
||||||
};
|
};
|
||||||
return (
|
const getChangeJson = (r: string) => {
|
||||||
<ShowBox height={height}>
|
setImportString(r);
|
||||||
<Row>
|
try {
|
||||||
<Col>
|
setImports(JSON.parse(r));
|
||||||
<CodeEditor
|
setImportsAreValid(true);
|
||||||
value={squiggleString}
|
} catch (e) {
|
||||||
onChange={setSquiggleString}
|
setImportsAreValid(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const code = controlledCode ?? uncontrolledCode;
|
||||||
|
|
||||||
|
const samplingSettings = (
|
||||||
|
<div className="space-y-6 p-3 max-w-xl">
|
||||||
|
<div>
|
||||||
|
<InputItem
|
||||||
|
name="sampleCount"
|
||||||
|
type="number"
|
||||||
|
label="Sample Count"
|
||||||
|
register={register}
|
||||||
|
/>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Text>
|
||||||
|
How many samples to use for Monte Carlo simulations. This can
|
||||||
|
occasionally be overridden by specific Squiggle programs.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<InputItem
|
||||||
|
name="xyPointLength"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Coordinate Count (For PointSet Shapes)"
|
||||||
|
/>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Text>
|
||||||
|
When distributions are converted into PointSet shapes, we need to
|
||||||
|
know how many coordinates to use.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewSettings = (
|
||||||
|
<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"
|
||||||
|
register={register}
|
||||||
|
label="Chart Height (in pixels)"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
name="showTypes"
|
||||||
|
register={register}
|
||||||
|
label="Show information about displayed types"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
|
||||||
|
<div className="pt-8">
|
||||||
|
<HeadedSection title="Distribution Display Settings">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="showControls"
|
||||||
|
label="Show toggles to adjust scale of x and y axes"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="showSummary"
|
||||||
|
label="Show summary statistics"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-8">
|
||||||
|
<HeadedSection title="Function Display Settings">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Text>
|
||||||
|
When displaying functions of single variables that return numbers
|
||||||
|
or distributions, we need to use defaults for the x-axis. We need
|
||||||
|
to select a minimum and maximum value of x to sample, and a number
|
||||||
|
n of the number of points to sample.
|
||||||
|
</Text>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramStart"
|
||||||
|
register={register}
|
||||||
|
label="Min X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramStop"
|
||||||
|
register={register}
|
||||||
|
label="Max X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramCount"
|
||||||
|
register={register}
|
||||||
|
label="Points between X min and X max to sample"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const inputVariableSettings = (
|
||||||
|
<div className="p-3 max-w-3xl">
|
||||||
|
<HeadedSection title="Import Variables from JSON">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Text>
|
||||||
|
You can import variables from JSON into your Squiggle code.
|
||||||
|
Variables are accessed with dollar signs. For example, "timeNow"
|
||||||
|
would be accessed as "$timeNow".
|
||||||
|
</Text>
|
||||||
|
<div className="border border-slate-200 mt-6 mb-2">
|
||||||
|
<JsonEditor
|
||||||
|
value={importString}
|
||||||
|
onChange={getChangeJson}
|
||||||
oneLine={false}
|
oneLine={false}
|
||||||
showGutter={true}
|
showGutter={true}
|
||||||
height={height - 3}
|
height={150}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</div>
|
||||||
<Col>
|
<div className="p-1 pt-2">
|
||||||
<Display maxHeight={height - 3}>
|
{importsAreValid ? (
|
||||||
|
<SuccessAlert heading="Valid JSON" />
|
||||||
|
) : (
|
||||||
|
<ErrorAlert heading="Invalid JSON">
|
||||||
|
You must use valid JSON in this editor.
|
||||||
|
</ErrorAlert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const squiggleChart = (
|
||||||
<SquiggleChart
|
<SquiggleChart
|
||||||
squiggleString={squiggleString}
|
squiggleString={code}
|
||||||
environment={env}
|
environment={env}
|
||||||
chartSettings={chartSettings}
|
chartSettings={chartSettings}
|
||||||
height={150}
|
height={vars.chartHeight}
|
||||||
showTypes={showTypes}
|
showTypes={vars.showTypes}
|
||||||
showControls={showControls}
|
showControls={vars.showControls}
|
||||||
|
showSummary={vars.showSummary}
|
||||||
bindings={defaultBindings}
|
bindings={defaultBindings}
|
||||||
jsImports={defaultImports}
|
jsImports={imports}
|
||||||
showSummary={showSummary}
|
|
||||||
/>
|
/>
|
||||||
</Display>
|
);
|
||||||
</Col>
|
|
||||||
</Row>
|
const firstTab = vars.showEditor ? (
|
||||||
</ShowBox>
|
<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) {
|
export function renderSquigglePlaygroundToDom(props: PlaygroundProps) {
|
||||||
let parent = document.createElement("div");
|
const parent = document.createElement("div");
|
||||||
ReactDOM.render(<SquigglePlayground {...props} />, parent);
|
ReactDOM.render(<SquigglePlayground {...props} />, parent);
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@ export {
|
||||||
renderSquiggleEditorToDom,
|
renderSquiggleEditorToDom,
|
||||||
renderSquigglePartialToDom,
|
renderSquigglePartialToDom,
|
||||||
} from "./components/SquiggleEditor";
|
} from "./components/SquiggleEditor";
|
||||||
import SquigglePlayground, {
|
export {
|
||||||
|
SquigglePlayground,
|
||||||
renderSquigglePlaygroundToDom,
|
renderSquigglePlaygroundToDom,
|
||||||
} from "./components/SquigglePlayground";
|
} from "./components/SquigglePlayground";
|
||||||
export { SquigglePlayground, renderSquigglePlaygroundToDom };
|
export { SquiggleContainer } from "./components/SquiggleContainer";
|
||||||
|
|
||||||
export { mergeBindings } from "@quri/squiggle-lang";
|
export { mergeBindings } from "@quri/squiggle-lang";
|
||||||
|
|
63
packages/components/src/lib/hooks.ts
Normal file
63
packages/components/src/lib/hooks.ts
Normal 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);
|
||||||
|
};
|
|
@ -1 +0,0 @@
|
||||||
{"version":3,"file":"SquiggleChart.stories.js","sourceRoot":"","sources":["SquiggleChart.stories.tsx"],"names":[],"mappings":";;;AAAA,6BAA8B;AAC9B,iDAA+C;AAG/C,qBAAe;IACb,KAAK,EAAE,uBAAuB;IAC9B,SAAS,EAAE,6BAAa;CACzB,CAAA;AAED,IAAM,QAAQ,GAAG,UAAC,EAAgB;QAAf,cAAc,oBAAA;IAAM,OAAA,oBAAC,6BAAa,IAAC,cAAc,EAAE,cAAc,GAAI;AAAjD,CAAiD,CAAA;AAE3E,QAAA,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACxC,eAAO,CAAC,IAAI,GAAG;IACb,cAAc,EAAE,cAAc;CAC/B,CAAC"}
|
|
|
@ -153,11 +153,11 @@ to allow large and small numbers being printed cleanly.
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
## Functions
|
## Functions (Distribution Output)
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
<Story
|
<Story
|
||||||
name="Function"
|
name="Function to Distribution"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "foo(t) = normal(t,2)*normal(5,3); foo",
|
squiggleString: "foo(t) = normal(t,2)*normal(5,3); foo",
|
||||||
width,
|
width,
|
||||||
|
@ -167,6 +167,20 @@ to allow large and small numbers being printed cleanly.
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
## Functions (Number Output)
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Function to Number"
|
||||||
|
args={{
|
||||||
|
squiggleString: "foo(t) = t^2; foo",
|
||||||
|
width,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
## Records
|
## Records
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import SquigglePlayground from "../components/SquigglePlayground";
|
import { SquigglePlayground } from "../components/SquigglePlayground";
|
||||||
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
||||||
import styled from "styled-components";
|
|
||||||
|
|
||||||
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />
|
<Meta title="Squiggle/SquigglePlayground" component={SquigglePlayground} />
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ including sampling settings, in squiggle.
|
||||||
name="Normal"
|
name="Normal"
|
||||||
args={{
|
args={{
|
||||||
initialSquiggleString: "normal(5,2)",
|
initialSquiggleString: "normal(5,2)",
|
||||||
height: 500,
|
height: 800,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
|
371
packages/components/src/styles/base.css
Normal file
371
packages/components/src/styles/base.css
Normal file
|
@ -0,0 +1,371 @@
|
||||||
|
.squiggle {
|
||||||
|
/*
|
||||||
|
This file contains:
|
||||||
|
1) Base Tailwind preflight styles
|
||||||
|
2) Base https://github.com/tailwindlabs/tailwindcss-forms styles
|
||||||
|
|
||||||
|
(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 { */
|
||||||
|
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 */
|
||||||
|
/* } */
|
||||||
|
|
||||||
|
/*
|
||||||
|
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 { */
|
||||||
|
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 {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
border-width: 0; /* 2 */
|
||||||
|
border-style: solid; /* 2 */
|
||||||
|
border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
::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 {
|
||||||
|
height: 0; /* 1 */
|
||||||
|
color: inherit; /* 2 */
|
||||||
|
border-top-width: 1px; /* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr:where([title]) {
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the default font size and weight for headings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font weight in Edge and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 */
|
||||||
|
font-size: 1em; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
font-family: inherit; /* 1 */
|
||||||
|
font-size: 100%; /* 1 */
|
||||||
|
font-weight: inherit; /* 1 */
|
||||||
|
line-height: inherit; /* 1 */
|
||||||
|
color: inherit; /* 1 */
|
||||||
|
margin: 0; /* 2 */
|
||||||
|
padding: 0; /* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inheritance of text transform in Edge and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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'] {
|
||||||
|
-webkit-appearance: button; /* 1 */
|
||||||
|
background-color: transparent; /* 2 */
|
||||||
|
background-image: none; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the modern Firefox focus style for all focusable elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-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 {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct vertical alignment in Chrome and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Correct the cursor style of increment and decrement buttons in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-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'] {
|
||||||
|
-webkit-appearance: textfield; /* 1 */
|
||||||
|
outline-offset: -2px; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-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-appearance: button; /* 1 */
|
||||||
|
font: inherit; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct display in Chrome and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
menu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent resizing textareas horizontally by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 {
|
||||||
|
opacity: 1; /* 1 */
|
||||||
|
color: theme('colors.gray.400', #9ca3af); /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the default cursor for buttons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make sure disabled buttons don't get the pointer cursor.
|
||||||
|
*/
|
||||||
|
: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 {
|
||||||
|
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 {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
102
packages/components/src/styles/forms.css
Normal file
102
packages/components/src/styles/forms.css
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
}
|
24
packages/components/src/styles/main.css
Normal file
24
packages/components/src/styles/main.css
Normal file
|
@ -0,0 +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. */
|
||||||
|
.ace_cursor {
|
||||||
|
border-left: 2px solid !important;
|
||||||
|
}
|
|
@ -23,7 +23,7 @@
|
||||||
"tickOpacity": 0.0,
|
"tickOpacity": 0.0,
|
||||||
"domainColor": "#fff",
|
"domainColor": "#fff",
|
||||||
"domainOpacity": 0.0,
|
"domainOpacity": 0.0,
|
||||||
"format": "~g",
|
"format": ".9~s",
|
||||||
"tickCount": 10
|
"tickCount": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
"from": {
|
"from": {
|
||||||
"data": "con"
|
"data": "con"
|
||||||
},
|
},
|
||||||
|
"interpolate": "linear",
|
||||||
"encode": {
|
"encode": {
|
||||||
"update": {
|
"update": {
|
||||||
"x": {
|
"x": {
|
||||||
|
@ -48,10 +49,10 @@
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
},
|
||||||
"fill": {
|
"fill": {
|
||||||
"value": "#4C78A8"
|
"value": "#739ECC"
|
||||||
},
|
},
|
||||||
"interpolate": {
|
"interpolate": {
|
||||||
"value": "monotone"
|
"value": "linear"
|
||||||
},
|
},
|
||||||
"fillOpacity": {
|
"fillOpacity": {
|
||||||
"value": 1
|
"value": 1
|
||||||
|
@ -82,6 +83,9 @@
|
||||||
"y2": {
|
"y2": {
|
||||||
"scale": "yscale",
|
"scale": "yscale",
|
||||||
"value": 0
|
"value": 0
|
||||||
|
},
|
||||||
|
"fill": {
|
||||||
|
"value": "#2f65a7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
91
packages/components/src/vega-specs/spec-line-chart.json
Normal file
91
packages/components/src/vega-specs/spec-line-chart.json
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
||||||
|
"width": 500,
|
||||||
|
"height": 200,
|
||||||
|
"padding": 5,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"name": "facet",
|
||||||
|
"values": [],
|
||||||
|
"format": {
|
||||||
|
"type": "json",
|
||||||
|
"parse": {
|
||||||
|
"timestamp": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scales": [
|
||||||
|
{
|
||||||
|
"name": "x",
|
||||||
|
"type": "linear",
|
||||||
|
"nice": true,
|
||||||
|
"zero": false,
|
||||||
|
"domain": {
|
||||||
|
"data": "facet",
|
||||||
|
"field": "x"
|
||||||
|
},
|
||||||
|
"range": "width"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "y",
|
||||||
|
"type": "linear",
|
||||||
|
"range": "height",
|
||||||
|
"nice": true,
|
||||||
|
"zero": false,
|
||||||
|
"domain": {
|
||||||
|
"data": "facet",
|
||||||
|
"field": "y"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"signals": [
|
||||||
|
{
|
||||||
|
"name": "mousemove",
|
||||||
|
"on": [{ "events": "mousemove", "update": "invert('x', x())" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mouseout",
|
||||||
|
"on": [{ "events": "mouseout", "update": "invert('x', x())" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"axes": [
|
||||||
|
{
|
||||||
|
"orient": "bottom",
|
||||||
|
"scale": "x",
|
||||||
|
"grid": false,
|
||||||
|
"labelColor": "#727d93",
|
||||||
|
"tickColor": "#fff",
|
||||||
|
"tickOpacity": 0.0,
|
||||||
|
"domainColor": "#727d93",
|
||||||
|
"domainOpacity": 0.1,
|
||||||
|
"format": ".9~s",
|
||||||
|
"tickCount": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orient": "left",
|
||||||
|
"scale": "y",
|
||||||
|
"grid": false,
|
||||||
|
"labelColor": "#727d93",
|
||||||
|
"tickColor": "#fff",
|
||||||
|
"tickOpacity": 0.0,
|
||||||
|
"domainColor": "#727d93",
|
||||||
|
"domainOpacity": 0.1,
|
||||||
|
"format": ".9~s",
|
||||||
|
"tickCount": 5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"marks": [
|
||||||
|
{
|
||||||
|
"type": "line",
|
||||||
|
"from": { "data": "facet" },
|
||||||
|
"encode": {
|
||||||
|
"enter": {
|
||||||
|
"x": { "scale": "x", "field": "x" },
|
||||||
|
"y": { "scale": "y", "field": "y" },
|
||||||
|
"strokeWidth": { "value": 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -75,6 +75,7 @@
|
||||||
"name": "xscale",
|
"name": "xscale",
|
||||||
"type": "linear",
|
"type": "linear",
|
||||||
"nice": true,
|
"nice": true,
|
||||||
|
"zero": false,
|
||||||
"domain": {
|
"domain": {
|
||||||
"data": "facet",
|
"data": "facet",
|
||||||
"field": "x"
|
"field": "x"
|
||||||
|
@ -86,10 +87,10 @@
|
||||||
"type": "linear",
|
"type": "linear",
|
||||||
"range": "height",
|
"range": "height",
|
||||||
"nice": true,
|
"nice": true,
|
||||||
"zero": true,
|
"zero": false,
|
||||||
"domain": {
|
"domain": {
|
||||||
"data": "facet",
|
"data": "facet",
|
||||||
"field": "p99"
|
"fields": ["p1", "p99"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -113,12 +114,14 @@
|
||||||
"tickOpacity": 0.0,
|
"tickOpacity": 0.0,
|
||||||
"domainColor": "#727d93",
|
"domainColor": "#727d93",
|
||||||
"domainOpacity": 0.1,
|
"domainOpacity": 0.1,
|
||||||
|
"format": ".9~s",
|
||||||
"tickCount": 5
|
"tickCount": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"orient": "left",
|
"orient": "left",
|
||||||
"scale": "yscale",
|
"scale": "yscale",
|
||||||
"grid": false,
|
"grid": false,
|
||||||
|
"format": ".9~s",
|
||||||
"labelColor": "#727d93",
|
"labelColor": "#727d93",
|
||||||
"tickColor": "#fff",
|
"tickColor": "#fff",
|
||||||
"tickOpacity": 0.0,
|
"tickOpacity": 0.0,
|
||||||
|
|
10
packages/components/tailwind.config.js
Normal file
10
packages/components/tailwind.config.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module.exports = {
|
||||||
|
content: ["./src/**/*.{html,tsx,ts,js,jsx}"],
|
||||||
|
corePlugins: {
|
||||||
|
preflight: false,
|
||||||
|
},
|
||||||
|
important: ".squiggle",
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
};
|
|
@ -20,7 +20,8 @@
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src/vega-specs/spec-distributions.json",
|
"src/vega-specs/spec-distributions.json",
|
||||||
"src/vega-specs/spec-percentiles.json"
|
"src/vega-specs/spec-percentiles.json",
|
||||||
|
"src/vega-specs/spec-line-chart.json"
|
||||||
],
|
],
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"include": ["src/**/*", "src/*"],
|
"include": ["src/**/*", "src/*"],
|
||||||
|
|
|
@ -10,13 +10,9 @@ module.exports = {
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
loader: "ts-loader",
|
loader: "ts-loader",
|
||||||
options: { projectReferences: true, transpileOnly: true },
|
options: { projectReferences: true },
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
test: /\.css$/i,
|
|
||||||
use: ["style-loader", "css-loader"],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
@ -40,4 +36,18 @@ module.exports = {
|
||||||
compress: true,
|
compress: true,
|
||||||
port: 9000,
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,6 @@ lib
|
||||||
*.bs.js
|
*.bs.js
|
||||||
*.gen.tsx
|
*.gen.tsx
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
coverage/
|
_coverage/
|
||||||
.cache/
|
.cache/
|
||||||
Reducer_Peggy_GeneratedParser.js
|
Reducer_Peggy_GeneratedParser.js
|
||||||
|
|
|
@ -88,17 +88,13 @@ describe("block", () => {
|
||||||
)
|
)
|
||||||
testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok({y: 1,z: 1})")
|
testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok({y: 1,z: 1})")
|
||||||
// Block inside a block
|
// Block inside a block
|
||||||
testMacro(
|
testMacro([], eBlock(list{eBlock(list{exampleExpression})}), "Ok((:$$_bindExpression_$$ {1}))")
|
||||||
[],
|
|
||||||
eBlock(list{eBlock(list{exampleExpression})}),
|
|
||||||
"Ok((:$$_bindExpression_$$ (:$$_block_$$ 1)))",
|
|
||||||
)
|
|
||||||
testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)")
|
testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)")
|
||||||
// Block assigned to a variable
|
// Block assigned to a variable
|
||||||
testMacro(
|
testMacro(
|
||||||
[],
|
[],
|
||||||
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
|
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
|
||||||
"Ok((:$$_bindExpression_$$ (:$_let_$ :z (:$$_block_$$ (:$$_block_$$ :y)))))",
|
"Ok((:$$_bindExpression_$$ (:$_let_$ :z {{:y}})))",
|
||||||
)
|
)
|
||||||
testMacroEval(
|
testMacroEval(
|
||||||
[],
|
[],
|
||||||
|
@ -116,7 +112,7 @@ describe("block", () => {
|
||||||
eSymbol("y"),
|
eSymbol("y"),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
"Ok((:$$_bindExpression_$$ (:$$_block_$$ (:$_let_$ :y (:add :x 1)) :y)))",
|
"Ok((:$$_bindExpression_$$ {(:$_let_$ :y (:add :x 1)); :y}))",
|
||||||
)
|
)
|
||||||
testMacroEval(
|
testMacroEval(
|
||||||
[("x", EvNumber(1.))],
|
[("x", EvNumber(1.))],
|
||||||
|
|
|
@ -18,7 +18,7 @@ describe("builtin", () => {
|
||||||
testEval("2>1", "Ok(true)")
|
testEval("2>1", "Ok(true)")
|
||||||
testEval("concat('a','b')", "Ok('ab')")
|
testEval("concat('a','b')", "Ok('ab')")
|
||||||
testEval(
|
testEval(
|
||||||
"addOne(t)=t+1; toInternalSampleArray(mapSamples(fromSamples([1,2,3,4,5,6]), addOne))",
|
"addOne(t)=t+1; toList(mapSamples(fromSamples([1,2,3,4,5,6]), addOne))",
|
||||||
"Ok([2,3,4,5,6,7])",
|
"Ok([2,3,4,5,6,7])",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
19
packages/squiggle-lang/__tests__/Reducer/Reducer_Helpers.res
Normal file
19
packages/squiggle-lang/__tests__/Reducer/Reducer_Helpers.res
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
module ExpressionT = Reducer_Expression_T
|
||||||
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
|
module ErrorValue = Reducer_ErrorValue
|
||||||
|
module Bindings = Reducer_Category_Bindings
|
||||||
|
|
||||||
|
let removeDefaults = (ev: ExpressionT.expressionValue): ExpressionT.expressionValue =>
|
||||||
|
switch ev {
|
||||||
|
| EvRecord(extbindings) => {
|
||||||
|
let bindings: Bindings.t = Bindings.fromRecord(extbindings)
|
||||||
|
let keys = Js.Dict.keys(Reducer.defaultExternalBindings)
|
||||||
|
Belt.Map.String.keep(bindings, (key, _value) => {
|
||||||
|
let removeThis = Js.Array2.includes(keys, key)
|
||||||
|
!removeThis
|
||||||
|
})->Bindings.toExpressionValue
|
||||||
|
}
|
||||||
|
| value => value
|
||||||
|
}
|
||||||
|
|
||||||
|
let rRemoveDefaults = r => Belt.Result.map(r, ev => removeDefaults(ev))
|
|
@ -1,26 +1,5 @@
|
||||||
module Parse = Reducer_Peggy_Parse
|
|
||||||
module Result = Belt.Result
|
|
||||||
|
|
||||||
open Jest
|
open Jest
|
||||||
open Expect
|
open Reducer_Peggy_TestHelpers
|
||||||
|
|
||||||
let expectParseToBe = (expr, answer) =>
|
|
||||||
Parse.parse(expr)->Parse.toStringResult->expect->toBe(answer)
|
|
||||||
|
|
||||||
let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
|
|
||||||
module MySkip = {
|
|
||||||
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
|
|
||||||
let testDescriptionParse = (desc, expr, answer) =>
|
|
||||||
Skip.test(desc, () => expectParseToBe(expr, answer))
|
|
||||||
}
|
|
||||||
|
|
||||||
module MyOnly = {
|
|
||||||
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
|
||||||
let testDescriptionParse = (desc, expr, answer) =>
|
|
||||||
Only.test(desc, () => expectParseToBe(expr, answer))
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Peggy parse", () => {
|
describe("Peggy parse", () => {
|
||||||
describe("float", () => {
|
describe("float", () => {
|
||||||
|
@ -253,8 +232,12 @@ describe("Peggy parse", () => {
|
||||||
})
|
})
|
||||||
describe("unit", () => {
|
describe("unit", () => {
|
||||||
testParse("1m", "{(::fromUnit_m 1)}")
|
testParse("1m", "{(::fromUnit_m 1)}")
|
||||||
|
testParse("1M", "{(::fromUnit_M 1)}")
|
||||||
testParse("1m+2cm", "{(::add (::fromUnit_m 1) (::fromUnit_cm 2))}")
|
testParse("1m+2cm", "{(::add (::fromUnit_m 1) (::fromUnit_cm 2))}")
|
||||||
})
|
})
|
||||||
|
describe("Module", () => {
|
||||||
|
testParse("Math.pi", "{(::$_atIndex_$ @Math 'pi')}")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("parsing new line", () => {
|
describe("parsing new line", () => {
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
open Jest
|
||||||
|
open Reducer_Peggy_TestHelpers
|
||||||
|
|
||||||
|
describe("Peggy parse type", () => {
|
||||||
|
describe("type of", () => {
|
||||||
|
testParse("p: number", "{(::$_typeOf_$ :p #number)}")
|
||||||
|
})
|
||||||
|
describe("type alias", () => {
|
||||||
|
testParse("type index=number", "{(::$_typeAlias_$ #index #number)}")
|
||||||
|
})
|
||||||
|
describe("type or", () => {
|
||||||
|
testParse(
|
||||||
|
"answer: number|string",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string))))}",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("type function", () => {
|
||||||
|
testParse(
|
||||||
|
"f: number=>number=>number",
|
||||||
|
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("high priority modifier", () => {
|
||||||
|
testParse(
|
||||||
|
"answer: number<-min<-max(100)|string",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}",
|
||||||
|
)
|
||||||
|
testParse(
|
||||||
|
"answer: number<-memberOf([1,3,5])",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("low priority modifier", () => {
|
||||||
|
testParse(
|
||||||
|
"answer: number | string $ opaque",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("type array", () => {
|
||||||
|
testParse("answer: [number]", "{(::$_typeOf_$ :answer (::$_typeArray_$ #number))}")
|
||||||
|
})
|
||||||
|
describe("type record", () => {
|
||||||
|
testParse(
|
||||||
|
"answer: {a: number, b: string}",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string))))}",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("type constructor", () => {
|
||||||
|
testParse(
|
||||||
|
"answer: Age(number)",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number))))}",
|
||||||
|
)
|
||||||
|
testParse(
|
||||||
|
"answer: Complex(number, number)",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number))))}",
|
||||||
|
)
|
||||||
|
testParse(
|
||||||
|
"answer: Person({age: number, name: string})",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ ((::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))))}",
|
||||||
|
)
|
||||||
|
testParse(
|
||||||
|
"weekend: Saturday | Sunday",
|
||||||
|
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("type paranthesis", () => {
|
||||||
|
//$ is introduced to avoid paranthesis
|
||||||
|
testParse(
|
||||||
|
"answer: (number|string)<-opaque",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("squiggle expressions in type modifiers", () => {
|
||||||
|
testParse(
|
||||||
|
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
|
||||||
|
"{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,51 @@
|
||||||
|
module Expression = Reducer_Expression
|
||||||
|
module ExpressionT = Reducer_Expression_T
|
||||||
|
module ExpressionValue = ReducerInterface_ExpressionValue
|
||||||
|
module Parse = Reducer_Peggy_Parse
|
||||||
|
module Result = Belt.Result
|
||||||
|
module ToExpression = Reducer_Peggy_ToExpression
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
|
||||||
|
let expectParseToBe = (expr, answer) =>
|
||||||
|
Parse.parse(expr)->Parse.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
|
let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
||||||
|
|
||||||
|
let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
|
||||||
|
let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode)
|
||||||
|
let a1 = rExpr->ExpressionT.toStringResultOkless
|
||||||
|
|
||||||
|
if v == "_" {
|
||||||
|
a1->expect->toBe(answer)
|
||||||
|
} else {
|
||||||
|
let a2 =
|
||||||
|
rExpr
|
||||||
|
->Result.flatMap(expr =>
|
||||||
|
Expression.reduceExpression(
|
||||||
|
expr,
|
||||||
|
ReducerInterface_StdLib.internalStdLib,
|
||||||
|
ExpressionValue.defaultEnvironment,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->Reducer_Helpers.rRemoveDefaults
|
||||||
|
->ExpressionValue.toStringResultOkless
|
||||||
|
(a1, a2)->expect->toEqual((answer, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let testToExpression = (expr, answer, ~v="_", ()) =>
|
||||||
|
test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
||||||
|
|
||||||
|
module MyOnly = {
|
||||||
|
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
||||||
|
let testToExpression = (expr, answer, ~v="_", ()) =>
|
||||||
|
Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
module MySkip = {
|
||||||
|
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
||||||
|
let testToExpression = (expr, answer, ~v="_", ()) =>
|
||||||
|
Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
||||||
|
}
|
|
@ -1,181 +1,150 @@
|
||||||
module Expression = Reducer_Expression
|
|
||||||
module ExpressionT = Reducer_Expression_T
|
|
||||||
module ExpressionValue = ReducerInterface_ExpressionValue
|
|
||||||
module Parse = Reducer_Peggy_Parse
|
|
||||||
module ToExpression = Reducer_Peggy_ToExpression
|
|
||||||
module Result = Belt.Result
|
|
||||||
|
|
||||||
open Jest
|
open Jest
|
||||||
open Expect
|
open Reducer_Peggy_TestHelpers
|
||||||
|
|
||||||
let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
|
|
||||||
let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode)
|
|
||||||
let a1 = rExpr->ExpressionT.toStringResultOkless
|
|
||||||
if v == "_" {
|
|
||||||
a1->expect->toBe(answer)
|
|
||||||
} else {
|
|
||||||
let a2 =
|
|
||||||
rExpr
|
|
||||||
->Result.flatMap(expr =>
|
|
||||||
Expression.reduceExpression(expr, Belt.Map.String.empty, ExpressionValue.defaultEnvironment)
|
|
||||||
)
|
|
||||||
->ExpressionValue.toStringResultOkless
|
|
||||||
(a1, a2)->expect->toEqual((answer, v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let testToExpression = (expr, answer, ~v="_", ()) =>
|
|
||||||
test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
|
||||||
|
|
||||||
module MySkip = {
|
|
||||||
let testToExpression = (expr, answer, ~v="_", ()) =>
|
|
||||||
Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
|
||||||
}
|
|
||||||
|
|
||||||
module MyOnly = {
|
|
||||||
let testToExpression = (expr, answer, ~v="_", ()) =>
|
|
||||||
Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Peggy to Expression", () => {
|
describe("Peggy to Expression", () => {
|
||||||
describe("literals operators parenthesis", () => {
|
describe("literals operators parenthesis", () => {
|
||||||
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement
|
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement
|
||||||
testToExpression("1", "(:$$_block_$$ 1)", ~v="1", ())
|
testToExpression("1", "{1}", ~v="1", ())
|
||||||
testToExpression("'hello'", "(:$$_block_$$ 'hello')", ~v="'hello'", ())
|
testToExpression("'hello'", "{'hello'}", ~v="'hello'", ())
|
||||||
testToExpression("true", "(:$$_block_$$ true)", ~v="true", ())
|
testToExpression("true", "{true}", ~v="true", ())
|
||||||
testToExpression("1+2", "(:$$_block_$$ (:add 1 2))", ~v="3", ())
|
testToExpression("1+2", "{(:add 1 2)}", ~v="3", ())
|
||||||
testToExpression("add(1,2)", "(:$$_block_$$ (:add 1 2))", ~v="3", ())
|
testToExpression("add(1,2)", "{(:add 1 2)}", ~v="3", ())
|
||||||
testToExpression("(1)", "(:$$_block_$$ 1)", ())
|
testToExpression("(1)", "{1}", ())
|
||||||
testToExpression("(1+2)", "(:$$_block_$$ (:add 1 2))", ())
|
testToExpression("(1+2)", "{(:add 1 2)}", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("unary", () => {
|
describe("unary", () => {
|
||||||
testToExpression("-1", "(:$$_block_$$ (:unaryMinus 1))", ~v="-1", ())
|
testToExpression("-1", "{(:unaryMinus 1)}", ~v="-1", ())
|
||||||
testToExpression("!true", "(:$$_block_$$ (:not true))", ~v="false", ())
|
testToExpression("!true", "{(:not true)}", ~v="false", ())
|
||||||
testToExpression("1 + -1", "(:$$_block_$$ (:add 1 (:unaryMinus 1)))", ~v="0", ())
|
testToExpression("1 + -1", "{(:add 1 (:unaryMinus 1))}", ~v="0", ())
|
||||||
testToExpression("-a[0]", "(:$$_block_$$ (:unaryMinus (:$_atIndex_$ :a 0)))", ())
|
testToExpression("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("multi-line", () => {
|
describe("multi-line", () => {
|
||||||
testToExpression("x=1; 2", "(:$$_block_$$ (:$_let_$ :x (:$$_block_$$ 1)) 2)", ~v="2", ())
|
testToExpression("x=1; 2", "{(:$_let_$ :x {1}); 2}", ~v="2", ())
|
||||||
testToExpression(
|
testToExpression("x=1; y=2", "{(:$_let_$ :x {1}); (:$_let_$ :y {2})}", ~v="{x: 1,y: 2}", ())
|
||||||
"x=1; y=2",
|
|
||||||
"(:$$_block_$$ (:$_let_$ :x (:$$_block_$$ 1)) (:$_let_$ :y (:$$_block_$$ 2)))",
|
|
||||||
~v="{x: 1,y: 2}",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("variables", () => {
|
describe("variables", () => {
|
||||||
testToExpression("x = 1", "(:$$_block_$$ (:$_let_$ :x (:$$_block_$$ 1)))", ~v="{x: 1}", ())
|
testToExpression("x = 1", "{(:$_let_$ :x {1})}", ~v="{x: 1}", ())
|
||||||
testToExpression("x", "(:$$_block_$$ :x)", ~v=":x", ()) //TODO: value should return error
|
testToExpression("x", "{:x}", ~v=":x", ()) //TODO: value should return error
|
||||||
testToExpression("x = 1; x", "(:$$_block_$$ (:$_let_$ :x (:$$_block_$$ 1)) :x)", ~v="1", ())
|
testToExpression("x = 1; x", "{(:$_let_$ :x {1}); :x}", ~v="1", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("functions", () => {
|
describe("functions", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"identity(x) = x",
|
"identity(x) = x",
|
||||||
"(:$$_block_$$ (:$_let_$ :identity (:$$_lambda_$$ [x] (:$$_block_$$ :x))))",
|
"{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x}))}",
|
||||||
~v="{identity: lambda(x=>internal code)}",
|
~v="{identity: lambda(x=>internal code)}",
|
||||||
(),
|
(),
|
||||||
) // Function definitions become lambda assignments
|
) // Function definitions become lambda assignments
|
||||||
testToExpression("identity(x)", "(:$$_block_$$ (:identity :x))", ()) // Note value returns error properly
|
testToExpression("identity(x)", "{(:identity :x)}", ()) // Note value returns error properly
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"f(x) = x> 2 ? 0 : 1; f(3)",
|
"f(x) = x> 2 ? 0 : 1; f(3)",
|
||||||
"(:$$_block_$$ (:$_let_$ :f (:$$_lambda_$$ [x] (:$$_block_$$ (:$$_ternary_$$ (:larger :x 2) 0 1)))) (:f 3))",
|
"{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:f 3)}",
|
||||||
~v="0",
|
~v="0",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("arrays", () => {
|
describe("arrays", () => {
|
||||||
testToExpression("[]", "(:$$_block_$$ (:$_constructArray_$ ()))", ~v="[]", ())
|
testToExpression("[]", "{(:$_constructArray_$ ())}", ~v="[]", ())
|
||||||
testToExpression("[0, 1, 2]", "(:$$_block_$$ (:$_constructArray_$ (0 1 2)))", ~v="[0,1,2]", ())
|
testToExpression("[0, 1, 2]", "{(:$_constructArray_$ (0 1 2))}", ~v="[0,1,2]", ())
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"['hello', 'world']",
|
"['hello', 'world']",
|
||||||
"(:$$_block_$$ (:$_constructArray_$ ('hello' 'world')))",
|
"{(:$_constructArray_$ ('hello' 'world'))}",
|
||||||
~v="['hello','world']",
|
~v="['hello','world']",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression("([0,1,2])[1]", "{(:$_atIndex_$ (:$_constructArray_$ (0 1 2)) 1)}", ~v="1", ())
|
||||||
"([0,1,2])[1]",
|
|
||||||
"(:$$_block_$$ (:$_atIndex_$ (:$_constructArray_$ (0 1 2)) 1))",
|
|
||||||
~v="1",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("records", () => {
|
describe("records", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"{a: 1, b: 2}",
|
"{a: 1, b: 2}",
|
||||||
"(:$$_block_$$ (:$_constructRecord_$ (('a' 1) ('b' 2))))",
|
"{(:$_constructRecord_$ (('a' 1) ('b' 2)))}",
|
||||||
~v="{a: 1,b: 2}",
|
~v="{a: 1,b: 2}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"{1+0: 1, 2+0: 2}",
|
"{1+0: 1, 2+0: 2}",
|
||||||
"(:$$_block_$$ (:$_constructRecord_$ (((:add 1 0) 1) ((:add 2 0) 2))))",
|
"{(:$_constructRecord_$ (((:add 1 0) 1) ((:add 2 0) 2)))}",
|
||||||
(),
|
(),
|
||||||
) // key can be any expression
|
) // key can be any expression
|
||||||
testToExpression("record.property", "(:$$_block_$$ (:$_atIndex_$ :record 'property'))", ())
|
testToExpression("record.property", "{(:$_atIndex_$ :record 'property')}", ())
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"record={property: 1}; record.property",
|
"record={property: 1}; record.property",
|
||||||
"(:$$_block_$$ (:$_let_$ :record (:$$_block_$$ (:$_constructRecord_$ (('property' 1))))) (:$_atIndex_$ :record 'property'))",
|
"{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_atIndex_$ :record 'property')}",
|
||||||
~v="1",
|
~v="1",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("comments", () => {
|
describe("comments", () => {
|
||||||
testToExpression("1 # This is a line comment", "(:$$_block_$$ 1)", ~v="1", ())
|
testToExpression("1 # This is a line comment", "{1}", ~v="1", ())
|
||||||
testToExpression("1 // This is a line comment", "(:$$_block_$$ 1)", ~v="1", ())
|
testToExpression("1 // This is a line comment", "{1}", ~v="1", ())
|
||||||
testToExpression("1 /* This is a multi line comment */", "(:$$_block_$$ 1)", ~v="1", ())
|
testToExpression("1 /* This is a multi line comment */", "{1}", ~v="1", ())
|
||||||
testToExpression("/* This is a multi line comment */ 1", "(:$$_block_$$ 1)", ~v="1", ())
|
testToExpression("/* This is a multi line comment */ 1", "{1}", ~v="1", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("ternary operator", () => {
|
describe("ternary operator", () => {
|
||||||
testToExpression("true ? 1 : 0", "(:$$_block_$$ (:$$_ternary_$$ true 1 0))", ~v="1", ())
|
testToExpression("true ? 1 : 0", "{(:$$_ternary_$$ true 1 0)}", ~v="1", ())
|
||||||
testToExpression("false ? 1 : 0", "(:$$_block_$$ (:$$_ternary_$$ false 1 0))", ~v="0", ())
|
testToExpression("false ? 1 : 0", "{(:$$_ternary_$$ false 1 0)}", ~v="0", ())
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"true ? 1 : false ? 2 : 0",
|
"true ? 1 : false ? 2 : 0",
|
||||||
"(:$$_block_$$ (:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0)))",
|
"{(:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0))}",
|
||||||
~v="1",
|
~v="1",
|
||||||
(),
|
(),
|
||||||
) // nested ternary
|
) // nested ternary
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"false ? 1 : false ? 2 : 0",
|
"false ? 1 : false ? 2 : 0",
|
||||||
"(:$$_block_$$ (:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0)))",
|
"{(:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0))}",
|
||||||
~v="0",
|
~v="0",
|
||||||
(),
|
(),
|
||||||
) // nested ternary
|
) // nested ternary
|
||||||
|
describe("ternary bindings", () => {
|
||||||
|
testToExpression(
|
||||||
|
// expression binding
|
||||||
|
"f(a) = a > 5 ? 1 : 0; f(6)",
|
||||||
|
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:f 6)}",
|
||||||
|
~v="1",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
testToExpression(
|
||||||
|
// when true binding
|
||||||
|
"f(a) = a > 5 ? a : 0; f(6)",
|
||||||
|
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:f 6)}",
|
||||||
|
~v="6",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
testToExpression(
|
||||||
|
// when false binding
|
||||||
|
"f(a) = a < 5 ? 1 : a; f(6)",
|
||||||
|
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:f 6)}",
|
||||||
|
~v="6",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("if then else", () => {
|
describe("if then else", () => {
|
||||||
testToExpression(
|
testToExpression("if true then 2 else 3", "{(:$$_ternary_$$ true {2} {3})}", ())
|
||||||
"if true then 2 else 3",
|
testToExpression("if true then {2} else {3}", "{(:$$_ternary_$$ true {2} {3})}", ())
|
||||||
"(:$$_block_$$ (:$$_ternary_$$ true (:$$_block_$$ 2) (:$$_block_$$ 3)))",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
testToExpression(
|
|
||||||
"if true then {2} else {3}",
|
|
||||||
"(:$$_block_$$ (:$$_ternary_$$ true (:$$_block_$$ 2) (:$$_block_$$ 3)))",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"if false then {2} else if false then {4} else {5}",
|
"if false then {2} else if false then {4} else {5}",
|
||||||
"(:$$_block_$$ (:$$_ternary_$$ false (:$$_block_$$ 2) (:$$_ternary_$$ false (:$$_block_$$ 4) (:$$_block_$$ 5))))",
|
"{(:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5}))}",
|
||||||
(),
|
(),
|
||||||
) //nested if
|
) //nested if
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("pipe", () => {
|
describe("pipe", () => {
|
||||||
testToExpression("1 -> add(2)", "(:$$_block_$$ (:add 1 2))", ~v="3", ())
|
testToExpression("1 -> add(2)", "{(:add 1 2)}", ~v="3", ())
|
||||||
testToExpression("-1 -> add(2)", "(:$$_block_$$ (:add (:unaryMinus 1) 2))", ~v="1", ()) // note that unary has higher priority naturally
|
testToExpression("-1 -> add(2)", "{(:add (:unaryMinus 1) 2)}", ~v="1", ()) // note that unary has higher priority naturally
|
||||||
testToExpression("1 -> add(2) * 3", "(:$$_block_$$ (:multiply (:add 1 2) 3))", ~v="9", ())
|
testToExpression("1 -> add(2) * 3", "{(:multiply (:add 1 2) 3)}", ~v="9", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("elixir pipe", () => {
|
describe("elixir pipe", () => {
|
||||||
testToExpression("1 |> add(2)", "(:$$_block_$$ (:add 1 2))", ~v="3", ())
|
testToExpression("1 |> add(2)", "{(:add 1 2)}", ~v="3", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
// see testParse for priorities of to and credibleIntervalToDistribution
|
// see testParse for priorities of to and credibleIntervalToDistribution
|
||||||
|
@ -185,36 +154,35 @@ describe("Peggy to Expression", () => {
|
||||||
// Like lambdas they have a local scope.
|
// Like lambdas they have a local scope.
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"y=99; x={y=1; y}",
|
"y=99; x={y=1; y}",
|
||||||
"(:$$_block_$$ (:$_let_$ :y (:$$_block_$$ 99)) (:$_let_$ :x (:$$_block_$$ (:$_let_$ :y (:$$_block_$$ 1)) :y)))",
|
"{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y})}",
|
||||||
~v="{x: 1,y: 99}",
|
~v="{x: 1,y: 99}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("lambda", () => {
|
describe("lambda", () => {
|
||||||
testToExpression(
|
testToExpression("{|x| x}", "{(:$$_lambda_$$ [x] {:x})}", ~v="lambda(x=>internal code)", ())
|
||||||
"{|x| x}",
|
|
||||||
"(:$$_block_$$ (:$$_lambda_$$ [x] (:$$_block_$$ :x)))",
|
|
||||||
~v="lambda(x=>internal code)",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"f={|x| x}",
|
"f={|x| x}",
|
||||||
"(:$$_block_$$ (:$_let_$ :f (:$$_block_$$ (:$$_lambda_$$ [x] (:$$_block_$$ :x)))))",
|
"{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})})}",
|
||||||
~v="{f: lambda(x=>internal code)}",
|
~v="{f: lambda(x=>internal code)}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"f(x)=x",
|
"f(x)=x",
|
||||||
"(:$$_block_$$ (:$_let_$ :f (:$$_lambda_$$ [x] (:$$_block_$$ :x))))",
|
"{(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))}",
|
||||||
~v="{f: lambda(x=>internal code)}",
|
~v="{f: lambda(x=>internal code)}",
|
||||||
(),
|
(),
|
||||||
) // Function definitions are lambda assignments
|
) // Function definitions are lambda assignments
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"f(x)=x ? 1 : 0",
|
"f(x)=x ? 1 : 0",
|
||||||
"(:$$_block_$$ (:$_let_$ :f (:$$_lambda_$$ [x] (:$$_block_$$ (:$$_ternary_$$ :x 1 0)))))",
|
"{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ :x 1 0)}))}",
|
||||||
~v="{f: lambda(x=>internal code)}",
|
~v="{f: lambda(x=>internal code)}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("module", () => {
|
||||||
|
testToExpression("Math.pi", "{(:$_atIndex_$ :Math 'pi')}", ~v="3.141592653589793", ())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
open Jest
|
||||||
|
open Reducer_Peggy_TestHelpers
|
||||||
|
|
||||||
|
describe("Peggy Types to Expression", () => {
|
||||||
|
describe("type of", () => {
|
||||||
|
testToExpression(
|
||||||
|
"p: number",
|
||||||
|
"{(:$_typeOf_$ :p #number)}",
|
||||||
|
~v="{_typeReferences_: {p: #number}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("type alias", () => {
|
||||||
|
testToExpression(
|
||||||
|
"type index=number",
|
||||||
|
"{(:$_typeAlias_$ #index #number)}",
|
||||||
|
~v="{_typeAliases_: {index: #number}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("type or", () => {
|
||||||
|
testToExpression(
|
||||||
|
"answer: number|string|distribution",
|
||||||
|
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution))))}",
|
||||||
|
~v="{_typeReferences_: {answer: {typeTag: 'typeOr',typeOr: [#number,#string,#distribution]}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("type function", () => {
|
||||||
|
testToExpression(
|
||||||
|
"f: number=>number=>number",
|
||||||
|
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number))))}",
|
||||||
|
~v="{_typeReferences_: {f: {typeTag: 'typeFunction',inputs: [#number,#number],output: #number}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
testToExpression(
|
||||||
|
"f: number=>number",
|
||||||
|
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number))))}",
|
||||||
|
~v="{_typeReferences_: {f: {typeTag: 'typeFunction',inputs: [#number],output: #number}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("high priority modifier", () => {
|
||||||
|
testToExpression(
|
||||||
|
"answer: number<-min(1)<-max(100)|string",
|
||||||
|
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}",
|
||||||
|
~v="{_typeReferences_: {answer: {typeTag: 'typeOr',typeOr: [{typeTag: 'typeIdentifier',typeIdentifier: #number,min: 1,max: 100},#string]}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
testToExpression(
|
||||||
|
"answer: number<-memberOf([1,3,5])",
|
||||||
|
"{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ (1 3 5))))}",
|
||||||
|
~v="{_typeReferences_: {answer: {typeTag: 'typeIdentifier',typeIdentifier: #number,memberOf: [1,3,5]}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
testToExpression(
|
||||||
|
"answer: number<-min(1)",
|
||||||
|
"{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1))}",
|
||||||
|
~v="{_typeReferences_: {answer: {typeTag: 'typeIdentifier',typeIdentifier: #number,min: 1}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
testToExpression(
|
||||||
|
"answer: number<-max(10)",
|
||||||
|
"{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10))}",
|
||||||
|
~v="{_typeReferences_: {answer: {typeTag: 'typeIdentifier',typeIdentifier: #number,max: 10}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
testToExpression(
|
||||||
|
"answer: number<-min(1)<-max(10)",
|
||||||
|
"{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10))}",
|
||||||
|
~v="{_typeReferences_: {answer: {typeTag: 'typeIdentifier',typeIdentifier: #number,min: 1,max: 10}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
testToExpression(
|
||||||
|
"answer: number<-max(10)<-min(1)",
|
||||||
|
"{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1))}",
|
||||||
|
~v="{_typeReferences_: {answer: {typeTag: 'typeIdentifier',typeIdentifier: #number,max: 10,min: 1}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("low priority modifier", () => {
|
||||||
|
testToExpression(
|
||||||
|
"answer: number | string $ opaque",
|
||||||
|
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}",
|
||||||
|
~v="{_typeReferences_: {answer: {typeTag: 'typeOr',typeOr: [#number,#string],opaque: true}}}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
describe("squiggle expressions in type modifiers", () => {
|
||||||
|
testToExpression(
|
||||||
|
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
|
||||||
|
"{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}",
|
||||||
|
~v="{_typeAliases_: {odds: {typeTag: 'typeIdentifier',typeIdentifier: #number,memberOf: [1,3,5,7,9]}},odds1: [1,3,5],odds2: [7,9]}",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// TODO: type bindings. Type bindings are not yet supported.
|
||||||
|
// TODO: type constructor
|
||||||
|
})
|
|
@ -1,6 +1,7 @@
|
||||||
module ExpressionT = Reducer_Expression_T
|
module ExpressionT = Reducer_Expression_T
|
||||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
module ErrorValue = Reducer_ErrorValue
|
module ErrorValue = Reducer_ErrorValue
|
||||||
|
module Bindings = Reducer_Category_Bindings
|
||||||
|
|
||||||
open Jest
|
open Jest
|
||||||
open Expect
|
open Expect
|
||||||
|
@ -17,13 +18,18 @@ let expectParseToBe = (expr: string, answer: string) =>
|
||||||
Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer)
|
Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
let expectEvalToBe = (expr: string, answer: string) =>
|
let expectEvalToBe = (expr: string, answer: string) =>
|
||||||
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
Reducer.evaluate(expr)
|
||||||
|
->Reducer_Helpers.rRemoveDefaults
|
||||||
|
->ExpressionValue.toStringResult
|
||||||
|
->expect
|
||||||
|
->toBe(answer)
|
||||||
|
|
||||||
let expectEvalError = (expr: string) =>
|
let expectEvalError = (expr: string) =>
|
||||||
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toMatch("Error\(")
|
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toMatch("Error\(")
|
||||||
|
|
||||||
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
|
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
|
||||||
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
||||||
|
->Reducer_Helpers.rRemoveDefaults
|
||||||
->ExpressionValue.toStringResult
|
->ExpressionValue.toStringResult
|
||||||
->expect
|
->expect
|
||||||
->toBe(answer)
|
->toBe(answer)
|
||||||
|
|
|
@ -5,7 +5,7 @@ open Reducer_TestHelpers
|
||||||
describe("Eval with Bindings", () => {
|
describe("Eval with Bindings", () => {
|
||||||
testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)")
|
testEvalBindingsToBe("x", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(1)")
|
||||||
testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
testEvalBindingsToBe("x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
||||||
testParseToBe("y = x+1; y", "Ok((:$$_block_$$ (:$_let_$ :y (:$$_block_$$ (:add :x 1))) :y))")
|
testParseToBe("y = x+1; y", "Ok({(:$_let_$ :y {(:add :x 1)}); :y})")
|
||||||
testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
testEvalBindingsToBe("y = x+1; y", list{("x", ExpressionValue.EvNumber(1.))}, "Ok(2)")
|
||||||
testEvalBindingsToBe("y = x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 2})")
|
testEvalBindingsToBe("y = x+1", list{("x", ExpressionValue.EvNumber(1.))}, "Ok({x: 1,y: 2})")
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,11 +2,8 @@ open Jest
|
||||||
open Reducer_TestHelpers
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
describe("Parse function assignment", () => {
|
describe("Parse function assignment", () => {
|
||||||
testParseToBe("f(x)=x", "Ok((:$$_block_$$ (:$_let_$ :f (:$$_lambda_$$ [x] (:$$_block_$$ :x)))))")
|
testParseToBe("f(x)=x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))})")
|
||||||
testParseToBe(
|
testParseToBe("f(x)=2*x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)}))})")
|
||||||
"f(x)=2*x",
|
|
||||||
"Ok((:$$_block_$$ (:$_let_$ :f (:$$_lambda_$$ [x] (:$$_block_$$ (:multiply 2 :x))))))",
|
|
||||||
)
|
|
||||||
//MathJs does not allow blocks in function definitions
|
//MathJs does not allow blocks in function definitions
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ describe("call and bindings", () => {
|
||||||
)
|
)
|
||||||
testParseToBe(
|
testParseToBe(
|
||||||
"f=99; g(x)=f; g(2)",
|
"f=99; g(x)=f; g(2)",
|
||||||
"Ok((:$$_block_$$ (:$_let_$ :f (:$$_block_$$ 99)) (:$_let_$ :g (:$$_lambda_$$ [x] (:$$_block_$$ :f))) (:g 2)))",
|
"Ok({(:$_let_$ :f {99}); (:$_let_$ :g (:$$_lambda_$$ [x] {:f})); (:g 2)})",
|
||||||
)
|
)
|
||||||
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
|
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
|
||||||
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
|
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe("map reduce", () => {
|
||||||
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; reduce(arr, 0, change)", "Ok(15)")
|
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; reduce(arr, 0, change)", "Ok(15)")
|
||||||
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; reduceReverse(arr, 0, change)", "Ok(9)")
|
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; reduceReverse(arr, 0, change)", "Ok(9)")
|
||||||
testEvalToBe("arr=[1,2,3]; reverse(arr)", "Ok([3,2,1])")
|
testEvalToBe("arr=[1,2,3]; reverse(arr)", "Ok([3,2,1])")
|
||||||
testEvalToBe("check(x)=(x==2);arr=[1,2,3]; keep(arr,check)", "Ok([2])")
|
testEvalToBe("check(x)=(x==2);arr=[1,2,3]; filter(arr,check)", "Ok([2])")
|
||||||
})
|
})
|
||||||
|
|
||||||
Skip.describe("map reduce (sam)", () => {
|
Skip.describe("map reduce (sam)", () => {
|
||||||
|
|
|
@ -2,7 +2,7 @@ open Jest
|
||||||
open Reducer_TestHelpers
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
describe("Parse ternary operator", () => {
|
describe("Parse ternary operator", () => {
|
||||||
testParseToBe("true ? 'YES' : 'NO'", "Ok((:$$_block_$$ (:$$_ternary_$$ true 'YES' 'NO')))")
|
testParseToBe("true ? 'YES' : 'NO'", "Ok({(:$$_ternary_$$ true 'YES' 'NO')})")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Evaluate ternary operator", () => {
|
describe("Evaluate ternary operator", () => {
|
||||||
|
|
|
@ -80,6 +80,7 @@ describe("eval on distribution functions", () => {
|
||||||
testEval("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)")
|
testEval("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)")
|
||||||
testEval("truncateRight(normal(5,2), 3)", "Ok(Point Set Distribution)")
|
testEval("truncateRight(normal(5,2), 3)", "Ok(Point Set Distribution)")
|
||||||
testEval("truncate(normal(5,2), 3, 8)", "Ok(Point Set Distribution)")
|
testEval("truncate(normal(5,2), 3, 8)", "Ok(Point Set Distribution)")
|
||||||
|
testEval("isNormalized(truncate(normal(5,2), 3, 8))", "Ok(true)")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("exp", () => {
|
describe("exp", () => {
|
||||||
|
@ -123,13 +124,13 @@ describe("eval on distribution functions", () => {
|
||||||
|
|
||||||
describe("parse on distribution functions", () => {
|
describe("parse on distribution functions", () => {
|
||||||
describe("power", () => {
|
describe("power", () => {
|
||||||
testParse("normal(5,2) ^ normal(5,1)", "Ok((:$$_block_$$ (:pow (:normal 5 2) (:normal 5 1))))")
|
testParse("normal(5,2) ^ normal(5,1)", "Ok({(:pow (:normal 5 2) (:normal 5 1))})")
|
||||||
testParse("3 ^ normal(5,1)", "Ok((:$$_block_$$ (:pow 3 (:normal 5 1))))")
|
testParse("3 ^ normal(5,1)", "Ok({(:pow 3 (:normal 5 1))})")
|
||||||
testParse("normal(5,2) ^ 3", "Ok((:$$_block_$$ (:pow (:normal 5 2) 3)))")
|
testParse("normal(5,2) ^ 3", "Ok({(:pow (:normal 5 2) 3)})")
|
||||||
})
|
})
|
||||||
describe("subtraction", () => {
|
describe("subtraction", () => {
|
||||||
testParse("10 - normal(5,1)", "Ok((:$$_block_$$ (:subtract 10 (:normal 5 1))))")
|
testParse("10 - normal(5,1)", "Ok({(:subtract 10 (:normal 5 1))})")
|
||||||
testParse("normal(5,1) - 10", "Ok((:$$_block_$$ (:subtract (:normal 5 1) 10)))")
|
testParse("normal(5,1) - 10", "Ok({(:subtract (:normal 5 1) 10)})")
|
||||||
})
|
})
|
||||||
describe("pointwise arithmetic expressions", () => {
|
describe("pointwise arithmetic expressions", () => {
|
||||||
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
|
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
|
||||||
|
@ -137,23 +138,14 @@ describe("parse on distribution functions", () => {
|
||||||
~skip=true,
|
~skip=true,
|
||||||
"normal(5,2) .- normal(5,1)",
|
"normal(5,2) .- normal(5,1)",
|
||||||
"Ok((:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1))))",
|
"Ok((:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1))))",
|
||||||
// TODO: !!! returns "Ok((:$$_block_$$ (:dotPow (:normal 5 2) (:normal 5 1))))"
|
// TODO: !!! returns "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})"
|
||||||
)
|
|
||||||
testParse(
|
|
||||||
"normal(5,2) .* normal(5,1)",
|
|
||||||
"Ok((:$$_block_$$ (:dotMultiply (:normal 5 2) (:normal 5 1))))",
|
|
||||||
)
|
|
||||||
testParse(
|
|
||||||
"normal(5,2) ./ normal(5,1)",
|
|
||||||
"Ok((:$$_block_$$ (:dotDivide (:normal 5 2) (:normal 5 1))))",
|
|
||||||
)
|
|
||||||
testParse(
|
|
||||||
"normal(5,2) .^ normal(5,1)",
|
|
||||||
"Ok((:$$_block_$$ (:dotPow (:normal 5 2) (:normal 5 1))))",
|
|
||||||
)
|
)
|
||||||
|
testParse("normal(5,2) .* normal(5,1)", "Ok({(:dotMultiply (:normal 5 2) (:normal 5 1))})")
|
||||||
|
testParse("normal(5,2) ./ normal(5,1)", "Ok({(:dotDivide (:normal 5 2) (:normal 5 1))})")
|
||||||
|
testParse("normal(5,2) .^ normal(5,1)", "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})")
|
||||||
})
|
})
|
||||||
describe("equality", () => {
|
describe("equality", () => {
|
||||||
testParse("5 == normal(5,2)", "Ok((:$$_block_$$ (:equal 5 (:normal 5 2))))")
|
testParse("5 == normal(5,2)", "Ok({(:equal 5 (:normal 5 2))})")
|
||||||
})
|
})
|
||||||
describe("pointwise adding two normals", () => {
|
describe("pointwise adding two normals", () => {
|
||||||
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
|
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
open Jest
|
||||||
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
|
describe("Math Library", () => {
|
||||||
|
testEvalToBe("Math.e", "Ok(2.718281828459045)")
|
||||||
|
testEvalToBe("Math.pi", "Ok(3.141592653589793)")
|
||||||
|
})
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@quri/squiggle-lang",
|
"name": "@quri/squiggle-lang",
|
||||||
"version": "0.2.9",
|
"version": "0.2.11",
|
||||||
"homepage": "https://squiggle-language.com",
|
"homepage": "https://squiggle-language.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -38,7 +38,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@stdlib/stats": "^0.0.13",
|
"@stdlib/stats": "^0.0.13",
|
||||||
"jstat": "^1.9.5",
|
"jstat": "^1.9.5",
|
||||||
"mathjs": "^10.5.2",
|
"lodash": "^4.17.21",
|
||||||
|
"mathjs": "^10.6.0",
|
||||||
"pdfast": "^0.2.0",
|
"pdfast": "^0.2.0",
|
||||||
"rescript": "^9.1.4"
|
"rescript": "^9.1.4"
|
||||||
},
|
},
|
||||||
|
@ -51,20 +52,19 @@
|
||||||
"chalk": "^5.0.1",
|
"chalk": "^5.0.1",
|
||||||
"codecov": "^3.8.3",
|
"codecov": "^3.8.3",
|
||||||
"fast-check": "^2.25.0",
|
"fast-check": "^2.25.0",
|
||||||
"gentype": "^4.3.0",
|
"gentype": "^4.4.0",
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"moduleserve": "^0.9.1",
|
"moduleserve": "^0.9.1",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"peggy": "^1.2.0",
|
"peggy": "^2.0.1",
|
||||||
"reanalyze": "^2.19.0",
|
"reanalyze": "^2.23.0",
|
||||||
"rescript-fast-check": "^1.1.1",
|
"rescript-fast-check": "^1.1.1",
|
||||||
"ts-jest": "^27.1.4",
|
"ts-jest": "^27.1.4",
|
||||||
"ts-loader": "^9.3.0",
|
"ts-loader": "^9.3.0",
|
||||||
"ts-node": "^10.8.0",
|
"ts-node": "^10.8.1",
|
||||||
"typescript": "^4.6.3",
|
"typescript": "^4.7.3",
|
||||||
"webpack": "^5.72.1",
|
"webpack": "^5.73.0",
|
||||||
"webpack-cli": "^4.9.2"
|
"webpack-cli": "^4.10.0"
|
||||||
},
|
},
|
||||||
"source": "./src/js/index.ts",
|
"source": "./src/js/index.ts",
|
||||||
"main": "./dist/src/js/index.js",
|
"main": "./dist/src/js/index.js",
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
import { result, resultMap, Ok } from "./types";
|
import { result, resultMap, Ok } from "./types";
|
||||||
import {
|
import {
|
||||||
Constructors_mean,
|
Constructors_mean,
|
||||||
|
Constructors_stdev,
|
||||||
Constructors_sample,
|
Constructors_sample,
|
||||||
Constructors_pdf,
|
Constructors_pdf,
|
||||||
Constructors_cdf,
|
Constructors_cdf,
|
||||||
|
@ -69,6 +70,10 @@ export class Distribution {
|
||||||
return Constructors_mean({ env: this.env }, this.t);
|
return Constructors_mean({ env: this.env }, this.t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stdev(): result<number, distributionError> {
|
||||||
|
return Constructors_stdev({ env: this.env }, this.t);
|
||||||
|
}
|
||||||
|
|
||||||
sample(): result<number, distributionError> {
|
sample(): result<number, distributionError> {
|
||||||
return Constructors_sample({ env: this.env }, this.t);
|
return Constructors_sample({ env: this.env }, this.t);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
import * as _ from "lodash";
|
import * as _ from "lodash";
|
||||||
import {
|
import type {
|
||||||
environment,
|
environment,
|
||||||
|
expressionValue,
|
||||||
|
externalBindings,
|
||||||
|
errorValue,
|
||||||
|
} from "../rescript/TypescriptInterface.gen";
|
||||||
|
import {
|
||||||
defaultEnvironment,
|
defaultEnvironment,
|
||||||
evaluatePartialUsingExternalBindings,
|
evaluatePartialUsingExternalBindings,
|
||||||
evaluateUsingOptions,
|
evaluateUsingOptions,
|
||||||
externalBindings,
|
|
||||||
expressionValue,
|
|
||||||
errorValue,
|
|
||||||
foreignFunctionInterface,
|
foreignFunctionInterface,
|
||||||
} from "../rescript/TypescriptInterface.gen";
|
} from "../rescript/TypescriptInterface.gen";
|
||||||
export {
|
export {
|
||||||
makeSampleSetDist,
|
makeSampleSetDist,
|
||||||
errorValueToString,
|
errorValueToString,
|
||||||
distributionErrorToString,
|
distributionErrorToString,
|
||||||
|
} from "../rescript/TypescriptInterface.gen";
|
||||||
|
export type {
|
||||||
distributionError,
|
distributionError,
|
||||||
|
declarationArg,
|
||||||
|
declaration,
|
||||||
} from "../rescript/TypescriptInterface.gen";
|
} from "../rescript/TypescriptInterface.gen";
|
||||||
export type { errorValue, externalBindings as bindings, jsImports };
|
export type { errorValue, externalBindings as bindings, jsImports };
|
||||||
import {
|
import {
|
||||||
|
@ -28,16 +34,8 @@ import {
|
||||||
import { result, resultMap, tag, tagged } from "./types";
|
import { result, resultMap, tag, tagged } from "./types";
|
||||||
import { Distribution, shape } from "./distribution";
|
import { Distribution, shape } from "./distribution";
|
||||||
|
|
||||||
export {
|
export { Distribution, resultMap, defaultEnvironment };
|
||||||
Distribution,
|
export type { result, shape, environment, lambdaValue, squiggleExpression };
|
||||||
squiggleExpression,
|
|
||||||
result,
|
|
||||||
resultMap,
|
|
||||||
shape,
|
|
||||||
lambdaValue,
|
|
||||||
environment,
|
|
||||||
defaultEnvironment,
|
|
||||||
};
|
|
||||||
|
|
||||||
export let defaultSamplingInputs: environment = {
|
export let defaultSamplingInputs: environment = {
|
||||||
sampleCount: 10000,
|
sampleCount: 10000,
|
||||||
|
@ -185,5 +183,20 @@ function createTsExport(
|
||||||
return tag("date", x.value);
|
return tag("date", x.value);
|
||||||
case "EvTimeDuration":
|
case "EvTimeDuration":
|
||||||
return tag("timeDuration", x.value);
|
return tag("timeDuration", x.value);
|
||||||
|
case "EvDeclaration":
|
||||||
|
return tag("lambdaDeclaration", x.value);
|
||||||
|
case "EvTypeIdentifier":
|
||||||
|
return tag("typeIdentifier", x.value);
|
||||||
|
case "EvModule":
|
||||||
|
let moduleResult: tagged<
|
||||||
|
"module",
|
||||||
|
{ [key: string]: squiggleExpression }
|
||||||
|
> = tag(
|
||||||
|
"module",
|
||||||
|
_.mapValues(x.value, (x: unknown) =>
|
||||||
|
convertRawToTypescript(x as rescriptExport, environment)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return moduleResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as _ from "lodash";
|
import * as _ from "lodash";
|
||||||
import {
|
import type {
|
||||||
expressionValue,
|
expressionValue,
|
||||||
mixedShape,
|
mixedShape,
|
||||||
sampleSetDist,
|
sampleSetDist,
|
||||||
|
@ -9,6 +9,8 @@ import {
|
||||||
discreteShape,
|
discreteShape,
|
||||||
continuousShape,
|
continuousShape,
|
||||||
lambdaValue,
|
lambdaValue,
|
||||||
|
lambdaDeclaration,
|
||||||
|
declarationArg,
|
||||||
} from "../rescript/TypescriptInterface.gen";
|
} from "../rescript/TypescriptInterface.gen";
|
||||||
import { Distribution } from "./distribution";
|
import { Distribution } from "./distribution";
|
||||||
import { tagged, tag } from "./types";
|
import { tagged, tag } from "./types";
|
||||||
|
@ -63,6 +65,18 @@ export type rescriptExport =
|
||||||
| {
|
| {
|
||||||
TAG: 11; // EvTimeDuration
|
TAG: 11; // EvTimeDuration
|
||||||
_0: number;
|
_0: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
TAG: 12; // EvDeclaration
|
||||||
|
_0: rescriptLambdaDeclaration;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
TAG: 13; // EvTypeIdentifier
|
||||||
|
_0: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
TAG: 14; // EvModule
|
||||||
|
_0: { [key: string]: rescriptExport };
|
||||||
};
|
};
|
||||||
|
|
||||||
type rescriptDist =
|
type rescriptDist =
|
||||||
|
@ -84,6 +98,23 @@ type rescriptPointSetDist =
|
||||||
_0: continuousShape;
|
_0: continuousShape;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type rescriptLambdaDeclaration = {
|
||||||
|
readonly fn: lambdaValue;
|
||||||
|
readonly args: rescriptDeclarationArg[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type rescriptDeclarationArg =
|
||||||
|
| {
|
||||||
|
TAG: 0; // Float
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
TAG: 1; // Date
|
||||||
|
min: Date;
|
||||||
|
max: Date;
|
||||||
|
};
|
||||||
|
|
||||||
export type squiggleExpression =
|
export type squiggleExpression =
|
||||||
| tagged<"symbol", string>
|
| tagged<"symbol", string>
|
||||||
| tagged<"string", string>
|
| tagged<"string", string>
|
||||||
|
@ -96,7 +127,10 @@ export type squiggleExpression =
|
||||||
| tagged<"number", number>
|
| tagged<"number", number>
|
||||||
| tagged<"date", Date>
|
| tagged<"date", Date>
|
||||||
| tagged<"timeDuration", number>
|
| tagged<"timeDuration", number>
|
||||||
| tagged<"record", { [key: string]: squiggleExpression }>;
|
| tagged<"lambdaDeclaration", lambdaDeclaration>
|
||||||
|
| tagged<"record", { [key: string]: squiggleExpression }>
|
||||||
|
| tagged<"typeIdentifier", string>
|
||||||
|
| tagged<"module", { [key: string]: squiggleExpression }>;
|
||||||
|
|
||||||
export { lambdaValue };
|
export { lambdaValue };
|
||||||
|
|
||||||
|
@ -141,6 +175,29 @@ export function convertRawToTypescript(
|
||||||
return tag("date", result._0);
|
return tag("date", result._0);
|
||||||
case 11: // EvTimeDuration
|
case 11: // EvTimeDuration
|
||||||
return tag("number", result._0);
|
return tag("number", result._0);
|
||||||
|
case 12: // EvDeclaration
|
||||||
|
return tag("lambdaDeclaration", {
|
||||||
|
fn: result._0.fn,
|
||||||
|
args: result._0.args.map(convertDeclaration),
|
||||||
|
});
|
||||||
|
case 13: // EvSymbol
|
||||||
|
return tag("typeIdentifier", result._0);
|
||||||
|
case 14: // EvModule
|
||||||
|
return tag(
|
||||||
|
"module",
|
||||||
|
_.mapValues(result._0, (x) => convertRawToTypescript(x, environment))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertDeclaration(
|
||||||
|
declarationArg: rescriptDeclarationArg
|
||||||
|
): declarationArg {
|
||||||
|
switch (declarationArg.TAG) {
|
||||||
|
case 0: // Float
|
||||||
|
return tag("Float", { min: declarationArg.min, max: declarationArg.max });
|
||||||
|
case 1: // Date
|
||||||
|
return tag("Date", { min: declarationArg.min, max: declarationArg.max });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,11 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
|
||||||
)
|
)
|
||||||
->E.R2.fmap(r => Dist(r))
|
->E.R2.fmap(r => Dist(r))
|
||||||
->OutputLocal.fromResult
|
->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)) =>
|
| ToDist(Scale(#Logarithm, f)) =>
|
||||||
dist
|
dist
|
||||||
->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~algebraicCombination=#Logarithm, ~f)
|
->GenericDist.pointwiseCombinationFloat(~toPointSetFn, ~algebraicCombination=#Logarithm, ~f)
|
||||||
|
@ -256,6 +261,8 @@ module Constructors = {
|
||||||
module C = DistributionTypes.Constructors.UsingDists
|
module C = DistributionTypes.Constructors.UsingDists
|
||||||
open OutputLocal
|
open OutputLocal
|
||||||
let mean = (~env, dist) => C.mean(dist)->run(~env)->toFloatR
|
let mean = (~env, dist) => C.mean(dist)->run(~env)->toFloatR
|
||||||
|
let stdev = (~env, dist) => C.stdev(dist)->run(~env)->toFloatR
|
||||||
|
let variance = (~env, dist) => C.variance(dist)->run(~env)->toFloatR
|
||||||
let sample = (~env, dist) => C.sample(dist)->run(~env)->toFloatR
|
let sample = (~env, dist) => C.sample(dist)->run(~env)->toFloatR
|
||||||
let cdf = (~env, dist, f) => C.cdf(dist, f)->run(~env)->toFloatR
|
let cdf = (~env, dist, f) => C.cdf(dist, f)->run(~env)->toFloatR
|
||||||
let inv = (~env, dist, f) => C.inv(dist, f)->run(~env)->toFloatR
|
let inv = (~env, dist, f) => C.inv(dist, f)->run(~env)->toFloatR
|
||||||
|
@ -298,6 +305,7 @@ module Constructors = {
|
||||||
let algebraicLogarithm = (~env, dist1, dist2) =>
|
let algebraicLogarithm = (~env, dist1, dist2) =>
|
||||||
C.algebraicLogarithm(dist1, dist2)->run(~env)->toDistR
|
C.algebraicLogarithm(dist1, dist2)->run(~env)->toDistR
|
||||||
let algebraicPower = (~env, dist1, dist2) => C.algebraicPower(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 scalePower = (~env, dist, n) => C.scalePower(dist, n)->run(~env)->toDistR
|
||||||
let scaleLogarithm = (~env, dist, n) => C.scaleLogarithm(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
|
let pointwiseAdd = (~env, dist1, dist2) => C.pointwiseAdd(dist1, dist2)->run(~env)->toDistR
|
||||||
|
|
|
@ -49,6 +49,10 @@ module Constructors: {
|
||||||
@genType
|
@genType
|
||||||
let mean: (~env: env, genericDist) => result<float, error>
|
let mean: (~env: env, genericDist) => result<float, error>
|
||||||
@genType
|
@genType
|
||||||
|
let stdev: (~env: env, genericDist) => result<float, error>
|
||||||
|
@genType
|
||||||
|
let variance: (~env: env, genericDist) => result<float, error>
|
||||||
|
@genType
|
||||||
let sample: (~env: env, genericDist) => result<float, error>
|
let sample: (~env: env, genericDist) => result<float, error>
|
||||||
@genType
|
@genType
|
||||||
let cdf: (~env: env, genericDist, float) => result<float, error>
|
let cdf: (~env: env, genericDist, float) => result<float, error>
|
||||||
|
@ -127,6 +131,8 @@ module Constructors: {
|
||||||
@genType
|
@genType
|
||||||
let scaleLogarithm: (~env: env, genericDist, float) => result<genericDist, error>
|
let scaleLogarithm: (~env: env, genericDist, float) => result<genericDist, error>
|
||||||
@genType
|
@genType
|
||||||
|
let scaleMultiply: (~env: env, genericDist, float) => result<genericDist, error>
|
||||||
|
@genType
|
||||||
let scalePower: (~env: env, genericDist, float) => result<genericDist, error>
|
let scalePower: (~env: env, genericDist, float) => result<genericDist, error>
|
||||||
@genType
|
@genType
|
||||||
let pointwiseAdd: (~env: env, genericDist, genericDist) => result<genericDist, error>
|
let pointwiseAdd: (~env: env, genericDist, genericDist) => result<genericDist, error>
|
||||||
|
|
|
@ -30,9 +30,9 @@ module Error = {
|
||||||
@genType
|
@genType
|
||||||
let toString = (err: error): string =>
|
let toString = (err: error): string =>
|
||||||
switch err {
|
switch err {
|
||||||
| NotYetImplemented => "Function Not Yet Implemented"
|
| NotYetImplemented => "Function not yet implemented"
|
||||||
| Unreachable => "Unreachable"
|
| Unreachable => "Unreachable"
|
||||||
| DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift is Invalid"
|
| DistributionVerticalShiftIsInvalid => "Distribution vertical shift is invalid"
|
||||||
| ArgumentError(s) => `Argument Error ${s}`
|
| ArgumentError(s) => `Argument Error ${s}`
|
||||||
| LogarithmOfDistributionError(s) => `Logarithm of input error: ${s}`
|
| LogarithmOfDistributionError(s) => `Logarithm of input error: ${s}`
|
||||||
| SampleSetError(TooFewSamples) => "Too Few Samples"
|
| SampleSetError(TooFewSamples) => "Too Few Samples"
|
||||||
|
@ -68,9 +68,15 @@ module DistributionOperation = {
|
||||||
| #Mean
|
| #Mean
|
||||||
| #Sample
|
| #Sample
|
||||||
| #IntegralSum
|
| #IntegralSum
|
||||||
|
| #Mode
|
||||||
|
| #Stdev
|
||||||
|
| #Min
|
||||||
|
| #Max
|
||||||
|
| #Variance
|
||||||
]
|
]
|
||||||
|
|
||||||
type toScaleFn = [
|
type toScaleFn = [
|
||||||
|
| #Multiply
|
||||||
| #Power
|
| #Power
|
||||||
| #Logarithm
|
| #Logarithm
|
||||||
| #LogarithmWithThreshold(float)
|
| #LogarithmWithThreshold(float)
|
||||||
|
@ -119,6 +125,11 @@ module DistributionOperation = {
|
||||||
| ToFloat(#Cdf(r)) => `cdf(${E.Float.toFixed(r)})`
|
| ToFloat(#Cdf(r)) => `cdf(${E.Float.toFixed(r)})`
|
||||||
| ToFloat(#Inv(r)) => `inv(${E.Float.toFixed(r)})`
|
| ToFloat(#Inv(r)) => `inv(${E.Float.toFixed(r)})`
|
||||||
| ToFloat(#Mean) => `mean`
|
| ToFloat(#Mean) => `mean`
|
||||||
|
| ToFloat(#Min) => `min`
|
||||||
|
| ToFloat(#Max) => `max`
|
||||||
|
| ToFloat(#Stdev) => `stdev`
|
||||||
|
| ToFloat(#Variance) => `variance`
|
||||||
|
| ToFloat(#Mode) => `mode`
|
||||||
| ToFloat(#Pdf(r)) => `pdf(${E.Float.toFixed(r)})`
|
| ToFloat(#Pdf(r)) => `pdf(${E.Float.toFixed(r)})`
|
||||||
| ToFloat(#Sample) => `sample`
|
| ToFloat(#Sample) => `sample`
|
||||||
| ToFloat(#IntegralSum) => `integralSum`
|
| ToFloat(#IntegralSum) => `integralSum`
|
||||||
|
@ -129,11 +140,12 @@ module DistributionOperation = {
|
||||||
| ToDist(Truncate(_, _)) => `truncate`
|
| ToDist(Truncate(_, _)) => `truncate`
|
||||||
| ToDist(Inspect) => `inspect`
|
| ToDist(Inspect) => `inspect`
|
||||||
| ToDist(Scale(#Power, r)) => `scalePower(${E.Float.toFixed(r)})`
|
| 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(#Logarithm, r)) => `scaleLog(${E.Float.toFixed(r)})`
|
||||||
| ToDist(Scale(#LogarithmWithThreshold(eps), r)) =>
|
| ToDist(Scale(#LogarithmWithThreshold(eps), r)) =>
|
||||||
`scaleLogWithThreshold(${E.Float.toFixed(r)}, epsilon=${E.Float.toFixed(eps)})`
|
`scaleLogWithThreshold(${E.Float.toFixed(r)}, epsilon=${E.Float.toFixed(eps)})`
|
||||||
| ToString(ToString) => `toString`
|
| ToString(ToString) => `toString`
|
||||||
| ToString(ToSparkline(n)) => `toSparkline(${E.I.toString(n)})`
|
| ToString(ToSparkline(n)) => `sparkline(${E.I.toString(n)})`
|
||||||
| ToBool(IsNormalized) => `isNormalized`
|
| ToBool(IsNormalized) => `isNormalized`
|
||||||
| ToDistCombination(Algebraic(_), _, _) => `algebraic`
|
| ToDistCombination(Algebraic(_), _, _) => `algebraic`
|
||||||
| ToDistCombination(Pointwise, _, _) => `pointwise`
|
| ToDistCombination(Pointwise, _, _) => `pointwise`
|
||||||
|
@ -152,6 +164,8 @@ module Constructors = {
|
||||||
module UsingDists = {
|
module UsingDists = {
|
||||||
@genType
|
@genType
|
||||||
let mean = (dist): t => FromDist(ToFloat(#Mean), dist)
|
let mean = (dist): t => FromDist(ToFloat(#Mean), dist)
|
||||||
|
let stdev = (dist): t => FromDist(ToFloat(#Stdev), dist)
|
||||||
|
let variance = (dist): t => FromDist(ToFloat(#Variance), dist)
|
||||||
let sample = (dist): t => FromDist(ToFloat(#Sample), dist)
|
let sample = (dist): t => FromDist(ToFloat(#Sample), dist)
|
||||||
let cdf = (dist, x): t => FromDist(ToFloat(#Cdf(x)), dist)
|
let cdf = (dist, x): t => FromDist(ToFloat(#Cdf(x)), dist)
|
||||||
let inv = (dist, x): t => FromDist(ToFloat(#Inv(x)), dist)
|
let inv = (dist, x): t => FromDist(ToFloat(#Inv(x)), dist)
|
||||||
|
@ -197,6 +211,7 @@ module Constructors = {
|
||||||
estimate,
|
estimate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
let scaleMultiply = (dist, n): t => FromDist(ToDist(Scale(#Multiply, n)), dist)
|
||||||
let scalePower = (dist, n): t => FromDist(ToDist(Scale(#Power, 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 scaleLogarithm = (dist, n): t => FromDist(ToDist(Scale(#Logarithm, n)), dist)
|
||||||
let scaleLogarithmWithThreshold = (dist, n, eps): t => FromDist(
|
let scaleLogarithmWithThreshold = (dist, n, eps): t => FromDist(
|
||||||
|
|
|
@ -68,7 +68,7 @@ let toFloatOperation = (
|
||||||
) => {
|
) => {
|
||||||
switch distToFloatOperation {
|
switch distToFloatOperation {
|
||||||
| #IntegralSum => Ok(integralEndY(t))
|
| #IntegralSum => Ok(integralEndY(t))
|
||||||
| (#Pdf(_) | #Cdf(_) | #Inv(_) | #Mean | #Sample) as op => {
|
| (#Pdf(_) | #Cdf(_) | #Inv(_) | #Mean | #Sample | #Min | #Max) as op => {
|
||||||
let trySymbolicSolution = switch (t: t) {
|
let trySymbolicSolution = switch (t: t) {
|
||||||
| Symbolic(r) => SymbolicDist.T.operate(op, r)->E.R.toOption
|
| Symbolic(r) => SymbolicDist.T.operate(op, r)->E.R.toOption
|
||||||
| _ => None
|
| _ => None
|
||||||
|
@ -78,6 +78,8 @@ let toFloatOperation = (
|
||||||
| (SampleSet(sampleSet), #Mean) => SampleSetDist.mean(sampleSet)->Some
|
| (SampleSet(sampleSet), #Mean) => SampleSetDist.mean(sampleSet)->Some
|
||||||
| (SampleSet(sampleSet), #Sample) => SampleSetDist.sample(sampleSet)->Some
|
| (SampleSet(sampleSet), #Sample) => SampleSetDist.sample(sampleSet)->Some
|
||||||
| (SampleSet(sampleSet), #Inv(r)) => SampleSetDist.percentile(sampleSet, r)->Some
|
| (SampleSet(sampleSet), #Inv(r)) => SampleSetDist.percentile(sampleSet, r)->Some
|
||||||
|
| (SampleSet(sampleSet), #Min) => SampleSetDist.min(sampleSet)->Some
|
||||||
|
| (SampleSet(sampleSet), #Max) => SampleSetDist.max(sampleSet)->Some
|
||||||
| _ => None
|
| _ => None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +92,16 @@ let toFloatOperation = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
| (#Stdev | #Variance | #Mode) as op =>
|
||||||
|
switch t {
|
||||||
|
| SampleSet(s) =>
|
||||||
|
switch op {
|
||||||
|
| #Stdev => SampleSetDist.stdev(s)->Ok
|
||||||
|
| #Variance => SampleSetDist.variance(s)->Ok
|
||||||
|
| #Mode => SampleSetDist.mode(s)->Ok
|
||||||
|
}
|
||||||
|
| _ => Error(DistributionTypes.NotYetImplemented)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +256,9 @@ module Truncate = {
|
||||||
| Some(r) => Ok(r)
|
| Some(r) => Ok(r)
|
||||||
| None =>
|
| None =>
|
||||||
toPointSetFn(t)->E.R2.fmap(t => {
|
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,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,7 +337,7 @@ module AlgebraicCombination = {
|
||||||
let fn = Operation.Algebraic.toFn(arithmeticOperation)
|
let fn = Operation.Algebraic.toFn(arithmeticOperation)
|
||||||
E.R.merge(toSampleSet(t1), toSampleSet(t2))
|
E.R.merge(toSampleSet(t1), toSampleSet(t2))
|
||||||
->E.R.bind(((t1, t2)) => {
|
->E.R.bind(((t1, t2)) => {
|
||||||
SampleSetDist.map2(~fn, ~t1, ~t2)->E.R2.errMap(x => DistributionTypes.OperationError(x))
|
SampleSetDist.map2(~fn, ~t1, ~t2)->E.R2.errMap(x => DistributionTypes.SampleSetError(x))
|
||||||
})
|
})
|
||||||
->E.R2.fmap(r => DistributionTypes.SampleSet(r))
|
->E.R2.fmap(r => DistributionTypes.SampleSet(r))
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,9 +225,8 @@ let doN = (n, fn) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let sample = (t: t): float => {
|
let sample = (t: t): float => {
|
||||||
let randomItem = Random.float(1.)
|
let randomItem = Random.float(1.0)
|
||||||
let bar = t |> T.Integral.yToX(randomItem)
|
t |> T.Integral.yToX(randomItem)
|
||||||
bar
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFloat = (t: t) =>
|
let isFloat = (t: t) =>
|
||||||
|
@ -249,6 +248,8 @@ let operate = (distToFloatOp: Operation.distToFloatOperation, s): float =>
|
||||||
| #Inv(f) => inv(f, s)
|
| #Inv(f) => inv(f, s)
|
||||||
| #Sample => sample(s)
|
| #Sample => sample(s)
|
||||||
| #Mean => T.mean(s)
|
| #Mean => T.mean(s)
|
||||||
|
| #Min => T.minX(s)
|
||||||
|
| #Max => T.maxX(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
let toSparkline = (t: t, bucketCount): result<string, PointSetTypes.sparklineError> =>
|
let toSparkline = (t: t, bucketCount): result<string, PointSetTypes.sparklineError> =>
|
||||||
|
|
|
@ -20,6 +20,14 @@ module Error = {
|
||||||
}
|
}
|
||||||
|
|
||||||
let fromOperationError = e => OperationError(e)
|
let fromOperationError = e => OperationError(e)
|
||||||
|
|
||||||
|
let toString = (err: sampleSetError) => {
|
||||||
|
switch err {
|
||||||
|
| TooFewSamples => "Too few samples when constructing sample set"
|
||||||
|
| NonNumericInput(err) => `Found a non-number in input: ${err}`
|
||||||
|
| OperationError(err) => Operation.Error.toString(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include Error
|
include Error
|
||||||
|
@ -87,30 +95,27 @@ let sampleN = (t: t, n) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _fromSampleResultArray = (samples: array<result<float, QuriSquiggleLang.Operation.Error.t>>) =>
|
||||||
|
E.A.R.firstErrorOrOpen(samples)->E.R2.errMap(Error.fromOperationError) |> E.R2.bind(make)
|
||||||
|
|
||||||
let samplesMap = (~fn: float => result<float, Operation.Error.t>, t: t): result<
|
let samplesMap = (~fn: float => result<float, Operation.Error.t>, t: t): result<
|
||||||
t,
|
t,
|
||||||
sampleSetError,
|
sampleSetError,
|
||||||
> => {
|
> => T.get(t)->E.A2.fmap(fn)->_fromSampleResultArray
|
||||||
let samples = T.get(t)->E.A2.fmap(fn)
|
|
||||||
E.A.R.firstErrorOrOpen(samples)->E.R2.errMap(Error.fromOperationError) |> E.R2.bind(make)
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Figure out what to do if distributions are different lengths. ``zip`` is kind of inelegant for this.
|
//TODO: Figure out what to do if distributions are different lengths. ``zip`` is kind of inelegant for this.
|
||||||
let map2 = (~fn: (float, float) => result<float, Operation.Error.t>, ~t1: t, ~t2: t): result<
|
let map2 = (~fn: (float, float) => result<float, Operation.Error.t>, ~t1: t, ~t2: t): result<
|
||||||
t,
|
t,
|
||||||
Operation.Error.t,
|
sampleSetError,
|
||||||
> => {
|
> => E.A.zip(get(t1), get(t2))->E.A2.fmap(E.Tuple2.toFnCall(fn))->_fromSampleResultArray
|
||||||
let samples = Belt.Array.zip(get(t1), get(t2))->E.A2.fmap(((a, b)) => fn(a, b))
|
|
||||||
|
|
||||||
// This assertion should never be reached. In order for it to be reached, one
|
let map3 = (
|
||||||
// of the input parameters would need to be a sample set distribution with less
|
~fn: (float, float, float) => result<float, Operation.Error.t>,
|
||||||
// than 6 samples. Which should be impossible due to the smart constructor.
|
~t1: t,
|
||||||
// I could prove this to the type system (say, creating a {first: float, second: float, ..., fifth: float, rest: array<float>}
|
~t2: t,
|
||||||
// But doing so would take too much time, so I'll leave it as an assertion
|
~t3: t,
|
||||||
E.A.R.firstErrorOrOpen(samples)->E.R2.fmap(x =>
|
): result<t, sampleSetError> =>
|
||||||
E.R.toExnFnString(Error.sampleSetErrorToString, make(x))
|
E.A.zip3(get(t1), get(t2), get(t3))->E.A2.fmap(E.Tuple3.toFnCall(fn))->_fromSampleResultArray
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mean = t => T.get(t)->E.A.Floats.mean
|
let mean = t => T.get(t)->E.A.Floats.mean
|
||||||
let geomean = t => T.get(t)->E.A.Floats.geomean
|
let geomean = t => T.get(t)->E.A.Floats.geomean
|
||||||
|
|
|
@ -295,7 +295,7 @@ module Float = {
|
||||||
let inv = (p, t: t) => p < t ? 0.0 : 1.0
|
let inv = (p, t: t) => p < t ? 0.0 : 1.0
|
||||||
let mean = (t: t) => Ok(t)
|
let mean = (t: t) => Ok(t)
|
||||||
let sample = (t: t) => 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(
|
let toPointSetDist = (t: t): PointSetTypes.pointSetDist => Discrete(
|
||||||
Discrete.make(~integralSumCache=Some(1.0), {xs: [t], ys: [1.0]}),
|
Discrete.make(~integralSumCache=Some(1.0), {xs: [t], ys: [1.0]}),
|
||||||
)
|
)
|
||||||
|
@ -449,6 +449,8 @@ module T = {
|
||||||
| #Cdf(f) => Ok(cdf(f, s))
|
| #Cdf(f) => Ok(cdf(f, s))
|
||||||
| #Pdf(f) => Ok(pdf(f, s))
|
| #Pdf(f) => Ok(pdf(f, s))
|
||||||
| #Inv(f) => Ok(inv(f, s))
|
| #Inv(f) => Ok(inv(f, s))
|
||||||
|
| #Min => Ok(min(s))
|
||||||
|
| #Max => Ok(max(s))
|
||||||
| #Sample => Ok(sample(s))
|
| #Sample => Ok(sample(s))
|
||||||
| #Mean => mean(s)
|
| #Mean => mean(s)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,13 @@ type rec frType =
|
||||||
| FRTypeNumber
|
| FRTypeNumber
|
||||||
| FRTypeNumeric
|
| FRTypeNumeric
|
||||||
| FRTypeDistOrNumber
|
| FRTypeDistOrNumber
|
||||||
|
| FRTypeLambda
|
||||||
| FRTypeRecord(frTypeRecord)
|
| FRTypeRecord(frTypeRecord)
|
||||||
| FRTypeArray(array<frType>)
|
| FRTypeDict(frType)
|
||||||
| FRTypeOption(frType)
|
| FRTypeArray(frType)
|
||||||
|
| FRTypeString
|
||||||
|
| FRTypeAny
|
||||||
|
| FRTypeVariant(array<string>)
|
||||||
and frTypeRecord = array<frTypeRecordParam>
|
and frTypeRecord = array<frTypeRecordParam>
|
||||||
and frTypeRecordParam = (string, frType)
|
and frTypeRecordParam = (string, frType)
|
||||||
|
|
||||||
|
@ -21,11 +25,17 @@ and frTypeRecordParam = (string, frType)
|
||||||
type rec frValue =
|
type rec frValue =
|
||||||
| FRValueNumber(float)
|
| FRValueNumber(float)
|
||||||
| FRValueDist(DistributionTypes.genericDist)
|
| FRValueDist(DistributionTypes.genericDist)
|
||||||
| FRValueOption(option<frValue>)
|
| FRValueArray(array<frValue>)
|
||||||
| FRValueDistOrNumber(frValueDistOrNumber)
|
| FRValueDistOrNumber(frValueDistOrNumber)
|
||||||
| FRValueRecord(frValueRecord)
|
| FRValueRecord(frValueRecord)
|
||||||
|
| FRValueLambda(ReducerInterface_ExpressionValue.lambdaValue)
|
||||||
|
| FRValueString(string)
|
||||||
|
| FRValueVariant(string)
|
||||||
|
| FRValueAny(frValue)
|
||||||
|
| FRValueDict(Js.Dict.t<frValue>)
|
||||||
and frValueRecord = array<frValueRecordParam>
|
and frValueRecord = array<frValueRecordParam>
|
||||||
and frValueRecordParam = (string, frValue)
|
and frValueRecordParam = (string, frValue)
|
||||||
|
and frValueDictParam = (string, frValue)
|
||||||
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
|
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
|
||||||
|
|
||||||
type fnDefinition = {
|
type fnDefinition = {
|
||||||
|
@ -37,6 +47,9 @@ type fnDefinition = {
|
||||||
type function = {
|
type function = {
|
||||||
name: string,
|
name: string,
|
||||||
definitions: array<fnDefinition>,
|
definitions: array<fnDefinition>,
|
||||||
|
examples: option<string>,
|
||||||
|
description: option<string>,
|
||||||
|
isExperimental: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
type registry = array<function>
|
type registry = array<function>
|
||||||
|
@ -47,17 +60,39 @@ module FRType = {
|
||||||
switch t {
|
switch t {
|
||||||
| FRTypeNumber => "number"
|
| FRTypeNumber => "number"
|
||||||
| FRTypeNumeric => "numeric"
|
| FRTypeNumeric => "numeric"
|
||||||
| FRTypeDistOrNumber => "frValueDistOrNumber"
|
| FRTypeDistOrNumber => "distribution|number"
|
||||||
| FRTypeRecord(r) => {
|
| FRTypeRecord(r) => {
|
||||||
let input = ((name, frType): frTypeRecordParam) => `${name}: ${toString(frType)}`
|
let input = ((name, frType): frTypeRecordParam) => `${name}: ${toString(frType)}`
|
||||||
`record({${r->E.A2.fmap(input)->E.A2.joinWith(", ")}})`
|
`{${r->E.A2.fmap(input)->E.A2.joinWith(", ")}}`
|
||||||
}
|
}
|
||||||
| FRTypeArray(r) => `record(${r->E.A2.fmap(toString)->E.A2.joinWith(", ")})`
|
| FRTypeArray(r) => `list(${toString(r)})`
|
||||||
| FRTypeOption(v) => `option(${toString(v)})`
|
| FRTypeLambda => `lambda`
|
||||||
|
| FRTypeString => `string`
|
||||||
|
| FRTypeVariant(_) => "variant"
|
||||||
|
| FRTypeDict(r) => `dict(${toString(r)})`
|
||||||
|
| FRTypeAny => `any`
|
||||||
|
}
|
||||||
|
|
||||||
|
let rec toFrValue = (r: expressionValue): option<frValue> =>
|
||||||
|
switch r {
|
||||||
|
| EvNumber(f) => Some(FRValueNumber(f))
|
||||||
|
| EvString(f) => Some(FRValueString(f))
|
||||||
|
| EvDistribution(f) => Some(FRValueDistOrNumber(FRValueDist(f)))
|
||||||
|
| EvLambda(f) => Some(FRValueLambda(f))
|
||||||
|
| EvArray(elements) =>
|
||||||
|
elements->E.A2.fmap(toFrValue)->E.A.O.openIfAllSome->E.O2.fmap(r => FRValueArray(r))
|
||||||
|
| EvRecord(record) =>
|
||||||
|
Js.Dict.entries(record)
|
||||||
|
->E.A2.fmap(((key, item)) => item->toFrValue->E.O2.fmap(o => (key, o)))
|
||||||
|
->E.A.O.openIfAllSome
|
||||||
|
->E.O2.fmap(r => FRValueRecord(r))
|
||||||
|
| _ => None
|
||||||
}
|
}
|
||||||
|
|
||||||
let rec matchWithExpressionValue = (t: t, r: expressionValue): option<frValue> =>
|
let rec matchWithExpressionValue = (t: t, r: expressionValue): option<frValue> =>
|
||||||
switch (t, r) {
|
switch (t, r) {
|
||||||
|
| (FRTypeAny, f) => toFrValue(f)
|
||||||
|
| (FRTypeString, EvString(f)) => Some(FRValueString(f))
|
||||||
| (FRTypeNumber, EvNumber(f)) => Some(FRValueNumber(f))
|
| (FRTypeNumber, EvNumber(f)) => Some(FRValueNumber(f))
|
||||||
| (FRTypeDistOrNumber, EvNumber(f)) => Some(FRValueDistOrNumber(FRValueNumber(f)))
|
| (FRTypeDistOrNumber, EvNumber(f)) => Some(FRValueDistOrNumber(FRValueNumber(f)))
|
||||||
| (FRTypeDistOrNumber, EvDistribution(Symbolic(#Float(f)))) =>
|
| (FRTypeDistOrNumber, EvDistribution(Symbolic(#Float(f)))) =>
|
||||||
|
@ -65,7 +100,17 @@ module FRType = {
|
||||||
| (FRTypeDistOrNumber, EvDistribution(f)) => Some(FRValueDistOrNumber(FRValueDist(f)))
|
| (FRTypeDistOrNumber, EvDistribution(f)) => Some(FRValueDistOrNumber(FRValueDist(f)))
|
||||||
| (FRTypeNumeric, EvNumber(f)) => Some(FRValueNumber(f))
|
| (FRTypeNumeric, EvNumber(f)) => Some(FRValueNumber(f))
|
||||||
| (FRTypeNumeric, EvDistribution(Symbolic(#Float(f)))) => Some(FRValueNumber(f))
|
| (FRTypeNumeric, EvDistribution(Symbolic(#Float(f)))) => Some(FRValueNumber(f))
|
||||||
| (FRTypeOption(v), _) => Some(FRValueOption(matchWithExpressionValue(v, r)))
|
| (FRTypeLambda, EvLambda(f)) => Some(FRValueLambda(f))
|
||||||
|
| (FRTypeArray(intendedType), EvArray(elements)) => {
|
||||||
|
let el = elements->E.A2.fmap(matchWithExpressionValue(intendedType))
|
||||||
|
E.A.O.openIfAllSome(el)->E.O2.fmap(r => FRValueArray(r))
|
||||||
|
}
|
||||||
|
| (FRTypeDict(r), EvRecord(record)) =>
|
||||||
|
record
|
||||||
|
->Js.Dict.entries
|
||||||
|
->E.A2.fmap(((key, item)) => matchWithExpressionValue(r, item)->E.O2.fmap(o => (key, o)))
|
||||||
|
->E.A.O.openIfAllSome
|
||||||
|
->E.O2.fmap(r => FRValueDict(Js.Dict.fromArray(r)))
|
||||||
| (FRTypeRecord(recordParams), EvRecord(record)) => {
|
| (FRTypeRecord(recordParams), EvRecord(record)) => {
|
||||||
let getAndMatch = (name, input) =>
|
let getAndMatch = (name, input) =>
|
||||||
E.Dict.get(record, name)->E.O.bind(matchWithExpressionValue(input))
|
E.Dict.get(record, name)->E.O.bind(matchWithExpressionValue(input))
|
||||||
|
@ -80,6 +125,32 @@ module FRType = {
|
||||||
| _ => None
|
| _ => None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let rec matchReverse = (e: frValue): expressionValue =>
|
||||||
|
switch e {
|
||||||
|
| FRValueNumber(f) => EvNumber(f)
|
||||||
|
| FRValueDistOrNumber(FRValueNumber(n)) => EvNumber(n)
|
||||||
|
| FRValueDistOrNumber(FRValueDist(n)) => EvDistribution(n)
|
||||||
|
| FRValueDist(dist) => EvDistribution(dist)
|
||||||
|
| FRValueArray(elements) => EvArray(elements->E.A2.fmap(matchReverse))
|
||||||
|
| FRValueRecord(frValueRecord) => {
|
||||||
|
let record =
|
||||||
|
frValueRecord->E.A2.fmap(((name, value)) => (name, matchReverse(value)))->E.Dict.fromArray
|
||||||
|
EvRecord(record)
|
||||||
|
}
|
||||||
|
| FRValueDict(frValueRecord) => {
|
||||||
|
let record =
|
||||||
|
frValueRecord
|
||||||
|
->Js.Dict.entries
|
||||||
|
->E.A2.fmap(((name, value)) => (name, matchReverse(value)))
|
||||||
|
->E.Dict.fromArray
|
||||||
|
EvRecord(record)
|
||||||
|
}
|
||||||
|
| FRValueLambda(l) => EvLambda(l)
|
||||||
|
| FRValueString(string) => EvString(string)
|
||||||
|
| FRValueVariant(string) => EvString(string)
|
||||||
|
| FRValueAny(f) => matchReverse(f)
|
||||||
|
}
|
||||||
|
|
||||||
let matchWithExpressionValueArray = (inputs: array<t>, args: array<expressionValue>): option<
|
let matchWithExpressionValueArray = (inputs: array<t>, args: array<expressionValue>): option<
|
||||||
array<frValue>,
|
array<frValue>,
|
||||||
> => {
|
> => {
|
||||||
|
@ -263,13 +334,34 @@ module FnDefinition = {
|
||||||
module Function = {
|
module Function = {
|
||||||
type t = function
|
type t = function
|
||||||
|
|
||||||
let make = (~name, ~definitions): t => {
|
type functionJson = {
|
||||||
|
name: string,
|
||||||
|
definitions: array<string>,
|
||||||
|
examples: option<string>,
|
||||||
|
description: option<string>,
|
||||||
|
isExperimental: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
let make = (~name, ~definitions, ~examples=?, ~description=?, ~isExperimental=false, ()): t => {
|
||||||
name: name,
|
name: name,
|
||||||
definitions: definitions,
|
definitions: definitions,
|
||||||
|
examples: examples,
|
||||||
|
isExperimental: isExperimental,
|
||||||
|
description: description,
|
||||||
|
}
|
||||||
|
|
||||||
|
let toJson = (t: t): functionJson => {
|
||||||
|
name: t.name,
|
||||||
|
definitions: t.definitions->E.A2.fmap(FnDefinition.toString),
|
||||||
|
examples: t.examples,
|
||||||
|
description: t.description,
|
||||||
|
isExperimental: t.isExperimental,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module Registry = {
|
module Registry = {
|
||||||
|
let toJson = (r: registry) => r->E.A2.fmap(Function.toJson)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
There's a (potential+minor) bug here: If a function definition is called outside of the calls
|
There's a (potential+minor) bug here: If a function definition is called outside of the calls
|
||||||
to the registry, then it's possible that there could be a match after the registry is
|
to the registry, then it's possible that there could be a match after the registry is
|
||||||
|
@ -282,6 +374,7 @@ module Registry = {
|
||||||
~env: DistributionOperation.env,
|
~env: DistributionOperation.env,
|
||||||
) => {
|
) => {
|
||||||
let matchToDef = m => Matcher.Registry.matchToDef(registry, m)
|
let matchToDef = m => Matcher.Registry.matchToDef(registry, m)
|
||||||
|
//Js.log(toSimple(registry))
|
||||||
let showNameMatchDefinitions = matches => {
|
let showNameMatchDefinitions = matches => {
|
||||||
let defs =
|
let defs =
|
||||||
matches
|
matches
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
type expressionValue = ReducerInterface_ExpressionValue.expressionValue
|
|
||||||
|
|
||||||
type rec frType =
|
|
||||||
| FRTypeNumber
|
|
||||||
| FRTypeNumeric
|
|
||||||
| FRTypeDistOrNumber
|
|
||||||
| FRTypeRecord(frTypeRecord)
|
|
||||||
| FRTypeArray(array<frType>)
|
|
||||||
| FRTypeOption(frType)
|
|
||||||
and frTypeRecord = array<frTypeRecordParam>
|
|
||||||
and frTypeRecordParam = (string, frType)
|
|
||||||
|
|
||||||
type rec frValue =
|
|
||||||
| FRValueNumber(float)
|
|
||||||
| FRValueDist(DistributionTypes.genericDist)
|
|
||||||
| FRValueOption(option<frValue>)
|
|
||||||
| FRValueDistOrNumber(frValueDistOrNumber)
|
|
||||||
| FRValueRecord(frValueRecord)
|
|
||||||
and frValueRecord = array<frValueRecordParam>
|
|
||||||
and frValueRecordParam = (string, frValue)
|
|
||||||
and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.genericDist)
|
|
||||||
|
|
||||||
type fnDefinition = {
|
|
||||||
name: string,
|
|
||||||
inputs: array<frType>,
|
|
||||||
run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
|
|
||||||
}
|
|
||||||
|
|
||||||
type function = {
|
|
||||||
name: string,
|
|
||||||
definitions: array<fnDefinition>,
|
|
||||||
}
|
|
||||||
|
|
||||||
type registry = array<function>
|
|
||||||
|
|
||||||
// Note: The function "name" is just used for documentation purposes
|
|
||||||
module Function: {
|
|
||||||
type t = function
|
|
||||||
let make: (~name: string, ~definitions: array<fnDefinition>) => t
|
|
||||||
}
|
|
||||||
|
|
||||||
module FnDefinition: {
|
|
||||||
type t = fnDefinition
|
|
||||||
let make: (
|
|
||||||
~name: string,
|
|
||||||
~inputs: array<frType>,
|
|
||||||
~run: (array<frValue>, DistributionOperation.env) => result<expressionValue, string>,
|
|
||||||
) => t
|
|
||||||
}
|
|
||||||
|
|
||||||
module Registry: {
|
|
||||||
let matchAndRun: (
|
|
||||||
~registry: registry,
|
|
||||||
~fnName: string,
|
|
||||||
~args: array<expressionValue>,
|
|
||||||
~env: QuriSquiggleLang.DistributionOperation.env,
|
|
||||||
) => option<result<expressionValue, string>>
|
|
||||||
}
|
|
|
@ -5,10 +5,17 @@ let impossibleError = "Wrong inputs / Logically impossible"
|
||||||
module Wrappers = {
|
module Wrappers = {
|
||||||
let symbolic = r => DistributionTypes.Symbolic(r)
|
let symbolic = r => DistributionTypes.Symbolic(r)
|
||||||
let evDistribution = r => ReducerInterface_ExpressionValue.EvDistribution(r)
|
let evDistribution = r => ReducerInterface_ExpressionValue.EvDistribution(r)
|
||||||
|
let evNumber = r => ReducerInterface_ExpressionValue.EvNumber(r)
|
||||||
|
let evArray = r => ReducerInterface_ExpressionValue.EvArray(r)
|
||||||
|
let evRecord = r => ReducerInterface_ExpressionValue.EvRecord(r)
|
||||||
|
let evString = r => ReducerInterface_ExpressionValue.EvString(r)
|
||||||
let symbolicEvDistribution = r => r->DistributionTypes.Symbolic->evDistribution
|
let symbolicEvDistribution = r => r->DistributionTypes.Symbolic->evDistribution
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let getOrError = (a, g) => E.A.get(a, g) |> E.O.toResult(impossibleError)
|
||||||
|
|
||||||
module Prepare = {
|
module Prepare = {
|
||||||
|
type t = frValue
|
||||||
type ts = array<frValue>
|
type ts = array<frValue>
|
||||||
type err = string
|
type err = string
|
||||||
|
|
||||||
|
@ -19,6 +26,26 @@ module Prepare = {
|
||||||
| [FRValueRecord([(_, n1), (_, n2)])] => Ok([n1, n2])
|
| [FRValueRecord([(_, n1), (_, n2)])] => Ok([n1, n2])
|
||||||
| _ => Error(impossibleError)
|
| _ => Error(impossibleError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let toArgs = (inputs: ts): result<ts, err> =>
|
||||||
|
switch inputs {
|
||||||
|
| [FRValueRecord(args)] => args->E.A2.fmap(((_, b)) => b)->Ok
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module Array = {
|
||||||
|
let openA = (inputs: t): result<ts, err> =>
|
||||||
|
switch inputs {
|
||||||
|
| FRValueArray(n) => Ok(n)
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
|
||||||
|
let arrayOfArrays = (inputs: t): result<array<ts>, err> =>
|
||||||
|
switch inputs {
|
||||||
|
| FRValueArray(n) => n->E.A2.fmap(openA)->E.A.R.firstErrorOrOpen
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +57,20 @@ module Prepare = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let twoNumbers = (values: ts): result<(float, float), err> => {
|
||||||
|
switch values {
|
||||||
|
| [FRValueNumber(a1), FRValueNumber(a2)] => Ok(a1, a2)
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let threeNumbers = (values: ts): result<(float, float, float), err> => {
|
||||||
|
switch values {
|
||||||
|
| [FRValueNumber(a1), FRValueNumber(a2), FRValueNumber(a3)] => Ok(a1, a2, a3)
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let oneDistOrNumber = (values: ts): result<frValueDistOrNumber, err> => {
|
let oneDistOrNumber = (values: ts): result<frValueDistOrNumber, err> => {
|
||||||
switch values {
|
switch values {
|
||||||
| [FRValueDistOrNumber(a1)] => Ok(a1)
|
| [FRValueDistOrNumber(a1)] => Ok(a1)
|
||||||
|
@ -42,6 +83,46 @@ module Prepare = {
|
||||||
values->ToValueArray.Record.twoArgs->E.R.bind(twoDistOrNumber)
|
values->ToValueArray.Record.twoArgs->E.R.bind(twoDistOrNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module ToArrayRecordPairs = {
|
||||||
|
let twoArgs = (input: t): result<array<ts>, err> => {
|
||||||
|
let array = input->ToValueArray.Array.openA
|
||||||
|
let pairs =
|
||||||
|
array->E.R.bind(pairs =>
|
||||||
|
pairs
|
||||||
|
->E.A2.fmap(xyCoord => [xyCoord]->ToValueArray.Record.twoArgs)
|
||||||
|
->E.A.R.firstErrorOrOpen
|
||||||
|
)
|
||||||
|
pairs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let oneNumber = (values: t): result<float, err> => {
|
||||||
|
switch values {
|
||||||
|
| FRValueNumber(a1) => Ok(a1)
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let oneDict = (values: t): result<Js.Dict.t<frValue>, err> => {
|
||||||
|
switch values {
|
||||||
|
| FRValueDict(a1) => Ok(a1)
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module ToTypedArray = {
|
||||||
|
let numbers = (inputs: ts): result<array<float>, err> => {
|
||||||
|
let openNumbers = (elements: array<t>) =>
|
||||||
|
elements->E.A2.fmap(oneNumber)->E.A.R.firstErrorOrOpen
|
||||||
|
inputs->getOrError(0)->E.R.bind(ToValueArray.Array.openA)->E.R.bind(openNumbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
let dicts = (inputs: ts): Belt.Result.t<array<Js.Dict.t<frValue>>, err> => {
|
||||||
|
let openDicts = (elements: array<t>) => elements->E.A2.fmap(oneDict)->E.A.R.firstErrorOrOpen
|
||||||
|
inputs->getOrError(0)->E.R.bind(ToValueArray.Array.openA)->E.R.bind(openDicts)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module Process = {
|
module Process = {
|
||||||
|
@ -75,7 +156,7 @@ module Process = {
|
||||||
| Ok((t1, t2)) =>
|
| Ok((t1, t2)) =>
|
||||||
switch SampleSetDist.map2(~fn=altFn, ~t1, ~t2) {
|
switch SampleSetDist.map2(~fn=altFn, ~t1, ~t2) {
|
||||||
| Ok(r) => Ok(DistributionTypes.SampleSet(r))
|
| Ok(r) => Ok(DistributionTypes.SampleSet(r))
|
||||||
| Error(r) => Error(Operation.Error.toString(r))
|
| Error(r) => Error(SampleSetDist.Error.toString(r))
|
||||||
}
|
}
|
||||||
| Error(r) => Error(DistributionTypes.Error.toString(r))
|
| Error(r) => Error(DistributionTypes.Error.toString(r))
|
||||||
}
|
}
|
||||||
|
@ -148,9 +229,36 @@ module OneArgDist = {
|
||||||
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
|
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
|
||||||
->E.R2.fmap(Wrappers.evDistribution)
|
->E.R2.fmap(Wrappers.evDistribution)
|
||||||
|
|
||||||
let make = (name, fn) => {
|
let make = (name, fn) =>
|
||||||
FnDefinition.make(~name, ~inputs=[FRTypeDistOrNumber], ~run=(inputs, env) =>
|
FnDefinition.make(~name, ~inputs=[FRTypeDistOrNumber], ~run=(inputs, env) =>
|
||||||
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env)
|
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module ArrayNumberDist = {
|
||||||
|
let make = (name, fn) => {
|
||||||
|
FnDefinition.make(~name, ~inputs=[FRTypeArray(FRTypeNumber)], ~run=(inputs, _) =>
|
||||||
|
Prepare.ToTypedArray.numbers(inputs)
|
||||||
|
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
|
||||||
|
->E.R.bind(fn)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let make2 = (name, fn) => {
|
||||||
|
FnDefinition.make(~name, ~inputs=[FRTypeArray(FRTypeAny)], ~run=(inputs, _) =>
|
||||||
|
Prepare.ToTypedArray.numbers(inputs)
|
||||||
|
->E.R.bind(r => E.A.length(r) === 0 ? Error("List is empty") : Ok(r))
|
||||||
|
->E.R.bind(fn)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module NumberToNumber = {
|
||||||
|
let make = (name, fn) =>
|
||||||
|
FnDefinition.make(~name, ~inputs=[FRTypeNumber], ~run=(inputs, _) => {
|
||||||
|
inputs
|
||||||
|
->getOrError(0)
|
||||||
|
->E.R.bind(Prepare.oneNumber)
|
||||||
|
->E.R2.fmap(fn)
|
||||||
|
->E.R2.fmap(Wrappers.evNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -3,9 +3,87 @@ open FunctionRegistry_Helpers
|
||||||
|
|
||||||
let twoArgs = E.Tuple2.toFnCall
|
let twoArgs = E.Tuple2.toFnCall
|
||||||
|
|
||||||
|
module Declaration = {
|
||||||
|
let frType = FRTypeRecord([
|
||||||
|
("fn", FRTypeLambda),
|
||||||
|
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
|
||||||
|
])
|
||||||
|
|
||||||
|
let fromExpressionValue = (e: frValue): result<expressionValue, string> => {
|
||||||
|
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs([e]) {
|
||||||
|
| Ok([FRValueLambda(lambda), FRValueArray(inputs)]) => {
|
||||||
|
open FunctionRegistry_Helpers.Prepare
|
||||||
|
let getMinMax = arg =>
|
||||||
|
ToValueArray.Record.toArgs([arg])
|
||||||
|
->E.R.bind(ToValueTuple.twoNumbers)
|
||||||
|
->E.R2.fmap(((min, max)) => Declaration.ContinuousFloatArg.make(min, max))
|
||||||
|
inputs
|
||||||
|
->E.A2.fmap(getMinMax)
|
||||||
|
->E.A.R.firstErrorOrOpen
|
||||||
|
->E.R2.fmap(args => ReducerInterface_ExpressionValue.EvDeclaration(
|
||||||
|
Declaration.make(lambda, args),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
| Error(r) => Error(r)
|
||||||
|
| Ok(_) => Error(FunctionRegistry_Helpers.impossibleError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputsTodist = (inputs: array<FunctionRegistry_Core.frValue>, makeDist) => {
|
||||||
|
let array = inputs->getOrError(0)->E.R.bind(Prepare.ToValueArray.Array.openA)
|
||||||
|
let xyCoords =
|
||||||
|
array->E.R.bind(xyCoords =>
|
||||||
|
xyCoords
|
||||||
|
->E.A2.fmap(xyCoord =>
|
||||||
|
[xyCoord]->Prepare.ToValueArray.Record.twoArgs->E.R.bind(Prepare.ToValueTuple.twoNumbers)
|
||||||
|
)
|
||||||
|
->E.A.R.firstErrorOrOpen
|
||||||
|
)
|
||||||
|
let expressionValue =
|
||||||
|
xyCoords
|
||||||
|
->E.R.bind(r => r->XYShape.T.makeFromZipped->E.R2.errMap(XYShape.Error.toString))
|
||||||
|
->E.R2.fmap(r => ReducerInterface_ExpressionValue.EvDistribution(PointSet(makeDist(r))))
|
||||||
|
expressionValue
|
||||||
|
}
|
||||||
|
|
||||||
let registry = [
|
let registry = [
|
||||||
|
Function.make(
|
||||||
|
~name="toContinuousPointSet",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(
|
||||||
|
~name="toContinuousPointSet",
|
||||||
|
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
|
||||||
|
~run=(inputs, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="toDiscretePointSet",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(
|
||||||
|
~name="toDiscretePointSet",
|
||||||
|
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
|
||||||
|
~run=(inputs, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Declaration",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(~name="declareFn", ~inputs=[Declaration.frType], ~run=(inputs, _) => {
|
||||||
|
inputs->getOrError(0)->E.R.bind(Declaration.fromExpressionValue)
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Normal",
|
~name="Normal",
|
||||||
|
~examples=`normal(5,1)
|
||||||
|
normal({p5: 4, p95: 10})
|
||||||
|
normal({mean: 5, stdev: 2})`,
|
||||||
~definitions=[
|
~definitions=[
|
||||||
TwoArgDist.make("normal", twoArgs(SymbolicDist.Normal.make)),
|
TwoArgDist.make("normal", twoArgs(SymbolicDist.Normal.make)),
|
||||||
TwoArgDist.makeRecordP5P95("normal", r =>
|
TwoArgDist.makeRecordP5P95("normal", r =>
|
||||||
|
@ -13,9 +91,13 @@ let registry = [
|
||||||
),
|
),
|
||||||
TwoArgDist.makeRecordMeanStdev("normal", twoArgs(SymbolicDist.Normal.make)),
|
TwoArgDist.makeRecordMeanStdev("normal", twoArgs(SymbolicDist.Normal.make)),
|
||||||
],
|
],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Lognormal",
|
~name="Lognormal",
|
||||||
|
~examples=`lognormal(0.5, 0.8)
|
||||||
|
lognormal({p5: 4, p95: 10})
|
||||||
|
lognormal({mean: 5, stdev: 2})`,
|
||||||
~definitions=[
|
~definitions=[
|
||||||
TwoArgDist.make("lognormal", twoArgs(SymbolicDist.Lognormal.make)),
|
TwoArgDist.make("lognormal", twoArgs(SymbolicDist.Lognormal.make)),
|
||||||
TwoArgDist.makeRecordP5P95("lognormal", r =>
|
TwoArgDist.makeRecordP5P95("lognormal", r =>
|
||||||
|
@ -23,29 +105,43 @@ let registry = [
|
||||||
),
|
),
|
||||||
TwoArgDist.makeRecordMeanStdev("lognormal", twoArgs(SymbolicDist.Lognormal.fromMeanAndStdev)),
|
TwoArgDist.makeRecordMeanStdev("lognormal", twoArgs(SymbolicDist.Lognormal.fromMeanAndStdev)),
|
||||||
],
|
],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Uniform",
|
~name="Uniform",
|
||||||
|
~examples=`uniform(10, 12)`,
|
||||||
~definitions=[TwoArgDist.make("uniform", twoArgs(SymbolicDist.Uniform.make))],
|
~definitions=[TwoArgDist.make("uniform", twoArgs(SymbolicDist.Uniform.make))],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Beta",
|
~name="Beta",
|
||||||
|
~examples=`beta(20, 25)`,
|
||||||
~definitions=[TwoArgDist.make("beta", twoArgs(SymbolicDist.Beta.make))],
|
~definitions=[TwoArgDist.make("beta", twoArgs(SymbolicDist.Beta.make))],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Cauchy",
|
~name="Cauchy",
|
||||||
|
~examples=`cauchy(5, 1)`,
|
||||||
~definitions=[TwoArgDist.make("cauchy", twoArgs(SymbolicDist.Cauchy.make))],
|
~definitions=[TwoArgDist.make("cauchy", twoArgs(SymbolicDist.Cauchy.make))],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Gamma",
|
~name="Gamma",
|
||||||
|
~examples=`gamma(5, 1)`,
|
||||||
~definitions=[TwoArgDist.make("gamma", twoArgs(SymbolicDist.Gamma.make))],
|
~definitions=[TwoArgDist.make("gamma", twoArgs(SymbolicDist.Gamma.make))],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Logistic",
|
~name="Logistic",
|
||||||
|
~examples=`gamma(5, 1)`,
|
||||||
~definitions=[TwoArgDist.make("logistic", twoArgs(SymbolicDist.Logistic.make))],
|
~definitions=[TwoArgDist.make("logistic", twoArgs(SymbolicDist.Logistic.make))],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="To",
|
~name="To (Distribution)",
|
||||||
|
~examples=`5 to 10
|
||||||
|
to(5,10)
|
||||||
|
-5 to 5`,
|
||||||
~definitions=[
|
~definitions=[
|
||||||
TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make)),
|
TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make)),
|
||||||
TwoArgDist.make(
|
TwoArgDist.make(
|
||||||
|
@ -53,13 +149,357 @@ let registry = [
|
||||||
twoArgs(SymbolicDist.From90thPercentile.make),
|
twoArgs(SymbolicDist.From90thPercentile.make),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Exponential",
|
~name="Exponential",
|
||||||
|
~examples=`exponential(2)`,
|
||||||
~definitions=[OneArgDist.make("exponential", SymbolicDist.Exponential.make)],
|
~definitions=[OneArgDist.make("exponential", SymbolicDist.Exponential.make)],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
Function.make(
|
Function.make(
|
||||||
~name="Bernoulli",
|
~name="Bernoulli",
|
||||||
|
~examples=`bernoulli(0.5)`,
|
||||||
~definitions=[OneArgDist.make("bernoulli", SymbolicDist.Bernoulli.make)],
|
~definitions=[OneArgDist.make("bernoulli", SymbolicDist.Bernoulli.make)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="PointMass",
|
||||||
|
~examples=`pointMass(0.5)`,
|
||||||
|
~definitions=[OneArgDist.make("pointMass", SymbolicDist.Float.makeSafe)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="toContinuousPointSet",
|
||||||
|
~description="Converts a set of points to a continuous distribution",
|
||||||
|
~examples=`toContinuousPointSet([
|
||||||
|
{x: 0, y: 0.1},
|
||||||
|
{x: 1, y: 0.2},
|
||||||
|
{x: 2, y: 0.15},
|
||||||
|
{x: 3, y: 0.1}
|
||||||
|
])`,
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(
|
||||||
|
~name="toContinuousPointSet",
|
||||||
|
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
|
||||||
|
~run=(inputs, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="toDiscretePointSet",
|
||||||
|
~description="Converts a set of points to a discrete distribution",
|
||||||
|
~examples=`toDiscretePointSet([
|
||||||
|
{x: 0, y: 0.1},
|
||||||
|
{x: 1, y: 0.2},
|
||||||
|
{x: 2, y: 0.15},
|
||||||
|
{x: 3, y: 0.1}
|
||||||
|
])`,
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(
|
||||||
|
~name="toDiscretePointSet",
|
||||||
|
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
|
||||||
|
~run=(inputs, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Declaration (Continuous Function)",
|
||||||
|
~description="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.",
|
||||||
|
~examples=`declareFn({
|
||||||
|
fn: {|a,b| a },
|
||||||
|
inputs: [
|
||||||
|
{min: 0, max: 100},
|
||||||
|
{min: 30, max: 50}
|
||||||
|
]
|
||||||
|
})`,
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(~name="declareFn", ~inputs=[Declaration.frType], ~run=(inputs, _) => {
|
||||||
|
inputs->E.A.unsafe_get(0)->Declaration.fromExpressionValue
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
~isExperimental=true,
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Floor",
|
||||||
|
~definitions=[NumberToNumber.make("floor", Js.Math.floor_float)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Ceiling",
|
||||||
|
~definitions=[NumberToNumber.make("ceil", Js.Math.ceil_float)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Absolute Value",
|
||||||
|
~definitions=[NumberToNumber.make("abs", Js.Math.abs_float)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(~name="Exponent", ~definitions=[NumberToNumber.make("exp", Js.Math.exp)], ()),
|
||||||
|
Function.make(~name="Log", ~definitions=[NumberToNumber.make("log", Js.Math.log)], ()),
|
||||||
|
Function.make(
|
||||||
|
~name="Log Base 10",
|
||||||
|
~definitions=[NumberToNumber.make("log10", Js.Math.log10)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(~name="Log Base 2", ~definitions=[NumberToNumber.make("log2", Js.Math.log2)], ()),
|
||||||
|
Function.make(~name="Round", ~definitions=[NumberToNumber.make("round", Js.Math.round)], ()),
|
||||||
|
Function.make(
|
||||||
|
~name="Sum",
|
||||||
|
~definitions=[ArrayNumberDist.make("sum", r => r->E.A.Floats.sum->Wrappers.evNumber->Ok)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Product",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make("product", r => r->E.A.Floats.product->Wrappers.evNumber->Ok),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Min",
|
||||||
|
~definitions=[ArrayNumberDist.make("min", r => r->E.A.Floats.min->Wrappers.evNumber->Ok)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Max",
|
||||||
|
~definitions=[ArrayNumberDist.make("max", r => r->E.A.Floats.max->Wrappers.evNumber->Ok)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Mean",
|
||||||
|
~definitions=[ArrayNumberDist.make("mean", r => r->E.A.Floats.mean->Wrappers.evNumber->Ok)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Geometric Mean",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make("geomean", r => r->E.A.Floats.geomean->Wrappers.evNumber->Ok),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Standard Deviation",
|
||||||
|
~definitions=[ArrayNumberDist.make("stdev", r => r->E.A.Floats.stdev->Wrappers.evNumber->Ok)],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Variance",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make("variance", r => r->E.A.Floats.stdev->Wrappers.evNumber->Ok),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="First",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make2("first", r =>
|
||||||
|
r->E.A.first |> E.O.toResult(impossibleError) |> E.R.fmap(Wrappers.evNumber)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Last",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make2("last", r =>
|
||||||
|
r->E.A.last |> E.O.toResult(impossibleError) |> E.R.fmap(Wrappers.evNumber)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Sort",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make("sort", r =>
|
||||||
|
r->E.A.Floats.sort->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Reverse",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make("reverse", r =>
|
||||||
|
r->Belt_Array.reverse->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Cumulative Sum",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make("cumsum", r =>
|
||||||
|
r->E.A.Floats.cumsum->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Cumulative Prod",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make("cumprod", r =>
|
||||||
|
r->E.A.Floats.cumsum->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Diff",
|
||||||
|
~definitions=[
|
||||||
|
ArrayNumberDist.make("diff", r =>
|
||||||
|
r->E.A.Floats.diff->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Dict.merge",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(
|
||||||
|
~name="merge",
|
||||||
|
~inputs=[FRTypeDict(FRTypeAny), FRTypeDict(FRTypeAny)],
|
||||||
|
~run=(inputs, _) => {
|
||||||
|
switch inputs {
|
||||||
|
| [FRValueDict(d1), FRValueDict(d2)] => {
|
||||||
|
let newDict =
|
||||||
|
E.Dict.concat(d1, d2) |> Js.Dict.map((. r) =>
|
||||||
|
FunctionRegistry_Core.FRType.matchReverse(r)
|
||||||
|
)
|
||||||
|
newDict->Wrappers.evRecord->Ok
|
||||||
|
}
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
//TODO: Make sure that two functions can't have the same name. This causes chaos elsewhere.
|
||||||
|
Function.make(
|
||||||
|
~name="Dict.mergeMany",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(~name="mergeMany", ~inputs=[FRTypeArray(FRTypeDict(FRTypeAny))], ~run=(
|
||||||
|
inputs,
|
||||||
|
_,
|
||||||
|
) =>
|
||||||
|
inputs
|
||||||
|
->Prepare.ToTypedArray.dicts
|
||||||
|
->E.R2.fmap(E.Dict.concatMany)
|
||||||
|
->E.R2.fmap(Js.Dict.map((. r) => FunctionRegistry_Core.FRType.matchReverse(r)))
|
||||||
|
->E.R2.fmap(Wrappers.evRecord)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Dict.keys",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(~name="keys", ~inputs=[FRTypeDict(FRTypeAny)], ~run=(inputs, _) =>
|
||||||
|
switch inputs {
|
||||||
|
| [FRValueDict(d1)] => Js.Dict.keys(d1)->E.A2.fmap(Wrappers.evString)->Wrappers.evArray->Ok
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Dict.values",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(~name="values", ~inputs=[FRTypeDict(FRTypeAny)], ~run=(inputs, _) =>
|
||||||
|
switch inputs {
|
||||||
|
| [FRValueDict(d1)] =>
|
||||||
|
Js.Dict.values(d1)
|
||||||
|
->E.A2.fmap(FunctionRegistry_Core.FRType.matchReverse)
|
||||||
|
->Wrappers.evArray
|
||||||
|
->Ok
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Dict.toList",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(~name="dictToList", ~inputs=[FRTypeDict(FRTypeAny)], ~run=(inputs, _) =>
|
||||||
|
switch inputs {
|
||||||
|
| [FRValueDict(dict)] =>
|
||||||
|
dict
|
||||||
|
->Js.Dict.entries
|
||||||
|
->E.A2.fmap(((key, value)) =>
|
||||||
|
Wrappers.evArray([
|
||||||
|
Wrappers.evString(key),
|
||||||
|
FunctionRegistry_Core.FRType.matchReverse(value),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
->Wrappers.evArray
|
||||||
|
->Ok
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="Dict.fromList",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(~name="dictFromList", ~inputs=[FRTypeArray(FRTypeArray(FRTypeAny))], ~run=(
|
||||||
|
inputs,
|
||||||
|
_,
|
||||||
|
) => {
|
||||||
|
let convertInternalItems = items =>
|
||||||
|
items
|
||||||
|
->E.A2.fmap(item => {
|
||||||
|
switch item {
|
||||||
|
| [FRValueString(string), value] =>
|
||||||
|
(string, FunctionRegistry_Core.FRType.matchReverse(value))->Ok
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->E.A.R.firstErrorOrOpen
|
||||||
|
->E.R2.fmap(Js.Dict.fromArray)
|
||||||
|
->E.R2.fmap(Wrappers.evRecord)
|
||||||
|
inputs->getOrError(0)->E.R.bind(Prepare.ToValueArray.Array.arrayOfArrays)
|
||||||
|
|> E.R2.bind(convertInternalItems)
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="List.make",
|
||||||
|
~definitions=[
|
||||||
|
//Todo: If the second item is a function with no args, it could be nice to run this function and return the result.
|
||||||
|
FnDefinition.make(~name="listMake", ~inputs=[FRTypeNumber, FRTypeAny], ~run=(inputs, _) => {
|
||||||
|
switch inputs {
|
||||||
|
| [FRValueNumber(number), value] =>
|
||||||
|
Belt.Array.make(E.Float.toInt(number), value)
|
||||||
|
->E.A2.fmap(FunctionRegistry_Core.FRType.matchReverse)
|
||||||
|
->Wrappers.evArray
|
||||||
|
->Ok
|
||||||
|
| _ => Error(impossibleError)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
(),
|
||||||
|
),
|
||||||
|
Function.make(
|
||||||
|
~name="upTo",
|
||||||
|
~definitions=[
|
||||||
|
FnDefinition.make(~name="upTo", ~inputs=[FRTypeNumber, FRTypeNumber], ~run=(inputs, _) =>
|
||||||
|
inputs
|
||||||
|
->Prepare.ToValueTuple.twoNumbers
|
||||||
|
->E.R2.fmap(((low, high)) =>
|
||||||
|
E.A.Floats.range(low, high, (high -. low +. 1.0)->E.Float.toInt)
|
||||||
|
->E.A2.fmap(Wrappers.evNumber)
|
||||||
|
->Wrappers.evArray
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
(),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -24,4 +24,4 @@ let foreignFunctionInterface = (
|
||||||
|
|
||||||
let defaultEnvironment = ExpressionValue.defaultEnvironment
|
let defaultEnvironment = ExpressionValue.defaultEnvironment
|
||||||
|
|
||||||
let defaultExternalBindings = ExpressionValue.defaultExternalBindings
|
let defaultExternalBindings = ReducerInterface_StdLib.externalStdLib
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
include Reducer_Category_Module // Bindings inherit from Module
|
||||||
|
|
||||||
|
open ReducerInterface_ExpressionValue
|
||||||
|
|
||||||
|
let emptyBindings = emptyModule
|
||||||
|
|
||||||
|
let toExpressionValue = (container: t): expressionValue => EvRecord(toRecord(container))
|
||||||
|
let fromExpressionValue = (aValue: expressionValue): t =>
|
||||||
|
switch aValue {
|
||||||
|
| EvRecord(r) => fromRecord(r)
|
||||||
|
| _ => emptyBindings
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
module ExpressionT = Reducer_Expression_T
|
||||||
|
open ReducerInterface_ExpressionValue
|
||||||
|
let expressionValueToString = toString
|
||||||
|
|
||||||
|
type t = ExpressionT.bindings
|
||||||
|
|
||||||
|
let typeAliasesKey = "_typeAliases_"
|
||||||
|
let typeReferencesKey = "_typeReferences_"
|
||||||
|
|
||||||
|
let emptyModule: t = Belt.Map.String.empty
|
||||||
|
|
||||||
|
let cloneRecord = (r: record): record => r->Js.Dict.entries->Js.Dict.fromArray
|
||||||
|
let fromRecord = (r: record): t => Js.Dict.entries(r)->Belt.Map.String.fromArray
|
||||||
|
let toRecord = (container: t): record => Belt.Map.String.toArray(container)->Js.Dict.fromArray
|
||||||
|
|
||||||
|
let toExpressionValue = (container: t): expressionValue => EvModule(toRecord(container))
|
||||||
|
let fromExpressionValue = (aValue: expressionValue): t =>
|
||||||
|
switch aValue {
|
||||||
|
| EvModule(r) => fromRecord(r)
|
||||||
|
| _ => emptyModule
|
||||||
|
}
|
||||||
|
|
||||||
|
let toString = (container: t): string => container->toRecord->EvRecord->expressionValueToString
|
||||||
|
|
||||||
|
// -- Module definition
|
||||||
|
let define = (container: t, identifier: string, ev: expressionValue): t =>
|
||||||
|
Belt.Map.String.set(container, identifier, ev) // TODO build lambda for polymorphic functions here
|
||||||
|
|
||||||
|
let defineNumber = (container: t, identifier: string, value: float): t =>
|
||||||
|
container->define(identifier, EvNumber(value))
|
||||||
|
|
||||||
|
let defineModule = (container: t, identifier: string, value: t): t =>
|
||||||
|
container->define(identifier, toExpressionValue(value))
|
|
@ -52,6 +52,16 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
||||||
| None => RERecordPropertyNotFound("Record property not found", sIndex)->Error
|
| None => RERecordPropertyNotFound("Record property not found", sIndex)->Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let doAddArray = (originalA, b) => {
|
||||||
|
let a = originalA->Js.Array2.copy
|
||||||
|
let _ = Js.Array2.pushMany(a, b)
|
||||||
|
a->EvArray->Ok
|
||||||
|
}
|
||||||
|
let doAddString = (a, b) => {
|
||||||
|
let answer = Js.String2.concat(a, b)
|
||||||
|
answer->EvString->Ok
|
||||||
|
}
|
||||||
|
|
||||||
let inspect = (value: expressionValue) => {
|
let inspect = (value: expressionValue) => {
|
||||||
Js.log(value->toString)
|
Js.log(value->toString)
|
||||||
value->Ok
|
value->Ok
|
||||||
|
@ -74,6 +84,40 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
||||||
->Ok
|
->Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let doSetBindingsInNamespace = (
|
||||||
|
externalBindings: externalBindings,
|
||||||
|
symbol: string,
|
||||||
|
value: expressionValue,
|
||||||
|
namespace: string,
|
||||||
|
) => {
|
||||||
|
let bindings = Bindings.fromExternalBindings(externalBindings)
|
||||||
|
let evAliases = bindings->Belt.Map.String.getWithDefault(namespace, EvRecord(Js.Dict.empty()))
|
||||||
|
let newEvAliases = switch evAliases {
|
||||||
|
| EvRecord(dict) => {
|
||||||
|
Js.Dict.set(dict, symbol, value)
|
||||||
|
dict->EvRecord
|
||||||
|
}
|
||||||
|
| _ => Js.Dict.empty()->EvRecord
|
||||||
|
}
|
||||||
|
bindings
|
||||||
|
->Belt.Map.String.set(namespace, newEvAliases)
|
||||||
|
->Bindings.toExternalBindings
|
||||||
|
->EvRecord
|
||||||
|
->Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
let doSetTypeAliasBindings = (
|
||||||
|
externalBindings: externalBindings,
|
||||||
|
symbol: string,
|
||||||
|
value: expressionValue,
|
||||||
|
) => doSetBindingsInNamespace(externalBindings, symbol, value, Bindings.typeAliasesKey)
|
||||||
|
|
||||||
|
let doSetTypeOfBindings = (
|
||||||
|
externalBindings: externalBindings,
|
||||||
|
symbol: string,
|
||||||
|
value: expressionValue,
|
||||||
|
) => doSetBindingsInNamespace(externalBindings, symbol, value, Bindings.typeReferencesKey)
|
||||||
|
|
||||||
let doExportBindings = (externalBindings: externalBindings) => EvRecord(externalBindings)->Ok
|
let doExportBindings = (externalBindings: externalBindings) => EvRecord(externalBindings)->Ok
|
||||||
|
|
||||||
let doKeepArray = (aValueArray, aLambdaValue) => {
|
let doKeepArray = (aValueArray, aLambdaValue) => {
|
||||||
|
@ -101,16 +145,34 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
||||||
rMappedList->Result.map(mappedList => mappedList->Belt.List.toArray->EvArray)
|
rMappedList->Result.map(mappedList => mappedList->Belt.List.toArray->EvArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
let doMapSampleSetDist = (sampleSetDist: SampleSetDist.t, aLambdaValue) => {
|
module SampleMap = {
|
||||||
let fn = r =>
|
type t = SampleSetDist.t
|
||||||
switch Lambda.doLambdaCall(aLambdaValue, list{EvNumber(r)}, environment, reducer) {
|
let doLambdaCall = (aLambdaValue, list) =>
|
||||||
|
switch Lambda.doLambdaCall(aLambdaValue, list, environment, reducer) {
|
||||||
| Ok(EvNumber(f)) => Ok(f)
|
| Ok(EvNumber(f)) => Ok(f)
|
||||||
| _ => Error(Operation.SampleMapNeedsNtoNFunction)
|
| _ => Error(Operation.SampleMapNeedsNtoNFunction)
|
||||||
}
|
}
|
||||||
switch SampleSetDist.samplesMap(~fn, sampleSetDist) {
|
|
||||||
|
let toType = r =>
|
||||||
|
switch r {
|
||||||
| Ok(r) => Ok(EvDistribution(SampleSet(r)))
|
| Ok(r) => Ok(EvDistribution(SampleSet(r)))
|
||||||
| Error(r) => Error(REDistributionError(SampleSetError(r)))
|
| Error(r) => Error(REDistributionError(SampleSetError(r)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let map1 = (sampleSetDist: t, aLambdaValue) => {
|
||||||
|
let fn = r => doLambdaCall(aLambdaValue, list{EvNumber(r)})
|
||||||
|
toType(SampleSetDist.samplesMap(~fn, sampleSetDist))
|
||||||
|
}
|
||||||
|
|
||||||
|
let map2 = (t1: t, t2: t, aLambdaValue) => {
|
||||||
|
let fn = (a, b) => doLambdaCall(aLambdaValue, list{EvNumber(a), EvNumber(b)})
|
||||||
|
SampleSetDist.map2(~fn, ~t1, ~t2)->toType
|
||||||
|
}
|
||||||
|
|
||||||
|
let map3 = (t1: t, t2: t, t3: t, aLambdaValue) => {
|
||||||
|
let fn = (a, b, c) => doLambdaCall(aLambdaValue, list{EvNumber(a), EvNumber(b), EvNumber(c)})
|
||||||
|
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let doReduceArray = (aValueArray, initialValue, aLambdaValue) => {
|
let doReduceArray = (aValueArray, initialValue, aLambdaValue) => {
|
||||||
|
@ -129,21 +191,122 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let typeModifier_memberOf = (aType, anArray) => {
|
||||||
|
let newRecord = Js.Dict.fromArray([
|
||||||
|
("typeTag", EvString("typeIdentifier")),
|
||||||
|
("typeIdentifier", aType),
|
||||||
|
])
|
||||||
|
newRecord->Js.Dict.set("memberOf", anArray)
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
let typeModifier_memberOf_update = (aRecord, anArray) => {
|
||||||
|
let newRecord = aRecord->Js.Dict.entries->Js.Dict.fromArray
|
||||||
|
newRecord->Js.Dict.set("memberOf", anArray)
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
let typeModifier_min = (aType, value) => {
|
||||||
|
let newRecord = Js.Dict.fromArray([
|
||||||
|
("typeTag", EvString("typeIdentifier")),
|
||||||
|
("typeIdentifier", aType),
|
||||||
|
])
|
||||||
|
newRecord->Js.Dict.set("min", value)
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
let typeModifier_min_update = (aRecord, value) => {
|
||||||
|
let newRecord = aRecord->Js.Dict.entries->Js.Dict.fromArray
|
||||||
|
newRecord->Js.Dict.set("min", value)
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
let typeModifier_max = (aType, value) => {
|
||||||
|
let newRecord = Js.Dict.fromArray([
|
||||||
|
("typeTag", EvString("typeIdentifier")),
|
||||||
|
("typeIdentifier", aType),
|
||||||
|
])
|
||||||
|
newRecord->Js.Dict.set("max", value)
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
let typeModifier_max_update = (aRecord, value) => {
|
||||||
|
let newRecord = aRecord->Js.Dict.entries->Js.Dict.fromArray
|
||||||
|
newRecord->Js.Dict.set("max", value)
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
let typeModifier_opaque_update = aRecord => {
|
||||||
|
let newRecord = aRecord->Js.Dict.entries->Js.Dict.fromArray
|
||||||
|
newRecord->Js.Dict.set("opaque", EvBool(true))
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
let typeOr = evArray => {
|
||||||
|
let newRecord = Js.Dict.fromArray([("typeTag", EvString("typeOr")), ("typeOr", evArray)])
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
let typeFunction = anArray => {
|
||||||
|
let output = Belt.Array.getUnsafe(anArray, Js.Array2.length(anArray) - 1)
|
||||||
|
let inputs = Js.Array2.slice(anArray, ~start=0, ~end_=-1)
|
||||||
|
let newRecord = Js.Dict.fromArray([
|
||||||
|
("typeTag", EvString("typeFunction")),
|
||||||
|
("inputs", EvArray(inputs)),
|
||||||
|
("output", output),
|
||||||
|
])
|
||||||
|
newRecord->EvRecord->Ok
|
||||||
|
}
|
||||||
|
|
||||||
switch call {
|
switch call {
|
||||||
| ("$_atIndex_$", [EvArray(aValueArray), EvNumber(fIndex)]) => arrayAtIndex(aValueArray, fIndex)
|
| ("$_atIndex_$", [EvArray(aValueArray), EvNumber(fIndex)]) => arrayAtIndex(aValueArray, fIndex)
|
||||||
|
| ("$_atIndex_$", [EvModule(dict), EvString(sIndex)]) => recordAtIndex(dict, sIndex)
|
||||||
| ("$_atIndex_$", [EvRecord(dict), EvString(sIndex)]) => recordAtIndex(dict, sIndex)
|
| ("$_atIndex_$", [EvRecord(dict), EvString(sIndex)]) => recordAtIndex(dict, sIndex)
|
||||||
| ("$_constructArray_$", [EvArray(aValueArray)]) => EvArray(aValueArray)->Ok
|
| ("$_constructArray_$", [EvArray(aValueArray)]) => EvArray(aValueArray)->Ok
|
||||||
| ("$_constructRecord_$", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs)
|
| ("$_constructRecord_$", [EvArray(arrayOfPairs)]) => constructRecord(arrayOfPairs)
|
||||||
| ("$_exportBindings_$", [EvRecord(externalBindings)]) => doExportBindings(externalBindings)
|
| ("$_exportBindings_$", [EvRecord(externalBindings)]) => doExportBindings(externalBindings)
|
||||||
| ("$_setBindings_$", [EvRecord(externalBindings), EvSymbol(symbol), value]) =>
|
| ("$_setBindings_$", [EvRecord(externalBindings), EvSymbol(symbol), value]) =>
|
||||||
doSetBindings(externalBindings, symbol, value)
|
doSetBindings(externalBindings, symbol, value)
|
||||||
|
| ("$_setTypeAliasBindings_$", [EvRecord(externalBindings), EvTypeIdentifier(symbol), value]) =>
|
||||||
|
doSetTypeAliasBindings(externalBindings, symbol, value)
|
||||||
|
| ("$_setTypeOfBindings_$", [EvRecord(externalBindings), EvSymbol(symbol), value]) =>
|
||||||
|
doSetTypeOfBindings(externalBindings, symbol, value)
|
||||||
|
| ("$_typeModifier_memberOf_$", [EvTypeIdentifier(typeIdentifier), EvArray(arr)]) =>
|
||||||
|
typeModifier_memberOf(EvTypeIdentifier(typeIdentifier), EvArray(arr))
|
||||||
|
| ("$_typeModifier_memberOf_$", [EvRecord(typeRecord), EvArray(arr)]) =>
|
||||||
|
typeModifier_memberOf_update(typeRecord, EvArray(arr))
|
||||||
|
| ("$_typeModifier_min_$", [EvTypeIdentifier(typeIdentifier), value]) =>
|
||||||
|
typeModifier_min(EvTypeIdentifier(typeIdentifier), value)
|
||||||
|
| ("$_typeModifier_min_$", [EvRecord(typeRecord), value]) =>
|
||||||
|
typeModifier_min_update(typeRecord, value)
|
||||||
|
| ("$_typeModifier_max_$", [EvTypeIdentifier(typeIdentifier), value]) =>
|
||||||
|
typeModifier_max(EvTypeIdentifier(typeIdentifier), value)
|
||||||
|
| ("$_typeModifier_max_$", [EvRecord(typeRecord), value]) =>
|
||||||
|
typeModifier_max_update(typeRecord, value)
|
||||||
|
| ("$_typeModifier_opaque_$", [EvRecord(typeRecord)]) => typeModifier_opaque_update(typeRecord)
|
||||||
|
| ("$_typeOr_$", [EvArray(arr)]) => typeOr(EvArray(arr))
|
||||||
|
| ("$_typeFunction_$", [EvArray(arr)]) => typeFunction(arr)
|
||||||
|
| ("concat", [EvArray(aValueArray), EvArray(bValueArray)]) => doAddArray(aValueArray, bValueArray)
|
||||||
|
| ("concat", [EvString(aValueString), EvString(bValueString)]) =>
|
||||||
|
doAddString(aValueString, bValueString)
|
||||||
| ("inspect", [value, EvString(label)]) => inspectLabel(value, label)
|
| ("inspect", [value, EvString(label)]) => inspectLabel(value, label)
|
||||||
| ("inspect", [value]) => inspect(value)
|
| ("inspect", [value]) => inspect(value)
|
||||||
| ("keep", [EvArray(aValueArray), EvLambda(aLambdaValue)]) =>
|
| ("filter", [EvArray(aValueArray), EvLambda(aLambdaValue)]) =>
|
||||||
doKeepArray(aValueArray, aLambdaValue)
|
doKeepArray(aValueArray, aLambdaValue)
|
||||||
| ("map", [EvArray(aValueArray), EvLambda(aLambdaValue)]) => doMapArray(aValueArray, aLambdaValue)
|
| ("map", [EvArray(aValueArray), EvLambda(aLambdaValue)]) => doMapArray(aValueArray, aLambdaValue)
|
||||||
| ("mapSamples", [EvDistribution(SampleSet(dist)), EvLambda(aLambdaValue)]) =>
|
| ("mapSamples", [EvDistribution(SampleSet(dist)), EvLambda(aLambdaValue)]) =>
|
||||||
doMapSampleSetDist(dist, aLambdaValue)
|
SampleMap.map1(dist, aLambdaValue)
|
||||||
|
| (
|
||||||
|
"mapSamples2",
|
||||||
|
[EvDistribution(SampleSet(dist1)), EvDistribution(SampleSet(dist2)), EvLambda(aLambdaValue)],
|
||||||
|
) =>
|
||||||
|
SampleMap.map2(dist1, dist2, aLambdaValue)
|
||||||
|
| (
|
||||||
|
"mapSamples3",
|
||||||
|
[
|
||||||
|
EvDistribution(SampleSet(dist1)),
|
||||||
|
EvDistribution(SampleSet(dist2)),
|
||||||
|
EvDistribution(SampleSet(dist3)),
|
||||||
|
EvLambda(aLambdaValue),
|
||||||
|
],
|
||||||
|
) =>
|
||||||
|
SampleMap.map3(dist1, dist2, dist3, aLambdaValue)
|
||||||
| ("reduce", [EvArray(aValueArray), initialValue, EvLambda(aLambdaValue)]) =>
|
| ("reduce", [EvArray(aValueArray), initialValue, EvLambda(aLambdaValue)]) =>
|
||||||
doReduceArray(aValueArray, initialValue, aLambdaValue)
|
doReduceArray(aValueArray, initialValue, aLambdaValue)
|
||||||
| ("reduceReverse", [EvArray(aValueArray), initialValue, EvLambda(aLambdaValue)]) =>
|
| ("reduceReverse", [EvArray(aValueArray), initialValue, EvLambda(aLambdaValue)]) =>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
Macros are used to define language building blocks. They are like Lisp macros.
|
Macros are used to define language building blocks. They are like Lisp macros.
|
||||||
*/
|
*/
|
||||||
module Bindings = Reducer_Expression_Bindings
|
module Bindings = Reducer_Expression_Bindings
|
||||||
|
module ErrorValue = Reducer_ErrorValue
|
||||||
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
module ExpressionBuilder = Reducer_Expression_ExpressionBuilder
|
||||||
module ExpressionT = Reducer_Expression_T
|
module ExpressionT = Reducer_Expression_T
|
||||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
|
@ -12,7 +13,7 @@ module Result = Belt.Result
|
||||||
open Reducer_Expression_ExpressionBuilder
|
open Reducer_Expression_ExpressionBuilder
|
||||||
|
|
||||||
type environment = ExpressionValue.environment
|
type environment = ExpressionValue.environment
|
||||||
type errorValue = Reducer_ErrorValue.errorValue
|
type errorValue = ErrorValue.errorValue
|
||||||
type expression = ExpressionT.expression
|
type expression = ExpressionT.expression
|
||||||
type expressionValue = ExpressionValue.expressionValue
|
type expressionValue = ExpressionValue.expressionValue
|
||||||
type expressionWithContext = ExpressionWithContext.expressionWithContext
|
type expressionWithContext = ExpressionWithContext.expressionWithContext
|
||||||
|
@ -23,82 +24,76 @@ let dispatchMacroCall = (
|
||||||
environment,
|
environment,
|
||||||
reduceExpression: ExpressionT.reducerFn,
|
reduceExpression: ExpressionT.reducerFn,
|
||||||
): result<expressionWithContext, errorValue> => {
|
): result<expressionWithContext, errorValue> => {
|
||||||
let doBindStatement = (bindingExpr: expression, statement: expression, environment) =>
|
let useExpressionToSetBindings = (bindingExpr: expression, environment, statement, newCode) => {
|
||||||
switch statement {
|
|
||||||
| ExpressionT.EList(list{ExpressionT.EValue(EvCall("$_let_$")), symbolExpr, statement}) => {
|
|
||||||
let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment)
|
let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment)
|
||||||
|
|
||||||
rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
|
rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
|
||||||
let newBindings = Bindings.fromValue(externalBindingsValue)
|
let newBindings = Bindings.fromValue(externalBindingsValue)
|
||||||
|
|
||||||
// Js.log(
|
|
||||||
// `bindStatement ${Bindings.toString(newBindings)}<==${ExpressionT.toString(
|
|
||||||
// bindingExpr,
|
|
||||||
// )} statement: $_let_$ ${ExpressionT.toString(symbolExpr)}=${ExpressionT.toString(
|
|
||||||
// statement,
|
|
||||||
// )}`,
|
|
||||||
// )
|
|
||||||
|
|
||||||
let rNewStatement = Bindings.replaceSymbols(newBindings, statement)
|
let rNewStatement = Bindings.replaceSymbols(newBindings, statement)
|
||||||
rNewStatement->Result.map(newStatement =>
|
rNewStatement->Result.map(boundStatement =>
|
||||||
ExpressionWithContext.withContext(
|
ExpressionWithContext.withContext(
|
||||||
eFunction(
|
newCode(newBindings->Bindings.toExternalBindings->eRecord, boundStatement),
|
||||||
"$_setBindings_$",
|
|
||||||
list{newBindings->Bindings.toExternalBindings->eRecord, symbolExpr, newStatement},
|
|
||||||
),
|
|
||||||
newBindings,
|
newBindings,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
| _ => REAssignmentExpected->Error
|
|
||||||
|
let correspondingSetBindingsFn = (fnName: string): string =>
|
||||||
|
switch fnName {
|
||||||
|
| "$_let_$" => "$_setBindings_$"
|
||||||
|
| "$_typeOf_$" => "$_setTypeOfBindings_$"
|
||||||
|
| "$_typeAlias_$" => "$_setTypeAliasBindings_$"
|
||||||
|
| _ => ""
|
||||||
|
}
|
||||||
|
|
||||||
|
let doBindStatement = (bindingExpr: expression, statement: expression, environment) => {
|
||||||
|
let defaultStatement = ErrorValue.REAssignmentExpected->Error
|
||||||
|
switch statement {
|
||||||
|
| ExpressionT.EList(list{ExpressionT.EValue(EvCall(callName)), symbolExpr, statement}) => {
|
||||||
|
let setBindingsFn = correspondingSetBindingsFn(callName)
|
||||||
|
if setBindingsFn !== "" {
|
||||||
|
useExpressionToSetBindings(bindingExpr, environment, statement, (
|
||||||
|
newBindingsExpr,
|
||||||
|
boundStatement,
|
||||||
|
) => eFunction(setBindingsFn, list{newBindingsExpr, symbolExpr, boundStatement}))
|
||||||
|
} else {
|
||||||
|
defaultStatement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| _ => defaultStatement
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let doBindExpression = (bindingExpr: expression, statement: expression, environment): result<
|
let doBindExpression = (bindingExpr: expression, statement: expression, environment): result<
|
||||||
expressionWithContext,
|
expressionWithContext,
|
||||||
errorValue,
|
errorValue,
|
||||||
> =>
|
> => {
|
||||||
switch statement {
|
let defaultStatement = () =>
|
||||||
| ExpressionT.EList(list{ExpressionT.EValue(EvCall("$_let_$")), symbolExpr, statement}) => {
|
useExpressionToSetBindings(bindingExpr, environment, statement, (
|
||||||
let rExternalBindingsValue = reduceExpression(bindingExpr, bindings, environment)
|
_newBindingsExpr,
|
||||||
|
boundStatement,
|
||||||
|
) => boundStatement)
|
||||||
|
|
||||||
rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
|
switch statement {
|
||||||
let newBindings = Bindings.fromValue(externalBindingsValue)
|
| ExpressionT.EList(list{ExpressionT.EValue(EvCall(callName)), symbolExpr, statement}) => {
|
||||||
let rNewStatement = Bindings.replaceSymbols(newBindings, statement)
|
let setBindingsFn = correspondingSetBindingsFn(callName)
|
||||||
rNewStatement->Result.map(newStatement =>
|
if setBindingsFn !== "" {
|
||||||
ExpressionWithContext.withContext(
|
useExpressionToSetBindings(bindingExpr, environment, statement, (
|
||||||
|
newBindingsExpr,
|
||||||
|
boundStatement,
|
||||||
|
) =>
|
||||||
eFunction(
|
eFunction(
|
||||||
"$_exportBindings_$",
|
"$_exportBindings_$",
|
||||||
list{
|
list{eFunction(setBindingsFn, list{newBindingsExpr, symbolExpr, boundStatement})},
|
||||||
eFunction(
|
|
||||||
"$_setBindings_$",
|
|
||||||
list{
|
|
||||||
newBindings->Bindings.toExternalBindings->eRecord,
|
|
||||||
symbolExpr,
|
|
||||||
newStatement,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
newBindings,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
} else {
|
||||||
|
defaultStatement()
|
||||||
}
|
}
|
||||||
| _ => {
|
}
|
||||||
let rExternalBindingsValue: result<expressionValue, errorValue> = reduceExpression(
|
| _ => defaultStatement()
|
||||||
bindingExpr,
|
|
||||||
bindings,
|
|
||||||
environment,
|
|
||||||
)
|
|
||||||
|
|
||||||
rExternalBindingsValue->Result.flatMap(externalBindingsValue => {
|
|
||||||
let newBindings = Bindings.fromValue(externalBindingsValue)
|
|
||||||
let rNewStatement = Bindings.replaceSymbols(newBindings, statement)
|
|
||||||
rNewStatement->Result.map(newStatement =>
|
|
||||||
ExpressionWithContext.withContext(newStatement, newBindings)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,8 +139,14 @@ let dispatchMacroCall = (
|
||||||
let rCondition = reduceExpression(blockCondition, bindings, environment)
|
let rCondition = reduceExpression(blockCondition, bindings, environment)
|
||||||
rCondition->Result.flatMap(conditionValue =>
|
rCondition->Result.flatMap(conditionValue =>
|
||||||
switch conditionValue {
|
switch conditionValue {
|
||||||
| ExpressionValue.EvBool(false) => ExpressionWithContext.noContext(ifFalse)->Ok
|
| ExpressionValue.EvBool(false) => {
|
||||||
| ExpressionValue.EvBool(true) => ExpressionWithContext.noContext(ifTrue)->Ok
|
let ifFalseBlock = eBlock(list{ifFalse})
|
||||||
|
ExpressionWithContext.withContext(ifFalseBlock, bindings)->Ok
|
||||||
|
}
|
||||||
|
| ExpressionValue.EvBool(true) => {
|
||||||
|
let ifTrueBlock = eBlock(list{ifTrue})
|
||||||
|
ExpressionWithContext.withContext(ifTrueBlock, bindings)->Ok
|
||||||
|
}
|
||||||
| _ => REExpectedType("Boolean")->Error
|
| _ => REExpectedType("Boolean")->Error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -75,9 +75,9 @@ and reduceValueList = (valueList: list<expressionValue>, environment): result<
|
||||||
> =>
|
> =>
|
||||||
switch valueList {
|
switch valueList {
|
||||||
| list{EvCall(fName), ...args} => {
|
| list{EvCall(fName), ...args} => {
|
||||||
let rCheckedArgs = switch fName == "$_setBindings_$" {
|
let rCheckedArgs = switch fName {
|
||||||
| false => args->Lambda.checkIfReduced
|
| "$_setBindings_$" | "$_setTypeOfBindings_$" | "$_setTypeAliasBindings_$" => args->Ok
|
||||||
| true => args->Ok
|
| _ => args->Lambda.checkIfReduced
|
||||||
}
|
}
|
||||||
|
|
||||||
rCheckedArgs->Result.flatMap(checkedArgs =>
|
rCheckedArgs->Result.flatMap(checkedArgs =>
|
||||||
|
@ -116,14 +116,20 @@ let evaluateUsingOptions = (
|
||||||
~externalBindings: option<ReducerInterface_ExpressionValue.externalBindings>,
|
~externalBindings: option<ReducerInterface_ExpressionValue.externalBindings>,
|
||||||
code: string,
|
code: string,
|
||||||
): result<expressionValue, errorValue> => {
|
): result<expressionValue, errorValue> => {
|
||||||
let anEnvironment = switch environment {
|
let anEnvironment = Belt.Option.getWithDefault(
|
||||||
| Some(env) => env
|
environment,
|
||||||
| None => ReducerInterface_ExpressionValue.defaultEnvironment
|
ReducerInterface_ExpressionValue.defaultEnvironment,
|
||||||
}
|
)
|
||||||
|
|
||||||
let anExternalBindings = switch externalBindings {
|
let anExternalBindings = switch externalBindings {
|
||||||
| Some(bindings) => bindings
|
| Some(bindings) => {
|
||||||
| None => ReducerInterface_ExpressionValue.defaultExternalBindings
|
let cloneLib = ReducerInterface_StdLib.externalStdLib->Reducer_Category_Bindings.cloneRecord
|
||||||
|
Js.Dict.entries(bindings)->Js.Array2.reduce((acc, (key, value)) => {
|
||||||
|
acc->Js.Dict.set(key, value)
|
||||||
|
acc
|
||||||
|
}, cloneLib)
|
||||||
|
}
|
||||||
|
| None => ReducerInterface_StdLib.externalStdLib
|
||||||
}
|
}
|
||||||
|
|
||||||
let bindings = anExternalBindings->Bindings.fromExternalBindings
|
let bindings = anExternalBindings->Bindings.fromExternalBindings
|
||||||
|
|
|
@ -22,11 +22,16 @@ let callReducer = (
|
||||||
bindings: bindings,
|
bindings: bindings,
|
||||||
environment: environment,
|
environment: environment,
|
||||||
reducer: reducerFn,
|
reducer: reducerFn,
|
||||||
): result<expressionValue, errorValue> =>
|
): result<expressionValue, errorValue> => {
|
||||||
switch expressionWithContext {
|
switch expressionWithContext {
|
||||||
| ExpressionNoContext(expr) => reducer(expr, bindings, environment)
|
| ExpressionNoContext(expr) =>
|
||||||
| ExpressionWithContext(expr, context) => reducer(expr, context, environment)
|
// Js.log(`callReducer: bindings ${Bindings.toString(bindings)} expr ${ExpressionT.toString(expr)}`)
|
||||||
|
reducer(expr, bindings, environment)
|
||||||
|
| ExpressionWithContext(expr, context) =>
|
||||||
|
// Js.log(`callReducer: context ${Bindings.toString(context)} expr ${ExpressionT.toString(expr)}`)
|
||||||
|
reducer(expr, context, environment)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let withContext = (expression, context) => ExpressionWithContext(expression, context)
|
let withContext = (expression, context) => ExpressionWithContext(expression, context)
|
||||||
let noContext = expression => ExpressionNoContext(expression)
|
let noContext = expression => ExpressionNoContext(expression)
|
||||||
|
|
|
@ -2,38 +2,25 @@ module ErrorValue = Reducer_ErrorValue
|
||||||
module ExpressionT = Reducer_Expression_T
|
module ExpressionT = Reducer_Expression_T
|
||||||
module ExpressionValue = ReducerInterface.ExpressionValue
|
module ExpressionValue = ReducerInterface.ExpressionValue
|
||||||
module Result = Belt.Result
|
module Result = Belt.Result
|
||||||
|
module Bindings = Reducer_Category_Bindings
|
||||||
|
|
||||||
type errorValue = Reducer_ErrorValue.errorValue
|
type errorValue = Reducer_ErrorValue.errorValue
|
||||||
type expression = ExpressionT.expression
|
type expression = ExpressionT.expression
|
||||||
type expressionValue = ExpressionValue.expressionValue
|
type expressionValue = ExpressionValue.expressionValue
|
||||||
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
type externalBindings = ReducerInterface_ExpressionValue.externalBindings
|
||||||
|
|
||||||
let defaultBindings: ExpressionT.bindings = Belt.Map.String.empty
|
let emptyBindings = Reducer_Category_Bindings.emptyBindings
|
||||||
|
|
||||||
let fromExternalBindings = (externalBindings: externalBindings): ExpressionT.bindings => {
|
let typeAliasesKey = Bindings.typeAliasesKey
|
||||||
let keys = Js.Dict.keys(externalBindings)
|
let typeReferencesKey = Bindings.typeReferencesKey
|
||||||
keys->Belt.Array.reduce(defaultBindings, (acc, key) => {
|
|
||||||
let value = Js.Dict.unsafeGet(externalBindings, key)
|
|
||||||
acc->Belt.Map.String.set(key, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let toExternalBindings = (bindings: ExpressionT.bindings): externalBindings => {
|
let toExternalBindings = (bindings: ExpressionT.bindings): externalBindings =>
|
||||||
let keys = Belt.Map.String.keysToArray(bindings)
|
Bindings.toRecord(bindings)
|
||||||
keys->Belt.Array.reduce(Js.Dict.empty(), (acc, key) => {
|
|
||||||
let value = bindings->Belt.Map.String.getExn(key)
|
|
||||||
Js.Dict.set(acc, key, value)
|
|
||||||
acc
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let fromValue = (aValue: expressionValue) =>
|
let fromExternalBindings = (externalBindings: externalBindings): ExpressionT.bindings =>
|
||||||
switch aValue {
|
Bindings.fromRecord(externalBindings)
|
||||||
| EvRecord(externalBindings) => fromExternalBindings(externalBindings)
|
|
||||||
| _ => defaultBindings
|
|
||||||
}
|
|
||||||
|
|
||||||
let externalFromArray = anArray => Js.Dict.fromArray(anArray)
|
let fromValue = (aValue: expressionValue) => Bindings.fromExpressionValue(aValue)
|
||||||
|
|
||||||
let isMacroName = (fName: string): bool => fName->Js.String2.startsWith("$$")
|
let isMacroName = (fName: string): bool => fName->Js.String2.startsWith("$$")
|
||||||
|
|
||||||
|
|
|
@ -64,3 +64,8 @@ let eBindExpression = (bindingExpr: expression, expression: expression): express
|
||||||
|
|
||||||
let eBindExpressionDefault = (expression: expression): expression =>
|
let eBindExpressionDefault = (expression: expression): expression =>
|
||||||
eFunction("$$_bindExpression_$$", list{expression})
|
eFunction("$$_bindExpression_$$", list{expression})
|
||||||
|
|
||||||
|
let eIdentifier = (name: string): expression => name->BExpressionValue.EvSymbol->BExpressionT.EValue
|
||||||
|
|
||||||
|
let eTypeIdentifier = (name: string): expression =>
|
||||||
|
name->BExpressionValue.EvTypeIdentifier->BExpressionT.EValue
|
||||||
|
|
|
@ -28,6 +28,11 @@ type reducerFn = (
|
||||||
*/
|
*/
|
||||||
let rec toString = expression =>
|
let rec toString = expression =>
|
||||||
switch expression {
|
switch expression {
|
||||||
|
| EList(list{EValue(EvCall("$$_block_$$")), ...statements}) =>
|
||||||
|
`{${Belt.List.map(statements, aValue => toString(aValue))
|
||||||
|
->Extra.List.interperse("; ")
|
||||||
|
->Belt.List.toArray
|
||||||
|
->Js.String.concatMany("")}}`
|
||||||
| EList(aList) =>
|
| EList(aList) =>
|
||||||
`(${Belt.List.map(aList, aValue => toString(aValue))
|
`(${Belt.List.map(aList, aValue => toString(aValue))
|
||||||
->Extra.List.interperse(" ")
|
->Extra.List.interperse(" ")
|
||||||
|
|
|
@ -36,11 +36,6 @@
|
||||||
'[]': '$_atIndex_$',
|
'[]': '$_atIndex_$',
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodeBlock(statements) {return{type: 'Block', statements: statements}}
|
|
||||||
function nodeBoolean(value) {return {type: 'Boolean', value: value}}
|
|
||||||
function nodeCallIndentifier(value) {return {type: 'CallIdentifier', value: value}}
|
|
||||||
function nodeExpression(args) {return {type: 'Expression', nodes: args}}
|
|
||||||
function nodeFloat(value) {return {type: 'Float', value: value}}
|
|
||||||
function makeFunctionCall(fn, args) {
|
function makeFunctionCall(fn, args) {
|
||||||
if (fn === '$$_applyAll_$$') {
|
if (fn === '$$_applyAll_$$') {
|
||||||
// Any list of values is applied from left to right anyway.
|
// Any list of values is applied from left to right anyway.
|
||||||
|
@ -52,6 +47,16 @@
|
||||||
return nodeExpression([nodeCallIndentifier(fn), ...args])
|
return nodeExpression([nodeCallIndentifier(fn), ...args])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function apply(fn, arg) { return makeFunctionCall(fn, [arg]); }
|
||||||
|
function constructArray(elems) { return apply('$_constructArray_$', nodeExpression(elems)); }
|
||||||
|
function constructRecord(elems) { return apply('$_constructRecord_$', nodeExpression(elems)); }
|
||||||
|
|
||||||
|
function nodeBlock(statements) {return{type: 'Block', statements: statements}}
|
||||||
|
function nodeBoolean(value) {return {type: 'Boolean', value: value}}
|
||||||
|
function nodeCallIndentifier(value) {return {type: 'CallIdentifier', value: value}}
|
||||||
|
function nodeExpression(args) {return {type: 'Expression', nodes: args}}
|
||||||
|
function nodeFloat(value) {return {type: 'Float', value: value}}
|
||||||
function nodeIdentifier(value) {return {type: 'Identifier', value: value}}
|
function nodeIdentifier(value) {return {type: 'Identifier', value: value}}
|
||||||
function nodeInteger(value) {return {type: 'Integer', value: value}}
|
function nodeInteger(value) {return {type: 'Integer', value: value}}
|
||||||
function nodeKeyValue(key, value) {
|
function nodeKeyValue(key, value) {
|
||||||
|
@ -59,11 +64,15 @@
|
||||||
return {type: 'KeyValue', key: key, value: value}}
|
return {type: 'KeyValue', key: key, value: value}}
|
||||||
function nodeLambda(args, body) {return {type: 'Lambda', args: args, body: body}}
|
function nodeLambda(args, body) {return {type: 'Lambda', args: args, body: body}}
|
||||||
function nodeLetStatment(variable, value) {return {type: 'LetStatement', variable: variable, value: value}}
|
function nodeLetStatment(variable, value) {return {type: 'LetStatement', variable: variable, value: value}}
|
||||||
|
function nodeModuleIdentifier(value) {return {type: 'ModuleIdentifier', value: value}}
|
||||||
function nodeString(value) {return {type: 'String', value: value}}
|
function nodeString(value) {return {type: 'String', value: value}}
|
||||||
function nodeTernary(condition, trueExpression, falseExpression) {return {type: 'Ternary', condition: condition, trueExpression: trueExpression, falseExpression: falseExpression}}
|
function nodeTernary(condition, trueExpression, falseExpression) {return {type: 'Ternary', condition: condition, trueExpression: trueExpression, falseExpression: falseExpression}}
|
||||||
|
|
||||||
|
function nodeTypeIdentifier(typeValue) {return {type: 'TypeIdentifier', value: typeValue}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
start
|
start
|
||||||
|
// = _nl start:typeExpression _nl finalComment? {return start}
|
||||||
= _nl start:outerBlock _nl finalComment? {return start}
|
= _nl start:outerBlock _nl finalComment? {return start}
|
||||||
|
|
||||||
zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda
|
zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda
|
||||||
|
@ -96,6 +105,7 @@ array_statements
|
||||||
statement
|
statement
|
||||||
= letStatement
|
= letStatement
|
||||||
/ defunStatement
|
/ defunStatement
|
||||||
|
/ typeStatement
|
||||||
|
|
||||||
letStatement
|
letStatement
|
||||||
= variable:identifier _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
|
= variable:identifier _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
|
||||||
|
@ -205,7 +215,7 @@ chainFunctionCall
|
||||||
|
|
||||||
unary
|
unary
|
||||||
= unaryOperator:unaryOperator _nl right:(unary/postOperator)
|
= unaryOperator:unaryOperator _nl right:(unary/postOperator)
|
||||||
{ return makeFunctionCall(unaryToFunction[unaryOperator], [right])}
|
{ return apply(unaryToFunction[unaryOperator], right)}
|
||||||
/ postOperator
|
/ postOperator
|
||||||
|
|
||||||
unaryOperator "unary operator"
|
unaryOperator "unary operator"
|
||||||
|
@ -215,10 +225,9 @@ postOperator = indexedValue
|
||||||
|
|
||||||
indexedValue
|
indexedValue
|
||||||
= collectionElement
|
= collectionElement
|
||||||
/ recordElement
|
|
||||||
/ atom
|
/ atom
|
||||||
|
|
||||||
collectionElement
|
collectionElement
|
||||||
= head:atom &('['/'('/'.')
|
= head:atom &('['/'('/'.')
|
||||||
tail:(
|
tail:(
|
||||||
_ '[' _nl arg:expression _nl ']' {return {fn: postOperatorToFunction['[]'], args: [arg]}}
|
_ '[' _nl arg:expression _nl ']' {return {fn: postOperatorToFunction['[]'], args: [arg]}}
|
||||||
|
@ -233,13 +242,6 @@ indexedValue
|
||||||
= head:expression tail:(_ ',' _nl @expression)*
|
= head:expression tail:(_ ',' _nl @expression)*
|
||||||
{ return [head, ...tail]; }
|
{ return [head, ...tail]; }
|
||||||
|
|
||||||
recordElement
|
|
||||||
= head:dollarIdentifier &'.'
|
|
||||||
tail:(_ '.' _nl arg:$dollarIdentifier {return {fn: postOperatorToFunction['.'], args: [nodeString(arg)]}})*
|
|
||||||
{ return tail.reduce(function(result, element) {
|
|
||||||
return makeFunctionCall(element.fn, [result, ...element.args])
|
|
||||||
}, head)}
|
|
||||||
|
|
||||||
atom
|
atom
|
||||||
= '(' _nl expression:expression _nl ')' {return expression}
|
= '(' _nl expression:expression _nl ')' {return expression}
|
||||||
/ basicValue
|
/ basicValue
|
||||||
|
@ -250,24 +252,41 @@ basicLiteral
|
||||||
= string
|
= string
|
||||||
/ number
|
/ number
|
||||||
/ boolean
|
/ boolean
|
||||||
|
/ dollarIdentifierWithModule
|
||||||
/ dollarIdentifier
|
/ dollarIdentifier
|
||||||
|
|
||||||
|
dollarIdentifierWithModule 'identifier'
|
||||||
|
= head:moduleIdentifier
|
||||||
|
tail:('.' _nl @$moduleIdentifier)* '.' _nl
|
||||||
|
final:$dollarIdentifier
|
||||||
|
{ tail.push(final);
|
||||||
|
return tail.reduce(function(result, element) {
|
||||||
|
return makeFunctionCall(postOperatorToFunction['[]'], [result, nodeString(element)])
|
||||||
|
}, head)}
|
||||||
|
|
||||||
identifier 'identifier'
|
identifier 'identifier'
|
||||||
= ([_a-z]+[_a-z0-9]i*) {return nodeIdentifier(text())}
|
= ([_a-z]+[_a-z0-9]i*) {return nodeIdentifier(text())}
|
||||||
|
|
||||||
|
unitIdentifier 'identifier'
|
||||||
|
= ([_a-zA-Z]+[_a-z0-9]i*) {return nodeIdentifier(text())}
|
||||||
|
|
||||||
dollarIdentifier '$identifier'
|
dollarIdentifier '$identifier'
|
||||||
= ([\$_a-z]+[\$_a-z0-9]i*) {return nodeIdentifier(text())}
|
= ([\$_a-z]+[\$_a-z0-9]i*) {return nodeIdentifier(text())}
|
||||||
|
|
||||||
|
moduleIdentifier 'identifier'
|
||||||
|
= ([A-Z]+[_a-z0-9]i*) {return nodeModuleIdentifier(text())}
|
||||||
|
|
||||||
|
|
||||||
string 'string'
|
string 'string'
|
||||||
= characters:("'" @([^'])* "'") {return nodeString(characters.join(''))}
|
= characters:("'" @([^'])* "'") {return nodeString(characters.join(''))}
|
||||||
/ characters:('"' @([^"])* '"') {return nodeString(characters.join(''))}
|
/ characters:('"' @([^"])* '"') {return nodeString(characters.join(''))}
|
||||||
|
|
||||||
number = number:(float / integer) unit:identifier?
|
number = number:(float / integer) unit:unitIdentifier?
|
||||||
{
|
{
|
||||||
if (unit === null)
|
if (unit === null)
|
||||||
{ return number }
|
{ return number }
|
||||||
else
|
else
|
||||||
{ return makeFunctionCall('fromUnit_'+unit.value, [number])
|
{ return apply('fromUnit_'+unit.value, number)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,9 +320,9 @@ lambda
|
||||||
|
|
||||||
arrayConstructor 'array'
|
arrayConstructor 'array'
|
||||||
= '[' _nl ']'
|
= '[' _nl ']'
|
||||||
{ return makeFunctionCall('$_constructArray_$', [nodeExpression([])])}
|
{ return constructArray([]); }
|
||||||
/ '[' _nl args:array_elements _nl ']'
|
/ '[' _nl args:array_elements _nl ']'
|
||||||
{ return makeFunctionCall('$_constructArray_$', [nodeExpression(args)])}
|
{ return constructArray(args); }
|
||||||
|
|
||||||
array_elements
|
array_elements
|
||||||
= head:expression tail:(_ ',' _nl @expression)*
|
= head:expression tail:(_ ',' _nl @expression)*
|
||||||
|
@ -311,7 +330,7 @@ arrayConstructor 'array'
|
||||||
|
|
||||||
recordConstructor 'record'
|
recordConstructor 'record'
|
||||||
= '{' _nl args:array_recordArguments _nl '}'
|
= '{' _nl args:array_recordArguments _nl '}'
|
||||||
{ return makeFunctionCall('$_constructRecord_$', [nodeExpression(args)])}
|
{ return constructRecord(args); }
|
||||||
|
|
||||||
array_recordArguments
|
array_recordArguments
|
||||||
= head:keyValuePair tail:(_ ',' _nl @keyValuePair)*
|
= head:keyValuePair tail:(_ ',' _nl @keyValuePair)*
|
||||||
|
@ -321,10 +340,12 @@ recordConstructor 'record'
|
||||||
= key:expression _ ':' _nl value:expression
|
= key:expression _ ':' _nl value:expression
|
||||||
{ return nodeKeyValue(key, value)}
|
{ return nodeKeyValue(key, value)}
|
||||||
|
|
||||||
|
// Separators
|
||||||
|
|
||||||
_ 'whitespace'
|
_ 'whitespace'
|
||||||
= whiteSpaceCharactersOrComment*
|
= whiteSpaceCharactersOrComment*
|
||||||
|
|
||||||
_nl 'optional whitespace or newline'
|
_nl 'whitespace or newline'
|
||||||
= (whiteSpaceCharactersOrComment / commentOrNewLine)*
|
= (whiteSpaceCharactersOrComment / commentOrNewLine)*
|
||||||
|
|
||||||
__ 'whitespace'
|
__ 'whitespace'
|
||||||
|
@ -351,4 +372,79 @@ statementSeparator 'statement separator'
|
||||||
newLine "newline"
|
newLine "newline"
|
||||||
= [\n\r]
|
= [\n\r]
|
||||||
|
|
||||||
|
// Types
|
||||||
|
|
||||||
|
noArguments = ('(' _nl ')' )?
|
||||||
|
|
||||||
|
typeIdentifier 'type identifier'
|
||||||
|
= ([a-z]+[_a-z0-9]i*) {return nodeTypeIdentifier(text())}
|
||||||
|
|
||||||
|
typeConstructorIdentifier 'type constructor identifier'
|
||||||
|
= ([A-Z]+[_a-z0-9]i*) {return nodeTypeIdentifier(text())}
|
||||||
|
|
||||||
|
typeExpression = typePostModifierExpression
|
||||||
|
|
||||||
|
typePostModifierExpression = head:typeOr tail:(_ '$' _nl @typeModifier)*
|
||||||
|
{
|
||||||
|
return tail.reduce((result, element) => {
|
||||||
|
return makeFunctionCall('$_typeModifier_'+element.modifier.value+'_$', [result, ...element.args])
|
||||||
|
}, head)
|
||||||
|
}
|
||||||
|
|
||||||
|
typeOr = head:typeFunction tail:(_ '|' _nl @typeFunction)*
|
||||||
|
{ return tail.length === 0 ? head : apply('$_typeOr_$', constructArray([head, ...tail])); }
|
||||||
|
|
||||||
|
typeFunction = head:typeModifierExpression tail:(_ '=>' _nl @typeModifierExpression)*
|
||||||
|
{ return tail.length === 0 ? head : apply( '$_typeFunction_$', constructArray([head, ...tail])); }
|
||||||
|
|
||||||
|
typeModifierExpression = head:basicType tail:(_ '<-' _nl @typeModifier)*
|
||||||
|
{
|
||||||
|
return tail.reduce((result, element) => {
|
||||||
|
return makeFunctionCall('$_typeModifier_'+element.modifier.value+'_$', [result, ...element.args])
|
||||||
|
}, head)
|
||||||
|
}
|
||||||
|
|
||||||
|
typeModifier
|
||||||
|
= modifier:identifier _ '(' _nl args:array_elements _nl ')'
|
||||||
|
{ return {modifier: modifier, args: args}; }
|
||||||
|
/ modifier:identifier _ noArguments
|
||||||
|
{ return {modifier: modifier, args: []}; }
|
||||||
|
|
||||||
|
basicType = typeConstructor / typeArray / typeRecord / typeInParanthesis / typeIdentifier
|
||||||
|
|
||||||
|
typeArray = '[' _nl elem:typeExpression _nl ']'
|
||||||
|
{return apply('$_typeArray_$', elem)}
|
||||||
|
|
||||||
|
typeRecord = '{' _nl elems:array_typeRecordArguments _nl '}'
|
||||||
|
{ return apply('$_typeRecord_$', constructRecord(elems)); }
|
||||||
|
|
||||||
|
array_typeRecordArguments
|
||||||
|
= head:typeKeyValuePair tail:(_ ',' _nl @typeKeyValuePair)*
|
||||||
|
{ return [head, ...tail]; }
|
||||||
|
|
||||||
|
typeKeyValuePair
|
||||||
|
= key:identifier _ ':' _nl value:typeExpression
|
||||||
|
{ return nodeKeyValue(key, value)}
|
||||||
|
|
||||||
|
typeConstructor
|
||||||
|
= constructor:typeConstructorIdentifier _ '(' _nl args:array_types _nl ')'
|
||||||
|
{ return makeFunctionCall('$_typeConstructor_$', [constructor, constructArray(args)]); }
|
||||||
|
/ constructor:typeConstructorIdentifier _ noArguments
|
||||||
|
{ return makeFunctionCall('$_typeConstructor_$', [constructor, constructArray([])]); }
|
||||||
|
|
||||||
|
array_types = head:typeExpression tail:(_ ',' _nl @typeExpression)*
|
||||||
|
{ return [head, ...tail]; }
|
||||||
|
|
||||||
|
typeStatement = typeAliasStatement / typeOfStatement
|
||||||
|
typeAliasStatement = 'type' __nl typeIdentifier:typeIdentifier _nl '=' _nl typeExpression:typeExpression
|
||||||
|
{ return makeFunctionCall('$_typeAlias_$', [typeIdentifier, typeExpression])}
|
||||||
|
typeOfStatement = identifier:identifier _ ':' _nl typeExpression:typeExpression
|
||||||
|
{ return makeFunctionCall('$_typeOf_$', [identifier, typeExpression])}
|
||||||
|
|
||||||
|
typeInParanthesis = '(' _nl typeExpression:typeExpression _nl ')' {return typeExpression}
|
||||||
|
|
||||||
|
// TODO: min max example
|
||||||
|
// TODO: Example of foo = {a: 2, b: 5}; type fooKeys = string $ memberOf(foo->keys)
|
||||||
|
// TODO: Example of memberOf( [1,2,3] )
|
||||||
|
// TODO: Example of $
|
||||||
|
// TODO: Cons(a, list) | EmptyList
|
|
@ -22,8 +22,10 @@ type nodeInteger = {...node, "value": int}
|
||||||
type nodeKeyValue = {...node, "key": node, "value": node}
|
type nodeKeyValue = {...node, "key": node, "value": node}
|
||||||
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": nodeBlock}
|
type nodeLambda = {...node, "args": array<nodeIdentifier>, "body": nodeBlock}
|
||||||
type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node}
|
type nodeLetStatement = {...node, "variable": nodeIdentifier, "value": node}
|
||||||
|
type nodeModuleIdentifier = {...node, "value": string}
|
||||||
type nodeString = {...node, "value": string}
|
type nodeString = {...node, "value": string}
|
||||||
type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node}
|
type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node}
|
||||||
|
type nodeTypeIdentifier = {...node, "value": string}
|
||||||
|
|
||||||
type peggyNode =
|
type peggyNode =
|
||||||
| PgNodeBlock(nodeBlock)
|
| PgNodeBlock(nodeBlock)
|
||||||
|
@ -36,8 +38,10 @@ type peggyNode =
|
||||||
| PgNodeKeyValue(nodeKeyValue)
|
| PgNodeKeyValue(nodeKeyValue)
|
||||||
| PgNodeLambda(nodeLambda)
|
| PgNodeLambda(nodeLambda)
|
||||||
| PgNodeLetStatement(nodeLetStatement)
|
| PgNodeLetStatement(nodeLetStatement)
|
||||||
|
| PgNodeModuleIdentifier(nodeModuleIdentifier)
|
||||||
| PgNodeString(nodeString)
|
| PgNodeString(nodeString)
|
||||||
| PgNodeTernary(nodeTernary)
|
| PgNodeTernary(nodeTernary)
|
||||||
|
| PgNodeTypeIdentifier(nodeTypeIdentifier)
|
||||||
|
|
||||||
external castNodeBlock: node => nodeBlock = "%identity"
|
external castNodeBlock: node => nodeBlock = "%identity"
|
||||||
external castNodeBoolean: node => nodeBoolean = "%identity"
|
external castNodeBoolean: node => nodeBoolean = "%identity"
|
||||||
|
@ -49,8 +53,10 @@ external castNodeInteger: node => nodeInteger = "%identity"
|
||||||
external castNodeKeyValue: node => nodeKeyValue = "%identity"
|
external castNodeKeyValue: node => nodeKeyValue = "%identity"
|
||||||
external castNodeLambda: node => nodeLambda = "%identity"
|
external castNodeLambda: node => nodeLambda = "%identity"
|
||||||
external castNodeLetStatement: node => nodeLetStatement = "%identity"
|
external castNodeLetStatement: node => nodeLetStatement = "%identity"
|
||||||
|
external castNodeModuleIdentifier: node => nodeModuleIdentifier = "%identity"
|
||||||
external castNodeString: node => nodeString = "%identity"
|
external castNodeString: node => nodeString = "%identity"
|
||||||
external castNodeTernary: node => nodeTernary = "%identity"
|
external castNodeTernary: node => nodeTernary = "%identity"
|
||||||
|
external castNodeTypeIdentifier: node => nodeTypeIdentifier = "%identity"
|
||||||
|
|
||||||
exception UnsupportedPeggyNodeType(string) // This should never happen; programming error
|
exception UnsupportedPeggyNodeType(string) // This should never happen; programming error
|
||||||
let castNodeType = (node: node) =>
|
let castNodeType = (node: node) =>
|
||||||
|
@ -65,8 +71,10 @@ let castNodeType = (node: node) =>
|
||||||
| "KeyValue" => node->castNodeKeyValue->PgNodeKeyValue
|
| "KeyValue" => node->castNodeKeyValue->PgNodeKeyValue
|
||||||
| "Lambda" => node->castNodeLambda->PgNodeLambda
|
| "Lambda" => node->castNodeLambda->PgNodeLambda
|
||||||
| "LetStatement" => node->castNodeLetStatement->PgNodeLetStatement
|
| "LetStatement" => node->castNodeLetStatement->PgNodeLetStatement
|
||||||
|
| "ModuleIdentifier" => node->castNodeModuleIdentifier->PgNodeModuleIdentifier
|
||||||
| "String" => node->castNodeString->PgNodeString
|
| "String" => node->castNodeString->PgNodeString
|
||||||
| "Ternary" => node->castNodeTernary->PgNodeTernary
|
| "Ternary" => node->castNodeTernary->PgNodeTernary
|
||||||
|
| "TypeIdentifier" => node->castNodeTypeIdentifier->PgNodeTypeIdentifier
|
||||||
| _ => raise(UnsupportedPeggyNodeType(node["type"]))
|
| _ => raise(UnsupportedPeggyNodeType(node["type"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +98,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
|
||||||
"{|" ++ node["args"]->argsToString ++ "| " ++ pgToString(PgNodeBlock(node["body"])) ++ "}"
|
"{|" ++ node["args"]->argsToString ++ "| " ++ pgToString(PgNodeBlock(node["body"])) ++ "}"
|
||||||
| PgNodeLetStatement(node) =>
|
| PgNodeLetStatement(node) =>
|
||||||
pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"])
|
pgToString(PgNodeIdentifier(node["variable"])) ++ " = " ++ toString(node["value"])
|
||||||
|
| PgNodeModuleIdentifier(node) => `@${node["value"]}`
|
||||||
| PgNodeString(node) => `'${node["value"]->Js.String.make}'`
|
| PgNodeString(node) => `'${node["value"]->Js.String.make}'`
|
||||||
| PgNodeTernary(node) =>
|
| PgNodeTernary(node) =>
|
||||||
"(::$$_ternary_$$ " ++
|
"(::$$_ternary_$$ " ++
|
||||||
|
@ -98,6 +107,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
|
||||||
toString(node["trueExpression"]) ++
|
toString(node["trueExpression"]) ++
|
||||||
" " ++
|
" " ++
|
||||||
toString(node["falseExpression"]) ++ ")"
|
toString(node["falseExpression"]) ++ ")"
|
||||||
|
| PgNodeTypeIdentifier(node) => `#${node["value"]}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
and toString = (node: node): string => node->castNodeType->pgToString
|
and toString = (node: node): string => node->castNodeType->pgToString
|
||||||
|
|
|
@ -34,6 +34,8 @@ let rec fromNode = (node: Parse.node): expression => {
|
||||||
nodeLetStatement["variable"]["value"],
|
nodeLetStatement["variable"]["value"],
|
||||||
fromNode(nodeLetStatement["value"]),
|
fromNode(nodeLetStatement["value"]),
|
||||||
)
|
)
|
||||||
|
| PgNodeModuleIdentifier(nodeModuleIdentifier) =>
|
||||||
|
ExpressionBuilder.eIdentifier(nodeModuleIdentifier["value"])
|
||||||
| PgNodeString(nodeString) => ExpressionBuilder.eString(nodeString["value"])
|
| PgNodeString(nodeString) => ExpressionBuilder.eString(nodeString["value"])
|
||||||
| PgNodeTernary(nodeTernary) =>
|
| PgNodeTernary(nodeTernary) =>
|
||||||
ExpressionBuilder.eFunction(
|
ExpressionBuilder.eFunction(
|
||||||
|
@ -44,5 +46,7 @@ let rec fromNode = (node: Parse.node): expression => {
|
||||||
fromNode(nodeTernary["falseExpression"]),
|
fromNode(nodeTernary["falseExpression"]),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
| PgNodeTypeIdentifier(nodeTypeIdentifier) =>
|
||||||
|
ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
module EV = ReducerInterface_ExpressionValue
|
||||||
|
type expressionValue = EV.expressionValue
|
||||||
|
|
||||||
|
let dispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
|
||||||
|
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
||||||
|
> => {
|
||||||
|
switch call {
|
||||||
|
| ("toString", [EvDate(t)]) => EV.EvString(DateTime.Date.toString(t))->Ok->Some
|
||||||
|
| ("makeDateFromYear", [EvNumber(year)]) =>
|
||||||
|
switch DateTime.Date.makeFromYear(year) {
|
||||||
|
| Ok(t) => EV.EvDate(t)->Ok->Some
|
||||||
|
| Error(e) => Reducer_ErrorValue.RETodo(e)->Error->Some
|
||||||
|
}
|
||||||
|
| ("dateFromNumber", [EvNumber(f)]) => EV.EvDate(DateTime.Date.fromFloat(f))->Ok->Some
|
||||||
|
| ("toNumber", [EvDate(f)]) => EV.EvNumber(DateTime.Date.toFloat(f))->Ok->Some
|
||||||
|
| ("subtract", [EvDate(d1), EvDate(d2)]) =>
|
||||||
|
switch DateTime.Date.subtract(d1, d2) {
|
||||||
|
| Ok(d) => EV.EvTimeDuration(d)->Ok
|
||||||
|
| Error(e) => Error(RETodo(e))
|
||||||
|
}->Some
|
||||||
|
| ("subtract", [EvDate(d1), EvTimeDuration(d2)]) =>
|
||||||
|
EV.EvDate(DateTime.Date.subtractDuration(d1, d2))->Ok->Some
|
||||||
|
| ("add", [EvDate(d1), EvTimeDuration(d2)]) =>
|
||||||
|
EV.EvDate(DateTime.Date.addDuration(d1, d2))->Ok->Some
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +1,7 @@
|
||||||
module EV = ReducerInterface_ExpressionValue
|
module EV = ReducerInterface_ExpressionValue
|
||||||
type expressionValue = EV.expressionValue
|
type expressionValue = EV.expressionValue
|
||||||
|
|
||||||
let dateDispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
|
let dispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
|
||||||
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
|
||||||
> => {
|
|
||||||
switch call {
|
|
||||||
| ("toString", [EvDate(t)]) => EV.EvString(DateTime.Date.toString(t))->Ok->Some
|
|
||||||
| ("makeDateFromYear", [EvNumber(year)]) =>
|
|
||||||
switch DateTime.Date.makeFromYear(year) {
|
|
||||||
| Ok(t) => EV.EvDate(t)->Ok->Some
|
|
||||||
| Error(e) => Reducer_ErrorValue.RETodo(e)->Error->Some
|
|
||||||
}
|
|
||||||
| ("dateFromNumber", [EvNumber(f)]) => EV.EvDate(DateTime.Date.fromFloat(f))->Ok->Some
|
|
||||||
| ("toNumber", [EvDate(f)]) => EV.EvNumber(DateTime.Date.toFloat(f))->Ok->Some
|
|
||||||
| ("subtract", [EvDate(d1), EvDate(d2)]) =>
|
|
||||||
switch DateTime.Date.subtract(d1, d2) {
|
|
||||||
| Ok(d) => EV.EvTimeDuration(d)->Ok
|
|
||||||
| Error(e) => Error(RETodo(e))
|
|
||||||
}->Some
|
|
||||||
| ("subtract", [EvDate(d1), EvTimeDuration(d2)]) =>
|
|
||||||
EV.EvDate(DateTime.Date.subtractDuration(d1, d2))->Ok->Some
|
|
||||||
| ("add", [EvDate(d1), EvTimeDuration(d2)]) =>
|
|
||||||
EV.EvDate(DateTime.Date.addDuration(d1, d2))->Ok->Some
|
|
||||||
| _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let durationDispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
|
|
||||||
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
||||||
> => {
|
> => {
|
||||||
switch call {
|
switch call {
|
||||||
|
@ -52,19 +27,7 @@ let durationDispatch = (call: EV.functionCall, _: DistributionOperation.env): op
|
||||||
EV.EvTimeDuration(DateTime.Duration.multiply(d1, d2))->Ok->Some
|
EV.EvTimeDuration(DateTime.Duration.multiply(d1, d2))->Ok->Some
|
||||||
| ("divide", [EvTimeDuration(d1), EvNumber(d2)]) =>
|
| ("divide", [EvTimeDuration(d1), EvNumber(d2)]) =>
|
||||||
EV.EvTimeDuration(DateTime.Duration.divide(d1, d2))->Ok->Some
|
EV.EvTimeDuration(DateTime.Duration.divide(d1, d2))->Ok->Some
|
||||||
|
| ("divide", [EvTimeDuration(d1), EvTimeDuration(d2)]) => EV.EvNumber(d1 /. d2)->Ok->Some
|
||||||
| _ => None
|
| _ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dispatch = (call: EV.functionCall, env: DistributionOperation.env): option<
|
|
||||||
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
|
||||||
> => {
|
|
||||||
switch dateDispatch(call, env) {
|
|
||||||
| Some(r) => Some(r)
|
|
||||||
| None =>
|
|
||||||
switch durationDispatch(call, env) {
|
|
||||||
| Some(r) => Some(r)
|
|
||||||
| None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@
|
||||||
*/
|
*/
|
||||||
module Extra_Array = Reducer_Extra_Array
|
module Extra_Array = Reducer_Extra_Array
|
||||||
module ErrorValue = Reducer_ErrorValue
|
module ErrorValue = Reducer_ErrorValue
|
||||||
|
|
||||||
@genType.opaque
|
@genType.opaque
|
||||||
type internalCode = Object
|
type internalCode = Object
|
||||||
|
|
||||||
|
@ -22,6 +21,9 @@ type rec expressionValue =
|
||||||
| EvSymbol(string)
|
| EvSymbol(string)
|
||||||
| EvDate(Js.Date.t)
|
| EvDate(Js.Date.t)
|
||||||
| EvTimeDuration(float)
|
| EvTimeDuration(float)
|
||||||
|
| EvDeclaration(lambdaDeclaration)
|
||||||
|
| EvTypeIdentifier(string)
|
||||||
|
| EvModule(record)
|
||||||
and record = Js.Dict.t<expressionValue>
|
and record = Js.Dict.t<expressionValue>
|
||||||
and externalBindings = record
|
and externalBindings = record
|
||||||
and lambdaValue = {
|
and lambdaValue = {
|
||||||
|
@ -29,9 +31,7 @@ and lambdaValue = {
|
||||||
context: externalBindings,
|
context: externalBindings,
|
||||||
body: internalCode,
|
body: internalCode,
|
||||||
}
|
}
|
||||||
|
and lambdaDeclaration = Declaration.declaration<lambdaValue>
|
||||||
@genType
|
|
||||||
let defaultExternalBindings: externalBindings = Js.Dict.empty()
|
|
||||||
|
|
||||||
type functionCall = (string, array<expressionValue>)
|
type functionCall = (string, array<expressionValue>)
|
||||||
|
|
||||||
|
@ -55,6 +55,9 @@ let rec toString = aValue =>
|
||||||
| EvDistribution(dist) => GenericDist.toString(dist)
|
| EvDistribution(dist) => GenericDist.toString(dist)
|
||||||
| EvDate(date) => DateTime.Date.toString(date)
|
| EvDate(date) => DateTime.Date.toString(date)
|
||||||
| EvTimeDuration(t) => DateTime.Duration.toString(t)
|
| EvTimeDuration(t) => DateTime.Duration.toString(t)
|
||||||
|
| EvDeclaration(d) => Declaration.toString(d, r => toString(EvLambda(r)))
|
||||||
|
| EvTypeIdentifier(id) => `#${id}`
|
||||||
|
| EvModule(m) => `@${m->toStringRecord}`
|
||||||
}
|
}
|
||||||
and toStringRecord = aRecord => {
|
and toStringRecord = aRecord => {
|
||||||
let pairs =
|
let pairs =
|
||||||
|
@ -79,6 +82,9 @@ let toStringWithType = aValue =>
|
||||||
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
| EvSymbol(_) => `Symbol::${toString(aValue)}`
|
||||||
| EvDate(_) => `Date::${toString(aValue)}`
|
| EvDate(_) => `Date::${toString(aValue)}`
|
||||||
| EvTimeDuration(_) => `Date::${toString(aValue)}`
|
| EvTimeDuration(_) => `Date::${toString(aValue)}`
|
||||||
|
| EvDeclaration(_) => `Declaration::${toString(aValue)}`
|
||||||
|
| EvTypeIdentifier(_) => `TypeIdentifier::${toString(aValue)}`
|
||||||
|
| EvModule(_) => `Module::${toString(aValue)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
let argsToString = (args: array<expressionValue>): string => {
|
let argsToString = (args: array<expressionValue>): string => {
|
||||||
|
@ -124,6 +130,9 @@ type expressionValueType =
|
||||||
| EvtSymbol
|
| EvtSymbol
|
||||||
| EvtDate
|
| EvtDate
|
||||||
| EvtTimeDuration
|
| EvtTimeDuration
|
||||||
|
| EvtDeclaration
|
||||||
|
| EvtTypeIdentifier
|
||||||
|
| EvtModule
|
||||||
|
|
||||||
type functionCallSignature = CallSignature(string, array<expressionValueType>)
|
type functionCallSignature = CallSignature(string, array<expressionValueType>)
|
||||||
type functionDefinitionSignature =
|
type functionDefinitionSignature =
|
||||||
|
@ -143,6 +152,9 @@ let valueToValueType = value =>
|
||||||
| EvSymbol(_) => EvtSymbol
|
| EvSymbol(_) => EvtSymbol
|
||||||
| EvDate(_) => EvtDate
|
| EvDate(_) => EvtDate
|
||||||
| EvTimeDuration(_) => EvtTimeDuration
|
| EvTimeDuration(_) => EvtTimeDuration
|
||||||
|
| EvDeclaration(_) => EvtDeclaration
|
||||||
|
| EvTypeIdentifier(_) => EvtTypeIdentifier
|
||||||
|
| EvModule(_) => EvtModule
|
||||||
}
|
}
|
||||||
|
|
||||||
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
|
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
|
||||||
|
@ -164,6 +176,9 @@ let valueTypeToString = (valueType: expressionValueType): string =>
|
||||||
| EvtSymbol => `Symbol`
|
| EvtSymbol => `Symbol`
|
||||||
| EvtDate => `Date`
|
| EvtDate => `Date`
|
||||||
| EvtTimeDuration => `Duration`
|
| EvtTimeDuration => `Duration`
|
||||||
|
| EvtDeclaration => `Declaration`
|
||||||
|
| EvtTypeIdentifier => `TypeIdentifier`
|
||||||
|
| EvtModule => `Module`
|
||||||
}
|
}
|
||||||
|
|
||||||
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
|
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
|
||||||
|
|
|
@ -14,15 +14,27 @@ type expressionValue = ExpressionValue.expressionValue
|
||||||
Map external calls of Reducer
|
Map external calls of Reducer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// I expect that it's important to build this first, so it doesn't get recalculated for each tryRegistry() call.
|
||||||
|
let registry = FunctionRegistry_Library.registry
|
||||||
|
|
||||||
|
let tryRegistry = ((fnName, args): ExpressionValue.functionCall, env) => {
|
||||||
|
FunctionRegistry_Core.Registry.matchAndRun(~registry, ~fnName, ~args, ~env)->E.O2.fmap(
|
||||||
|
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let dispatch = (call: ExpressionValue.functionCall, environment, chain): result<
|
let dispatch = (call: ExpressionValue.functionCall, environment, chain): result<
|
||||||
expressionValue,
|
expressionValue,
|
||||||
'e,
|
'e,
|
||||||
> =>
|
> => {
|
||||||
switch ReducerInterface_GenericDistribution.dispatch(call, environment) {
|
E.A.O.firstSomeFn([
|
||||||
| Some(r) => r
|
() => ReducerInterface_GenericDistribution.dispatch(call, environment),
|
||||||
| None =>
|
() => ReducerInterface_Date.dispatch(call, environment),
|
||||||
ReducerInterface_DateTime.dispatch(call, environment) |> E.O.default(chain(call, environment))
|
() => ReducerInterface_Duration.dispatch(call, environment),
|
||||||
}
|
() => ReducerInterface_Number.dispatch(call, environment),
|
||||||
|
() => tryRegistry(call, environment),
|
||||||
|
])->E.O2.default(chain(call, environment))
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
||||||
|
|
||||||
|
|
|
@ -186,8 +186,6 @@ let dispatchToGenericOutput = (
|
||||||
): option<DistributionOperation.outputType> => {
|
): option<DistributionOperation.outputType> => {
|
||||||
let (fnName, args) = call
|
let (fnName, args) = call
|
||||||
switch (fnName, args) {
|
switch (fnName, args) {
|
||||||
| ("delta", [EvNumber(f)]) =>
|
|
||||||
SymbolicDist.Float.makeSafe(f)->SymbolicConstructors.symbolicResultToOutput
|
|
||||||
| ("triangular" as fnName, [EvNumber(f1), EvNumber(f2), EvNumber(f3)]) =>
|
| ("triangular" as fnName, [EvNumber(f1), EvNumber(f2), EvNumber(f3)]) =>
|
||||||
SymbolicConstructors.threeFloat(fnName)
|
SymbolicConstructors.threeFloat(fnName)
|
||||||
->E.R.bind(r => r(f1, f2, f3))
|
->E.R.bind(r => r(f1, f2, f3))
|
||||||
|
@ -195,12 +193,23 @@ let dispatchToGenericOutput = (
|
||||||
| ("sample", [EvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist, ~env)
|
| ("sample", [EvDistribution(dist)]) => Helpers.toFloatFn(#Sample, dist, ~env)
|
||||||
| ("sampleN", [EvDistribution(dist), EvNumber(n)]) =>
|
| ("sampleN", [EvDistribution(dist), EvNumber(n)]) =>
|
||||||
Some(FloatArray(GenericDist.sampleN(dist, Belt.Int.fromFloat(n))))
|
Some(FloatArray(GenericDist.sampleN(dist, Belt.Int.fromFloat(n))))
|
||||||
| ("mean", [EvDistribution(dist)]) => Helpers.toFloatFn(#Mean, dist, ~env)
|
| (("mean" | "stdev" | "variance" | "min" | "max" | "mode") as op, [EvDistribution(dist)]) => {
|
||||||
|
let fn = switch op {
|
||||||
|
| "mean" => #Mean
|
||||||
|
| "stdev" => #Stdev
|
||||||
|
| "variance" => #Variance
|
||||||
|
| "min" => #Min
|
||||||
|
| "max" => #Max
|
||||||
|
| "mode" => #Mode
|
||||||
|
| _ => #Mean
|
||||||
|
}
|
||||||
|
Helpers.toFloatFn(fn, dist, ~env)
|
||||||
|
}
|
||||||
| ("integralSum", [EvDistribution(dist)]) => Helpers.toFloatFn(#IntegralSum, dist, ~env)
|
| ("integralSum", [EvDistribution(dist)]) => Helpers.toFloatFn(#IntegralSum, dist, ~env)
|
||||||
| ("toString", [EvDistribution(dist)]) => Helpers.toStringFn(ToString, dist, ~env)
|
| ("toString", [EvDistribution(dist)]) => Helpers.toStringFn(ToString, dist, ~env)
|
||||||
| ("toSparkline", [EvDistribution(dist)]) =>
|
| ("sparkline", [EvDistribution(dist)]) =>
|
||||||
Helpers.toStringFn(ToSparkline(MagicNumbers.Environment.sparklineLength), dist, ~env)
|
Helpers.toStringFn(ToSparkline(MagicNumbers.Environment.sparklineLength), dist, ~env)
|
||||||
| ("toSparkline", [EvDistribution(dist), EvNumber(n)]) =>
|
| ("sparkline", [EvDistribution(dist), EvNumber(n)]) =>
|
||||||
Helpers.toStringFn(ToSparkline(Belt.Float.toInt(n)), dist, ~env)
|
Helpers.toStringFn(ToSparkline(Belt.Float.toInt(n)), dist, ~env)
|
||||||
| ("exp", [EvDistribution(a)]) =>
|
| ("exp", [EvDistribution(a)]) =>
|
||||||
// https://mathjs.org/docs/reference/functions/exp.html
|
// https://mathjs.org/docs/reference/functions/exp.html
|
||||||
|
@ -232,6 +241,8 @@ let dispatchToGenericOutput = (
|
||||||
Helpers.toDistFn(Scale(#Logarithm, float), dist, ~env)
|
Helpers.toDistFn(Scale(#Logarithm, float), dist, ~env)
|
||||||
| ("scaleLogWithThreshold", [EvDistribution(dist), EvNumber(base), EvNumber(eps)]) =>
|
| ("scaleLogWithThreshold", [EvDistribution(dist), EvNumber(base), EvNumber(eps)]) =>
|
||||||
Helpers.toDistFn(Scale(#LogarithmWithThreshold(eps), base), dist, ~env)
|
Helpers.toDistFn(Scale(#LogarithmWithThreshold(eps), base), dist, ~env)
|
||||||
|
| ("scaleMultiply", [EvDistribution(dist), EvNumber(float)]) =>
|
||||||
|
Helpers.toDistFn(Scale(#Multiply, float), dist, ~env)
|
||||||
| ("scalePow", [EvDistribution(dist), EvNumber(float)]) =>
|
| ("scalePow", [EvDistribution(dist), EvNumber(float)]) =>
|
||||||
Helpers.toDistFn(Scale(#Power, float), dist, ~env)
|
Helpers.toDistFn(Scale(#Power, float), dist, ~env)
|
||||||
| ("scaleExp", [EvDistribution(dist)]) =>
|
| ("scaleExp", [EvDistribution(dist)]) =>
|
||||||
|
@ -239,12 +250,13 @@ let dispatchToGenericOutput = (
|
||||||
| ("cdf", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Cdf(float), dist, ~env)
|
| ("cdf", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Cdf(float), dist, ~env)
|
||||||
| ("pdf", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Pdf(float), dist, ~env)
|
| ("pdf", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Pdf(float), dist, ~env)
|
||||||
| ("inv", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Inv(float), dist, ~env)
|
| ("inv", [EvDistribution(dist), EvNumber(float)]) => Helpers.toFloatFn(#Inv(float), dist, ~env)
|
||||||
|
| ("quantile", [EvDistribution(dist), EvNumber(float)]) =>
|
||||||
|
Helpers.toFloatFn(#Inv(float), dist, ~env)
|
||||||
| ("toSampleSet", [EvDistribution(dist), EvNumber(float)]) =>
|
| ("toSampleSet", [EvDistribution(dist), EvNumber(float)]) =>
|
||||||
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist, ~env)
|
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist, ~env)
|
||||||
| ("toSampleSet", [EvDistribution(dist)]) =>
|
| ("toSampleSet", [EvDistribution(dist)]) =>
|
||||||
Helpers.toDistFn(ToSampleSet(env.sampleCount), dist, ~env)
|
Helpers.toDistFn(ToSampleSet(env.sampleCount), dist, ~env)
|
||||||
| ("toInternalSampleArray", [EvDistribution(SampleSet(dist))]) =>
|
| ("toList", [EvDistribution(SampleSet(dist))]) => Some(FloatArray(SampleSetDist.T.get(dist)))
|
||||||
Some(FloatArray(SampleSetDist.T.get(dist)))
|
|
||||||
| ("fromSamples", [EvArray(inputArray)]) => {
|
| ("fromSamples", [EvArray(inputArray)]) => {
|
||||||
let _wrapInputErrors = x => SampleSetDist.NonNumericInput(x)
|
let _wrapInputErrors = x => SampleSetDist.NonNumericInput(x)
|
||||||
let parsedArray = Helpers.parseNumberArray(inputArray)->E.R2.errMap(_wrapInputErrors)
|
let parsedArray = Helpers.parseNumberArray(inputArray)->E.R2.errMap(_wrapInputErrors)
|
||||||
|
@ -325,20 +337,5 @@ let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
|
||||||
| GenDistError(err) => Error(REDistributionError(err))
|
| GenDistError(err) => Error(REDistributionError(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// I expect that it's important to build this first, so it doesn't get recalculated for each tryRegistry() call.
|
let dispatch = (call: ExpressionValue.functionCall, environment) =>
|
||||||
let registry = FunctionRegistry_Library.registry
|
|
||||||
|
|
||||||
let tryRegistry = ((fnName, args): ExpressionValue.functionCall, env) => {
|
|
||||||
FunctionRegistry_Core.Registry.matchAndRun(~registry, ~fnName, ~args, ~env)->E.O2.fmap(
|
|
||||||
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let dispatch = (call: ExpressionValue.functionCall, environment) => {
|
|
||||||
let regularDispatch =
|
|
||||||
dispatchToGenericOutput(call, environment)->E.O2.fmap(genericOutputToReducerValue)
|
dispatchToGenericOutput(call, environment)->E.O2.fmap(genericOutputToReducerValue)
|
||||||
switch regularDispatch {
|
|
||||||
| Some(x) => Some(x)
|
|
||||||
| None => tryRegistry(call, environment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
module EV = ReducerInterface_ExpressionValue
|
||||||
|
type expressionValue = EV.expressionValue
|
||||||
|
|
||||||
|
module ScientificUnit = {
|
||||||
|
let nameToMultiplier = str =>
|
||||||
|
switch str {
|
||||||
|
| "n" => Some(1E-9)
|
||||||
|
| "m" => Some(1E-3)
|
||||||
|
| "k" => Some(1E3)
|
||||||
|
| "M" => Some(1E6)
|
||||||
|
| "B" => Some(1E9)
|
||||||
|
| "G" => Some(1E9)
|
||||||
|
| "T" => Some(1E12)
|
||||||
|
| "P" => Some(1E15)
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
|
||||||
|
let getMultiplier = (r: string) => {
|
||||||
|
let match = Js.String2.match_(r, %re(`/fromUnit_([_a-zA-Z]*)/`))
|
||||||
|
switch match {
|
||||||
|
| Some([_, unit]) => nameToMultiplier(unit)
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
|
||||||
|
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
|
||||||
|
> => {
|
||||||
|
switch call {
|
||||||
|
| (
|
||||||
|
("fromUnit_n"
|
||||||
|
| "fromUnit_m"
|
||||||
|
| "fromUnit_k"
|
||||||
|
| "fromUnit_M"
|
||||||
|
| "fromUnit_B"
|
||||||
|
| "fromUnit_G"
|
||||||
|
| "fromUnit_T"
|
||||||
|
| "fromUnit_P") as op,
|
||||||
|
[EvNumber(f)],
|
||||||
|
) =>
|
||||||
|
op->ScientificUnit.getMultiplier->E.O2.fmap(multiplier => EV.EvNumber(f *. multiplier)->Ok)
|
||||||
|
| _ => None
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module Bindings = Reducer_Category_Bindings
|
||||||
|
|
||||||
|
let internalStdLib = Bindings.emptyBindings->SquiggleLibrary_Math.makeBindings
|
||||||
|
|
||||||
|
@genType
|
||||||
|
let externalStdLib = internalStdLib->Bindings.toRecord
|
|
@ -0,0 +1,23 @@
|
||||||
|
module Bindings = Reducer_Category_Bindings
|
||||||
|
module Module = Reducer_Category_Module
|
||||||
|
|
||||||
|
let availableNumbers: array<(string, float)> = [
|
||||||
|
("pi", Js.Math._PI),
|
||||||
|
("e", Js.Math._E),
|
||||||
|
("ln2", Js.Math._LN2),
|
||||||
|
("ln10", Js.Math._LN10),
|
||||||
|
("log2e", Js.Math._LOG2E),
|
||||||
|
("log10e", Js.Math._LOG10E),
|
||||||
|
("sqrt2", Js.Math._SQRT2),
|
||||||
|
("sqrt1_2", Js.Math._SQRT1_2),
|
||||||
|
("phi", 1.618033988749895),
|
||||||
|
("tau", 6.283185307179586),
|
||||||
|
]
|
||||||
|
|
||||||
|
let mathBindings: Bindings.ExpressionT.bindings =
|
||||||
|
availableNumbers
|
||||||
|
->E.A2.fmap(((name, v)) => (name, ReducerInterface_ExpressionValue.EvNumber(v)))
|
||||||
|
->Belt.Map.String.fromArray
|
||||||
|
|
||||||
|
let makeBindings = (previousBindings: Bindings.t): Bindings.t =>
|
||||||
|
previousBindings->Bindings.defineModule("Math", mathBindings)
|
|
@ -76,6 +76,9 @@ let distributionErrorToString = DistributionTypes.Error.toString
|
||||||
@genType
|
@genType
|
||||||
type lambdaValue = ReducerInterface_ExpressionValue.lambdaValue
|
type lambdaValue = ReducerInterface_ExpressionValue.lambdaValue
|
||||||
|
|
||||||
|
@genType
|
||||||
|
type lambdaDeclaration = ReducerInterface_ExpressionValue.lambdaDeclaration
|
||||||
|
|
||||||
@genType
|
@genType
|
||||||
let defaultSamplingEnv = DistributionOperation.defaultEnv
|
let defaultSamplingEnv = DistributionOperation.defaultEnv
|
||||||
|
|
||||||
|
@ -87,3 +90,9 @@ let defaultEnvironment = ReducerInterface_ExpressionValue.defaultEnvironment
|
||||||
|
|
||||||
@genType
|
@genType
|
||||||
let foreignFunctionInterface = Reducer.foreignFunctionInterface
|
let foreignFunctionInterface = Reducer.foreignFunctionInterface
|
||||||
|
|
||||||
|
@genType
|
||||||
|
type declarationArg = Declaration.arg
|
||||||
|
|
||||||
|
@genType
|
||||||
|
type declaration<'a> = Declaration.declaration<'a>
|
||||||
|
|
42
packages/squiggle-lang/src/rescript/Utility/Declaration.res
Normal file
42
packages/squiggle-lang/src/rescript/Utility/Declaration.res
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
@genType
|
||||||
|
type arg = Float({min: float, max: float}) | Date({min: Js.Date.t, max: Js.Date.t})
|
||||||
|
|
||||||
|
@genType
|
||||||
|
type declaration<'a> = {
|
||||||
|
fn: 'a,
|
||||||
|
args: array<arg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
module ContinuousFloatArg = {
|
||||||
|
let make = (min: float, max: float): arg => {
|
||||||
|
Float({min: min, max: max})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module ContinuousTimeArg = {
|
||||||
|
let make = (min: Js.Date.t, max: Js.Date.t): arg => {
|
||||||
|
Date({min: min, max: max})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module Arg = {
|
||||||
|
let toString = (arg: arg) => {
|
||||||
|
switch arg {
|
||||||
|
| Float({min, max}) =>
|
||||||
|
`Float({min: ${E.Float.with2DigitsPrecision(min)}, max: ${E.Float.with2DigitsPrecision(
|
||||||
|
max,
|
||||||
|
)}})`
|
||||||
|
| Date({min, max}) =>
|
||||||
|
`Date({min: ${DateTime.Date.toString(min)}, max: ${DateTime.Date.toString(max)}})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let make = (fn: 'a, args: array<arg>): declaration<'a> => {
|
||||||
|
{fn: fn, args: args}
|
||||||
|
}
|
||||||
|
|
||||||
|
let toString = (r: declaration<'a>, fnToString): string => {
|
||||||
|
let args = r.args->E.A2.fmap(Arg.toString) |> E.A.joinWith(", ")
|
||||||
|
return`fn: ${fnToString(r.fn)}, args: [${args}]`
|
||||||
|
}
|
|
@ -55,6 +55,10 @@ module Tuple2 = {
|
||||||
let toFnCall = (fn, (a1, a2)) => fn(a1, a2)
|
let toFnCall = (fn, (a1, a2)) => fn(a1, a2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module Tuple3 = {
|
||||||
|
let toFnCall = (fn, (a1, a2, a3)) => fn(a1, a2, a3)
|
||||||
|
}
|
||||||
|
|
||||||
module O = {
|
module O = {
|
||||||
let dimap = (sFn, rFn, e) =>
|
let dimap = (sFn, rFn, e) =>
|
||||||
switch e {
|
switch e {
|
||||||
|
@ -203,6 +207,7 @@ module Float = {
|
||||||
let toFixed = Js.Float.toFixed
|
let toFixed = Js.Float.toFixed
|
||||||
let toString = Js.Float.toString
|
let toString = Js.Float.toString
|
||||||
let isFinite = Js.Float.isFinite
|
let isFinite = Js.Float.isFinite
|
||||||
|
let toInt = Belt.Float.toInt
|
||||||
}
|
}
|
||||||
|
|
||||||
module I = {
|
module I = {
|
||||||
|
@ -535,6 +540,7 @@ module A = {
|
||||||
let hasBy = (r, fn) => Belt.Array.getBy(r, fn) |> O.isSome
|
let hasBy = (r, fn) => Belt.Array.getBy(r, fn) |> O.isSome
|
||||||
let fold_left = Array.fold_left
|
let fold_left = Array.fold_left
|
||||||
let fold_right = Array.fold_right
|
let fold_right = Array.fold_right
|
||||||
|
let concat = Belt.Array.concat
|
||||||
let concatMany = Belt.Array.concatMany
|
let concatMany = Belt.Array.concatMany
|
||||||
let keepMap = Belt.Array.keepMap
|
let keepMap = Belt.Array.keepMap
|
||||||
let slice = Belt.Array.slice
|
let slice = Belt.Array.slice
|
||||||
|
@ -568,6 +574,9 @@ module A = {
|
||||||
let tail = Belt.Array.sliceToEnd(_, 1)
|
let tail = Belt.Array.sliceToEnd(_, 1)
|
||||||
|
|
||||||
let zip = Belt.Array.zip
|
let zip = Belt.Array.zip
|
||||||
|
let unzip = Belt.Array.unzip
|
||||||
|
let zip3 = (a, b, c) =>
|
||||||
|
Belt.Array.zip(a, b)->Belt.Array.zip(c)->Belt.Array.map((((v1, v2), v3)) => (v1, v2, v3))
|
||||||
// This zips while taking the longest elements of each array.
|
// This zips while taking the longest elements of each array.
|
||||||
let zipMaxLength = (array1, array2) => {
|
let zipMaxLength = (array1, array2) => {
|
||||||
let maxLength = Int.max(length(array1), length(array2))
|
let maxLength = Int.max(length(array1), length(array2))
|
||||||
|
@ -714,6 +723,7 @@ module A = {
|
||||||
let variance = Jstat.variance
|
let variance = Jstat.variance
|
||||||
let stdev = Jstat.stdev
|
let stdev = Jstat.stdev
|
||||||
let sum = Jstat.sum
|
let sum = Jstat.sum
|
||||||
|
let product = Jstat.product
|
||||||
let random = Js.Math.random_int
|
let random = Js.Math.random_int
|
||||||
|
|
||||||
let floatCompare: (float, float) => int = compare
|
let floatCompare: (float, float) => int = compare
|
||||||
|
@ -742,6 +752,9 @@ module A = {
|
||||||
let diff = (t: t): array<float> =>
|
let diff = (t: t): array<float> =>
|
||||||
Belt.Array.zipBy(t, Belt.Array.sliceToEnd(t, 1), (left, right) => right -. left)
|
Belt.Array.zipBy(t, Belt.Array.sliceToEnd(t, 1), (left, right) => right -. left)
|
||||||
|
|
||||||
|
let cumsum = (t: t): array<float> => accumulate((a, b) => a +. b, t)
|
||||||
|
let cumProd = (t: t): array<float> => accumulate((a, b) => a *. b, t)
|
||||||
|
|
||||||
exception RangeError(string)
|
exception RangeError(string)
|
||||||
let range = (min: float, max: float, n: int): array<float> =>
|
let range = (min: float, max: float, n: int): array<float> =>
|
||||||
switch n {
|
switch n {
|
||||||
|
@ -865,4 +878,8 @@ module Dict = {
|
||||||
type t<'a> = Js.Dict.t<'a>
|
type t<'a> = Js.Dict.t<'a>
|
||||||
let get = Js.Dict.get
|
let get = Js.Dict.get
|
||||||
let keys = Js.Dict.keys
|
let keys = Js.Dict.keys
|
||||||
|
let fromArray = Js.Dict.fromArray
|
||||||
|
let toArray = Js.Dict.entries
|
||||||
|
let concat = (a, b) => A.concat(toArray(a), toArray(b))->fromArray
|
||||||
|
let concatMany = ts => ts->A2.fmap(toArray)->A.concatMany->fromArray
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ type distToFloatOperation = [
|
||||||
| #Inv(float)
|
| #Inv(float)
|
||||||
| #Mean
|
| #Mean
|
||||||
| #Sample
|
| #Sample
|
||||||
|
| #Min
|
||||||
|
| #Max
|
||||||
]
|
]
|
||||||
|
|
||||||
module Convolution = {
|
module Convolution = {
|
||||||
|
|
|
@ -148,6 +148,11 @@ module T = {
|
||||||
| None => Ok(attempt)
|
| None => Ok(attempt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let makeFromZipped = (values: array<(float, float)>) => {
|
||||||
|
let (xs, ys) = E.A.unzip(values)
|
||||||
|
make(~xs, ~ys)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module Ts = {
|
module Ts = {
|
||||||
|
|
24
packages/vscode-ext/.eslintrc.json
Normal file
24
packages/vscode-ext/.eslintrc.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
3
packages/vscode-ext/.gitignore
vendored
Normal file
3
packages/vscode-ext/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/media/vendor
|
||||||
|
/out
|
||||||
|
/*.vsix
|
7
packages/vscode-ext/.vscode/extensions.json
vendored
Normal file
7
packages/vscode-ext/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"dbaeumer.vscode-eslint"
|
||||||
|
]
|
||||||
|
}
|
34
packages/vscode-ext/.vscode/launch.json
vendored
Normal file
34
packages/vscode-ext/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// 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}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user