Merge branch 'develop' into metalog
This commit is contained in:
commit
ad73ff98cc
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
|
@ -11,3 +11,10 @@ updates:
|
|||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "⬆️"
|
||||
open-pull-requests-limit: 100
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
commit-message:
|
||||
prefix: "⬆️"
|
||||
|
|
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
|
@ -24,27 +24,27 @@ jobs:
|
|||
steps:
|
||||
- id: skip_lang_check
|
||||
name: Check if the changes are about squiggle-lang src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/squiggle-lang/**"]'
|
||||
- id: skip_components_check
|
||||
name: Check if the changes are about components src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/components/**"]'
|
||||
- id: skip_website_check
|
||||
name: Check if the changes are about website src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/website/**"]'
|
||||
- id: skip_vscodeext_check
|
||||
name: Check if the changes are about vscode extension src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/vscode-ext/**"]'
|
||||
- id: skip_cli_check
|
||||
name: Check if the changes are about cli src files
|
||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/cli/**"]'
|
||||
|
||||
|
@ -58,7 +58,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/squiggle-lang
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Dependencies
|
||||
run: cd ../../ && yarn
|
||||
- name: Check rescript lint
|
||||
|
@ -79,7 +79,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/squiggle-lang
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Install dependencies from monorepo level
|
||||
|
@ -107,7 +107,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/components
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check javascript, typescript, and markdown lint
|
||||
uses: creyD/prettier_action@v4.2
|
||||
with:
|
||||
|
@ -124,7 +124,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/components
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies from monorepo level
|
||||
run: cd ../../ && yarn
|
||||
- name: Build rescript codebase in squiggle-lang
|
||||
|
@ -144,7 +144,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/website
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check javascript, typescript, and markdown lint
|
||||
uses: creyD/prettier_action@v4.2
|
||||
with:
|
||||
|
@ -161,7 +161,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/website
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies from monorepo level
|
||||
run: cd ../../ && yarn
|
||||
- name: Build rescript in squiggle-lang
|
||||
|
@ -181,7 +181,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/vscode-ext
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies from monorepo level
|
||||
run: cd ../../ && yarn
|
||||
- name: Lint the VSCode Extension source code
|
||||
|
@ -197,7 +197,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/vscode-ext
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies from monorepo level
|
||||
run: cd ../../ && yarn
|
||||
- name: Build
|
||||
|
@ -213,7 +213,7 @@ jobs:
|
|||
shell: bash
|
||||
working-directory: packages/cli
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check javascript, typescript, and markdown lint
|
||||
uses: creyD/prettier_action@v4.2
|
||||
with:
|
||||
|
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
|
@ -33,11 +33,11 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -48,7 +48,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
- name: Build rescript
|
||||
|
@ -65,4 +65,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
140
.github/workflows/release-please.yml
vendored
Normal file
140
.github/workflows/release-please.yml
vendored
Normal file
|
@ -0,0 +1,140 @@
|
|||
name: Run Release Please
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
pre_check:
|
||||
name: Precheck for skipping redundant jobs
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
|
||||
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
|
||||
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
|
||||
should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }}
|
||||
should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_lang_check
|
||||
name: Check if the changes are about squiggle-lang src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/squiggle-lang/**"]'
|
||||
- id: skip_components_check
|
||||
name: Check if the changes are about components src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/components/**"]'
|
||||
- id: skip_website_check
|
||||
name: Check if the changes are about website src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/website/**"]'
|
||||
- id: skip_vscodeext_check
|
||||
name: Check if the changes are about vscode extension src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/vscode-ext/**"]'
|
||||
- id: skip_cli_check
|
||||
name: Check if the changes are about cli src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
paths: '["packages/cli/**"]'
|
||||
|
||||
relplz-lang:
|
||||
name: for squiggle-lang
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
||||
steps:
|
||||
- name: Release please (squiggle-lang)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
id: release
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/squiggle-lang
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
||||
# - name: Publish: Checkout source
|
||||
# uses: actions/checkout@v2
|
||||
# # these if statements ensure that a publication only occurs when
|
||||
# # a new release is created:
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
# - name: Publish: Install dependencies
|
||||
# run: yarn
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
# - name: Publish
|
||||
# run: cd packages/squiggle-lang && yarn publish
|
||||
# env:
|
||||
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
relplz-components:
|
||||
name: for components
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
|
||||
steps:
|
||||
- name: Release please (components)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/components
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
||||
# - name: Publish: Checkout source
|
||||
# uses: actions/checkout@v2
|
||||
# # these if statements ensure that a publication only occurs when
|
||||
# # a new release is created:
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
# - name: Publish: Install dependencies
|
||||
# run: yarn
|
||||
# if: ${{ steps.release.outputs.release_created }}
|
||||
# - name: Publish
|
||||
# run: cd packages/components && yarn publish
|
||||
# env:
|
||||
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
relplz-website:
|
||||
name: for website
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
|
||||
steps:
|
||||
- name: Release please (website)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/website
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
||||
relplz-vscodeext:
|
||||
name: for vscode-ext
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
|
||||
steps:
|
||||
- name: Release please (vscode-ext)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/vscode-ext
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
||||
relplz-cl:
|
||||
name: for cli
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_check
|
||||
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
|
||||
steps:
|
||||
- name: Release please (cli)
|
||||
uses: google-github-actions/release-please-action@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
command: manifest-pr
|
||||
path: packages/cli
|
||||
bump-patch-for-minor-pre-major: true
|
||||
skip-github-release: true
|
7
.release-please-manifest.json
Normal file
7
.release-please-manifest.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"packages/cli": "0.0.3",
|
||||
"packages/components": "0.3.1",
|
||||
"packages/squiggle-lang": "0.3.0",
|
||||
"packages/vscode-ext": "0.3.1",
|
||||
"packages/website": "0.3.0"
|
||||
}
|
|
@ -12,8 +12,8 @@ _An estimation language_.
|
|||
|
||||
- [Gallery](https://www.squiggle-language.com/docs/Discussions/Gallery)
|
||||
- [Squiggle playground](https://squiggle-language.com/playground)
|
||||
- [Language basics](https://www.squiggle-language.com/docs/Features/Language)
|
||||
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Features/Functions)
|
||||
- [Language basics](https://www.squiggle-language.com/docs/Guides/Language)
|
||||
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Guides/Functions)
|
||||
- [Known bugs](https://www.squiggle-language.com/docs/Discussions/Bugs)
|
||||
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
|
||||
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)
|
||||
|
|
|
@ -54,7 +54,6 @@ export function DynamicSquiggleChart({ squiggleString }) {
|
|||
width={445}
|
||||
height={200}
|
||||
showSummary={true}
|
||||
showTypes={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,43 +1,45 @@
|
|||
{
|
||||
"name": "@quri/squiggle-components",
|
||||
"version": "0.2.23",
|
||||
"version": "0.3.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^1.0.0",
|
||||
"@floating-ui/react-dom-interactions": "^0.9.1",
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@hookform/resolvers": "^2.9.6",
|
||||
"@quri/squiggle-lang": "^0.2.8",
|
||||
"@hookform/resolvers": "^2.9.7",
|
||||
"@quri/squiggle-lang": "^0.3.0",
|
||||
"@react-hook/size": "^2.1.2",
|
||||
"clsx": "^1.2.1",
|
||||
"framer-motion": "^6.5.1",
|
||||
"framer-motion": "^7.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.1.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-hook-form": "^7.33.1",
|
||||
"react-hook-form": "^7.34.0",
|
||||
"react-use": "^17.4.0",
|
||||
"react-vega": "^7.6.0",
|
||||
"vega": "^5.22.1",
|
||||
"vega-embed": "^6.21.0",
|
||||
"vega-lite": "^5.3.0",
|
||||
"vega-lite": "^5.4.0",
|
||||
"vscode-uri": "^3.0.3",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
|
||||
"@storybook/addon-actions": "^6.5.9",
|
||||
"@storybook/addon-essentials": "^6.5.9",
|
||||
"@storybook/addon-links": "^6.5.9",
|
||||
"@storybook/builder-webpack5": "^6.5.9",
|
||||
"@storybook/manager-webpack5": "^6.5.9",
|
||||
"@storybook/addon-essentials": "^6.5.10",
|
||||
"@storybook/addon-links": "^6.5.10",
|
||||
"@storybook/builder-webpack5": "^6.5.10",
|
||||
"@storybook/manager-webpack5": "^6.5.10",
|
||||
"@storybook/node-logger": "^6.5.9",
|
||||
"@storybook/preset-create-react-app": "^4.1.2",
|
||||
"@storybook/react": "^6.5.9",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@storybook/react": "^6.5.10",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^14.2.6",
|
||||
"@testing-library/user-event": "^14.4.2",
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^18.0.6",
|
||||
"@types/node": "^18.6.4",
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/styled-components": "^5.1.24",
|
||||
"@types/webpack": "^5.28.0",
|
||||
|
@ -49,12 +51,12 @@
|
|||
"react": "^18.1.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwindcss": "^3.1.6",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"ts-loader": "^9.3.0",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"typescript": "^4.7.4",
|
||||
"web-vitals": "^2.1.4",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.9.3"
|
||||
},
|
||||
|
|
|
@ -8,26 +8,24 @@ import {
|
|||
import { Vega } from "react-vega";
|
||||
import { ErrorAlert } from "./Alert";
|
||||
import { useSize } from "react-use";
|
||||
import clsx from "clsx";
|
||||
|
||||
import {
|
||||
buildVegaSpec,
|
||||
DistributionChartSpecOptions,
|
||||
} from "../lib/distributionSpecBuilder";
|
||||
import { NumberShower } from "./NumberShower";
|
||||
import { hasMassBelowZero } from "../lib/distributionUtils";
|
||||
|
||||
export type DistributionPlottingSettings = {
|
||||
/** Whether to show a summary of means, stdev, percentiles etc */
|
||||
showSummary: boolean;
|
||||
/** Whether to show the user graph controls (scale etc) */
|
||||
showControls: boolean;
|
||||
actions?: boolean;
|
||||
} & DistributionChartSpecOptions;
|
||||
|
||||
export type DistributionChartProps = {
|
||||
distribution: Distribution;
|
||||
width?: number;
|
||||
height: number;
|
||||
actions?: boolean;
|
||||
} & DistributionPlottingSettings;
|
||||
|
||||
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||
|
@ -36,17 +34,9 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
height,
|
||||
showSummary,
|
||||
width,
|
||||
showControls,
|
||||
logX,
|
||||
expY,
|
||||
actions = false,
|
||||
} = props;
|
||||
const [isLogX, setLogX] = React.useState(logX);
|
||||
const [isExpY, setExpY] = React.useState(expY);
|
||||
|
||||
React.useEffect(() => setLogX(logX), [logX]);
|
||||
React.useEffect(() => setExpY(expY), [expY]);
|
||||
|
||||
const shape = distribution.pointSet();
|
||||
const [sized] = useSize((size) => {
|
||||
if (shape.tag === "Error") {
|
||||
|
@ -57,9 +47,6 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
);
|
||||
}
|
||||
|
||||
const massBelow0 =
|
||||
shape.value.continuous.some((x) => x.x <= 0) ||
|
||||
shape.value.discrete.some((x) => x.x <= 0);
|
||||
const spec = buildVegaSpec(props);
|
||||
|
||||
let widthProp = width ? width : size.width;
|
||||
|
@ -72,7 +59,11 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
|
||||
return (
|
||||
<div style={{ width: widthProp }}>
|
||||
{!(isLogX && massBelow0) ? (
|
||||
{logX && hasMassBelowZero(shape.value) ? (
|
||||
<ErrorAlert heading="Log Domain Error">
|
||||
Cannot graph distribution with negative values on logarithmic scale.
|
||||
</ErrorAlert>
|
||||
) : (
|
||||
<Vega
|
||||
spec={spec}
|
||||
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
|
||||
|
@ -80,67 +71,16 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
height={height}
|
||||
actions={actions}
|
||||
/>
|
||||
) : (
|
||||
<ErrorAlert heading="Log Domain Error">
|
||||
Cannot graph distribution with negative values on logarithmic scale.
|
||||
</ErrorAlert>
|
||||
)}
|
||||
<div className="flex justify-center">
|
||||
{showSummary && <SummaryTable distribution={distribution} />}
|
||||
</div>
|
||||
{showControls && (
|
||||
<div>
|
||||
<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} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return sized;
|
||||
};
|
||||
|
||||
interface CheckBoxProps {
|
||||
label: string;
|
||||
onChange: (x: boolean) => void;
|
||||
value: boolean;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const CheckBox: React.FC<CheckBoxProps> = ({
|
||||
label,
|
||||
onChange,
|
||||
value,
|
||||
disabled = false,
|
||||
tooltip,
|
||||
}) => {
|
||||
return (
|
||||
<span title={tooltip}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={() => onChange(!value)}
|
||||
disabled={disabled}
|
||||
className="form-checkbox"
|
||||
/>
|
||||
<label className={clsx(disabled && "text-slate-400")}> {label}</label>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => (
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import * as React from "react";
|
||||
import { lambdaValue, environment, runForeign } from "@quri/squiggle-lang";
|
||||
import {
|
||||
lambdaValue,
|
||||
environment,
|
||||
runForeign,
|
||||
errorValueToString,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
||||
import { FunctionChart1Number } from "./FunctionChart1Number";
|
||||
import { DistributionPlottingSettings } from "./DistributionChart";
|
||||
|
@ -45,10 +50,16 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
|||
}
|
||||
};
|
||||
const validResult = getValidResult();
|
||||
const resultType =
|
||||
validResult.tag === "Ok" ? validResult.value.tag : ("Error" as const);
|
||||
|
||||
switch (resultType) {
|
||||
if (validResult.tag === "Error") {
|
||||
return (
|
||||
<ErrorAlert heading="Error">
|
||||
{errorValueToString(validResult.value)}
|
||||
</ErrorAlert>
|
||||
);
|
||||
}
|
||||
|
||||
switch (validResult.value.tag) {
|
||||
case "distribution":
|
||||
return (
|
||||
<FunctionChart1Dist
|
||||
|
@ -68,15 +79,11 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
|||
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>
|
||||
<span className="font-bold">{validResult.value.tag}</span>
|
||||
</MessageAlert>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
|
|||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||
let result = runForeign(fn, [x], environment);
|
||||
if (result.tag === "Ok") {
|
||||
if (result.value.tag == "distribution") {
|
||||
if (result.value.tag === "distribution") {
|
||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
||||
} else {
|
||||
return {
|
||||
|
@ -165,12 +165,14 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
|||
setMouseOverlay(NaN);
|
||||
}
|
||||
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
||||
|
||||
//TODO: This custom error handling is a bit hacky and should be improved.
|
||||
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
|
||||
? runForeign(fn, [mouseOverlay], environment)
|
||||
: {
|
||||
tag: "Error",
|
||||
value: {
|
||||
tag: "REExpectedType",
|
||||
tag: "RETodo",
|
||||
value: "Hover x-coordinate returned NaN. Expected a number.",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -39,13 +39,10 @@ interface FunctionChart1NumberProps {
|
|||
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
|
||||
chartSettings.count
|
||||
);
|
||||
|
||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||
|
|
|
@ -9,12 +9,13 @@ import {
|
|||
defaultEnvironment,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { useSquiggle } from "../lib/hooks";
|
||||
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
||||
import { SquiggleItem } from "./SquiggleItem";
|
||||
import { SquiggleViewer } from "./SquiggleViewer";
|
||||
|
||||
export interface SquiggleChartProps {
|
||||
/** The input string for squiggle */
|
||||
code?: string;
|
||||
/** Allows to re-run the code if code hasn't changed */
|
||||
executionId?: number;
|
||||
/** If the output requires monte carlo sampling, the amount of samples */
|
||||
sampleCount?: number;
|
||||
/** The amount of points returned to draw the distribution */
|
||||
|
@ -36,10 +37,6 @@ export interface SquiggleChartProps {
|
|||
jsImports?: jsImports;
|
||||
/** Whether to show a summary of the distribution */
|
||||
showSummary?: boolean;
|
||||
/** Whether to show type information about returns, default false */
|
||||
showTypes?: boolean;
|
||||
/** Whether to show graph controls (scale etc)*/
|
||||
showControls?: boolean;
|
||||
/** Set the x scale to be logarithmic by deault */
|
||||
logX?: boolean;
|
||||
/** Set the y scale to be exponential by deault */
|
||||
|
@ -56,6 +53,7 @@ export interface SquiggleChartProps {
|
|||
maxX?: number;
|
||||
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
||||
distributionChartActions?: boolean;
|
||||
enableLocalSettings?: boolean;
|
||||
}
|
||||
|
||||
const defaultOnChange = () => {};
|
||||
|
@ -63,6 +61,7 @@ const defaultOnChange = () => {};
|
|||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||
({
|
||||
code = "",
|
||||
executionId = 0,
|
||||
environment,
|
||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||
height = 200,
|
||||
|
@ -70,19 +69,18 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
jsImports = defaultImports,
|
||||
showSummary = false,
|
||||
width,
|
||||
showTypes = false,
|
||||
showControls = false,
|
||||
logX = false,
|
||||
expY = false,
|
||||
diagramStart = 0,
|
||||
diagramStop = 10,
|
||||
diagramCount = 100,
|
||||
diagramCount = 20,
|
||||
tickFormat,
|
||||
minX,
|
||||
maxX,
|
||||
color,
|
||||
title,
|
||||
distributionChartActions,
|
||||
enableLocalSettings = false,
|
||||
}) => {
|
||||
const result = useSquiggle({
|
||||
code,
|
||||
|
@ -90,14 +88,10 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
environment,
|
||||
jsImports,
|
||||
onChange,
|
||||
executionId,
|
||||
});
|
||||
|
||||
if (result.tag !== "Ok") {
|
||||
return <SquiggleErrorAlert error={result.value} />;
|
||||
}
|
||||
|
||||
let distributionPlotSettings = {
|
||||
showControls,
|
||||
const distributionPlotSettings = {
|
||||
showSummary,
|
||||
logX,
|
||||
expY,
|
||||
|
@ -109,21 +103,21 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
|||
actions: distributionChartActions,
|
||||
};
|
||||
|
||||
let chartSettings = {
|
||||
const chartSettings = {
|
||||
start: diagramStart,
|
||||
stop: diagramStop,
|
||||
count: diagramCount,
|
||||
};
|
||||
|
||||
return (
|
||||
<SquiggleItem
|
||||
expression={result.value}
|
||||
<SquiggleViewer
|
||||
result={result}
|
||||
width={width}
|
||||
height={height}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
showTypes={showTypes}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment ?? defaultEnvironment}
|
||||
enableLocalSettings={enableLocalSettings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ const SquiggleContext = React.createContext<SquiggleContextShape>({
|
|||
|
||||
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
||||
const context = useContext(SquiggleContext);
|
||||
|
||||
if (context.containerized) {
|
||||
return <>{children}</>;
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import React from "react";
|
||||
import { SquiggleEditor } from "./SquiggleEditor";
|
||||
import type { SquiggleEditorProps } from "./SquiggleEditor";
|
||||
import { runPartial, defaultBindings } from "@quri/squiggle-lang";
|
||||
import type {
|
||||
result,
|
||||
errorValue,
|
||||
bindings as bindingsType,
|
||||
} from "@quri/squiggle-lang";
|
||||
|
||||
function resultDefault(x: result<bindingsType, errorValue>): bindingsType {
|
||||
switch (x.tag) {
|
||||
case "Ok":
|
||||
return x.value;
|
||||
case "Error":
|
||||
return defaultBindings;
|
||||
}
|
||||
}
|
||||
|
||||
export type SquiggleEditorWithImportedBindingsProps = SquiggleEditorProps & {
|
||||
bindingsImportUrl: string;
|
||||
};
|
||||
|
||||
export const SquiggleEditorWithImportedBindings: React.FC<
|
||||
SquiggleEditorWithImportedBindingsProps
|
||||
> = (props) => {
|
||||
const { bindingsImportUrl, ...editorProps } = props;
|
||||
const [bindingsResult, setBindingsResult] = React.useState({
|
||||
tag: "Ok",
|
||||
value: defaultBindings,
|
||||
} as result<bindingsType, errorValue>);
|
||||
React.useEffect(() => {
|
||||
async function retrieveBindings(fileName: string) {
|
||||
let contents = await fetch(fileName).then((response) => {
|
||||
return response.text();
|
||||
});
|
||||
setBindingsResult(
|
||||
runPartial(
|
||||
contents,
|
||||
editorProps.bindings,
|
||||
editorProps.environment,
|
||||
editorProps.jsImports
|
||||
)
|
||||
);
|
||||
}
|
||||
retrieveBindings(bindingsImportUrl);
|
||||
}, [bindingsImportUrl]);
|
||||
const deliveredBindings = resultDefault(bindingsResult);
|
||||
return (
|
||||
<SquiggleEditor {...{ ...editorProps, bindings: deliveredBindings }} />
|
||||
);
|
||||
};
|
|
@ -1,281 +0,0 @@
|
|||
import * as React from "react";
|
||||
import {
|
||||
squiggleExpression,
|
||||
environment,
|
||||
declaration,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { NumberShower } from "./NumberShower";
|
||||
import {
|
||||
DistributionChart,
|
||||
DistributionPlottingSettings,
|
||||
} 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;
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
/** Whether to show type information */
|
||||
showTypes: boolean;
|
||||
/** Settings for displaying functions */
|
||||
chartSettings: FunctionChartSettings;
|
||||
/** Environment for further function executions */
|
||||
environment: environment;
|
||||
}
|
||||
|
||||
export const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
||||
expression,
|
||||
width,
|
||||
height,
|
||||
distributionPlotSettings,
|
||||
showTypes = 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}
|
||||
{...distributionPlotSettings}
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
</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}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
showTypes={showTypes}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment}
|
||||
/>
|
||||
</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}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
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}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
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)}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
height={height}
|
||||
environment={{
|
||||
sampleCount: environment.sampleCount / 10,
|
||||
xyPointLength: environment.xyPointLength / 10,
|
||||
}}
|
||||
/>
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "module": {
|
||||
return (
|
||||
<VariableBox heading="Module" showTypes={showTypes}>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(expression.value)
|
||||
.filter(([key, r]) => key !== "Math")
|
||||
.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}
|
||||
distributionPlotSettings={distributionPlotSettings}
|
||||
chartSettings={chartSettings}
|
||||
environment={environment}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<div>
|
||||
<span>No display for type: </span>{" "}
|
||||
<span className="font-semibold text-slate-600">{expression.tag}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,11 +1,19 @@
|
|||
import React, { FC, useState, useEffect, useMemo } from "react";
|
||||
import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||
import React, {
|
||||
FC,
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||
import * as yup from "yup";
|
||||
import { useMaybeControlledValue } from "../lib/hooks";
|
||||
import { useMaybeControlledValue, useRunnerState } from "../lib/hooks";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import {
|
||||
ChartSquareBarIcon,
|
||||
CheckCircleIcon,
|
||||
ClipboardCopyIcon,
|
||||
CodeIcon,
|
||||
CogIcon,
|
||||
CurrencyDollarIcon,
|
||||
|
@ -24,21 +32,32 @@ import { JsonEditor } from "./JsonEditor";
|
|||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||
import { SquiggleContainer } from "./SquiggleContainer";
|
||||
import { Toggle } from "./ui/Toggle";
|
||||
import { Checkbox } from "./ui/Checkbox";
|
||||
import { StyledTab } from "./ui/StyledTab";
|
||||
import { InputItem } from "./ui/InputItem";
|
||||
import { Text } from "./ui/Text";
|
||||
import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
|
||||
import { HeadedSection } from "./ui/HeadedSection";
|
||||
import {
|
||||
defaultColor,
|
||||
defaultTickFormat,
|
||||
} from "../lib/distributionSpecBuilder";
|
||||
import { Button } from "./ui/Button";
|
||||
|
||||
type PlaygroundProps = SquiggleChartProps & {
|
||||
/** The initial squiggle string to put in the playground */
|
||||
defaultCode?: string;
|
||||
/** How many pixels high is the playground */
|
||||
onCodeChange?(expr: string): void;
|
||||
/* When settings change */
|
||||
onSettingsChange?(settings: any): void;
|
||||
/** Should we show the editor? */
|
||||
showEditor?: boolean;
|
||||
/** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */
|
||||
showShareButton?: boolean;
|
||||
};
|
||||
|
||||
const schema = yup.object({}).shape({
|
||||
const schema = yup
|
||||
.object({})
|
||||
.shape({
|
||||
sampleCount: yup
|
||||
.number()
|
||||
.required()
|
||||
|
@ -55,74 +74,11 @@ const schema = yup.object({}).shape({
|
|||
.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().required(),
|
||||
showControls: yup.boolean().required(),
|
||||
showSummary: yup.boolean().required(),
|
||||
showEditor: yup.boolean().required(),
|
||||
logX: yup.boolean().required(),
|
||||
expY: yup.boolean().required(),
|
||||
tickFormat: yup.string().default(".9~s"),
|
||||
title: yup.string(),
|
||||
color: yup.string().default("#739ECC").required(),
|
||||
minX: yup.number(),
|
||||
maxX: yup.number(),
|
||||
distributionChartActions: 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),
|
||||
});
|
||||
})
|
||||
.concat(viewSettingsSchema);
|
||||
|
||||
type FormFields = yup.InferType<typeof schema>;
|
||||
|
||||
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" | "text" | "color";
|
||||
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, { valueAsNumber: type === "number" })}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
||||
register,
|
||||
}) => (
|
||||
|
@ -158,128 +114,6 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
|||
</div>
|
||||
);
|
||||
|
||||
const ViewSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
||||
register,
|
||||
}) => (
|
||||
<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="logX"
|
||||
label="Show x scale logarithmically"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="expY"
|
||||
label="Show y scale exponentially"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="distributionChartActions"
|
||||
label="Show vega chart controls"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<InputItem
|
||||
name="minX"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Min X Value"
|
||||
/>
|
||||
<InputItem
|
||||
name="maxX"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Max X Value"
|
||||
/>
|
||||
<InputItem
|
||||
name="title"
|
||||
type="text"
|
||||
register={register}
|
||||
label="Title"
|
||||
/>
|
||||
<InputItem
|
||||
name="tickFormat"
|
||||
type="text"
|
||||
register={register}
|
||||
label="Tick Format"
|
||||
/>
|
||||
<InputItem
|
||||
name="color"
|
||||
type="color"
|
||||
register={register}
|
||||
label="Color"
|
||||
/>
|
||||
</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 InputVariablesSettings: React.FC<{
|
||||
initialImports: any; // TODO - any json type
|
||||
setImports: (imports: any) => void;
|
||||
|
@ -361,69 +195,59 @@ const RunControls: React.FC<{
|
|||
icons={[CheckCircleIcon, PauseIcon]}
|
||||
status={autorunMode}
|
||||
onChange={onAutorunModeChange}
|
||||
spinIcon={autorunMode && isRunning}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useRunnerState = (code: string) => {
|
||||
const [autorunMode, setAutorunMode] = useState(true);
|
||||
const [renderedCode, setRenderedCode] = useState(code); // used in manual run mode only
|
||||
const [isRunning, setIsRunning] = useState(false); // used in manual run mode only
|
||||
|
||||
// This part is tricky and fragile; we need to re-render first to make sure that the icon is spinning,
|
||||
// and only then evaluate the squiggle code (which freezes the UI).
|
||||
// Also note that `useEffect` execution order matters here.
|
||||
// Hopefully it'll all go away after we make squiggle code evaluation async.
|
||||
useEffect(() => {
|
||||
if (renderedCode === code && isRunning) {
|
||||
// It's not possible to put this after `setRenderedCode(code)` below because React would apply
|
||||
// `setIsRunning` and `setRenderedCode` together and spinning icon will disappear immediately.
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [renderedCode, code, isRunning]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!autorunMode && isRunning) {
|
||||
setRenderedCode(code); // TODO - force run even if code hasn't changed
|
||||
}
|
||||
}, [autorunMode, code, isRunning]);
|
||||
|
||||
const run = () => {
|
||||
// The rest will be handled by useEffects above, but we need to update the spinner first.
|
||||
setIsRunning(true);
|
||||
};
|
||||
|
||||
return {
|
||||
run,
|
||||
renderedCode: autorunMode ? code : renderedCode,
|
||||
isRunning,
|
||||
autorunMode,
|
||||
setAutorunMode: (newValue: boolean) => {
|
||||
if (!newValue) setRenderedCode(code);
|
||||
setAutorunMode(newValue);
|
||||
},
|
||||
const ShareButton: React.FC = () => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const copy = () => {
|
||||
navigator.clipboard.writeText((window.top || window).location.href);
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 1000);
|
||||
};
|
||||
return (
|
||||
<div className="w-36">
|
||||
<Button onClick={copy} wide>
|
||||
{isCopied ? (
|
||||
"Copied to clipboard!"
|
||||
) : (
|
||||
<div className="flex items-center space-x-1">
|
||||
<ClipboardCopyIcon className="w-4 h-4" />
|
||||
<span>Copy share link</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type PlaygroundContextShape = {
|
||||
getLeftPanelElement: () => HTMLDivElement | undefined;
|
||||
};
|
||||
export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
||||
getLeftPanelElement: () => undefined,
|
||||
});
|
||||
|
||||
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||
defaultCode = "",
|
||||
height = 500,
|
||||
showTypes = false,
|
||||
showControls = false,
|
||||
showSummary = false,
|
||||
logX = false,
|
||||
expY = false,
|
||||
title,
|
||||
minX,
|
||||
maxX,
|
||||
color = "#739ECC",
|
||||
tickFormat = ".9~s",
|
||||
color = defaultColor,
|
||||
tickFormat = defaultTickFormat,
|
||||
distributionChartActions,
|
||||
code: controlledCode,
|
||||
onCodeChange,
|
||||
onSettingsChange,
|
||||
showEditor = true,
|
||||
showShareButton = false,
|
||||
}) => {
|
||||
const [code, setCode] = useMaybeControlledValue({
|
||||
value: controlledCode,
|
||||
|
@ -439,8 +263,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
sampleCount: 1000,
|
||||
xyPointLength: 1000,
|
||||
chartHeight: 150,
|
||||
showTypes,
|
||||
showControls,
|
||||
logX,
|
||||
expY,
|
||||
title,
|
||||
|
@ -451,8 +273,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
distributionChartActions,
|
||||
showSummary,
|
||||
showEditor,
|
||||
leftSizePercent: 50,
|
||||
showSettingsPage: false,
|
||||
diagramStart: 0,
|
||||
diagramStop: 10,
|
||||
diagramCount: 20,
|
||||
|
@ -474,17 +294,31 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
[vars.sampleCount, vars.xyPointLength]
|
||||
);
|
||||
|
||||
const { run, autorunMode, setAutorunMode, isRunning, renderedCode } =
|
||||
useRunnerState(code);
|
||||
const {
|
||||
run,
|
||||
autorunMode,
|
||||
setAutorunMode,
|
||||
isRunning,
|
||||
renderedCode,
|
||||
executionId,
|
||||
} = useRunnerState(code);
|
||||
|
||||
const squiggleChart = (
|
||||
const squiggleChart =
|
||||
renderedCode === "" ? null : (
|
||||
<div className="relative">
|
||||
{isRunning ? (
|
||||
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
||||
) : null}
|
||||
<SquiggleChart
|
||||
code={renderedCode}
|
||||
executionId={executionId}
|
||||
environment={env}
|
||||
{...vars}
|
||||
bindings={defaultBindings}
|
||||
jsImports={imports}
|
||||
enableLocalSettings={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const firstTab = vars.showEditor ? (
|
||||
|
@ -509,7 +343,15 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
<SamplingSettings register={register} />
|
||||
</StyledTab.Panel>
|
||||
<StyledTab.Panel>
|
||||
<ViewSettings register={register} />
|
||||
<ViewSettings
|
||||
register={
|
||||
// This is dangerous, but doesn't cause any problems.
|
||||
// I tried to make `ViewSettings` generic (to allow it to accept any extension of a settings schema), but it didn't work.
|
||||
register as unknown as UseFormRegister<
|
||||
yup.InferType<typeof viewSettingsSchema>
|
||||
>
|
||||
}
|
||||
/>
|
||||
</StyledTab.Panel>
|
||||
<StyledTab.Panel>
|
||||
<InputVariablesSettings
|
||||
|
@ -520,21 +362,33 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
</StyledTab.Panels>
|
||||
);
|
||||
|
||||
const leftPanelRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const withEditor = (
|
||||
<div className="flex mt-2">
|
||||
<div className="w-1/2">{tabs}</div>
|
||||
<div
|
||||
className="w-1/2 relative"
|
||||
style={{ minHeight: height }}
|
||||
ref={leftPanelRef}
|
||||
>
|
||||
{tabs}
|
||||
</div>
|
||||
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
||||
|
||||
console.log(vars);
|
||||
const getLeftPanelElement = useCallback(() => {
|
||||
return leftPanelRef.current ?? undefined;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SquiggleContainer>
|
||||
<PlaygroundContext.Provider value={{ getLeftPanelElement }}>
|
||||
<StyledTab.Group>
|
||||
<div className="pb-4">
|
||||
<div className="flex justify-between items-center mt-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<StyledTab.List>
|
||||
<StyledTab
|
||||
name={vars.showEditor ? "Code" : "Display"}
|
||||
|
@ -544,6 +398,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
||||
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
||||
</StyledTab.List>
|
||||
<div className="flex space-x-2 items-center">
|
||||
<RunControls
|
||||
autorunMode={autorunMode}
|
||||
isStale={renderedCode !== code}
|
||||
|
@ -551,10 +406,13 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|||
isRunning={isRunning}
|
||||
onAutorunModeChange={setAutorunMode}
|
||||
/>
|
||||
{showShareButton && <ShareButton />}
|
||||
</div>
|
||||
</div>
|
||||
{vars.showEditor ? withEditor : withoutEditor}
|
||||
</div>
|
||||
</StyledTab.Group>
|
||||
</PlaygroundContext.Provider>
|
||||
</SquiggleContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,304 @@
|
|||
import React from "react";
|
||||
import { squiggleExpression, declaration } from "@quri/squiggle-lang";
|
||||
import { NumberShower } from "../NumberShower";
|
||||
import { DistributionChart } from "../DistributionChart";
|
||||
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
|
||||
import clsx from "clsx";
|
||||
import { VariableBox } from "./VariableBox";
|
||||
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
||||
import { hasMassBelowZero } from "../../lib/distributionUtils";
|
||||
import { MergedItemSettings } from "./utils";
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
const VariableList: React.FC<{
|
||||
path: string[];
|
||||
heading: string;
|
||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||
}> = ({ path, heading, children }) => (
|
||||
<VariableBox path={path} heading={heading}>
|
||||
{(settings) => (
|
||||
<div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}>
|
||||
{children(settings)}
|
||||
</div>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
|
||||
export interface Props {
|
||||
/** The output of squiggle's run */
|
||||
expression: squiggleExpression;
|
||||
/** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */
|
||||
path: string[];
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export const ExpressionViewer: React.FC<Props> = ({
|
||||
path,
|
||||
expression,
|
||||
width,
|
||||
}) => {
|
||||
if (typeof expression !== "object") {
|
||||
return (
|
||||
<VariableList path={path} heading="Error">
|
||||
{() => `Unknown expression: ${expression}`}
|
||||
</VariableList>
|
||||
);
|
||||
}
|
||||
switch (expression.tag) {
|
||||
case "number":
|
||||
return (
|
||||
<VariableBox path={path} heading="Number">
|
||||
{() => (
|
||||
<div className="font-semibold text-slate-600">
|
||||
<NumberShower precision={3} number={expression.value} />
|
||||
</div>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case "distribution": {
|
||||
const distType = expression.value.type();
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
heading={`Distribution (${distType})\n${
|
||||
distType === "Symbolic" ? expression.value.toString() : ""
|
||||
}`}
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
const shape = expression.value.pointSet();
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
path={path}
|
||||
onChange={onChange}
|
||||
disableLogX={
|
||||
shape.tag === "Ok" && hasMassBelowZero(shape.value)
|
||||
}
|
||||
withFunctionSettings={false}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{(settings) => {
|
||||
return (
|
||||
<DistributionChart
|
||||
distribution={expression.value}
|
||||
{...settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "string":
|
||||
return (
|
||||
<VariableBox path={path} heading="String">
|
||||
{() => (
|
||||
<>
|
||||
<span className="text-slate-400">"</span>
|
||||
<span className="text-slate-600 font-semibold font-mono">
|
||||
{expression.value}
|
||||
</span>
|
||||
<span className="text-slate-400">"</span>
|
||||
</>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case "boolean":
|
||||
return (
|
||||
<VariableBox path={path} heading="Boolean">
|
||||
{() => expression.value.toString()}
|
||||
</VariableBox>
|
||||
);
|
||||
case "symbol":
|
||||
return (
|
||||
<VariableBox path={path} heading="Symbol">
|
||||
{() => (
|
||||
<>
|
||||
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
|
||||
<span className="text-slate-600">{expression.value}</span>
|
||||
</>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case "call":
|
||||
return (
|
||||
<VariableBox path={path} heading="Call">
|
||||
{() => expression.value}
|
||||
</VariableBox>
|
||||
);
|
||||
case "arraystring":
|
||||
return (
|
||||
<VariableBox path={path} heading="Array String">
|
||||
{() => expression.value.map((r) => `"${r}"`).join(", ")}
|
||||
</VariableBox>
|
||||
);
|
||||
case "date":
|
||||
return (
|
||||
<VariableBox path={path} heading="Date">
|
||||
{() => expression.value.toDateString()}
|
||||
</VariableBox>
|
||||
);
|
||||
case "void":
|
||||
return (
|
||||
<VariableBox path={path} heading="Void">
|
||||
{() => "Void"}
|
||||
</VariableBox>
|
||||
);
|
||||
case "timeDuration": {
|
||||
return (
|
||||
<VariableBox path={path} heading="Time Duration">
|
||||
{() => <NumberShower precision={3} number={expression.value} />}
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "lambda":
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
heading="Function"
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
path={path}
|
||||
onChange={onChange}
|
||||
withFunctionSettings={true}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{(settings) => (
|
||||
<>
|
||||
<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={settings.chartSettings}
|
||||
distributionPlotSettings={settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
environment={{
|
||||
sampleCount: settings.environment.sampleCount / 10,
|
||||
xyPointLength: settings.environment.xyPointLength / 10,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
case "lambdaDeclaration": {
|
||||
return (
|
||||
<VariableBox
|
||||
path={path}
|
||||
heading="Function Declaration"
|
||||
renderSettingsMenu={({ onChange }) => {
|
||||
return (
|
||||
<ItemSettingsMenu
|
||||
onChange={onChange}
|
||||
path={path}
|
||||
withFunctionSettings={true}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{(settings) => (
|
||||
<FunctionChart
|
||||
fn={expression.value.fn}
|
||||
chartSettings={getChartSettings(expression.value)}
|
||||
distributionPlotSettings={settings.distributionPlotSettings}
|
||||
height={settings.height}
|
||||
environment={{
|
||||
sampleCount: settings.environment.sampleCount / 10,
|
||||
xyPointLength: settings.environment.xyPointLength / 10,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VariableBox>
|
||||
);
|
||||
}
|
||||
case "module": {
|
||||
return (
|
||||
<VariableList path={path} heading="Module">
|
||||
{(settings) =>
|
||||
Object.entries(expression.value)
|
||||
.filter(([key, r]) => !key.match(/^(Math|System)\./))
|
||||
.map(([key, r]) => (
|
||||
<ExpressionViewer
|
||||
key={key}
|
||||
path={[...path, key]}
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</VariableList>
|
||||
);
|
||||
}
|
||||
case "record":
|
||||
return (
|
||||
<VariableList path={path} heading="Record">
|
||||
{(settings) =>
|
||||
Object.entries(expression.value).map(([key, r]) => (
|
||||
<ExpressionViewer
|
||||
key={key}
|
||||
path={[...path, key]}
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</VariableList>
|
||||
);
|
||||
case "array":
|
||||
return (
|
||||
<VariableList path={path} heading="Array">
|
||||
{(settings) =>
|
||||
expression.value.map((r, i) => (
|
||||
<ExpressionViewer
|
||||
key={i}
|
||||
path={[...path, String(i)]}
|
||||
expression={r}
|
||||
width={width !== undefined ? width - 20 : width}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</VariableList>
|
||||
);
|
||||
default: {
|
||||
return (
|
||||
<VariableList path={path} heading="Error">
|
||||
{() => (
|
||||
<div>
|
||||
<span>No display for type: </span>{" "}
|
||||
<span className="font-semibold text-slate-600">
|
||||
{expression.tag}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</VariableList>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,168 @@
|
|||
import { CogIcon } from "@heroicons/react/solid";
|
||||
import React, { useContext, useRef, useState, useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Modal } from "../ui/Modal";
|
||||
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
||||
import { Path, pathAsString } from "./utils";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
import {
|
||||
defaultColor,
|
||||
defaultTickFormat,
|
||||
} from "../../lib/distributionSpecBuilder";
|
||||
import { PlaygroundContext } from "../SquigglePlayground";
|
||||
|
||||
type Props = {
|
||||
path: Path;
|
||||
onChange: () => void;
|
||||
disableLogX?: boolean;
|
||||
withFunctionSettings: boolean;
|
||||
};
|
||||
|
||||
const ItemSettingsModal: React.FC<
|
||||
Props & { close: () => void; resetScroll: () => void }
|
||||
> = ({
|
||||
path,
|
||||
onChange,
|
||||
disableLogX,
|
||||
withFunctionSettings,
|
||||
close,
|
||||
resetScroll,
|
||||
}) => {
|
||||
const { setSettings, getSettings, getMergedSettings } =
|
||||
useContext(ViewerContext);
|
||||
|
||||
const mergedSettings = getMergedSettings(path);
|
||||
|
||||
const { register, watch } = useForm({
|
||||
resolver: yupResolver(viewSettingsSchema),
|
||||
defaultValues: {
|
||||
// this is a mess and should be fixed
|
||||
showEditor: true, // doesn't matter
|
||||
chartHeight: mergedSettings.height,
|
||||
showSummary: mergedSettings.distributionPlotSettings.showSummary,
|
||||
logX: mergedSettings.distributionPlotSettings.logX,
|
||||
expY: mergedSettings.distributionPlotSettings.expY,
|
||||
tickFormat:
|
||||
mergedSettings.distributionPlotSettings.format || defaultTickFormat,
|
||||
title: mergedSettings.distributionPlotSettings.title,
|
||||
color: mergedSettings.distributionPlotSettings.color || defaultColor,
|
||||
minX: mergedSettings.distributionPlotSettings.minX,
|
||||
maxX: mergedSettings.distributionPlotSettings.maxX,
|
||||
distributionChartActions: mergedSettings.distributionPlotSettings.actions,
|
||||
diagramStart: mergedSettings.chartSettings.start,
|
||||
diagramStop: mergedSettings.chartSettings.stop,
|
||||
diagramCount: mergedSettings.chartSettings.count,
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
const subscription = watch((vars) => {
|
||||
const settings = getSettings(path); // get the latest version
|
||||
setSettings(path, {
|
||||
...settings,
|
||||
distributionPlotSettings: {
|
||||
showSummary: vars.showSummary,
|
||||
logX: vars.logX,
|
||||
expY: vars.expY,
|
||||
format: vars.tickFormat,
|
||||
title: vars.title,
|
||||
color: vars.color,
|
||||
minX: vars.minX,
|
||||
maxX: vars.maxX,
|
||||
actions: vars.distributionChartActions,
|
||||
},
|
||||
chartSettings: {
|
||||
start: vars.diagramStart,
|
||||
stop: vars.diagramStop,
|
||||
count: vars.diagramCount,
|
||||
},
|
||||
});
|
||||
onChange();
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, [getSettings, setSettings, onChange, path, watch]);
|
||||
|
||||
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
||||
|
||||
return (
|
||||
<Modal container={getLeftPanelElement()} close={close}>
|
||||
<Modal.Header>
|
||||
Chart settings
|
||||
{path.length ? (
|
||||
<>
|
||||
{" for "}
|
||||
<span
|
||||
title="Scroll to item"
|
||||
className="cursor-pointer"
|
||||
onClick={resetScroll}
|
||||
>
|
||||
{pathAsString(path)}
|
||||
</span>{" "}
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<ViewSettings
|
||||
register={register}
|
||||
withShowEditorSetting={false}
|
||||
withFunctionSettings={withFunctionSettings}
|
||||
disableLogXSetting={disableLogX}
|
||||
/>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { enableLocalSettings, setSettings, getSettings } =
|
||||
useContext(ViewerContext);
|
||||
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
if (!enableLocalSettings) {
|
||||
return null;
|
||||
}
|
||||
const settings = getSettings(props.path);
|
||||
|
||||
const resetScroll = () => {
|
||||
if (!ref.current) return;
|
||||
window.scroll({
|
||||
top: ref.current.getBoundingClientRect().y + window.scrollY,
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-2" ref={ref}>
|
||||
<CogIcon
|
||||
className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
/>
|
||||
{settings.distributionPlotSettings || settings.chartSettings ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSettings(props.path, {
|
||||
...settings,
|
||||
distributionPlotSettings: undefined,
|
||||
chartSettings: undefined,
|
||||
});
|
||||
props.onChange();
|
||||
}}
|
||||
className="text-xs px-1 py-0.5 rounded bg-slate-300"
|
||||
>
|
||||
Reset settings
|
||||
</button>
|
||||
) : null}
|
||||
{isOpen ? (
|
||||
<ItemSettingsModal
|
||||
{...props}
|
||||
close={() => setIsOpen(false)}
|
||||
resetScroll={resetScroll}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
import React, { useContext, useReducer } from "react";
|
||||
import { Tooltip } from "../ui/Tooltip";
|
||||
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
|
||||
type SettingsMenuParams = {
|
||||
onChange: () => void; // used to notify VariableBox that settings have changed, so that VariableBox could re-render itself
|
||||
};
|
||||
|
||||
type VariableBoxProps = {
|
||||
path: string[];
|
||||
heading: string;
|
||||
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
|
||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||
};
|
||||
|
||||
export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||
path,
|
||||
heading = "Error",
|
||||
renderSettingsMenu,
|
||||
children,
|
||||
}) => {
|
||||
const { setSettings, getSettings, getMergedSettings } =
|
||||
useContext(ViewerContext);
|
||||
|
||||
// Since ViewerContext doesn't keep the actual settings, VariableBox won't rerender when setSettings is called.
|
||||
// So we use `forceUpdate` to force rerendering.
|
||||
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||
|
||||
const settings = getSettings(path);
|
||||
|
||||
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
|
||||
setSettings(path, newSettings);
|
||||
forceUpdate();
|
||||
};
|
||||
|
||||
const toggleCollapsed = () => {
|
||||
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
|
||||
};
|
||||
|
||||
const isTopLevel = path.length === 0;
|
||||
const name = isTopLevel ? "Result" : path[path.length - 1];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header className="inline-flex space-x-1">
|
||||
<Tooltip text={heading}>
|
||||
<span
|
||||
className="text-slate-500 font-mono text-sm cursor-pointer"
|
||||
onClick={toggleCollapsed}
|
||||
>
|
||||
{name}:
|
||||
</span>
|
||||
</Tooltip>
|
||||
{settings.collapsed ? (
|
||||
<span
|
||||
className="rounded p-0.5 bg-slate-200 text-slate-500 font-mono text-xs cursor-pointer"
|
||||
onClick={toggleCollapsed}
|
||||
>
|
||||
...
|
||||
</span>
|
||||
) : renderSettingsMenu ? (
|
||||
renderSettingsMenu({ onChange: forceUpdate })
|
||||
) : null}
|
||||
</header>
|
||||
{settings.collapsed ? null : (
|
||||
<div className="flex w-full">
|
||||
{path.length ? (
|
||||
<div
|
||||
className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
|
||||
onClick={toggleCollapsed}
|
||||
></div>
|
||||
) : null}
|
||||
<div className="grow">{children(getMergedSettings(path))}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
import { defaultEnvironment } from "@quri/squiggle-lang";
|
||||
import React from "react";
|
||||
import { LocalItemSettings, MergedItemSettings, Path } from "./utils";
|
||||
|
||||
type ViewerContextShape = {
|
||||
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
|
||||
// Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
|
||||
// See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
|
||||
getSettings(path: Path): LocalItemSettings;
|
||||
getMergedSettings(path: Path): MergedItemSettings;
|
||||
setSettings(path: Path, value: LocalItemSettings): void;
|
||||
enableLocalSettings: boolean; // show local settings icon in the UI
|
||||
};
|
||||
|
||||
export const ViewerContext = React.createContext<ViewerContextShape>({
|
||||
getSettings: () => ({ collapsed: false }),
|
||||
getMergedSettings: () => ({
|
||||
collapsed: false,
|
||||
// copy-pasted from SquiggleChart
|
||||
chartSettings: {
|
||||
start: 0,
|
||||
stop: 10,
|
||||
count: 100,
|
||||
},
|
||||
distributionPlotSettings: {
|
||||
showSummary: false,
|
||||
logX: false,
|
||||
expY: false,
|
||||
},
|
||||
environment: defaultEnvironment,
|
||||
height: 150,
|
||||
}),
|
||||
setSettings() {},
|
||||
enableLocalSettings: false,
|
||||
});
|
100
packages/components/src/components/SquiggleViewer/index.tsx
Normal file
100
packages/components/src/components/SquiggleViewer/index.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import React, { useCallback, useRef } from "react";
|
||||
import { environment } from "@quri/squiggle-lang";
|
||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||
import { FunctionChartSettings } from "../FunctionChart";
|
||||
import { ExpressionViewer } from "./ExpressionViewer";
|
||||
import { ViewerContext } from "./ViewerContext";
|
||||
import {
|
||||
LocalItemSettings,
|
||||
MergedItemSettings,
|
||||
Path,
|
||||
pathAsString,
|
||||
} from "./utils";
|
||||
import { useSquiggle } from "../../lib/hooks";
|
||||
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
||||
|
||||
type Props = {
|
||||
/** The output of squiggle's run */
|
||||
result: ReturnType<typeof useSquiggle>;
|
||||
width?: number;
|
||||
height: number;
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
/** Settings for displaying functions */
|
||||
chartSettings: FunctionChartSettings;
|
||||
/** Environment for further function executions */
|
||||
environment: environment;
|
||||
enableLocalSettings?: boolean;
|
||||
};
|
||||
|
||||
type Settings = {
|
||||
[k: string]: LocalItemSettings;
|
||||
};
|
||||
|
||||
const defaultSettings: LocalItemSettings = { collapsed: false };
|
||||
|
||||
export const SquiggleViewer: React.FC<Props> = ({
|
||||
result,
|
||||
width,
|
||||
height,
|
||||
distributionPlotSettings,
|
||||
chartSettings,
|
||||
environment,
|
||||
enableLocalSettings = false,
|
||||
}) => {
|
||||
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
||||
const settingsRef = useRef<Settings>({});
|
||||
|
||||
const getSettings = useCallback(
|
||||
(path: Path) => {
|
||||
return settingsRef.current[pathAsString(path)] || defaultSettings;
|
||||
},
|
||||
[settingsRef]
|
||||
);
|
||||
|
||||
const setSettings = useCallback(
|
||||
(path: Path, value: LocalItemSettings) => {
|
||||
settingsRef.current[pathAsString(path)] = value;
|
||||
},
|
||||
[settingsRef]
|
||||
);
|
||||
|
||||
const getMergedSettings = useCallback(
|
||||
(path: Path) => {
|
||||
const localSettings = getSettings(path);
|
||||
const result: MergedItemSettings = {
|
||||
distributionPlotSettings: {
|
||||
...distributionPlotSettings,
|
||||
...(localSettings.distributionPlotSettings || {}),
|
||||
},
|
||||
chartSettings: {
|
||||
...chartSettings,
|
||||
...(localSettings.chartSettings || {}),
|
||||
},
|
||||
environment: {
|
||||
...environment,
|
||||
...(localSettings.environment || {}),
|
||||
},
|
||||
height: localSettings.height || height,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
[distributionPlotSettings, chartSettings, environment, height, getSettings]
|
||||
);
|
||||
|
||||
return (
|
||||
<ViewerContext.Provider
|
||||
value={{
|
||||
getSettings,
|
||||
setSettings,
|
||||
getMergedSettings,
|
||||
enableLocalSettings,
|
||||
}}
|
||||
>
|
||||
{result.tag === "Ok" ? (
|
||||
<ExpressionViewer path={[]} expression={result.value} width={width} />
|
||||
) : (
|
||||
<SquiggleErrorAlert error={result.value} />
|
||||
)}
|
||||
</ViewerContext.Provider>
|
||||
);
|
||||
};
|
22
packages/components/src/components/SquiggleViewer/utils.ts
Normal file
22
packages/components/src/components/SquiggleViewer/utils.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||
import { FunctionChartSettings } from "../FunctionChart";
|
||||
import { environment } from "@quri/squiggle-lang";
|
||||
|
||||
export type LocalItemSettings = {
|
||||
collapsed: boolean;
|
||||
distributionPlotSettings?: Partial<DistributionPlottingSettings>;
|
||||
chartSettings?: Partial<FunctionChartSettings>;
|
||||
height?: number;
|
||||
environment?: Partial<environment>;
|
||||
};
|
||||
|
||||
export type MergedItemSettings = {
|
||||
distributionPlotSettings: DistributionPlottingSettings;
|
||||
chartSettings: FunctionChartSettings;
|
||||
height: number;
|
||||
environment: environment;
|
||||
};
|
||||
|
||||
export type Path = string[];
|
||||
|
||||
export const pathAsString = (path: Path) => path.join(".");
|
163
packages/components/src/components/ViewSettings.tsx
Normal file
163
packages/components/src/components/ViewSettings.tsx
Normal file
|
@ -0,0 +1,163 @@
|
|||
import React from "react";
|
||||
import * as yup from "yup";
|
||||
import { UseFormRegister } from "react-hook-form";
|
||||
import { InputItem } from "./ui/InputItem";
|
||||
import { Checkbox } from "./ui/Checkbox";
|
||||
import { HeadedSection } from "./ui/HeadedSection";
|
||||
import { Text } from "./ui/Text";
|
||||
import {
|
||||
defaultColor,
|
||||
defaultTickFormat,
|
||||
} from "../lib/distributionSpecBuilder";
|
||||
|
||||
export const viewSettingsSchema = yup.object({}).shape({
|
||||
chartHeight: yup.number().required().positive().integer().default(350),
|
||||
showSummary: yup.boolean().required(),
|
||||
showEditor: yup.boolean().required(),
|
||||
logX: yup.boolean().required(),
|
||||
expY: yup.boolean().required(),
|
||||
tickFormat: yup.string().default(defaultTickFormat),
|
||||
title: yup.string(),
|
||||
color: yup.string().default(defaultColor).required(),
|
||||
minX: yup.number(),
|
||||
maxX: yup.number(),
|
||||
distributionChartActions: yup.boolean(),
|
||||
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),
|
||||
});
|
||||
|
||||
type FormFields = yup.InferType<typeof viewSettingsSchema>;
|
||||
|
||||
// This component is used in two places: for global settings in SquigglePlayground, and for item-specific settings in modal dialogs.
|
||||
export const ViewSettings: React.FC<{
|
||||
withShowEditorSetting?: boolean;
|
||||
withFunctionSettings?: boolean;
|
||||
disableLogXSetting?: boolean;
|
||||
register: UseFormRegister<FormFields>;
|
||||
}> = ({
|
||||
withShowEditorSetting = true,
|
||||
withFunctionSettings = true,
|
||||
disableLogXSetting,
|
||||
register,
|
||||
}) => {
|
||||
return (
|
||||
<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">
|
||||
{withShowEditorSetting ? (
|
||||
<Checkbox
|
||||
name="showEditor"
|
||||
register={register}
|
||||
label="Show code editor on left"
|
||||
/>
|
||||
) : null}
|
||||
<InputItem
|
||||
name="chartHeight"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Chart Height (in pixels)"
|
||||
/>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
|
||||
<div className="pt-8">
|
||||
<HeadedSection title="Distribution Display Settings">
|
||||
<div className="space-y-2">
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="logX"
|
||||
label="Show x scale logarithmically"
|
||||
disabled={disableLogXSetting}
|
||||
tooltip={
|
||||
disableLogXSetting
|
||||
? "Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="expY"
|
||||
label="Show y scale exponentially"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="distributionChartActions"
|
||||
label="Show vega chart controls"
|
||||
/>
|
||||
<Checkbox
|
||||
register={register}
|
||||
name="showSummary"
|
||||
label="Show summary statistics"
|
||||
/>
|
||||
<InputItem
|
||||
name="minX"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Min X Value"
|
||||
/>
|
||||
<InputItem
|
||||
name="maxX"
|
||||
type="number"
|
||||
register={register}
|
||||
label="Max X Value"
|
||||
/>
|
||||
<InputItem
|
||||
name="title"
|
||||
type="text"
|
||||
register={register}
|
||||
label="Title"
|
||||
/>
|
||||
<InputItem
|
||||
name="tickFormat"
|
||||
type="text"
|
||||
register={register}
|
||||
label="Tick Format"
|
||||
/>
|
||||
<InputItem
|
||||
name="color"
|
||||
type="color"
|
||||
register={register}
|
||||
label="Color"
|
||||
/>
|
||||
</div>
|
||||
</HeadedSection>
|
||||
</div>
|
||||
|
||||
{withFunctionSettings ? (
|
||||
<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>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
22
packages/components/src/components/ui/Button.tsx
Normal file
22
packages/components/src/components/ui/Button.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
wide?: boolean; // stretch the button horizontally
|
||||
};
|
||||
|
||||
export const Button: React.FC<Props> = ({ onClick, wide, children }) => {
|
||||
return (
|
||||
<button
|
||||
className={clsx(
|
||||
"rounded-md py-1.5 px-2 bg-slate-500 text-white text-xs font-semibold flex items-center justify-center space-x-1",
|
||||
wide && "w-full"
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
|
@ -1,3 +1,4 @@
|
|||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { Path, UseFormRegister } from "react-hook-form";
|
||||
|
||||
|
@ -5,20 +6,32 @@ export function Checkbox<T>({
|
|||
name,
|
||||
label,
|
||||
register,
|
||||
disabled,
|
||||
tooltip,
|
||||
}: {
|
||||
name: Path<T>;
|
||||
label: string;
|
||||
register: UseFormRegister<T>;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
}) {
|
||||
return (
|
||||
<label className="flex items-center">
|
||||
<label className="flex items-center" title={tooltip}>
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled={disabled}
|
||||
{...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>
|
||||
<div
|
||||
className={clsx(
|
||||
"ml-3 text-sm font-medium",
|
||||
disabled ? "text-gray-400" : "text-gray-700"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
|
13
packages/components/src/components/ui/HeadedSection.tsx
Normal file
13
packages/components/src/components/ui/HeadedSection.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import React from "react";
|
||||
|
||||
export const HeadedSection: React.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>
|
||||
);
|
25
packages/components/src/components/ui/InputItem.tsx
Normal file
25
packages/components/src/components/ui/InputItem.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from "react";
|
||||
import { Path, UseFormRegister } from "react-hook-form";
|
||||
|
||||
export function InputItem<T>({
|
||||
name,
|
||||
label,
|
||||
type,
|
||||
register,
|
||||
}: {
|
||||
name: Path<T>;
|
||||
label: string;
|
||||
type: "number" | "text" | "color";
|
||||
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, { valueAsNumber: type === "number" })}
|
||||
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>
|
||||
);
|
||||
}
|
184
packages/components/src/components/ui/Modal.tsx
Normal file
184
packages/components/src/components/ui/Modal.tsx
Normal file
|
@ -0,0 +1,184 @@
|
|||
import { motion } from "framer-motion";
|
||||
import React, { useContext } from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import { XIcon } from "@heroicons/react/solid";
|
||||
import clsx from "clsx";
|
||||
import { useWindowScroll, useWindowSize } from "react-use";
|
||||
|
||||
type ModalContextShape = {
|
||||
close: () => void;
|
||||
};
|
||||
const ModalContext = React.createContext<ModalContextShape>({
|
||||
close: () => undefined,
|
||||
});
|
||||
|
||||
const Overlay: React.FC = () => {
|
||||
const { close } = useContext(ModalContext);
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute inset-0 -z-10 bg-black"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.1 }}
|
||||
onClick={close}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ModalHeader: React.FC<{
|
||||
children: React.ReactNode;
|
||||
}> = ({ children }) => {
|
||||
const { close } = useContext(ModalContext);
|
||||
return (
|
||||
<header className="px-5 py-3 border-b border-gray-200 font-bold flex items-center justify-between">
|
||||
<div>{children}</div>
|
||||
<button
|
||||
className="px-1 bg-transparent cursor-pointer text-gray-700 hover:text-accent-500"
|
||||
type="button"
|
||||
onClick={close}
|
||||
>
|
||||
<XIcon className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500" />
|
||||
</button>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
// TODO - get rid of forwardRef, support `focus` and `{...hotkeys}` via smart props
|
||||
const ModalBody = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
JSX.IntrinsicElements["div"]
|
||||
>(function ModalBody(props, ref) {
|
||||
return <div ref={ref} className="px-5 py-3 overflow-auto" {...props} />;
|
||||
});
|
||||
|
||||
const ModalFooter: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<div className="px-5 py-3 border-t border-gray-200">{children}</div>
|
||||
);
|
||||
|
||||
const ModalWindow: React.FC<{
|
||||
children: React.ReactNode;
|
||||
container?: HTMLElement;
|
||||
}> = ({ children, container }) => {
|
||||
// This component works in two possible modes:
|
||||
// 1. container mode - the modal is rendered inside a container element
|
||||
// 2. centered mode - the modal is rendered in the middle of the screen
|
||||
// The mode is determined by the presence of the `container` prop and by whether the available space is large enough to fit the modal.
|
||||
|
||||
// Necessary for container mode - need to reposition the modal on scroll and resize events.
|
||||
useWindowSize();
|
||||
useWindowScroll();
|
||||
|
||||
let position:
|
||||
| {
|
||||
left: number;
|
||||
top: number;
|
||||
maxWidth: number;
|
||||
maxHeight: number;
|
||||
transform: string;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
// If available space in `visibleRect` is smaller than these, fallback to positioning in the middle of the screen.
|
||||
const minWidth = 384;
|
||||
const minHeight = 300;
|
||||
const offset = 8;
|
||||
const naturalWidth = 576; // maximum possible width; modal tries to take this much space, but can be smaller
|
||||
|
||||
if (container) {
|
||||
const { clientWidth: screenWidth, clientHeight: screenHeight } =
|
||||
document.documentElement;
|
||||
const rect = container?.getBoundingClientRect();
|
||||
|
||||
const visibleRect = {
|
||||
left: Math.max(rect.left, 0),
|
||||
right: Math.min(rect.right, screenWidth),
|
||||
top: Math.max(rect.top, 0),
|
||||
bottom: Math.min(rect.bottom, screenHeight),
|
||||
};
|
||||
const maxWidth = visibleRect.right - visibleRect.left - 2 * offset;
|
||||
const maxHeight = visibleRect.bottom - visibleRect.top - 2 * offset;
|
||||
|
||||
const center = {
|
||||
left: visibleRect.left + (visibleRect.right - visibleRect.left) / 2,
|
||||
top: visibleRect.top + (visibleRect.bottom - visibleRect.top) / 2,
|
||||
};
|
||||
position = {
|
||||
left: center.left,
|
||||
top: center.top,
|
||||
transform: "translate(-50%, -50%)",
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
};
|
||||
if (maxWidth < minWidth || maxHeight < minHeight) {
|
||||
position = undefined; // modal is hard to fit in the container, fallback to positioning it in the middle of the screen
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-white rounded-md shadow-toast flex flex-col overflow-auto border",
|
||||
position ? "fixed" : null
|
||||
)}
|
||||
style={{
|
||||
width: naturalWidth,
|
||||
...(position ?? {
|
||||
maxHeight: "calc(100% - 20px)",
|
||||
maxWidth: "calc(100% - 20px)",
|
||||
width: naturalWidth,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type ModalType = React.FC<{
|
||||
children: React.ReactNode;
|
||||
container?: HTMLElement; // if specified, modal will be positioned over the visible part of the container, if it's not too small
|
||||
close: () => void;
|
||||
}> & {
|
||||
Body: typeof ModalBody;
|
||||
Footer: typeof ModalFooter;
|
||||
Header: typeof ModalHeader;
|
||||
};
|
||||
|
||||
export const Modal: ModalType = ({ children, container, close }) => {
|
||||
const [el] = React.useState(() => document.createElement("div"));
|
||||
|
||||
React.useEffect(() => {
|
||||
document.body.appendChild(el);
|
||||
return () => {
|
||||
document.body.removeChild(el);
|
||||
};
|
||||
}, [el]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
close();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
};
|
||||
}, [close]);
|
||||
|
||||
const modal = (
|
||||
<ModalContext.Provider value={{ close }}>
|
||||
<div className="squiggle">
|
||||
<div className="fixed inset-0 z-40 flex justify-center items-center">
|
||||
<Overlay />
|
||||
<ModalWindow container={container}>{children}</ModalWindow>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
|
||||
return ReactDOM.createPortal(modal, container || el);
|
||||
};
|
||||
|
||||
Modal.Body = ModalBody;
|
||||
Modal.Footer = ModalFooter;
|
||||
Modal.Header = ModalHeader;
|
5
packages/components/src/components/ui/Text.tsx
Normal file
5
packages/components/src/components/ui/Text.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React from "react";
|
||||
|
||||
export const Text: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<p className="text-sm text-gray-500">{children}</p>
|
||||
);
|
|
@ -1,5 +1,5 @@
|
|||
import { RefreshIcon } from "@heroicons/react/solid";
|
||||
import clsx from "clsx";
|
||||
import { motion } from "framer-motion";
|
||||
import React from "react";
|
||||
|
||||
type IconType = (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||
|
@ -9,19 +9,19 @@ type Props = {
|
|||
onChange: (status: boolean) => void;
|
||||
texts: [string, string];
|
||||
icons: [IconType, IconType];
|
||||
spinIcon?: boolean;
|
||||
};
|
||||
|
||||
export const Toggle: React.FC<Props> = ({
|
||||
texts: [onText, offText],
|
||||
icons: [OnIcon, OffIcon],
|
||||
status,
|
||||
onChange,
|
||||
texts: [onText, offText],
|
||||
icons: [OnIcon, OffIcon],
|
||||
spinIcon,
|
||||
}) => {
|
||||
const CurrentIcon = status ? OnIcon : OffIcon;
|
||||
return (
|
||||
<motion.button
|
||||
layout
|
||||
transition={{ duration: 0.2 }}
|
||||
<button
|
||||
className={clsx(
|
||||
"rounded-md py-0.5 bg-slate-500 text-white text-xs font-semibold flex items-center space-x-1",
|
||||
status ? "bg-slate-500" : "bg-gray-400",
|
||||
|
@ -30,12 +30,18 @@ export const Toggle: React.FC<Props> = ({
|
|||
)}
|
||||
onClick={() => onChange(!status)}
|
||||
>
|
||||
<motion.div layout transition={{ duration: 0.2 }}>
|
||||
<CurrentIcon className="w-6 h-6" />
|
||||
</motion.div>
|
||||
<motion.span layout transition={{ duration: 0.2 }}>
|
||||
{status ? onText : offText}
|
||||
</motion.span>
|
||||
</motion.button>
|
||||
<div className="relative w-6 h-6" key={String(spinIcon)}>
|
||||
<CurrentIcon
|
||||
className={clsx(
|
||||
"w-6 h-6 absolute opacity-100",
|
||||
spinIcon && "animate-hide"
|
||||
)}
|
||||
/>
|
||||
{spinIcon && (
|
||||
<RefreshIcon className="w-6 h-6 absolute opacity-0 animate-appear-and-spin" />
|
||||
)}
|
||||
</div>
|
||||
<span>{status ? onText : offText}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
64
packages/components/src/components/ui/Tooltip.tsx
Normal file
64
packages/components/src/components/ui/Tooltip.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import React, { cloneElement, useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
flip,
|
||||
shift,
|
||||
useDismiss,
|
||||
useFloating,
|
||||
useHover,
|
||||
useInteractions,
|
||||
useRole,
|
||||
} from "@floating-ui/react-dom-interactions";
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export const Tooltip: React.FC<Props> = ({ text, children }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { x, y, reference, floating, strategy, context } = useFloating({
|
||||
placement: "top",
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
middleware: [shift(), flip()],
|
||||
});
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||
useHover(context),
|
||||
useRole(context, { role: "tooltip" }),
|
||||
useDismiss(context),
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{cloneElement(
|
||||
children,
|
||||
getReferenceProps({ ref: reference, ...children.props })
|
||||
)}
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.15 }}
|
||||
{...getFloatingProps({
|
||||
ref: floating,
|
||||
className:
|
||||
"text-xs p-2 border border-gray-300 rounded bg-white z-10",
|
||||
style: {
|
||||
position: strategy,
|
||||
top: y ?? 0,
|
||||
left: x ?? 0,
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div className="font-mono whitespace-pre">{text}</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -2,5 +2,6 @@ export { SquiggleChart } from "./components/SquiggleChart";
|
|||
export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor";
|
||||
export { SquigglePlayground } from "./components/SquigglePlayground";
|
||||
export { SquiggleContainer } from "./components/SquiggleContainer";
|
||||
export { SquiggleEditorWithImportedBindings } from "./components/SquiggleEditorWithImportedBindings";
|
||||
|
||||
export { mergeBindings } from "@quri/squiggle-lang";
|
||||
|
|
|
@ -100,12 +100,15 @@ export let expYScale: PowScale = {
|
|||
},
|
||||
};
|
||||
|
||||
export const defaultTickFormat = ".9~s";
|
||||
export const defaultColor = "#739ECC";
|
||||
|
||||
export function buildVegaSpec(
|
||||
specOptions: DistributionChartSpecOptions
|
||||
): VisualizationSpec {
|
||||
let {
|
||||
format = ".9~s",
|
||||
color = "#739ECC",
|
||||
format = defaultTickFormat,
|
||||
color = defaultColor,
|
||||
title,
|
||||
minX,
|
||||
maxX,
|
||||
|
@ -223,7 +226,7 @@ export function buildVegaSpec(
|
|||
},
|
||||
size: [{ value: 100 }],
|
||||
tooltip: {
|
||||
signal: "datum.y",
|
||||
signal: "{ probability: datum.y, value: datum.x }",
|
||||
},
|
||||
},
|
||||
update: {
|
||||
|
|
5
packages/components/src/lib/distributionUtils.ts
Normal file
5
packages/components/src/lib/distributionUtils.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { shape } from "@quri/squiggle-lang";
|
||||
|
||||
export const hasMassBelowZero = (shape: shape) =>
|
||||
shape.continuous.some((x) => x.x <= 0) ||
|
||||
shape.discrete.some((x) => x.x <= 0);
|
3
packages/components/src/lib/hooks/index.ts
Normal file
3
packages/components/src/lib/hooks/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export { useMaybeControlledValue } from "./useMaybeControlledValue";
|
||||
export { useSquiggle, useSquigglePartial } from "./useSquiggle";
|
||||
export { useRunnerState } from "./useRunnerState";
|
22
packages/components/src/lib/hooks/useMaybeControlledValue.ts
Normal file
22
packages/components/src/lib/hooks/useMaybeControlledValue.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { useState } from "react";
|
||||
|
||||
type ControlledValueArgs<T> = {
|
||||
value?: T;
|
||||
defaultValue: T;
|
||||
onChange?: (x: T) => void;
|
||||
};
|
||||
|
||||
export function useMaybeControlledValue<T>(
|
||||
args: ControlledValueArgs<T>
|
||||
): [T, (x: T) => void] {
|
||||
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
|
||||
let value = args.value ?? uncontrolledValue;
|
||||
let onChange = (newValue: T) => {
|
||||
if (args.value === undefined) {
|
||||
// uncontrolled mode
|
||||
setUncontrolledValue(newValue);
|
||||
}
|
||||
args.onChange?.(newValue);
|
||||
};
|
||||
return [value, onChange];
|
||||
}
|
100
packages/components/src/lib/hooks/useRunnerState.ts
Normal file
100
packages/components/src/lib/hooks/useRunnerState.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
import { useLayoutEffect, useReducer } from "react";
|
||||
|
||||
type State = {
|
||||
autorunMode: boolean;
|
||||
renderedCode: string;
|
||||
// "prepared" is for rendering a spinner; "run" for executing squiggle code; then it gets back to "none" on the next render
|
||||
runningState: "none" | "prepared" | "run";
|
||||
executionId: number;
|
||||
};
|
||||
|
||||
const buildInitialState = (code: string): State => ({
|
||||
autorunMode: true,
|
||||
renderedCode: "",
|
||||
runningState: "none",
|
||||
executionId: 1,
|
||||
});
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: "SET_AUTORUN_MODE";
|
||||
value: boolean;
|
||||
code: string;
|
||||
}
|
||||
| {
|
||||
type: "PREPARE_RUN";
|
||||
}
|
||||
| {
|
||||
type: "RUN";
|
||||
code: string;
|
||||
}
|
||||
| {
|
||||
type: "STOP_RUN";
|
||||
};
|
||||
|
||||
const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case "SET_AUTORUN_MODE":
|
||||
return {
|
||||
...state,
|
||||
autorunMode: action.value,
|
||||
};
|
||||
case "PREPARE_RUN":
|
||||
return {
|
||||
...state,
|
||||
runningState: "prepared",
|
||||
};
|
||||
case "RUN":
|
||||
return {
|
||||
...state,
|
||||
runningState: "run",
|
||||
renderedCode: action.code,
|
||||
executionId: state.executionId + 1,
|
||||
};
|
||||
case "STOP_RUN":
|
||||
return {
|
||||
...state,
|
||||
runningState: "none",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const useRunnerState = (code: string) => {
|
||||
const [state, dispatch] = useReducer(reducer, buildInitialState(code));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (state.runningState === "prepared") {
|
||||
// this is necessary for async playground loading - otherwise it executes the code synchronously on the initial load
|
||||
// (it's surprising that this is necessary, but empirically it _is_ necessary, both with `useEffect` and `useLayoutEffect`)
|
||||
setTimeout(() => {
|
||||
dispatch({ type: "RUN", code });
|
||||
}, 0);
|
||||
} else if (state.runningState === "run") {
|
||||
dispatch({ type: "STOP_RUN" });
|
||||
}
|
||||
}, [state.runningState, code]);
|
||||
|
||||
const run = () => {
|
||||
// The rest will be handled by dispatches above on following renders, but we need to update the spinner first.
|
||||
dispatch({ type: "PREPARE_RUN" });
|
||||
};
|
||||
|
||||
if (
|
||||
state.autorunMode &&
|
||||
state.renderedCode !== code &&
|
||||
state.runningState === "none"
|
||||
) {
|
||||
run();
|
||||
}
|
||||
|
||||
return {
|
||||
run,
|
||||
autorunMode: state.autorunMode,
|
||||
renderedCode: state.renderedCode,
|
||||
isRunning: state.runningState !== "none",
|
||||
executionId: state.executionId,
|
||||
setAutorunMode: (newValue: boolean) => {
|
||||
dispatch({ type: "SET_AUTORUN_MODE", value: newValue, code });
|
||||
},
|
||||
};
|
||||
};
|
|
@ -5,10 +5,11 @@ import {
|
|||
run,
|
||||
runPartial,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
|
||||
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = {
|
||||
code: string;
|
||||
executionId?: number;
|
||||
bindings?: bindings;
|
||||
jsImports?: jsImports;
|
||||
environment?: environment;
|
||||
|
@ -21,7 +22,15 @@ const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>(
|
|||
) => {
|
||||
const result: T = useMemo<T>(
|
||||
() => f(args.code, args.bindings, args.environment, args.jsImports),
|
||||
[f, args.code, args.bindings, args.environment, args.jsImports]
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
f,
|
||||
args.code,
|
||||
args.bindings,
|
||||
args.environment,
|
||||
args.jsImports,
|
||||
args.executionId,
|
||||
]
|
||||
);
|
||||
|
||||
const { onChange } = args;
|
||||
|
@ -42,23 +51,3 @@ export const useSquigglePartial = (
|
|||
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
|
||||
return useSquiggleAny(args, run);
|
||||
};
|
||||
|
||||
type ControlledValueArgs<T> = {
|
||||
value?: T;
|
||||
defaultValue: T;
|
||||
onChange?: (x: T) => void;
|
||||
};
|
||||
export function useMaybeControlledValue<T>(
|
||||
args: ControlledValueArgs<T>
|
||||
): [T, (x: T) => void] {
|
||||
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
|
||||
let value = args.value ?? uncontrolledValue;
|
||||
let onChange = (newValue: T) => {
|
||||
if (args.value === undefined) {
|
||||
// uncontrolled mode
|
||||
setUncontrolledValue(newValue);
|
||||
}
|
||||
args.onChange?.(newValue);
|
||||
};
|
||||
return [value, onChange];
|
||||
}
|
|
@ -43,7 +43,7 @@ could be continuous, discrete or mixed.
|
|||
<Story
|
||||
name="Continuous Pointset"
|
||||
args={{
|
||||
code: "toPointSet(normal(5,2))",
|
||||
code: "PointSet.fromDist(normal(5,2))",
|
||||
width,
|
||||
}}
|
||||
>
|
||||
|
@ -57,7 +57,7 @@ could be continuous, discrete or mixed.
|
|||
<Story
|
||||
name="Continuous SampleSet"
|
||||
args={{
|
||||
code: "toSampleSet(normal(5,2), 1000)",
|
||||
code: "SampleSet.fromDist(normal(5,2))",
|
||||
width,
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -21,3 +21,16 @@ including sampling settings, in squiggle.
|
|||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="With share button"
|
||||
args={{
|
||||
defaultCode: "normal(5,2)",
|
||||
height: 800,
|
||||
showShareButton: true,
|
||||
}}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
|
|
@ -5,6 +5,27 @@ module.exports = {
|
|||
},
|
||||
important: ".squiggle",
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
animation: {
|
||||
"appear-and-spin":
|
||||
"spin 1s linear infinite, squiggle-appear 0.2s forwards",
|
||||
"semi-appear": "squiggle-semi-appear 0.2s forwards",
|
||||
hide: "squiggle-hide 0.2s forwards",
|
||||
},
|
||||
keyframes: {
|
||||
"squiggle-appear": {
|
||||
from: { opacity: 0 },
|
||||
to: { opacity: 1 },
|
||||
},
|
||||
"squiggle-semi-appear": {
|
||||
from: { opacity: 0 },
|
||||
to: { opacity: 0.5 },
|
||||
},
|
||||
"squiggle-hide": {
|
||||
from: { opacity: 1 },
|
||||
to: { opacity: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ environment created from the squiggle code.
|
|||
```js
|
||||
import { run } from "@quri/squiggle-lang";
|
||||
run(
|
||||
"normal(0, 1) * fromSamples([-3,-2,-1,1,2,3,3,3,4,9]"
|
||||
"normal(0, 1) * SampleSet.fromList([-3, 2,-1,1,2,3,3,3,4,9])"
|
||||
).value.value.toSparkline().value;
|
||||
```
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
open Jest
|
||||
open Expect
|
||||
|
||||
test("todo", () => expect("1")->toBe("1"))
|
|
@ -17,10 +17,6 @@ describe("builtin", () => {
|
|||
testEval("1-1", "Ok(0)")
|
||||
testEval("2>1", "Ok(true)")
|
||||
testEval("concat('a','b')", "Ok('ab')")
|
||||
testEval(
|
||||
"addOne(t)=t+1; toList(mapSamples(fromSamples([1,2,3,4,5,6]), addOne))",
|
||||
"Ok([2,3,4,5,6,7])",
|
||||
)
|
||||
})
|
||||
|
||||
describe("builtin exception", () => {
|
||||
|
|
|
@ -20,7 +20,7 @@ describe("Peggy parse type", () => {
|
|||
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}",
|
||||
)
|
||||
})
|
||||
describe("high priority modifier", () => {
|
||||
describe("high priority contract", () => {
|
||||
testParse(
|
||||
"answer: number<-min<-max(100)|string",
|
||||
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}",
|
||||
|
@ -30,7 +30,7 @@ describe("Peggy parse type", () => {
|
|||
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}",
|
||||
)
|
||||
})
|
||||
describe("low priority modifier", () => {
|
||||
describe("low priority contract", () => {
|
||||
testParse(
|
||||
"answer: number | string $ opaque",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
|
||||
|
@ -63,14 +63,14 @@ describe("Peggy parse type", () => {
|
|||
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}",
|
||||
)
|
||||
})
|
||||
describe("type paranthesis", () => {
|
||||
//$ is introduced to avoid paranthesis
|
||||
describe("type parenthesis", () => {
|
||||
//$ is introduced to avoid parenthesis
|
||||
testParse(
|
||||
"answer: (number|string)<-opaque",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
|
||||
)
|
||||
})
|
||||
describe("squiggle expressions in type modifiers", () => {
|
||||
describe("squiggle expressions in type contracts", () => {
|
||||
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)))}",
|
||||
|
|
|
@ -3,11 +3,10 @@ module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
|||
|
||||
open Jest
|
||||
open Reducer_Peggy_TestHelpers
|
||||
open Expect
|
||||
|
||||
describe("Peggy to Expression", () => {
|
||||
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 ignored at the first statement
|
||||
testToExpression("1", "{1}", ~v="1", ())
|
||||
testToExpression("'hello'", "{'hello'}", ~v="'hello'", ())
|
||||
testToExpression("true", "{true}", ~v="true", ())
|
||||
|
|
|
@ -40,7 +40,7 @@ describe("Peggy Types to Expression", () => {
|
|||
(),
|
||||
)
|
||||
})
|
||||
describe("high priority modifier", () => {
|
||||
describe("high priority contract", () => {
|
||||
testToExpression(
|
||||
"answer: number<-min(1)<-max(100)|string",
|
||||
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}",
|
||||
|
@ -78,7 +78,7 @@ describe("Peggy Types to Expression", () => {
|
|||
(),
|
||||
)
|
||||
})
|
||||
describe("low priority modifier", () => {
|
||||
describe("low priority contract", () => {
|
||||
testToExpression(
|
||||
"answer: number | string $ opaque",
|
||||
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}",
|
||||
|
@ -86,7 +86,7 @@ describe("Peggy Types to Expression", () => {
|
|||
(),
|
||||
)
|
||||
})
|
||||
describe("squiggle expressions in type modifiers", () => {
|
||||
describe("squiggle expressions in type contracts", () => {
|
||||
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)))}",
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
open Jest
|
||||
open Reducer_Peggy_TestHelpers
|
||||
|
||||
describe("Peggy void", () => {
|
||||
//literal
|
||||
testToExpression("()", "{()}", ~v="()", ())
|
||||
testToExpression(
|
||||
"fn()=1",
|
||||
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1}))}",
|
||||
~v="@{fn: lambda(_=>internal code)}",
|
||||
(),
|
||||
)
|
||||
testToExpression("fn()=1; fn()", "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:fn ())}", ~v="1", ())
|
||||
testToExpression(
|
||||
"fn(a)=(); call fn(1)",
|
||||
"{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)})}",
|
||||
~v="@{_: (),fn: lambda(a=>internal code)}",
|
||||
(),
|
||||
)
|
||||
})
|
|
@ -0,0 +1,52 @@
|
|||
module Expression = Reducer_Expression
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module Bindings = Reducer_Bindings
|
||||
module T = Reducer_Type_T
|
||||
module TypeCompile = Reducer_Type_Compile
|
||||
|
||||
open Jest
|
||||
open Expect
|
||||
|
||||
let myIevEval = (aTypeSourceCode: string) =>
|
||||
TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression)
|
||||
let myIevEvalToString = (aTypeSourceCode: string) =>
|
||||
myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult
|
||||
|
||||
let myIevExpectEqual = (aTypeSourceCode, answer) =>
|
||||
expect(myIevEvalToString(aTypeSourceCode))->toEqual(answer)
|
||||
|
||||
let myIevTest = (test, aTypeSourceCode, answer) =>
|
||||
test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer))
|
||||
|
||||
let myTypeEval = (aTypeSourceCode: string) =>
|
||||
TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression)
|
||||
let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult
|
||||
|
||||
let myTypeExpectEqual = (aTypeSourceCode, answer) =>
|
||||
expect(myTypeEvalToString(aTypeSourceCode))->toEqual(answer)
|
||||
|
||||
let myTypeTest = (test, aTypeSourceCode, answer) =>
|
||||
test(aTypeSourceCode, () => myTypeExpectEqual(aTypeSourceCode, answer))
|
||||
|
||||
// | ItTypeIdentifier(string)
|
||||
myTypeTest(test, "number", "number")
|
||||
myTypeTest(test, "(number)", "number")
|
||||
// | ItModifiedType({modifiedType: iType})
|
||||
myIevTest(test, "number<-min(0)", "Ok({min: 0,typeIdentifier: #number,typeTag: 'typeIdentifier'})")
|
||||
myTypeTest(test, "number<-min(0)", "number<-min(0)")
|
||||
// | ItTypeOr({typeOr: array<iType>})
|
||||
myTypeTest(test, "number | string", "(number | string)")
|
||||
// | ItTypeFunction({inputs: array<iType>, output: iType})
|
||||
myTypeTest(test, "number => number => number", "(number => number => number)")
|
||||
// | ItTypeArray({element: iType})
|
||||
myIevTest(test, "[number]", "Ok({element: #number,typeTag: 'typeArray'})")
|
||||
myTypeTest(test, "[number]", "[number]")
|
||||
// | ItTypeTuple({elements: array<iType>})
|
||||
myTypeTest(test, "[number, string]", "[number, string]")
|
||||
// | ItTypeRecord({properties: Belt.Map.String.t<iType>})
|
||||
myIevTest(
|
||||
test,
|
||||
"{age: number, name: string}",
|
||||
"Ok({properties: {age: #number,name: #string},typeTag: 'typeRecord'})",
|
||||
)
|
||||
myTypeTest(test, "{age: number, name: string}", "{age: number, name: string}")
|
|
@ -0,0 +1,41 @@
|
|||
module Expression = Reducer_Expression
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ErrorValue = Reducer_ErrorValue
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module Bindings = Reducer_Bindings
|
||||
module T = Reducer_Type_T
|
||||
module TypeChecker = Reducer_Type_TypeChecker
|
||||
|
||||
open Jest
|
||||
open Expect
|
||||
|
||||
let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
|
||||
'v,
|
||||
ErrorValue.t,
|
||||
> => {
|
||||
let reducerFn = Expression.reduceExpression
|
||||
let rResult =
|
||||
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr =>
|
||||
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment)
|
||||
)
|
||||
rResult->Belt.Result.flatMap(result =>
|
||||
switch result {
|
||||
| IEvArray(args) => TypeChecker.checkArguments(aTypeSourceCode, args, reducerFn)
|
||||
| _ => Js.Exn.raiseError("Arguments has to be an array")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let myCheckArguments = (aTypeSourceCode: string, sourceCode: string): string =>
|
||||
switch checkArgumentsSourceCode(aTypeSourceCode, sourceCode) {
|
||||
| Ok(_) => "Ok"
|
||||
| Error(error) => ErrorValue.errorToString(error)
|
||||
}
|
||||
|
||||
let myCheckArgumentsExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
|
||||
expect(myCheckArguments(aTypeSourceCode, sourceCode))->toEqual(answer)
|
||||
|
||||
let myCheckArgumentsTest = (test, aTypeSourceCode, sourceCode, answer) =>
|
||||
test(aTypeSourceCode, () => myCheckArgumentsExpectEqual(aTypeSourceCode, sourceCode, answer))
|
||||
|
||||
myCheckArgumentsTest(test, "number=>number=>number", "[1,2]", "Ok")
|
|
@ -0,0 +1,72 @@
|
|||
module Expression = Reducer_Expression
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module ErrorValue = Reducer_ErrorValue
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module Bindings = Reducer_Bindings
|
||||
module T = Reducer_Type_T
|
||||
module TypeChecker = Reducer_Type_TypeChecker
|
||||
|
||||
open Jest
|
||||
open Expect
|
||||
|
||||
// In development, you are expected to use TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn).
|
||||
// isTypeOfSourceCode is written to use strings instead of expression values.
|
||||
|
||||
let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
|
||||
'v,
|
||||
ErrorValue.t,
|
||||
> => {
|
||||
let reducerFn = Expression.reduceExpression
|
||||
let rResult =
|
||||
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr =>
|
||||
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment)
|
||||
)
|
||||
rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn))
|
||||
}
|
||||
|
||||
let myTypeCheck = (aTypeSourceCode: string, sourceCode: string): string =>
|
||||
switch isTypeOfSourceCode(aTypeSourceCode, sourceCode) {
|
||||
| Ok(_) => "Ok"
|
||||
| Error(error) => ErrorValue.errorToString(error)
|
||||
}
|
||||
|
||||
let myTypeCheckExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
|
||||
expect(myTypeCheck(aTypeSourceCode, sourceCode))->toEqual(answer)
|
||||
|
||||
let myTypeCheckTest = (test, aTypeSourceCode, sourceCode, answer) =>
|
||||
test(aTypeSourceCode, () => myTypeCheckExpectEqual(aTypeSourceCode, sourceCode, answer))
|
||||
|
||||
myTypeCheckTest(test, "number", "1", "Ok")
|
||||
myTypeCheckTest(test, "number", "'2'", "Expected type: number but got: '2'")
|
||||
myTypeCheckTest(test, "string", "3", "Expected type: string but got: 3")
|
||||
myTypeCheckTest(test, "string", "'a'", "Ok")
|
||||
myTypeCheckTest(test, "[number]", "[1,2,3]", "Ok")
|
||||
myTypeCheckTest(test, "[number]", "['a','a','a']", "Expected type: number but got: 'a'")
|
||||
myTypeCheckTest(test, "[number]", "[1,'a',3]", "Expected type: number but got: 'a'")
|
||||
myTypeCheckTest(test, "[number, string]", "[1,'a']", "Ok")
|
||||
myTypeCheckTest(test, "[number, string]", "[1, 2]", "Expected type: string but got: 2")
|
||||
myTypeCheckTest(
|
||||
test,
|
||||
"[number, string, string]",
|
||||
"[1,'a']",
|
||||
"Expected type: [number, string, string] but got: [1,'a']",
|
||||
)
|
||||
myTypeCheckTest(
|
||||
test,
|
||||
"[number, string]",
|
||||
"[1,'a', 3]",
|
||||
"Expected type: [number, string] but got: [1,'a',3]",
|
||||
)
|
||||
myTypeCheckTest(test, "{age: number, name: string}", "{age: 1, name: 'a'}", "Ok")
|
||||
myTypeCheckTest(
|
||||
test,
|
||||
"{age: number, name: string}",
|
||||
"{age: 1, name: 'a', job: 'IT'}",
|
||||
"Expected type: {age: number, name: string} but got: {age: 1,job: 'IT',name: 'a'}",
|
||||
)
|
||||
myTypeCheckTest(test, "number | string", "1", "Ok")
|
||||
myTypeCheckTest(test, "date | string", "1", "Expected type: (date | string) but got: 1")
|
||||
myTypeCheckTest(test, "number<-min(10)", "10", "Ok")
|
||||
myTypeCheckTest(test, "number<-min(10)", "0", "Expected type: number<-min(10) but got: 0")
|
||||
myTypeCheckTest(test, "any", "0", "Ok")
|
||||
myTypeCheckTest(test, "any", "'a'", "Ok")
|
|
@ -0,0 +1,123 @@
|
|||
open Jest
|
||||
open Expect
|
||||
|
||||
module DispatchT = Reducer_Dispatch_T
|
||||
module Expression = Reducer_Expression
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module TypeCompile = Reducer_Type_Compile
|
||||
module TypeChecker = Reducer_Type_TypeChecker
|
||||
open ReducerInterface_InternalExpressionValue
|
||||
|
||||
type errorValue = Reducer_ErrorValue.errorValue
|
||||
|
||||
// Let's build a function to replace switch statements
|
||||
// In dispatchChainPiece, we execute an return the result of execution if there is a type match.
|
||||
// Otherwise we return None so that the call chain can continue.
|
||||
// So we want to build a function like
|
||||
// dispatchChainPiece = (call: functionCall, environment): option<result<internalExpressionValue, errorValue>>
|
||||
|
||||
// Now lets make the dispatchChainPiece itself.
|
||||
// Note that I am not passing the reducer to the dispatchChainPiece as an argument because it is in the context anyway.
|
||||
// Keep in mind that reducerFn is necessary for map/reduce so dispatchChainPiece should have a reducerFn in context.
|
||||
|
||||
let makeMyDispatchChainPiece = (reducer: ExpressionT.reducerFn): DispatchT.dispatchChainPiece => {
|
||||
// Let's have a pure implementations
|
||||
module Implementation = {
|
||||
let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b)
|
||||
let arrayConcat = (
|
||||
a: Js.Array2.t<internalExpressionValue>,
|
||||
b: Js.Array2.t<internalExpressionValue>,
|
||||
): Js.Array2.t<internalExpressionValue> => Js.Array2.concat(a, b)
|
||||
let plot = _r => "yey, plotted"
|
||||
}
|
||||
|
||||
let extractStringString = args =>
|
||||
switch args {
|
||||
| [IEvString(a), IEvString(b)] => (a, b)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("extractStringString developer error"))
|
||||
}
|
||||
|
||||
let extractArrayArray = args =>
|
||||
switch args {
|
||||
| [IEvArray(a), IEvArray(b)] => (a, b)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("extractArrayArray developer error"))
|
||||
}
|
||||
|
||||
// Let's bridge the pure implementation to expression values
|
||||
module Bridge = {
|
||||
let stringConcat: DispatchT.genericIEvFunction = (args, _environment) => {
|
||||
let (a, b) = extractStringString(args)
|
||||
Implementation.stringConcat(a, b)->IEvString->Ok
|
||||
}
|
||||
let arrayConcat: DispatchT.genericIEvFunction = (args, _environment) => {
|
||||
let (a, b) = extractArrayArray(args)
|
||||
Implementation.arrayConcat(a, b)->IEvArray->Ok
|
||||
}
|
||||
let plot: DispatchT.genericIEvFunction = (args, _environment) => {
|
||||
switch args {
|
||||
// Just assume that we are doing the business of extracting and converting the deep record
|
||||
| [IEvRecord(_)] => Implementation.plot({"title": "This is a plot"})->IEvString->Ok
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("plot developer error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// concat functions are to illustrate polymoprhism. And the plot function is to illustrate complex types
|
||||
let jumpTable = [
|
||||
(
|
||||
"concat",
|
||||
TypeCompile.fromTypeExpressionExn("string=>string=>string", reducer),
|
||||
Bridge.stringConcat,
|
||||
),
|
||||
(
|
||||
"concat",
|
||||
TypeCompile.fromTypeExpressionExn("[any]=>[any]=>[any]", reducer),
|
||||
Bridge.arrayConcat,
|
||||
),
|
||||
(
|
||||
"plot",
|
||||
TypeCompile.fromTypeExpressionExn(
|
||||
// Nested complex types are available
|
||||
// records {property: type}
|
||||
// arrays [type]
|
||||
// tuples [type, type]
|
||||
// <- type contracts are available naturally and they become part of dispatching
|
||||
// Here we are not enumerating the possibilities because type checking has a dedicated test
|
||||
"{title: string, line: {width: number, color: string}}=>string",
|
||||
reducer,
|
||||
),
|
||||
Bridge.plot,
|
||||
),
|
||||
]
|
||||
|
||||
//Here we are creating a dispatchChainPiece function that will do the actual dispatch from the jumpTable
|
||||
Reducer_Dispatch_ChainPiece.makeFromTypes(jumpTable)
|
||||
}
|
||||
|
||||
// And finally, let's write a library dispatch for our external library
|
||||
// Exactly the same as the one used in real life
|
||||
let _dispatch = (
|
||||
call: functionCall,
|
||||
environment,
|
||||
reducer: Reducer_Expression_T.reducerFn,
|
||||
chain,
|
||||
): result<internalExpressionValue, 'e> => {
|
||||
let dispatchChainPiece = makeMyDispatchChainPiece(reducer)
|
||||
dispatchChainPiece(call, environment)->E.O2.defaultFn(() => chain(call, environment, reducer))
|
||||
}
|
||||
|
||||
// What is important about this implementation?
|
||||
// A) Exactly the same function jump table can be used to create type guarded lambda functions
|
||||
// Guarded lambda functions will be the basis of the next version of Squiggle
|
||||
// B) Complicated recursive record types are not a problem.
|
||||
|
||||
describe("Type Dispatch", () => {
|
||||
let reducerFn = Expression.reduceExpression
|
||||
let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn)
|
||||
test("stringConcat", () => {
|
||||
let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")])
|
||||
|
||||
let result = dispatchChainPiece(call, defaultEnvironment)
|
||||
expect(result)->toEqual(Some(Ok(IEvString("helloworld"))))
|
||||
})
|
||||
})
|
|
@ -1,15 +1,6 @@
|
|||
open Jest
|
||||
open Reducer_TestHelpers
|
||||
|
||||
describe("map reduce", () => {
|
||||
testEvalToBe("double(x)=2*x; arr=[1,2,3]; map(arr, double)", "Ok([2,4,6])")
|
||||
testEvalToBe("myadd(acc,x)=acc+x; arr=[1,2,3]; reduce(arr, 0, myadd)", "Ok(6)")
|
||||
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("arr=[1,2,3]; reverse(arr)", "Ok([3,2,1])")
|
||||
testEvalToBe("check(x)=(x==2);arr=[1,2,3]; filter(arr,check)", "Ok([2])")
|
||||
})
|
||||
|
||||
Skip.describe("map reduce (sam)", () => {
|
||||
testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???")
|
||||
testEvalToBe("addone(x)=x+1; map(2, {x: addone})", "Error???")
|
||||
|
|
|
@ -10,5 +10,5 @@ describe("Evaluate ternary operator", () => {
|
|||
testEvalToBe("false ? 'YES' : 'NO'", "Ok('NO')")
|
||||
testEvalToBe("2 > 1 ? 'YES' : 'NO'", "Ok('YES')")
|
||||
testEvalToBe("2 <= 1 ? 'YES' : 'NO'", "Ok('NO')")
|
||||
testEvalToBe("1+1 ? 'YES' : 'NO'", "Error(Expected type: Boolean)")
|
||||
testEvalToBe("1+1 ? 'YES' : 'NO'", "Error(Expected type: Boolean but got: )")
|
||||
})
|
||||
|
|
|
@ -27,6 +27,15 @@ describe("eval", () => {
|
|||
test("index", () => expectEvalToBe("r = {a: 1}; r.a", "Ok(1)"))
|
||||
test("index", () => expectEvalToBe("r = {a: 1}; r.b", "Error(Record property not found: b)"))
|
||||
testEvalError("{a: 1}.b") // invalid syntax
|
||||
test("always the same property ending", () =>
|
||||
expectEvalToBe(
|
||||
`{
|
||||
a: 1,
|
||||
b: 2,
|
||||
}`,
|
||||
"Ok({a: 1,b: 2})",
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
describe("multi-line", () => {
|
||||
|
|
|
@ -42,12 +42,6 @@ describe("eval on distribution functions", () => {
|
|||
describe("normalize", () => {
|
||||
testEval("normalize(normal(5,2))", "Ok(Normal(5,2))")
|
||||
})
|
||||
describe("toPointSet", () => {
|
||||
testEval("toPointSet(normal(5,2))", "Ok(Point Set Distribution)")
|
||||
})
|
||||
describe("toSampleSet", () => {
|
||||
testEval("toSampleSet(normal(5,2), 100)", "Ok(Sample Set Distribution)")
|
||||
})
|
||||
describe("add", () => {
|
||||
testEval("add(normal(5,2), normal(10,2))", "Ok(Normal(15,2.8284271247461903))")
|
||||
testEval("add(normal(5,2), lognormal(10,2))", "Ok(Sample Set Distribution)")
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
open Jest
|
||||
open Expect
|
||||
open Reducer_TestHelpers
|
||||
|
||||
let expectEvalToBeOk = (expr: string) =>
|
||||
Reducer.evaluate(expr)->Reducer_Helpers.rRemoveDefaultsExternal->E.R.isOk->expect->toBe(true)
|
||||
|
||||
let registry = FunctionRegistry_Library.registry
|
||||
let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry))
|
||||
|
||||
describe("FunctionRegistry Library", () => {
|
||||
describe("Regular tests", () => {
|
||||
testEvalToBe("List.make(3, 'HI')", "Ok(['HI','HI','HI'])")
|
||||
testEvalToBe("make(3, 'HI')", "Error(Function not found: make(Number,String))")
|
||||
testEvalToBe("List.upTo(1,3)", "Ok([1,2,3])")
|
||||
testEvalToBe("List.first([3,5,8])", "Ok(3)")
|
||||
testEvalToBe("List.last([3,5,8])", "Ok(8)")
|
||||
testEvalToBe("List.reverse([3,5,8])", "Ok([8,5,3])")
|
||||
testEvalToBe("double(x)=2*x; arr=[1,2,3]; List.map(arr, double)", "Ok([2,4,6])")
|
||||
testEvalToBe("double(x)=2*x; arr=[1,2,3]; map(arr, double)", "Ok([2,4,6])")
|
||||
testEvalToBe("myadd(acc,x)=acc+x; arr=[1,2,3]; List.reduce(arr, 0, myadd)", "Ok(6)")
|
||||
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; List.reduce(arr, 0, change)", "Ok(15)")
|
||||
testEvalToBe("change(acc,x)=acc*x+x; arr=[1,2,3]; List.reduceReverse(arr, 0, change)", "Ok(9)")
|
||||
testEvalToBe("check(x)=(x==2);arr=[1,2,3]; List.filter(arr,check)", "Ok([2])")
|
||||
testEvalToBe("arr=[1,2,3]; List.reverse(arr)", "Ok([3,2,1])")
|
||||
testEvalToBe("Dist.normal(5,2)", "Ok(Normal(5,2))")
|
||||
testEvalToBe("normal(5,2)", "Ok(Normal(5,2))")
|
||||
testEvalToBe("normal({mean:5,stdev:2})", "Ok(Normal(5,2))")
|
||||
testEvalToBe("-2 to 4", "Ok(Normal(1,1.8238704957353074))")
|
||||
testEvalToBe("pointMass(5)", "Ok(PointMass(5))")
|
||||
testEvalToBe("Number.floor(5.5)", "Ok(5)")
|
||||
testEvalToBe("Number.ceil(5.5)", "Ok(6)")
|
||||
testEvalToBe("floor(5.5)", "Ok(5)")
|
||||
testEvalToBe("ceil(5.5)", "Ok(6)")
|
||||
testEvalToBe("Number.abs(5.5)", "Ok(5.5)")
|
||||
testEvalToBe("abs(5.5)", "Ok(5.5)")
|
||||
testEvalToBe("Number.exp(10)", "Ok(22026.465794806718)")
|
||||
testEvalToBe("Number.log10(10)", "Ok(1)")
|
||||
testEvalToBe("Number.log2(10)", "Ok(3.321928094887362)")
|
||||
testEvalToBe("Number.sum([2,5,3])", "Ok(10)")
|
||||
testEvalToBe("sum([2,5,3])", "Ok(10)")
|
||||
testEvalToBe("Number.product([2,5,3])", "Ok(30)")
|
||||
testEvalToBe("Number.min([2,5,3])", "Ok(2)")
|
||||
testEvalToBe("Number.max([2,5,3])", "Ok(5)")
|
||||
testEvalToBe("Number.mean([0,5,10])", "Ok(5)")
|
||||
testEvalToBe("Number.geomean([1,5,18])", "Ok(4.481404746557164)")
|
||||
testEvalToBe("Number.stdev([0,5,10,15])", "Ok(5.5901699437494745)")
|
||||
testEvalToBe("Number.variance([0,5,10,15])", "Ok(31.25)")
|
||||
testEvalToBe("Number.sort([10,0,15,5])", "Ok([0,5,10,15])")
|
||||
testEvalToBe("Number.cumsum([1,5,3])", "Ok([1,6,9])")
|
||||
testEvalToBe("Number.cumprod([1,5,3])", "Ok([1,5,15])")
|
||||
testEvalToBe("Number.diff([1,5,3])", "Ok([4,-2])")
|
||||
testEvalToBe(
|
||||
"Dist.logScore({estimate: normal(5,2), answer: normal(5.2,1), prior: normal(5.5,3)})",
|
||||
"Ok(-0.33591375663884876)",
|
||||
)
|
||||
testEvalToBe(
|
||||
"Dist.logScore({estimate: normal(5,2), answer: normal(5.2,1)})",
|
||||
"Ok(0.32244107041564646)",
|
||||
)
|
||||
testEvalToBe("Dist.logScore({estimate: normal(5,2), answer: 4.5})", "Ok(1.6433360626394853)")
|
||||
testEvalToBe("Dist.klDivergence(normal(5,2), normal(5,1.5))", "Ok(0.06874342818671068)")
|
||||
testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)")
|
||||
testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)")
|
||||
testEvalToBe("SampleSet.fromFn({|| sample(normal(5,2))})", "Ok(Sample Set Distribution)")
|
||||
testEvalToBe(
|
||||
"addOne(t)=t+1; SampleSet.toList(SampleSet.map(SampleSet.fromList([1,2,3,4,5,6]), addOne))",
|
||||
"Ok([2,3,4,5,6,7])",
|
||||
)
|
||||
testEvalToBe(
|
||||
"SampleSet.toList(SampleSet.mapN([SampleSet.fromList([1,2,3,4,5,6]), SampleSet.fromList([6, 5, 4, 3, 2, 1])], {|x| x[0] > x[1] ? x[0] : x[1]}))",
|
||||
"Ok([6,5,4,4,5,6])",
|
||||
)
|
||||
})
|
||||
|
||||
describe("Fn auto-testing", () => {
|
||||
testAll("tests of validity", examples, r => {
|
||||
expectEvalToBeOk(r)
|
||||
})
|
||||
|
||||
testAll(
|
||||
"tests of type",
|
||||
E.A.to_list(
|
||||
FunctionRegistry_Core.Registry.allExamplesWithFns(registry)->E.A2.filter(((fn, _)) =>
|
||||
E.O.isSome(fn.output)
|
||||
),
|
||||
),
|
||||
((fn, example)) => {
|
||||
let responseType =
|
||||
example
|
||||
->Reducer.evaluate
|
||||
->E.R2.fmap(ReducerInterface_InternalExpressionValue.externalValueToValueType)
|
||||
let expectedOutputType = fn.output |> E.O.toExn("")
|
||||
expect(responseType)->toEqual(Ok(expectedOutputType))
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@quri/squiggle-lang",
|
||||
"version": "0.2.11",
|
||||
"version": "0.3.0",
|
||||
"homepage": "https://squiggle-language.com",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
@ -20,6 +20,7 @@
|
|||
"test:ts": "jest __tests__/TS/",
|
||||
"test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*",
|
||||
"test:watch": "jest --watchAll",
|
||||
"test:fnRegistry": "jest __tests__/SquiggleLibrary/SquiggleLibrary_FunctionRegistryLibrary_test.bs.js",
|
||||
"coverage:rescript": "rm -f *.coverage && yarn clean && BISECT_ENABLE=yes yarn build && yarn test:rescript && bisect-ppx-report html",
|
||||
"coverage:ts": "yarn clean && yarn build && nyc --reporter=lcov yarn test:ts",
|
||||
"coverage:rescript:ci": "yarn clean && BISECT_ENABLE=yes yarn build:rescript && yarn test:rescript && bisect-ppx-report send-to Codecov",
|
||||
|
@ -31,6 +32,7 @@
|
|||
"format:prettier": "prettier --write .",
|
||||
"format": "yarn format:rescript && yarn format:prettier",
|
||||
"prepack": "yarn build && yarn test && yarn bundle",
|
||||
"all:rescript": "yarn build:rescript && yarn test:rescript && yarn format:rescript",
|
||||
"all": "yarn build && yarn bundle && yarn test"
|
||||
},
|
||||
"keywords": [
|
||||
|
@ -42,7 +44,7 @@
|
|||
"@stdlib/stats": "^0.0.13",
|
||||
"jstat": "^1.9.5",
|
||||
"lodash": "^4.17.21",
|
||||
"mathjs": "^10.6.4",
|
||||
"mathjs": "^11.0.1",
|
||||
"pdfast": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -53,7 +55,7 @@
|
|||
"bisect_ppx": "^2.7.1",
|
||||
"chalk": "^5.0.1",
|
||||
"codecov": "^3.8.3",
|
||||
"fast-check": "^3.0.1",
|
||||
"fast-check": "^3.1.1",
|
||||
"gentype": "^4.5.0",
|
||||
"jest": "^27.5.1",
|
||||
"moduleserve": "^0.9.1",
|
||||
|
@ -67,7 +69,7 @@
|
|||
"ts-loader": "^9.3.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.7.4",
|
||||
"webpack": "^5.73.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
"source": "./src/js/index.ts",
|
||||
|
|
|
@ -120,6 +120,10 @@ function createTsExport(
|
|||
x: expressionValue,
|
||||
environment: environment
|
||||
): squiggleExpression {
|
||||
switch (x) {
|
||||
case "EvVoid":
|
||||
return tag("void", "");
|
||||
default: {
|
||||
switch (x.tag) {
|
||||
case "EvArray":
|
||||
// genType doesn't convert anything more than 2 layers down into {tag: x, value: x}
|
||||
|
@ -153,7 +157,8 @@ function createTsExport(
|
|||
return tag("number", x.value);
|
||||
case "EvRecord":
|
||||
// genType doesn't support records, so we have to do the raw conversion ourself
|
||||
let result: tagged<"record", { [key: string]: squiggleExpression }> = tag(
|
||||
let result: tagged<"record", { [key: string]: squiggleExpression }> =
|
||||
tag(
|
||||
"record",
|
||||
_.mapValues(x.value, (x: unknown) =>
|
||||
convertRawToTypescript(x as rescriptExport, environment)
|
||||
|
@ -173,8 +178,10 @@ function createTsExport(
|
|||
case "EvTypeIdentifier":
|
||||
return tag("typeIdentifier", x.value);
|
||||
case "EvType":
|
||||
let typeResult: tagged<"type", { [key: string]: squiggleExpression }> =
|
||||
tag(
|
||||
let typeResult: tagged<
|
||||
"type",
|
||||
{ [key: string]: squiggleExpression }
|
||||
> = tag(
|
||||
"type",
|
||||
_.mapValues(x.value, (x: unknown) =>
|
||||
convertRawToTypescript(x as rescriptExport, environment)
|
||||
|
@ -193,4 +200,6 @@ function createTsExport(
|
|||
);
|
||||
return moduleResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import { tagged, tag } from "./types";
|
|||
|
||||
// Raw rescript types.
|
||||
export type rescriptExport =
|
||||
| 0 // EvVoid
|
||||
| {
|
||||
TAG: 0; // EvArray
|
||||
_0: rescriptExport[];
|
||||
|
@ -131,7 +132,8 @@ export type squiggleExpression =
|
|||
| tagged<"record", { [key: string]: squiggleExpression }>
|
||||
| tagged<"type", { [key: string]: squiggleExpression }>
|
||||
| tagged<"typeIdentifier", string>
|
||||
| tagged<"module", { [key: string]: squiggleExpression }>;
|
||||
| tagged<"module", { [key: string]: squiggleExpression }>
|
||||
| tagged<"void", string>;
|
||||
|
||||
export { lambdaValue };
|
||||
|
||||
|
@ -139,6 +141,10 @@ export function convertRawToTypescript(
|
|||
result: rescriptExport,
|
||||
environment: environment
|
||||
): squiggleExpression {
|
||||
if (typeof result === "number") {
|
||||
// EvVoid
|
||||
return tag("void", "");
|
||||
}
|
||||
switch (result.TAG) {
|
||||
case 0: // EvArray
|
||||
return tag(
|
||||
|
|
|
@ -250,7 +250,7 @@ module T = Dist({
|
|||
|
||||
let downsample = (length, t): t =>
|
||||
t |> shapeMap(XYShape.XsConversion.proportionByProbabilityMass(length, integral(t).xyShape))
|
||||
let integralEndY = (t: t) => t.integralSumCache |> E.O.default(t |> integral |> lastY)
|
||||
let integralEndY = (t: t) => t.integralSumCache |> E.O.defaultFn(() => t |> integral |> lastY)
|
||||
let integralXtoY = (f, t: t) => t |> integral |> shapeFn(XYShape.XtoY.linear(f))
|
||||
let integralYtoX = (f, t: t) => t |> integral |> shapeFn(XYShape.YtoX.linear(f))
|
||||
let toContinuous = t => Some(t)
|
||||
|
|
|
@ -158,7 +158,8 @@ module T = Dist({
|
|||
Continuous.make(~interpolation=#Stepwise, integralShape)
|
||||
}
|
||||
|
||||
let integralEndY = (t: t) => t.integralSumCache |> E.O.default(t |> integral |> Continuous.lastY)
|
||||
let integralEndY = (t: t) =>
|
||||
t.integralSumCache |> E.O.defaultFn(() => t |> integral |> Continuous.lastY)
|
||||
let minX = shapeFn(XYShape.T.minX)
|
||||
let maxX = shapeFn(XYShape.T.maxX)
|
||||
let toDiscreteProbabilityMassFraction = _ => 1.0
|
||||
|
|
|
@ -13,9 +13,11 @@ let buildSimple = (
|
|||
~discrete: option<PointSetTypes.discreteShape>,
|
||||
): option<PointSetTypes.pointSetDist> => {
|
||||
let continuous =
|
||||
continuous |> E.O.default(Continuous.make(~integralSumCache=Some(0.0), {xs: [], ys: []}))
|
||||
continuous |> E.O.defaultFn(() =>
|
||||
Continuous.make(~integralSumCache=Some(0.0), {xs: [], ys: []})
|
||||
)
|
||||
let discrete =
|
||||
discrete |> E.O.default(Discrete.make(~integralSumCache=Some(0.0), {xs: [], ys: []}))
|
||||
discrete |> E.O.defaultFn(() => Discrete.make(~integralSumCache=Some(0.0), {xs: [], ys: []}))
|
||||
let cLength = continuous |> Continuous.getShape |> XYShape.T.xs |> E.A.length
|
||||
let dLength = discrete |> Discrete.getShape |> XYShape.T.xs |> E.A.length
|
||||
switch (cLength, dLength) {
|
||||
|
|
|
@ -117,6 +117,11 @@ let map3 = (
|
|||
): result<t, sampleSetError> =>
|
||||
E.A.zip3(get(t1), get(t2), get(t3))->E.A2.fmap(E.Tuple3.toFnCall(fn))->_fromSampleResultArray
|
||||
|
||||
let mapN = (~fn: array<float> => result<float, Operation.Error.t>, ~t1: array<t>): result<
|
||||
t,
|
||||
sampleSetError,
|
||||
> => E.A.transpose(E.A.fmap(get, t1))->E.A2.fmap(fn)->_fromSampleResultArray
|
||||
|
||||
let mean = t => T.get(t)->E.A.Floats.mean
|
||||
let geomean = t => T.get(t)->E.A.Floats.geomean
|
||||
let mode = t => T.get(t)->E.A.Floats.mode
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
type internalExpressionValue = ReducerInterface_InternalExpressionValue.t
|
||||
type internalExpressionValueType = ReducerInterface_InternalExpressionValue.internalExpressionValueType
|
||||
|
||||
/*
|
||||
Function Registry "Type". A type, without any other information.
|
||||
|
@ -42,18 +43,27 @@ and frValueDistOrNumber = FRValueNumber(float) | FRValueDist(DistributionTypes.g
|
|||
type fnDefinition = {
|
||||
name: string,
|
||||
inputs: array<frType>,
|
||||
run: (array<frValue>, GenericDist.env) => result<internalExpressionValue, string>,
|
||||
run: (
|
||||
array<internalExpressionValue>,
|
||||
array<frValue>,
|
||||
GenericDist.env,
|
||||
Reducer_Expression_T.reducerFn,
|
||||
) => result<internalExpressionValue, string>,
|
||||
}
|
||||
|
||||
type function = {
|
||||
name: string,
|
||||
definitions: array<fnDefinition>,
|
||||
examples: option<string>,
|
||||
requiresNamespace: bool,
|
||||
nameSpace: string,
|
||||
output: option<internalExpressionValueType>,
|
||||
examples: array<string>,
|
||||
description: option<string>,
|
||||
isExperimental: bool,
|
||||
}
|
||||
|
||||
type registry = array<function>
|
||||
type fnNameDict = Js.Dict.t<array<function>>
|
||||
type registry = {functions: array<function>, fnNameDict: fnNameDict}
|
||||
|
||||
module FRType = {
|
||||
type t = frType
|
||||
|
@ -175,6 +185,9 @@ module FRType = {
|
|||
This module, Matcher, is fairly lengthy. However, only two functions from it
|
||||
are meant to be used outside of it. These are findMatches and matchToDef in Matches.Registry.
|
||||
The rest of it is just called from those two functions.
|
||||
|
||||
Update: This really should be completely re-done sometime, and tested. It works, but it's pretty messy. I'm sure
|
||||
there are internal bugs, but the end functionality works, so I'm not too worried.
|
||||
*/
|
||||
module Matcher = {
|
||||
module MatchSimple = {
|
||||
|
@ -230,7 +243,15 @@ module Matcher = {
|
|||
type definitionId = int
|
||||
type match = Match.t<array<definitionId>, definitionId>
|
||||
|
||||
let match = (f: function, fnName: string, args: array<internalExpressionValue>): match => {
|
||||
let match = (
|
||||
f: function,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
): match => {
|
||||
switch nameSpace {
|
||||
| Some(ns) if ns !== f.nameSpace => Match.DifferentName
|
||||
| _ => {
|
||||
let matchedDefinition = () =>
|
||||
E.A.getIndexBy(f.definitions, r =>
|
||||
MatchSimple.isFullMatch(FnDefinition.match(r, fnName, args))
|
||||
|
@ -239,7 +260,9 @@ module Matcher = {
|
|||
let nameMatchIndexes =
|
||||
f.definitions
|
||||
->E.A2.fmapi((index, r) =>
|
||||
MatchSimple.isNameMatchOnly(FnDefinition.match(r, fnName, args)) ? Some(index) : None
|
||||
MatchSimple.isNameMatchOnly(FnDefinition.match(r, fnName, args))
|
||||
? Some(index)
|
||||
: None
|
||||
)
|
||||
->E.A.O.concatSomes
|
||||
switch nameMatchIndexes {
|
||||
|
@ -254,29 +277,48 @@ module Matcher = {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module RegistryMatch = {
|
||||
type match = {
|
||||
nameSpace: string,
|
||||
fnName: string,
|
||||
inputIndex: int,
|
||||
}
|
||||
let makeMatch = (fnName: string, inputIndex: int) => {fnName: fnName, inputIndex: inputIndex}
|
||||
let makeMatch = (nameSpace: string, fnName: string, inputIndex: int) => {
|
||||
nameSpace: nameSpace,
|
||||
fnName: fnName,
|
||||
inputIndex: inputIndex,
|
||||
}
|
||||
}
|
||||
|
||||
module Registry = {
|
||||
let _findExactMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
|
||||
let functionMatchPairs = r->E.A2.fmap(l => (l, Function.match(l, fnName, args)))
|
||||
let _findExactMatches = (
|
||||
r: registry,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
) => {
|
||||
let functionMatchPairs =
|
||||
r.functions->E.A2.fmap(l => (l, Function.match(l, nameSpace, fnName, args)))
|
||||
let fullMatch = functionMatchPairs->E.A.getBy(((_, match)) => Match.isFullMatch(match))
|
||||
fullMatch->E.O.bind(((fn, match)) =>
|
||||
switch match {
|
||||
| FullMatch(index) => Some(RegistryMatch.makeMatch(fn.name, index))
|
||||
| FullMatch(index) => Some(RegistryMatch.makeMatch(fn.nameSpace, fn.name, index))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let _findNameMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
|
||||
let functionMatchPairs = r->E.A2.fmap(l => (l, Function.match(l, fnName, args)))
|
||||
let _findNameMatches = (
|
||||
r: registry,
|
||||
nameSpace: option<string>,
|
||||
fnName: string,
|
||||
args: array<internalExpressionValue>,
|
||||
) => {
|
||||
let functionMatchPairs =
|
||||
r.functions->E.A2.fmap(l => (l, Function.match(l, nameSpace, fnName, args)))
|
||||
let getNameMatches =
|
||||
functionMatchPairs
|
||||
->E.A2.fmap(((fn, match)) => Match.isNameMatchOnly(match) ? Some((fn, match)) : None)
|
||||
|
@ -286,7 +328,7 @@ module Matcher = {
|
|||
->E.A2.fmap(((fn, match)) =>
|
||||
switch match {
|
||||
| SameNameDifferentArguments(indexes) =>
|
||||
indexes->E.A2.fmap(index => RegistryMatch.makeMatch(fn.name, index))
|
||||
indexes->E.A2.fmap(index => RegistryMatch.makeMatch(fn.nameSpace, fn.name, index))
|
||||
| _ => []
|
||||
}
|
||||
)
|
||||
|
@ -295,21 +337,28 @@ module Matcher = {
|
|||
}
|
||||
|
||||
let findMatches = (r: registry, fnName: string, args: array<internalExpressionValue>) => {
|
||||
switch _findExactMatches(r, fnName, args) {
|
||||
let fnNameInParts = Js.String.split(".", fnName)
|
||||
let fnToSearch = E.A.get(fnNameInParts, 1) |> E.O.default(fnNameInParts[0])
|
||||
let nameSpace = E.A.length(fnNameInParts) > 1 ? Some(fnNameInParts[0]) : None
|
||||
|
||||
switch _findExactMatches(r, nameSpace, fnToSearch, args) {
|
||||
| Some(r) => Match.FullMatch(r)
|
||||
| None =>
|
||||
switch _findNameMatches(r, fnName, args) {
|
||||
switch _findNameMatches(r, nameSpace, fnToSearch, args) {
|
||||
| Some(r) => Match.SameNameDifferentArguments(r)
|
||||
| None => Match.DifferentName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let matchToDef = (registry: registry, {fnName, inputIndex}: RegistryMatch.match): option<
|
||||
fnDefinition,
|
||||
> =>
|
||||
registry
|
||||
->E.A.getBy(fn => fn.name === fnName)
|
||||
let matchToDef = (
|
||||
registry: registry,
|
||||
{nameSpace, fnName, inputIndex}: RegistryMatch.match,
|
||||
): option<fnDefinition> =>
|
||||
registry.functions
|
||||
->E.A.getBy(fn => {
|
||||
nameSpace === fn.nameSpace && fnName === fn.name
|
||||
})
|
||||
->E.O.bind(fn => E.A.get(fn.definitions, inputIndex))
|
||||
}
|
||||
}
|
||||
|
@ -322,15 +371,28 @@ module FnDefinition = {
|
|||
t.name ++ `(${inputs})`
|
||||
}
|
||||
|
||||
let run = (t: t, args: array<internalExpressionValue>, env: GenericDist.env) => {
|
||||
let isMatch = (t: t, args: array<internalExpressionValue>) => {
|
||||
let argValues = FRType.matchWithExpressionValueArray(t.inputs, args)
|
||||
switch argValues {
|
||||
| Some(values) => t.run(values, env)
|
||||
| Some(_) => true
|
||||
| None => false
|
||||
}
|
||||
}
|
||||
|
||||
let run = (
|
||||
t: t,
|
||||
args: array<internalExpressionValue>,
|
||||
env: GenericDist.env,
|
||||
reducer: Reducer_Expression_T.reducerFn,
|
||||
) => {
|
||||
let argValues = FRType.matchWithExpressionValueArray(t.inputs, args)
|
||||
switch argValues {
|
||||
| Some(values) => t.run(args, values, env, reducer)
|
||||
| None => Error("Incorrect Types")
|
||||
}
|
||||
}
|
||||
|
||||
let make = (~name, ~inputs, ~run): t => {
|
||||
let make = (~name, ~inputs, ~run, ()): t => {
|
||||
name: name,
|
||||
inputs: inputs,
|
||||
run: run,
|
||||
|
@ -343,16 +405,29 @@ module Function = {
|
|||
type functionJson = {
|
||||
name: string,
|
||||
definitions: array<string>,
|
||||
examples: option<string>,
|
||||
examples: array<string>,
|
||||
description: option<string>,
|
||||
isExperimental: bool,
|
||||
}
|
||||
|
||||
let make = (~name, ~definitions, ~examples=?, ~description=?, ~isExperimental=false, ()): t => {
|
||||
let make = (
|
||||
~name,
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~definitions,
|
||||
~examples=?,
|
||||
~output=?,
|
||||
~description=?,
|
||||
~isExperimental=false,
|
||||
(),
|
||||
): t => {
|
||||
name: name,
|
||||
nameSpace: nameSpace,
|
||||
definitions: definitions,
|
||||
examples: examples,
|
||||
output: output,
|
||||
examples: examples |> E.O.default([]),
|
||||
isExperimental: isExperimental,
|
||||
requiresNamespace: requiresNamespace,
|
||||
description: description,
|
||||
}
|
||||
|
||||
|
@ -365,22 +440,65 @@ module Function = {
|
|||
}
|
||||
}
|
||||
|
||||
module NameSpace = {
|
||||
type t = {name: string, functions: array<function>}
|
||||
let definitions = (t: t) => t.functions->E.A2.fmap(f => f.definitions)->E.A.concatMany
|
||||
let uniqueFnNames = (t: t) => definitions(t)->E.A2.fmap(r => r.name)->E.A.uniq
|
||||
let nameToDefinitions = (t: t, name: string) => definitions(t)->E.A2.filter(r => r.name == name)
|
||||
}
|
||||
|
||||
module Registry = {
|
||||
let toJson = (r: registry) => r->E.A2.fmap(Function.toJson)
|
||||
let toJson = (r: registry) => r.functions->E.A2.fmap(Function.toJson)
|
||||
let allExamples = (r: registry) => r.functions->E.A2.fmap(r => r.examples)->E.A.concatMany
|
||||
let allExamplesWithFns = (r: registry) =>
|
||||
r.functions->E.A2.fmap(fn => fn.examples->E.A2.fmap(example => (fn, example)))->E.A.concatMany
|
||||
|
||||
let _buildFnNameDict = (r: array<function>): fnNameDict => {
|
||||
let allDefinitionsWithFns =
|
||||
r
|
||||
->E.A2.fmap(fn => fn.definitions->E.A2.fmap(definitions => (fn, definitions)))
|
||||
->E.A.concatMany
|
||||
let functionsWithFnNames =
|
||||
allDefinitionsWithFns
|
||||
->E.A2.fmap(((fn, def)) => {
|
||||
let nameWithNamespace = `${fn.nameSpace}.${def.name}`
|
||||
let nameWithoutNamespace = def.name
|
||||
fn.requiresNamespace
|
||||
? [(nameWithNamespace, fn)]
|
||||
: [(nameWithNamespace, fn), (nameWithoutNamespace, fn)]
|
||||
})
|
||||
->E.A.concatMany
|
||||
let uniqueNames = functionsWithFnNames->E.A2.fmap(((name, _)) => name)->E.A.uniq
|
||||
let cacheAsArray: array<(string, array<function>)> = uniqueNames->E.A2.fmap(uniqueName => {
|
||||
let relevantItems =
|
||||
E.A2.filter(functionsWithFnNames, ((defName, _)) => defName == uniqueName)->E.A2.fmap(
|
||||
E.Tuple2.second,
|
||||
)
|
||||
(uniqueName, relevantItems)
|
||||
})
|
||||
cacheAsArray->Js.Dict.fromArray
|
||||
}
|
||||
|
||||
let make = (fns: array<function>): registry => {
|
||||
let dict = _buildFnNameDict(fns)
|
||||
{functions: fns, fnNameDict: dict}
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
called. However, for now, we could just call the registry last.
|
||||
*/
|
||||
let matchAndRun = (
|
||||
let _matchAndRun = (
|
||||
~registry: registry,
|
||||
~fnName: string,
|
||||
~args: array<internalExpressionValue>,
|
||||
~env: GenericDist.env,
|
||||
~reducer: Reducer_Expression_T.reducerFn,
|
||||
) => {
|
||||
let relevantFunctions = Js.Dict.get(registry.fnNameDict, fnName) |> E.O.default([])
|
||||
let modified = {functions: relevantFunctions, fnNameDict: registry.fnNameDict}
|
||||
let matchToDef = m => Matcher.Registry.matchToDef(registry, m)
|
||||
//Js.log(toSimple(registry))
|
||||
let showNameMatchDefinitions = matches => {
|
||||
let defs =
|
||||
matches
|
||||
|
@ -391,10 +509,23 @@ module Registry = {
|
|||
->E.A2.joinWith("; ")
|
||||
`There are function matches for ${fnName}(), but with different arguments: ${defs}`
|
||||
}
|
||||
switch Matcher.Registry.findMatches(registry, fnName, args) {
|
||||
| Matcher.Match.FullMatch(match) => match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, env))
|
||||
|
||||
switch Matcher.Registry.findMatches(modified, fnName, args) {
|
||||
| Matcher.Match.FullMatch(match) =>
|
||||
match->matchToDef->E.O2.fmap(FnDefinition.run(_, args, env, reducer))
|
||||
| SameNameDifferentArguments(m) => Some(Error(showNameMatchDefinitions(m)))
|
||||
| _ => None
|
||||
}
|
||||
}
|
||||
|
||||
let dispatch = (
|
||||
registry,
|
||||
(fnName, args): ReducerInterface_InternalExpressionValue.functionCall,
|
||||
env,
|
||||
reducer: Reducer_Expression_T.reducerFn,
|
||||
) => {
|
||||
_matchAndRun(~registry, ~fnName, ~args, ~env, ~reducer)->E.O2.fmap(
|
||||
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ let impossibleError = "Wrong inputs / Logically impossible"
|
|||
|
||||
module Wrappers = {
|
||||
let symbolic = r => DistributionTypes.Symbolic(r)
|
||||
let pointSet = r => DistributionTypes.PointSet(r)
|
||||
let sampleSet = r => DistributionTypes.SampleSet(r)
|
||||
let evDistribution = r => ReducerInterface_InternalExpressionValue.IEvDistribution(r)
|
||||
let evNumber = r => ReducerInterface_InternalExpressionValue.IEvNumber(r)
|
||||
let evArray = r => ReducerInterface_InternalExpressionValue.IEvArray(r)
|
||||
|
@ -214,71 +216,27 @@ module Process = {
|
|||
}
|
||||
}
|
||||
|
||||
module TwoArgDist = {
|
||||
let process = (~fn, ~env, r) =>
|
||||
r
|
||||
->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env))
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
|
||||
let make = (name, fn) => {
|
||||
FnDefinition.make(~name, ~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber], ~run=(inputs, env) =>
|
||||
inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env)
|
||||
)
|
||||
}
|
||||
|
||||
let makeRecordP5P95 = (name, fn) => {
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
|
||||
~run=(inputs, env) => inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
|
||||
)
|
||||
}
|
||||
|
||||
let makeRecordMeanStdev = (name, fn) => {
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
|
||||
~run=(inputs, env) => inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module OneArgDist = {
|
||||
let process = (~fn, ~env, r) =>
|
||||
r
|
||||
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
|
||||
let make = (name, fn) =>
|
||||
FnDefinition.make(~name, ~inputs=[FRTypeDistOrNumber], ~run=(inputs, env) =>
|
||||
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env)
|
||||
)
|
||||
}
|
||||
|
||||
module ArrayNumberDist = {
|
||||
let make = (name, fn) => {
|
||||
FnDefinition.make(~name, ~inputs=[FRTypeArray(FRTypeNumber)], ~run=(inputs, _) =>
|
||||
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)
|
||||
->E.R.bind(fn),
|
||||
(),
|
||||
)
|
||||
}
|
||||
let make2 = (name, fn) => {
|
||||
FnDefinition.make(~name, ~inputs=[FRTypeArray(FRTypeAny)], ~run=(inputs, _) =>
|
||||
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)
|
||||
->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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,586 +1,13 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
let fnList = Belt.Array.concatMany([
|
||||
FR_Dict.library,
|
||||
FR_Dist.library,
|
||||
FR_Fn.library,
|
||||
FR_Sampleset.library,
|
||||
FR_List.library,
|
||||
FR_Number.library,
|
||||
FR_Pointset.library,
|
||||
FR_Scoring.library,
|
||||
])
|
||||
|
||||
let twoArgs = E.Tuple2.toFnCall
|
||||
|
||||
module Declaration = {
|
||||
let frType = FRTypeRecord([
|
||||
("fn", FRTypeLambda),
|
||||
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
|
||||
])
|
||||
|
||||
let fromExpressionValue = (e: frValue): result<internalExpressionValue, 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_InternalExpressionValue.IEvDeclaration(
|
||||
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_InternalExpressionValue.IEvDistribution(
|
||||
PointSet(makeDist(r)),
|
||||
))
|
||||
expressionValue
|
||||
}
|
||||
|
||||
let registryStart = [
|
||||
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(
|
||||
~name="Normal",
|
||||
~examples=`normal(5,1)
|
||||
normal({p5: 4, p95: 10})
|
||||
normal({mean: 5, stdev: 2})`,
|
||||
~definitions=[
|
||||
TwoArgDist.make("normal", twoArgs(SymbolicDist.Normal.make)),
|
||||
TwoArgDist.makeRecordP5P95("normal", r =>
|
||||
twoArgs(SymbolicDist.Normal.from90PercentCI, r)->Ok
|
||||
),
|
||||
TwoArgDist.makeRecordMeanStdev("normal", twoArgs(SymbolicDist.Normal.make)),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Lognormal",
|
||||
~examples=`lognormal(0.5, 0.8)
|
||||
lognormal({p5: 4, p95: 10})
|
||||
lognormal({mean: 5, stdev: 2})`,
|
||||
~definitions=[
|
||||
TwoArgDist.make("lognormal", twoArgs(SymbolicDist.Lognormal.make)),
|
||||
TwoArgDist.makeRecordP5P95("lognormal", r =>
|
||||
twoArgs(SymbolicDist.Lognormal.from90PercentCI, r)->Ok
|
||||
),
|
||||
TwoArgDist.makeRecordMeanStdev("lognormal", twoArgs(SymbolicDist.Lognormal.fromMeanAndStdev)),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Uniform",
|
||||
~examples=`uniform(10, 12)`,
|
||||
~definitions=[TwoArgDist.make("uniform", twoArgs(SymbolicDist.Uniform.make))],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Beta",
|
||||
~examples=`beta(20, 25)
|
||||
beta({mean: 0.39, stdev: 0.1})`,
|
||||
~definitions=[
|
||||
TwoArgDist.make("beta", twoArgs(SymbolicDist.Beta.make)),
|
||||
TwoArgDist.makeRecordMeanStdev("beta", twoArgs(SymbolicDist.Beta.fromMeanAndStdev)),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Cauchy",
|
||||
~examples=`cauchy(5, 1)`,
|
||||
~definitions=[TwoArgDist.make("cauchy", twoArgs(SymbolicDist.Cauchy.make))],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Gamma",
|
||||
~examples=`gamma(5, 1)`,
|
||||
~definitions=[TwoArgDist.make("gamma", twoArgs(SymbolicDist.Gamma.make))],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Logistic",
|
||||
~examples=`logistic(5, 1)`,
|
||||
~definitions=[TwoArgDist.make("logistic", twoArgs(SymbolicDist.Logistic.make))],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Metalog",
|
||||
~examples=`metalog([1, 2, 3])`,
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("metalog", x =>
|
||||
SymbolicDist.Metalog.make(x)->E.R2.fmap(x => Wrappers.symbolic(x)->Wrappers.evDistribution)
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="To (Distribution)",
|
||||
~examples=`5 to 10
|
||||
to(5,10)
|
||||
-5 to 5`,
|
||||
~definitions=[
|
||||
TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make)),
|
||||
TwoArgDist.make(
|
||||
"credibleIntervalToDistribution",
|
||||
twoArgs(SymbolicDist.From90thPercentile.make),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Exponential",
|
||||
~examples=`exponential(2)`,
|
||||
~definitions=[OneArgDist.make("exponential", SymbolicDist.Exponential.make)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="Bernoulli",
|
||||
~examples=`bernoulli(0.5)`,
|
||||
~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->Js.Dict.entries->Belt.Map.String.fromArray->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(r => r->Js.Dict.entries->Belt.Map.String.fromArray)
|
||||
->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(Belt.Map.String.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
|
||||
)
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
||||
|
||||
let runScoring = (estimate, answer, prior, env) => {
|
||||
GenericDist.Score.logScore(~estimate, ~answer, ~prior, ~env)
|
||||
->E.R2.fmap(FunctionRegistry_Helpers.Wrappers.evNumber)
|
||||
->E.R2.errMap(DistributionTypes.Error.toString)
|
||||
}
|
||||
|
||||
let scoreFunctions = [
|
||||
Function.make(
|
||||
~name="Score",
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="logScore",
|
||||
~inputs=[
|
||||
FRTypeRecord([
|
||||
("estimate", FRTypeDist),
|
||||
("answer", FRTypeDistOrNumber),
|
||||
("prior", FRTypeDist),
|
||||
]),
|
||||
],
|
||||
~run=(inputs, env) => {
|
||||
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(inputs) {
|
||||
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d)), FRValueDist(prior)]) =>
|
||||
runScoring(estimate, Score_Dist(d), Some(prior), env)
|
||||
| Ok([
|
||||
FRValueDist(estimate),
|
||||
FRValueDistOrNumber(FRValueNumber(d)),
|
||||
FRValueDist(prior),
|
||||
]) =>
|
||||
runScoring(estimate, Score_Scalar(d), Some(prior), env)
|
||||
| Error(e) => Error(e)
|
||||
| _ => Error(FunctionRegistry_Helpers.impossibleError)
|
||||
}
|
||||
},
|
||||
),
|
||||
FnDefinition.make(
|
||||
~name="logScore",
|
||||
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
|
||||
~run=(inputs, env) => {
|
||||
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(inputs) {
|
||||
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d))]) =>
|
||||
runScoring(estimate, Score_Dist(d), None, env)
|
||||
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueNumber(d))]) =>
|
||||
runScoring(estimate, Score_Scalar(d), None, env)
|
||||
| Error(e) => Error(e)
|
||||
| _ => Error(FunctionRegistry_Helpers.impossibleError)
|
||||
}
|
||||
},
|
||||
),
|
||||
FnDefinition.make(~name="klDivergence", ~inputs=[FRTypeDist, FRTypeDist], ~run=(
|
||||
inputs,
|
||||
env,
|
||||
) => {
|
||||
switch inputs {
|
||||
| [FRValueDist(estimate), FRValueDist(d)] => runScoring(estimate, Score_Dist(d), None, env)
|
||||
| _ => Error(FunctionRegistry_Helpers.impossibleError)
|
||||
}
|
||||
}),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
||||
|
||||
let registry = E.A.append(registryStart, scoreFunctions)
|
||||
let registry = FunctionRegistry_Core.Registry.make(fnList)
|
||||
let dispatch = FunctionRegistry_Core.Registry.dispatch(registry)
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let nameSpace = "Dict"
|
||||
|
||||
module Internals = {
|
||||
type t = ReducerInterface_InternalExpressionValue.map
|
||||
|
||||
let keys = (a: t): internalExpressionValue => IEvArray(
|
||||
Belt.Map.String.keysToArray(a)->E.A2.fmap(Wrappers.evString),
|
||||
)
|
||||
|
||||
let values = (a: t): internalExpressionValue => IEvArray(Belt.Map.String.valuesToArray(a))
|
||||
|
||||
let toList = (a: t): internalExpressionValue =>
|
||||
Belt.Map.String.toArray(a)
|
||||
->E.A2.fmap(((key, value)) => Wrappers.evArray([IEvString(key), value]))
|
||||
->Wrappers.evArray
|
||||
|
||||
let fromList = (items: array<internalExpressionValue>): result<internalExpressionValue, string> =>
|
||||
items
|
||||
->E.A2.fmap(item => {
|
||||
switch (item: internalExpressionValue) {
|
||||
| IEvArray([IEvString(string), value]) => (string, value)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
})
|
||||
->E.A.R.firstErrorOrOpen
|
||||
->E.R2.fmap(Belt.Map.String.fromArray)
|
||||
->E.R2.fmap(Wrappers.evRecord)
|
||||
|
||||
let merge = (a: t, b: t): internalExpressionValue => IEvRecord(
|
||||
Belt.Map.String.merge(a, b, (_, _, c) => c),
|
||||
)
|
||||
|
||||
//Belt.Map.String has a function for mergeMany, but I couldn't understand how to use it yet.
|
||||
let mergeMany = (a: array<t>): internalExpressionValue => {
|
||||
let mergedValues =
|
||||
a->E.A2.fmap(Belt.Map.String.toArray)->Belt.Array.concatMany->Belt.Map.String.fromArray
|
||||
IEvRecord(mergedValues)
|
||||
}
|
||||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="merge",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtRecord,
|
||||
~examples=[`Dict.merge({a: 1, b: 2}, {c: 3, d: 4})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="merge",
|
||||
~inputs=[FRTypeDict(FRTypeAny), FRTypeDict(FRTypeAny)],
|
||||
~run=(inputs, _, _, _) => {
|
||||
switch inputs {
|
||||
| [IEvRecord(d1), IEvRecord(d2)] => Internals.merge(d1, d2)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
//TODO: Change to use new mergeMany() function.
|
||||
Function.make(
|
||||
~name="mergeMany",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtRecord,
|
||||
~examples=[`Dict.mergeMany([{a: 1, b: 2}, {c: 3, d: 4}])`],
|
||||
~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(r => r->Js.Dict.entries->Belt.Map.String.fromArray)
|
||||
->E.R2.fmap(Wrappers.evRecord),
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="keys",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtArray,
|
||||
~examples=[`Dict.keys({a: 1, b: 2})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="keys",
|
||||
~inputs=[FRTypeDict(FRTypeAny)],
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvRecord(d1)] => Internals.keys(d1)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="values",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtArray,
|
||||
~examples=[`Dict.values({a: 1, b: 2})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="values",
|
||||
~inputs=[FRTypeDict(FRTypeAny)],
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvRecord(d1)] => Internals.values(d1)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="toList",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtArray,
|
||||
~examples=[`Dict.toList({a: 1, b: 2})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="toList",
|
||||
~inputs=[FRTypeDict(FRTypeAny)],
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvRecord(dict)] => dict->Internals.toList->Ok
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="fromList",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtRecord,
|
||||
~examples=[`Dict.fromList([["a", 1], ["b", 2]])`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromList",
|
||||
~inputs=[FRTypeArray(FRTypeArray(FRTypeAny))],
|
||||
~run=(inputs, _, _, _) => {
|
||||
switch inputs {
|
||||
| [IEvArray(items)] => Internals.fromList(items)
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,165 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
let twoArgs = E.Tuple2.toFnCall
|
||||
|
||||
module DistributionCreation = {
|
||||
let nameSpace = "Dist"
|
||||
let output = ReducerInterface_InternalExpressionValue.EvtDistribution
|
||||
let requiresNamespace = false
|
||||
|
||||
let fnMake = (~name, ~examples, ~definitions) => {
|
||||
Function.make(~name, ~nameSpace, ~output, ~examples, ~definitions, ~requiresNamespace, ())
|
||||
}
|
||||
|
||||
module TwoArgDist = {
|
||||
let process = (~fn, ~env, r) =>
|
||||
r
|
||||
->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env))
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
|
||||
let make = (name, fn) => {
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
|
||||
~run=(_, inputs, env, _) =>
|
||||
inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env),
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
||||
let makeRecordP5P95 = (name, fn) => {
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
|
||||
~run=(_, inputs, env, _) =>
|
||||
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
||||
let makeRecordMeanStdev = (name, fn) => {
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
|
||||
~run=(_, inputs, env, _) =>
|
||||
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber->process(~fn, ~env),
|
||||
(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module OneArgDist = {
|
||||
let process = (~fn, ~env, r) =>
|
||||
r
|
||||
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
|
||||
let make = (name, fn) =>
|
||||
FnDefinition.make(
|
||||
~name,
|
||||
~inputs=[FRTypeDistOrNumber],
|
||||
~run=(_, inputs, env, _) =>
|
||||
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env),
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
||||
let library = [
|
||||
fnMake(
|
||||
~name="normal",
|
||||
~examples=["normal(5,1)", "normal({p5: 4, p95: 10})", "normal({mean: 5, stdev: 2})"],
|
||||
~definitions=[
|
||||
TwoArgDist.make("normal", twoArgs(SymbolicDist.Normal.make)),
|
||||
TwoArgDist.makeRecordP5P95("normal", r =>
|
||||
twoArgs(SymbolicDist.Normal.from90PercentCI, r)->Ok
|
||||
),
|
||||
TwoArgDist.makeRecordMeanStdev("normal", twoArgs(SymbolicDist.Normal.make)),
|
||||
],
|
||||
),
|
||||
fnMake(
|
||||
~name="lognormal",
|
||||
~examples=[
|
||||
"lognormal(0.5, 0.8)",
|
||||
"lognormal({p5: 4, p95: 10})",
|
||||
"lognormal({mean: 5, stdev: 2})",
|
||||
],
|
||||
~definitions=[
|
||||
TwoArgDist.make("lognormal", twoArgs(SymbolicDist.Lognormal.make)),
|
||||
TwoArgDist.makeRecordP5P95("lognormal", r =>
|
||||
twoArgs(SymbolicDist.Lognormal.from90PercentCI, r)->Ok
|
||||
),
|
||||
TwoArgDist.makeRecordMeanStdev(
|
||||
"lognormal",
|
||||
twoArgs(SymbolicDist.Lognormal.fromMeanAndStdev),
|
||||
),
|
||||
],
|
||||
),
|
||||
fnMake(
|
||||
~name="uniform",
|
||||
~examples=[`uniform(10, 12)`],
|
||||
~definitions=[TwoArgDist.make("uniform", twoArgs(SymbolicDist.Uniform.make))],
|
||||
),
|
||||
fnMake(
|
||||
~name="beta",
|
||||
~examples=[`beta(20, 25)`, `beta({mean: 0.39, stdev: 0.1})`],
|
||||
~definitions=[
|
||||
TwoArgDist.make("beta", twoArgs(SymbolicDist.Beta.make)),
|
||||
TwoArgDist.makeRecordMeanStdev("beta", twoArgs(SymbolicDist.Beta.fromMeanAndStdev)),
|
||||
],
|
||||
),
|
||||
fnMake(
|
||||
~name="cauchy",
|
||||
~examples=[`cauchy(5, 1)`],
|
||||
~definitions=[TwoArgDist.make("cauchy", twoArgs(SymbolicDist.Cauchy.make))],
|
||||
),
|
||||
fnMake(
|
||||
~name="gamma",
|
||||
~examples=[`gamma(5, 1)`],
|
||||
~definitions=[TwoArgDist.make("gamma", twoArgs(SymbolicDist.Gamma.make))],
|
||||
),
|
||||
fnMake(
|
||||
~name="logistic",
|
||||
~examples=[`logistic(5, 1)`],
|
||||
~definitions=[TwoArgDist.make("logistic", twoArgs(SymbolicDist.Logistic.make))],
|
||||
),
|
||||
fnMake(
|
||||
~name="metalog",
|
||||
~examples=[`metalog([1, 2, 3])`],
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("metalog", x =>
|
||||
SymbolicDist.Metalog.make(x)->E.R2.fmap(x =>
|
||||
Wrappers.symbolic(x)->Wrappers.evDistribution
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
fnMake(
|
||||
~name="to (distribution)",
|
||||
~examples=[`5 to 10`, `to(5,10)`, `-5 to 5`],
|
||||
~definitions=[
|
||||
TwoArgDist.make("to", twoArgs(SymbolicDist.From90thPercentile.make)),
|
||||
TwoArgDist.make(
|
||||
"credibleIntervalToDistribution",
|
||||
twoArgs(SymbolicDist.From90thPercentile.make),
|
||||
),
|
||||
],
|
||||
),
|
||||
fnMake(
|
||||
~name="exponential",
|
||||
~examples=[`exponential(2)`],
|
||||
~definitions=[OneArgDist.make("exponential", SymbolicDist.Exponential.make)],
|
||||
),
|
||||
fnMake(
|
||||
~name="bernoulli",
|
||||
~examples=[`bernoulli(0.5)`],
|
||||
~definitions=[OneArgDist.make("bernoulli", SymbolicDist.Bernoulli.make)],
|
||||
),
|
||||
fnMake(
|
||||
~name="pointMass",
|
||||
~examples=[`pointMass(0.5)`],
|
||||
~definitions=[OneArgDist.make("pointMass", SymbolicDist.Float.makeSafe)],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
let library = DistributionCreation.library
|
|
@ -0,0 +1,62 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
module Declaration = {
|
||||
let frType = FRTypeRecord([
|
||||
("fn", FRTypeLambda),
|
||||
("inputs", FRTypeArray(FRTypeRecord([("min", FRTypeNumber), ("max", FRTypeNumber)]))),
|
||||
])
|
||||
|
||||
let fromExpressionValue = (e: frValue): result<internalExpressionValue, 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_InternalExpressionValue.IEvDeclaration(
|
||||
Declaration.make(lambda, args),
|
||||
))
|
||||
}
|
||||
| Error(r) => Error(r)
|
||||
| Ok(_) => Error(FunctionRegistry_Helpers.impossibleError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nameSpace = "Function"
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="declare",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtDeclaration,
|
||||
~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=[
|
||||
`Function.declare({
|
||||
fn: {|a,b| a },
|
||||
inputs: [
|
||||
{min: 0, max: 100},
|
||||
{min: 30, max: 50}
|
||||
]
|
||||
})`,
|
||||
],
|
||||
~isExperimental=true,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="declare",
|
||||
~inputs=[Declaration.frType],
|
||||
~run=(_, inputs, _, _) => {
|
||||
inputs->getOrError(0)->E.R.bind(Declaration.fromExpressionValue)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,265 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let nameSpace = "List"
|
||||
let requiresNamespace = true
|
||||
|
||||
module Internals = {
|
||||
let makeFromNumber = (
|
||||
n: float,
|
||||
value: internalExpressionValue,
|
||||
): internalExpressionValue => IEvArray(Belt.Array.make(E.Float.toInt(n), value))
|
||||
|
||||
let upTo = (low: float, high: float): internalExpressionValue => IEvArray(
|
||||
E.A.Floats.range(low, high, (high -. low +. 1.0)->E.Float.toInt)->E.A2.fmap(Wrappers.evNumber),
|
||||
)
|
||||
|
||||
let first = (v: array<internalExpressionValue>): result<internalExpressionValue, string> =>
|
||||
v->E.A.first |> E.O.toResult("No first element")
|
||||
|
||||
let last = (v: array<internalExpressionValue>): result<internalExpressionValue, string> =>
|
||||
v->E.A.last |> E.O.toResult("No last element")
|
||||
|
||||
let reverse = (array: array<internalExpressionValue>): internalExpressionValue => IEvArray(
|
||||
Belt.Array.reverse(array),
|
||||
)
|
||||
|
||||
let map = (array: array<internalExpressionValue>, environment, eLambdaValue, reducer): result<
|
||||
ReducerInterface_InternalExpressionValue.t,
|
||||
Reducer_ErrorValue.errorValue,
|
||||
> => {
|
||||
let rMappedList = array->E.A.reduceReverse(Ok(list{}), (rAcc, elem) =>
|
||||
rAcc->E.R.bind(_, acc => {
|
||||
let rNewElem = Reducer_Expression_Lambda.doLambdaCall(
|
||||
eLambdaValue,
|
||||
list{elem},
|
||||
environment,
|
||||
reducer,
|
||||
)
|
||||
rNewElem->E.R2.fmap(newElem => list{newElem, ...acc})
|
||||
})
|
||||
)
|
||||
rMappedList->E.R2.fmap(mappedList => mappedList->Belt.List.toArray->Wrappers.evArray)
|
||||
}
|
||||
|
||||
let reduce = (aValueArray, initialValue, aLambdaValue, environment, reducer) => {
|
||||
aValueArray->E.A.reduce(Ok(initialValue), (rAcc, elem) =>
|
||||
rAcc->E.R.bind(_, acc =>
|
||||
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, environment, reducer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let reduceReverse = (aValueArray, initialValue, aLambdaValue, environment, reducer) => {
|
||||
aValueArray->Belt.Array.reduceReverse(Ok(initialValue), (rAcc, elem) =>
|
||||
rAcc->Belt.Result.flatMap(acc =>
|
||||
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, environment, reducer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let filter = (aValueArray, aLambdaValue, environment, reducer) => {
|
||||
let rMappedList = aValueArray->Belt.Array.reduceReverse(Ok(list{}), (rAcc, elem) =>
|
||||
rAcc->E.R.bind(_, acc => {
|
||||
let rNewElem = Reducer_Expression_Lambda.doLambdaCall(
|
||||
aLambdaValue,
|
||||
list{elem},
|
||||
environment,
|
||||
reducer,
|
||||
)
|
||||
rNewElem->E.R2.fmap(newElem => {
|
||||
switch newElem {
|
||||
| IEvBool(true) => list{elem, ...acc}
|
||||
| _ => acc
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
rMappedList->E.R2.fmap(mappedList => mappedList->Belt.List.toArray->Wrappers.evArray)
|
||||
}
|
||||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="make",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtArray,
|
||||
~examples=[`List.make(2, "testValue")`],
|
||||
~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="make",
|
||||
~inputs=[FRTypeNumber, FRTypeAny],
|
||||
~run=(inputs, _, _, _) => {
|
||||
switch inputs {
|
||||
| [IEvNumber(number), value] => Internals.makeFromNumber(number, value)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="upTo",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~output=EvtArray,
|
||||
~examples=[`List.upTo(1,4)`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="upTo",
|
||||
~inputs=[FRTypeNumber, FRTypeNumber],
|
||||
~run=(_, inputs, _, _) =>
|
||||
inputs
|
||||
->Prepare.ToValueTuple.twoNumbers
|
||||
->E.R2.fmap(((low, high)) => Internals.upTo(low, high)),
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="first",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`List.first([1,4,5])`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="first",
|
||||
~inputs=[FRTypeArray(FRTypeAny)],
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array)] => Internals.first(array)
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="last",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`List.last([1,4,5])`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="last",
|
||||
~inputs=[FRTypeArray(FRTypeAny)],
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array)] => Internals.last(array)
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="reverse",
|
||||
~nameSpace,
|
||||
~output=EvtArray,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.reverse([1,4,5])`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="reverse",
|
||||
~inputs=[FRTypeArray(FRTypeAny)],
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array)] => Internals.reverse(array)->Ok
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="map",
|
||||
~nameSpace,
|
||||
~output=EvtArray,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.map([1,4,5], {|x| x+1})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="map",
|
||||
~inputs=[FRTypeArray(FRTypeAny), FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array), IEvLambda(lambda)] =>
|
||||
Internals.map(array, env, lambda, reducer)->E.R2.errMap(_ => "Error!")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="reduce",
|
||||
~nameSpace,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.reduce([1,4,5], 2, {|acc, el| acc+el})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="reduce",
|
||||
~inputs=[FRTypeArray(FRTypeAny), FRTypeAny, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array), initialValue, IEvLambda(lambda)] =>
|
||||
Internals.reduce(array, initialValue, lambda, env, reducer)->E.R2.errMap(_ => "Error!")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="reduceReverse",
|
||||
~nameSpace,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.reduceReverse([1,4,5], 2, {|acc, el| acc-el})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="reduceReverse",
|
||||
~inputs=[FRTypeArray(FRTypeAny), FRTypeAny, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array), initialValue, IEvLambda(lambda)] =>
|
||||
Internals.reduceReverse(array, initialValue, lambda, env, reducer)->E.R2.errMap(_ =>
|
||||
"Error!"
|
||||
)
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="filter",
|
||||
~nameSpace,
|
||||
~requiresNamespace=false,
|
||||
~examples=[`List.filter([1,4,5], {|x| x>3})`],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="filter",
|
||||
~inputs=[FRTypeArray(FRTypeAny), FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(array), IEvLambda(lambda)] =>
|
||||
Internals.filter(array, lambda, env, reducer)->E.R2.errMap(_ => "Error!")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,226 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let nameSpace = "Number"
|
||||
let requiresNamespace = false
|
||||
|
||||
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)
|
||||
},
|
||||
(),
|
||||
)
|
||||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="floor",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`floor(3.5)`],
|
||||
~definitions=[NumberToNumber.make("floor", Js.Math.floor_float)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="ceiling",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`ceil(3.5)`],
|
||||
~definitions=[NumberToNumber.make("ceil", Js.Math.ceil_float)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="absolute value",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`abs(3.5)`],
|
||||
~definitions=[NumberToNumber.make("abs", Js.Math.abs_float)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="exponent",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`exp(3.5)`],
|
||||
~definitions=[NumberToNumber.make("exp", Js.Math.exp)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="log",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`log(3.5)`],
|
||||
~definitions=[NumberToNumber.make("log", Js.Math.log)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="log base 10",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`log10(3.5)`],
|
||||
~definitions=[NumberToNumber.make("log10", Js.Math.log10)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="log base 2",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`log2(3.5)`],
|
||||
~definitions=[NumberToNumber.make("log2", Js.Math.log2)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="round",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`round(3.5)`],
|
||||
~definitions=[NumberToNumber.make("round", Js.Math.round)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="sum",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`sum([3,5,2])`],
|
||||
~definitions=[ArrayNumberDist.make("sum", r => r->E.A.Floats.sum->Wrappers.evNumber->Ok)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="product",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`product([3,5,2])`],
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("product", r => r->E.A.Floats.product->Wrappers.evNumber->Ok),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="min",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`min([3,5,2])`],
|
||||
~definitions=[ArrayNumberDist.make("min", r => r->E.A.Floats.min->Wrappers.evNumber->Ok)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="max",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`max([3,5,2])`],
|
||||
~definitions=[ArrayNumberDist.make("max", r => r->E.A.Floats.max->Wrappers.evNumber->Ok)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="mean",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`mean([3,5,2])`],
|
||||
~definitions=[ArrayNumberDist.make("mean", r => r->E.A.Floats.mean->Wrappers.evNumber->Ok)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="geometric mean",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`geomean([3,5,2])`],
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("geomean", r => r->E.A.Floats.geomean->Wrappers.evNumber->Ok),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="standard deviation",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`stdev([3,5,2,3,5])`],
|
||||
~definitions=[ArrayNumberDist.make("stdev", r => r->E.A.Floats.stdev->Wrappers.evNumber->Ok)],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="variance",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[`variance([3,5,2,3,5])`],
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("variance", r => r->E.A.Floats.variance->Wrappers.evNumber->Ok),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="sort",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtArray,
|
||||
~examples=[`sort([3,5,2,3,5])`],
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("sort", r =>
|
||||
r->E.A.Floats.sort->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="cumulative sum",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtArray,
|
||||
~examples=[`cumsum([3,5,2,3,5])`],
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("cumsum", r =>
|
||||
r->E.A.Floats.cumSum->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="cumulative prod",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtArray,
|
||||
~examples=[`cumprod([3,5,2,3,5])`],
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("cumprod", r =>
|
||||
r->E.A.Floats.cumProd->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="diff",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtArray,
|
||||
~examples=[`diff([3,5,2,3,5])`],
|
||||
~definitions=[
|
||||
ArrayNumberDist.make("diff", r =>
|
||||
r->E.A.Floats.diff->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,102 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let nameSpace = "PointSet"
|
||||
let requiresNamespace = true
|
||||
|
||||
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_InternalExpressionValue.IEvDistribution(
|
||||
PointSet(makeDist(r)),
|
||||
))
|
||||
expressionValue
|
||||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="fromDist",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`PointSet.fromDist(normal(5,2))`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromDist",
|
||||
~inputs=[FRTypeDist],
|
||||
~run=(_, inputs, env, _) =>
|
||||
switch inputs {
|
||||
| [FRValueDist(dist)] =>
|
||||
GenericDist.toPointSet(
|
||||
dist,
|
||||
~xyPointLength=env.xyPointLength,
|
||||
~sampleCount=env.sampleCount,
|
||||
(),
|
||||
)
|
||||
->E.R2.fmap(Wrappers.pointSet)
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="makeContinuous",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`PointSet.makeContinuous([
|
||||
{x: 0, y: 0.2},
|
||||
{x: 1, y: 0.7},
|
||||
{x: 2, y: 0.8},
|
||||
{x: 3, y: 0.2}
|
||||
])`,
|
||||
],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="makeContinuous",
|
||||
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
|
||||
~run=(_, inputs, _, _) => inputsTodist(inputs, r => Continuous(Continuous.make(r))),
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="makeDiscrete",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`PointSet.makeDiscrete([
|
||||
{x: 0, y: 0.2},
|
||||
{x: 1, y: 0.7},
|
||||
{x: 2, y: 0.8},
|
||||
{x: 3, y: 0.2}
|
||||
])`,
|
||||
],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="makeDiscrete",
|
||||
~inputs=[FRTypeArray(FRTypeRecord([("x", FRTypeNumeric), ("y", FRTypeNumeric)]))],
|
||||
~run=(_, inputs, _, _) => inputsTodist(inputs, r => Discrete(Discrete.make(r))),
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,272 @@
|
|||
open FunctionRegistry_Core
|
||||
open FunctionRegistry_Helpers
|
||||
|
||||
let nameSpace = "SampleSet"
|
||||
let requiresNamespace = true
|
||||
|
||||
module Internal = {
|
||||
type t = SampleSetDist.t
|
||||
let doLambdaCall = (aLambdaValue, list, environment, reducer) =>
|
||||
switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, environment, reducer) {
|
||||
| Ok(IEvNumber(f)) => Ok(f)
|
||||
| _ => Error(Operation.SampleMapNeedsNtoNFunction)
|
||||
}
|
||||
|
||||
let toType = (r): result<
|
||||
ReducerInterface_InternalExpressionValue.t,
|
||||
Reducer_ErrorValue.errorValue,
|
||||
> =>
|
||||
switch r {
|
||||
| Ok(r) => Ok(Wrappers.evDistribution(SampleSet(r)))
|
||||
| Error(r) => Error(REDistributionError(SampleSetError(r)))
|
||||
}
|
||||
|
||||
//TODO: I don't know why this seems to need at least one input
|
||||
let fromFn = (
|
||||
aLambdaValue,
|
||||
env: ReducerInterface_InternalExpressionValue.environment,
|
||||
reducer,
|
||||
) => {
|
||||
let sampleCount = env.sampleCount
|
||||
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)}, env, reducer)
|
||||
Belt_Array.makeBy(sampleCount, r => fn(r->Js.Int.toFloat))->E.A.R.firstErrorOrOpen
|
||||
}
|
||||
|
||||
let map1 = (sampleSetDist: t, aLambdaValue, env, reducer) => {
|
||||
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)}, env, reducer)
|
||||
SampleSetDist.samplesMap(~fn, sampleSetDist)->toType
|
||||
}
|
||||
|
||||
let map2 = (t1: t, t2: t, aLambdaValue, env, reducer) => {
|
||||
let fn = (a, b) => doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b)}, env, reducer)
|
||||
SampleSetDist.map2(~fn, ~t1, ~t2)->toType
|
||||
}
|
||||
|
||||
let map3 = (t1: t, t2: t, t3: t, aLambdaValue, env, reducer) => {
|
||||
let fn = (a, b, c) =>
|
||||
doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b), IEvNumber(c)}, env, reducer)
|
||||
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
|
||||
}
|
||||
|
||||
let parseSampleSetArray = (arr: array<internalExpressionValue>): option<
|
||||
array<SampleSetDist.t>,
|
||||
> => {
|
||||
let parseSampleSet = (value: internalExpressionValue): option<SampleSetDist.t> =>
|
||||
switch value {
|
||||
| IEvDistribution(SampleSet(dist)) => Some(dist)
|
||||
| _ => None
|
||||
}
|
||||
E.A.O.openIfAllSome(E.A.fmap(parseSampleSet, arr))
|
||||
}
|
||||
|
||||
let mapN = (aValueArray: array<internalExpressionValue>, aLambdaValue, env, reducer) => {
|
||||
switch parseSampleSetArray(aValueArray) {
|
||||
| Some(t1) =>
|
||||
let fn = a =>
|
||||
doLambdaCall(
|
||||
aLambdaValue,
|
||||
list{IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))},
|
||||
env,
|
||||
reducer,
|
||||
)
|
||||
SampleSetDist.mapN(~fn, ~t1)->toType
|
||||
| None => Error(REFunctionNotFound(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="fromDist",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`SampleSet.fromDist(normal(5,2))`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromDist",
|
||||
~inputs=[FRTypeDist],
|
||||
~run=(_, inputs, env, _) =>
|
||||
switch inputs {
|
||||
| [FRValueDist(dist)] =>
|
||||
GenericDist.toSampleSetDist(dist, env.sampleCount)
|
||||
->E.R2.fmap(Wrappers.sampleSet)
|
||||
->E.R2.fmap(Wrappers.evDistribution)
|
||||
->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="fromList",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5,3,2,3,1,1,3])`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromList",
|
||||
~inputs=[FRTypeArray(FRTypeNumber)],
|
||||
~run=(_, inputs, _, _) => {
|
||||
let sampleSet =
|
||||
Prepare.ToTypedArray.numbers(inputs) |> E.R2.bind(r =>
|
||||
SampleSetDist.make(r)->E.R2.errMap(_ => "AM I HERE? WHYERE AMI??")
|
||||
)
|
||||
sampleSet->E.R2.fmap(Wrappers.sampleSet)->E.R2.fmap(Wrappers.evDistribution)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="toList",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`SampleSet.toList(SampleSet.fromDist(normal(5,2)))`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtArray,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="toList",
|
||||
~inputs=[FRTypeDist],
|
||||
~run=(inputs, _, _, _) =>
|
||||
switch inputs {
|
||||
| [IEvDistribution(SampleSet(dist))] =>
|
||||
dist->E.A2.fmap(Wrappers.evNumber)->Wrappers.evArray->Ok
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="fromFn",
|
||||
~nameSpace,
|
||||
~requiresNamespace=true,
|
||||
~examples=[`SampleSet.fromFn({|| sample(normal(5,2))})`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="fromFn",
|
||||
~inputs=[FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvLambda(lambda)] =>
|
||||
switch Internal.fromFn(lambda, env, reducer) {
|
||||
| Ok(r) => Ok(r->Wrappers.sampleSet->Wrappers.evDistribution)
|
||||
| Error(_) => Error("issue")
|
||||
}
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="map",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[`SampleSet.map(SampleSet.fromDist(normal(5,2)), {|x| x + 1})`],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="map",
|
||||
~inputs=[FRTypeDist, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvDistribution(SampleSet(dist)), IEvLambda(lambda)] =>
|
||||
Internal.map1(dist, lambda, env, reducer)->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="map2",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`SampleSet.map2(SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), {|x, y| x + y})`,
|
||||
],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="map2",
|
||||
~inputs=[FRTypeDist, FRTypeDist, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) => {
|
||||
switch inputs {
|
||||
| [
|
||||
IEvDistribution(SampleSet(dist1)),
|
||||
IEvDistribution(SampleSet(dist2)),
|
||||
IEvLambda(lambda),
|
||||
] =>
|
||||
Internal.map2(dist1, dist2, lambda, env, reducer)->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="map3",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`SampleSet.map3(SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), {|x, y, z| max([x,y,z])})`,
|
||||
],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="map3",
|
||||
~inputs=[FRTypeDist, FRTypeDist, FRTypeDist, FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [
|
||||
IEvDistribution(SampleSet(dist1)),
|
||||
IEvDistribution(SampleSet(dist2)),
|
||||
IEvDistribution(SampleSet(dist3)),
|
||||
IEvLambda(lambda),
|
||||
] =>
|
||||
Internal.map3(dist1, dist2, dist3, lambda, env, reducer)->E.R2.errMap(_ => "")
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="mapN",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~examples=[
|
||||
`SampleSet.mapN([SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2)), SampleSet.fromDist(normal(5,2))], {|x| max(x)})`,
|
||||
],
|
||||
~output=ReducerInterface_InternalExpressionValue.EvtDistribution,
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="mapN",
|
||||
~inputs=[FRTypeArray(FRTypeDist), FRTypeLambda],
|
||||
~run=(inputs, _, env, reducer) =>
|
||||
switch inputs {
|
||||
| [IEvArray(dists), IEvLambda(lambda)] =>
|
||||
Internal.mapN(dists, lambda, env, reducer)->E.R2.errMap(_e => {
|
||||
"AHHH doesn't work"
|
||||
})
|
||||
| _ => Error(impossibleError)
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,89 @@
|
|||
open FunctionRegistry_Core
|
||||
|
||||
let nameSpace = "Dist"
|
||||
let requiresNamespace = true
|
||||
|
||||
let runScoring = (estimate, answer, prior, env) => {
|
||||
GenericDist.Score.logScore(~estimate, ~answer, ~prior, ~env)
|
||||
->E.R2.fmap(FunctionRegistry_Helpers.Wrappers.evNumber)
|
||||
->E.R2.errMap(DistributionTypes.Error.toString)
|
||||
}
|
||||
|
||||
let library = [
|
||||
Function.make(
|
||||
~name="logScore",
|
||||
~nameSpace,
|
||||
~requiresNamespace,
|
||||
~output=EvtNumber,
|
||||
~examples=[
|
||||
"Dist.logScore({estimate: normal(5,2), answer: normal(5.2,1), prior: normal(5.5,3)})",
|
||||
"Dist.logScore({estimate: normal(5,2), answer: normal(5.2,1)})",
|
||||
"Dist.logScore({estimate: normal(5,2), answer: 4.5})",
|
||||
],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="logScore",
|
||||
~inputs=[
|
||||
FRTypeRecord([
|
||||
("estimate", FRTypeDist),
|
||||
("answer", FRTypeDistOrNumber),
|
||||
("prior", FRTypeDist),
|
||||
]),
|
||||
],
|
||||
~run=(_, inputs, env, _) => {
|
||||
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(inputs) {
|
||||
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d)), FRValueDist(prior)]) =>
|
||||
runScoring(estimate, Score_Dist(d), Some(prior), env)
|
||||
| Ok([
|
||||
FRValueDist(estimate),
|
||||
FRValueDistOrNumber(FRValueNumber(d)),
|
||||
FRValueDist(prior),
|
||||
]) =>
|
||||
runScoring(estimate, Score_Scalar(d), Some(prior), env)
|
||||
| Error(e) => Error(e)
|
||||
| _ => Error(FunctionRegistry_Helpers.impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
FnDefinition.make(
|
||||
~name="logScore",
|
||||
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
|
||||
~run=(_, inputs, env, _) => {
|
||||
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(inputs) {
|
||||
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueDist(d))]) =>
|
||||
runScoring(estimate, Score_Dist(d), None, env)
|
||||
| Ok([FRValueDist(estimate), FRValueDistOrNumber(FRValueNumber(d))]) =>
|
||||
runScoring(estimate, Score_Scalar(d), None, env)
|
||||
| Error(e) => Error(e)
|
||||
| _ => Error(FunctionRegistry_Helpers.impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
Function.make(
|
||||
~name="klDivergence",
|
||||
~nameSpace,
|
||||
~output=EvtNumber,
|
||||
~requiresNamespace,
|
||||
~examples=["Dist.klDivergence(normal(5,2), normal(5,1.5))"],
|
||||
~definitions=[
|
||||
FnDefinition.make(
|
||||
~name="klDivergence",
|
||||
~inputs=[FRTypeDist, FRTypeDist],
|
||||
~run=(_, inputs, env, _) => {
|
||||
switch inputs {
|
||||
| [FRValueDist(estimate), FRValueDist(d)] =>
|
||||
runScoring(estimate, Score_Dist(d), None, env)
|
||||
| _ => Error(FunctionRegistry_Helpers.impossibleError)
|
||||
}
|
||||
},
|
||||
(),
|
||||
),
|
||||
],
|
||||
(),
|
||||
),
|
||||
]
|
|
@ -1,3 +1,6 @@
|
|||
// Only Bindings as the global module is supported
|
||||
// Other module operations such as import export will be prepreocessed jobs
|
||||
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
open Reducer_ErrorValue
|
||||
|
|
|
@ -10,8 +10,8 @@ open ReducerInterface_InternalExpressionValue
|
|||
open Reducer_ErrorValue
|
||||
|
||||
/*
|
||||
MathJs provides default implementations for builtins
|
||||
This is where all the expected builtins like + = * / sin cos log ln etc are handled
|
||||
MathJs provides default implementations for built-ins
|
||||
This is where all the expected built-ins like + = * / sin cos log ln etc are handled
|
||||
DO NOT try to add external function mapping here!
|
||||
*/
|
||||
|
||||
|
@ -95,33 +95,7 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
|
||||
let doExportBindings = (bindings: nameSpace) => bindings->Bindings.toExpressionValue->Ok
|
||||
|
||||
let doKeepArray = (aValueArray, aLambdaValue) => {
|
||||
let rMappedList = aValueArray->Belt.Array.reduceReverse(Ok(list{}), (rAcc, elem) =>
|
||||
rAcc->Result.flatMap(acc => {
|
||||
let rNewElem = Lambda.doLambdaCall(aLambdaValue, list{elem}, environment, reducer)
|
||||
rNewElem->Result.map(newElem =>
|
||||
switch newElem {
|
||||
| IEvBool(true) => list{elem, ...acc}
|
||||
| _ => acc
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
rMappedList->Result.map(mappedList => mappedList->Belt.List.toArray->IEvArray)
|
||||
}
|
||||
|
||||
let doMapArray = (aValueArray, aLambdaValue) => {
|
||||
let rMappedList = aValueArray->Belt.Array.reduceReverse(Ok(list{}), (rAcc, elem) =>
|
||||
rAcc->Result.flatMap(acc => {
|
||||
let rNewElem = Lambda.doLambdaCall(aLambdaValue, list{elem}, environment, reducer)
|
||||
rNewElem->Result.map(newElem => list{newElem, ...acc})
|
||||
})
|
||||
)
|
||||
rMappedList->Result.map(mappedList => mappedList->Belt.List.toArray->IEvArray)
|
||||
}
|
||||
|
||||
module SampleMap = {
|
||||
type t = SampleSetDist.t
|
||||
let doLambdaCall = (aLambdaValue, list) =>
|
||||
switch Lambda.doLambdaCall(aLambdaValue, list, environment, reducer) {
|
||||
| Ok(IEvNumber(f)) => Ok(f)
|
||||
|
@ -134,37 +108,26 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
| Error(r) => Error(REDistributionError(SampleSetError(r)))
|
||||
}
|
||||
|
||||
let map1 = (sampleSetDist: t, aLambdaValue) => {
|
||||
let fn = r => doLambdaCall(aLambdaValue, list{IEvNumber(r)})
|
||||
toType(SampleSetDist.samplesMap(~fn, sampleSetDist))
|
||||
let parseSampleSetArray = (arr: array<internalExpressionValue>): option<
|
||||
array<SampleSetDist.t>,
|
||||
> => {
|
||||
let parseSampleSet = (value: internalExpressionValue): option<SampleSetDist.t> =>
|
||||
switch value {
|
||||
| IEvDistribution(SampleSet(dist)) => Some(dist)
|
||||
| _ => None
|
||||
}
|
||||
E.A.O.openIfAllSome(E.A.fmap(parseSampleSet, arr))
|
||||
}
|
||||
|
||||
let map2 = (t1: t, t2: t, aLambdaValue) => {
|
||||
let fn = (a, b) => doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b)})
|
||||
SampleSetDist.map2(~fn, ~t1, ~t2)->toType
|
||||
}
|
||||
|
||||
let map3 = (t1: t, t2: t, t3: t, aLambdaValue) => {
|
||||
let fn = (a, b, c) =>
|
||||
doLambdaCall(aLambdaValue, list{IEvNumber(a), IEvNumber(b), IEvNumber(c)})
|
||||
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
|
||||
let _mapN = (aValueArray: array<internalExpressionValue>, aLambdaValue) => {
|
||||
switch parseSampleSetArray(aValueArray) {
|
||||
| Some(t1) =>
|
||||
let fn = a => doLambdaCall(aLambdaValue, list{IEvArray(E.A.fmap(x => IEvNumber(x), a))})
|
||||
SampleSetDist.mapN(~fn, ~t1)->toType
|
||||
| None =>
|
||||
Error(REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString))
|
||||
}
|
||||
}
|
||||
|
||||
let doReduceArray = (aValueArray, initialValue, aLambdaValue) => {
|
||||
aValueArray->Belt.Array.reduce(Ok(initialValue), (rAcc, elem) =>
|
||||
rAcc->Result.flatMap(acc =>
|
||||
Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, environment, reducer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let doReduceReverseArray = (aValueArray, initialValue, aLambdaValue) => {
|
||||
aValueArray->Belt.Array.reduceReverse(Ok(initialValue), (rAcc, elem) =>
|
||||
rAcc->Result.flatMap(acc =>
|
||||
Lambda.doLambdaCall(aLambdaValue, list{acc, elem}, environment, reducer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
switch call {
|
||||
|
@ -198,43 +161,13 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
| ("$_typeFunction_$", [IEvArray(arr)]) => TypeBuilder.typeFunction(arr)
|
||||
| ("$_typeTuple_$", [IEvArray(elems)]) => TypeBuilder.typeTuple(elems)
|
||||
| ("$_typeArray_$", [elem]) => TypeBuilder.typeArray(elem)
|
||||
| ("$_typeRecord_$", [IEvArray(arrayOfPairs)]) => TypeBuilder.typeRecord(arrayOfPairs)
|
||||
| ("$_typeRecord_$", [IEvRecord(propertyMap)]) => TypeBuilder.typeRecord(propertyMap)
|
||||
| ("concat", [IEvArray(aValueArray), IEvArray(bValueArray)]) =>
|
||||
doAddArray(aValueArray, bValueArray)
|
||||
| ("concat", [IEvString(aValueString), IEvString(bValueString)]) =>
|
||||
doAddString(aValueString, bValueString)
|
||||
| ("inspect", [value, IEvString(label)]) => inspectLabel(value, label)
|
||||
| ("inspect", [value]) => inspect(value)
|
||||
| ("filter", [IEvArray(aValueArray), IEvLambda(aLambdaValue)]) =>
|
||||
doKeepArray(aValueArray, aLambdaValue)
|
||||
| ("map", [IEvArray(aValueArray), IEvLambda(aLambdaValue)]) =>
|
||||
doMapArray(aValueArray, aLambdaValue)
|
||||
| ("mapSamples", [IEvDistribution(SampleSet(dist)), IEvLambda(aLambdaValue)]) =>
|
||||
SampleMap.map1(dist, aLambdaValue)
|
||||
| (
|
||||
"mapSamples2",
|
||||
[
|
||||
IEvDistribution(SampleSet(dist1)),
|
||||
IEvDistribution(SampleSet(dist2)),
|
||||
IEvLambda(aLambdaValue),
|
||||
],
|
||||
) =>
|
||||
SampleMap.map2(dist1, dist2, aLambdaValue)
|
||||
| (
|
||||
"mapSamples3",
|
||||
[
|
||||
IEvDistribution(SampleSet(dist1)),
|
||||
IEvDistribution(SampleSet(dist2)),
|
||||
IEvDistribution(SampleSet(dist3)),
|
||||
IEvLambda(aLambdaValue),
|
||||
],
|
||||
) =>
|
||||
SampleMap.map3(dist1, dist2, dist3, aLambdaValue)
|
||||
| ("reduce", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) =>
|
||||
doReduceArray(aValueArray, initialValue, aLambdaValue)
|
||||
| ("reduceReverse", [IEvArray(aValueArray), initialValue, IEvLambda(aLambdaValue)]) =>
|
||||
doReduceReverseArray(aValueArray, initialValue, aLambdaValue)
|
||||
| ("reverse", [IEvArray(aValueArray)]) => aValueArray->Belt.Array.reverse->IEvArray->Ok
|
||||
| (_, [IEvBool(_)])
|
||||
| (_, [IEvNumber(_)])
|
||||
| (_, [IEvString(_)])
|
||||
|
@ -246,7 +179,6 @@ let callInternal = (call: functionCall, environment, reducer: ExpressionT.reduce
|
|||
Error(REFunctionNotFound(call->functionCallToCallSignature->functionCallSignatureToString)) // Report full type signature as error
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Reducer uses Result monad while reducing expressions
|
||||
*/
|
||||
|
@ -255,11 +187,10 @@ let dispatch = (call: functionCall, environment, reducer: ExpressionT.reducerFn)
|
|||
errorValue,
|
||||
> =>
|
||||
try {
|
||||
let callInternalWithReducer = (call, environment) => callInternal(call, environment, reducer)
|
||||
let (fn, args) = call
|
||||
// There is a bug that prevents string match in patterns
|
||||
// So we have to recreate a copy of the string
|
||||
ExternalLibrary.dispatch((Js.String.make(fn), args), environment, callInternalWithReducer)
|
||||
ExternalLibrary.dispatch((Js.String.make(fn), args), environment, reducer, callInternal)
|
||||
} catch {
|
||||
| Js.Exn.Error(obj) => REJavaScriptExn(Js.Exn.message(obj), Js.Exn.name(obj))->Error
|
||||
| _ => RETodo("unhandled rescript exception")->Error
|
||||
|
|
|
@ -144,7 +144,7 @@ let dispatchMacroCall = (
|
|||
let ifTrueBlock = eBlock(list{ifTrue})
|
||||
ExpressionWithContext.withContext(ifTrueBlock, bindings)->Ok
|
||||
}
|
||||
| _ => REExpectedType("Boolean")->Error
|
||||
| _ => REExpectedType("Boolean", "")->Error
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
module TypeChecker = Reducer_Type_TypeChecker
|
||||
module T = Reducer_Dispatch_T
|
||||
open ReducerInterface_InternalExpressionValue
|
||||
|
||||
type errorValue = Reducer_ErrorValue.errorValue
|
||||
|
||||
let makeFromTypes = jumpTable => {
|
||||
let dispatchChainPiece: T.dispatchChainPiece = ((fnName, fnArgs): functionCall, environment) => {
|
||||
let jumpTableEntry = jumpTable->Js.Array2.find(elem => {
|
||||
let (candidName, candidType, _) = elem
|
||||
candidName == fnName && TypeChecker.checkITypeArgumentsBool(candidType, fnArgs)
|
||||
})
|
||||
switch jumpTableEntry {
|
||||
| Some((_, _, bridgeFn)) => bridgeFn(fnArgs, environment)->Some
|
||||
| _ => None
|
||||
}
|
||||
}
|
||||
dispatchChainPiece
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
|
||||
// Each piece of the dispatch chain computes the result or returns None so that the chain can continue
|
||||
type dispatchChainPiece = (
|
||||
InternalExpressionValue.functionCall,
|
||||
InternalExpressionValue.environment,
|
||||
) => option<result<InternalExpressionValue.t, Reducer_ErrorValue.errorValue>>
|
||||
|
||||
type dispatchChainPieceWithReducer = (
|
||||
InternalExpressionValue.functionCall,
|
||||
InternalExpressionValue.environment,
|
||||
ExpressionT.reducerFn,
|
||||
) => option<result<InternalExpressionValue.t, Reducer_ErrorValue.errorValue>>
|
||||
|
||||
// This is a switch statement case implementation: get the arguments and compute the result
|
||||
type genericIEvFunction = (
|
||||
array<InternalExpressionValue.t>,
|
||||
InternalExpressionValue.environment,
|
||||
) => result<InternalExpressionValue.t, Reducer_ErrorValue.errorValue>
|
|
@ -7,7 +7,7 @@ type errorValue =
|
|||
| REArrayIndexNotFound(string, int)
|
||||
| REAssignmentExpected
|
||||
| REDistributionError(DistributionTypes.error)
|
||||
| REExpectedType(string)
|
||||
| REExpectedType(string, string)
|
||||
| REExpressionExpected
|
||||
| REFunctionExpected(string)
|
||||
| REFunctionNotFound(string)
|
||||
|
@ -55,6 +55,6 @@ let errorToString = err =>
|
|||
| RESymbolNotFound(symbolName) => `${symbolName} is not defined`
|
||||
| RESyntaxError(desc, _) => `Syntax Error: ${desc}`
|
||||
| RETodo(msg) => `TODO: ${msg}`
|
||||
| REExpectedType(typeName) => `Expected type: ${typeName}`
|
||||
| REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}`
|
||||
| REUnitNotFound(unitName) => `Unit not found: ${unitName}`
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// There are switch stament cases in the code which are impossible to reach by design.
|
||||
// There are switch statement cases in the code which are impossible to reach by design.
|
||||
// ImpossibleException is a sign of programming error.
|
||||
exception ImpossibleException
|
||||
exception ImpossibleException(string)
|
||||
|
|
|
@ -82,3 +82,5 @@ let eIdentifier = (name: string): expression =>
|
|||
|
||||
let eTypeIdentifier = (name: string): expression =>
|
||||
name->BInternalExpressionValue.IEvTypeIdentifier->BExpressionT.EValue
|
||||
|
||||
let eVoid: expression = BInternalExpressionValue.IEvVoid->BExpressionT.EValue
|
||||
|
|
|
@ -30,12 +30,12 @@ let rec toString = expression =>
|
|||
switch expression {
|
||||
| EList(list{EValue(IEvCall("$$_block_$$")), ...statements}) =>
|
||||
`{${Belt.List.map(statements, aValue => toString(aValue))
|
||||
->Extra.List.interperse("; ")
|
||||
->Extra.List.intersperse("; ")
|
||||
->Belt.List.toArray
|
||||
->Js.String.concatMany("")}}`
|
||||
| EList(aList) =>
|
||||
`(${Belt.List.map(aList, aValue => toString(aValue))
|
||||
->Extra.List.interperse(" ")
|
||||
->Extra.List.intersperse(" ")
|
||||
->Belt.List.toArray
|
||||
->Js.String.concatMany("")})`
|
||||
| EValue(aValue) => InternalExpressionValue.toString(aValue)
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
*/
|
||||
module ExtraList = Reducer_Extra_List
|
||||
|
||||
let interperse = (anArray, seperator) =>
|
||||
anArray->Belt.List.fromArray->ExtraList.interperse(seperator)->Belt.List.toArray
|
||||
let intersperse = (anArray, seperator) =>
|
||||
anArray->Belt.List.fromArray->ExtraList.intersperse(seperator)->Belt.List.toArray
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/*
|
||||
Insert seperator between the elements of a list
|
||||
*/
|
||||
let rec interperse = (aList, seperator) =>
|
||||
let rec intersperse = (aList, seperator) =>
|
||||
switch aList {
|
||||
| list{} => list{}
|
||||
| list{a} => list{a}
|
||||
| list{a, ...rest} => list{a, seperator, ...interperse(rest, seperator)}
|
||||
| list{a, ...rest} => list{a, seperator, ...intersperse(rest, seperator)}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
}}
|
||||
|
||||
start
|
||||
// = _nl start:typeExpression _nl finalComment? {return start}
|
||||
= _nl start:outerBlock _nl finalComment? {return start}
|
||||
|
||||
zeroOMoreArgumentsBlockOrExpression = innerBlockOrExpression / lambda
|
||||
|
@ -39,13 +38,19 @@ statement
|
|||
= letStatement
|
||||
/ defunStatement
|
||||
/ typeStatement
|
||||
/ voidStatement
|
||||
|
||||
voidStatement
|
||||
= "call" _nl value:zeroOMoreArgumentsBlockOrExpression
|
||||
{ var variable = h.nodeIdentifier("_", location());
|
||||
return h.nodeLetStatement(variable, value); }
|
||||
|
||||
letStatement
|
||||
= variable:identifier _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
|
||||
= variable:variable _ assignmentOp _nl value:zeroOMoreArgumentsBlockOrExpression
|
||||
{ return h.nodeLetStatement(variable, value) }
|
||||
|
||||
defunStatement
|
||||
= variable:identifier '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression
|
||||
= variable:variable '(' _nl args:array_parameters _nl ')' _ assignmentOp _nl body:innerBlockOrExpression
|
||||
{ var value = h.nodeLambda(args, body)
|
||||
return h.nodeLetStatement(variable, value) }
|
||||
|
||||
|
@ -54,6 +59,8 @@ defunStatement
|
|||
array_parameters
|
||||
= head:dollarIdentifier tail:(_ ',' _nl @dollarIdentifier)*
|
||||
{ return [head, ...tail]; }
|
||||
/ ""
|
||||
{ return [h.nodeIdentifier("_", location())]; }
|
||||
|
||||
expression = ifthenelse / ternary / logicalAdditive
|
||||
|
||||
|
@ -137,11 +144,11 @@ chainFunctionCall
|
|||
}, head)}
|
||||
|
||||
chainedFunction
|
||||
= fn:dollarIdentifier '(' _nl args:array_functionArguments _nl ')'
|
||||
= fn:variable '(' _nl args:array_functionArguments _nl ')'
|
||||
{ return {fnName: fn.value, args: args}}
|
||||
/ fn:dollarIdentifier '(' _nl ')'
|
||||
/ fn:variable '(' _nl ')'
|
||||
{ return {fnName: fn.value, args: []}}
|
||||
/ fn:dollarIdentifier
|
||||
/ fn:variable
|
||||
{ return {fnName: fn.value, args: []}}
|
||||
|
||||
// end of binary operators
|
||||
|
@ -172,6 +179,8 @@ collectionElement
|
|||
array_functionArguments
|
||||
= head:expression tail:(_ ',' _nl @expression)*
|
||||
{ return [head, ...tail]; }
|
||||
/ ""
|
||||
{return [h.nodeVoid()];}
|
||||
|
||||
atom
|
||||
= '(' _nl expression:expression _nl ')' {return expression}
|
||||
|
@ -183,8 +192,13 @@ basicLiteral
|
|||
= string
|
||||
/ number
|
||||
/ boolean
|
||||
/ dollarIdentifierWithModule
|
||||
/ dollarIdentifier
|
||||
/ variable
|
||||
/ voidLiteral
|
||||
|
||||
voidLiteral 'void'
|
||||
= "()" {return h.nodeVoid();}
|
||||
|
||||
variable = dollarIdentifierWithModule / dollarIdentifier
|
||||
|
||||
dollarIdentifierWithModule 'identifier'
|
||||
= head:$moduleIdentifier
|
||||
|
@ -195,7 +209,7 @@ dollarIdentifierWithModule 'identifier'
|
|||
modifiers.unshift(head)
|
||||
modifiers.push(final)
|
||||
let modifiedIdentifier = modifiers.join('.')
|
||||
return h.nodeIdentifier(modifiedIdentifier)
|
||||
return h.nodeIdentifier(modifiedIdentifier, location())
|
||||
}
|
||||
|
||||
identifier 'identifier'
|
||||
|
@ -263,9 +277,13 @@ arrayConstructor 'array'
|
|||
{ return [head, ...tail]; }
|
||||
|
||||
recordConstructor 'record'
|
||||
= '{' _nl args:array_recordArguments _nl '}'
|
||||
= '{' _nl args:array_recordArguments _nl end_of_record
|
||||
{ return h.constructRecord(args); }
|
||||
|
||||
end_of_record
|
||||
= '}'
|
||||
/ ',' _nl '}'
|
||||
|
||||
array_recordArguments
|
||||
= head:keyValuePair tail:(_ ',' _nl @keyValuePair)*
|
||||
{ return [head, ...tail]; }
|
||||
|
|
|
@ -34,6 +34,7 @@ type nodeModuleIdentifier = {...node, "value": string}
|
|||
type nodeString = {...node, "value": string}
|
||||
type nodeTernary = {...node, "condition": node, "trueExpression": node, "falseExpression": node}
|
||||
type nodeTypeIdentifier = {...node, "value": string}
|
||||
type nodeVoid = node
|
||||
|
||||
type peggyNode =
|
||||
| PgNodeBlock(nodeBlock)
|
||||
|
@ -50,6 +51,7 @@ type peggyNode =
|
|||
| PgNodeString(nodeString)
|
||||
| PgNodeTernary(nodeTernary)
|
||||
| PgNodeTypeIdentifier(nodeTypeIdentifier)
|
||||
| PgNodeVoid(nodeVoid)
|
||||
|
||||
external castNodeBlock: node => nodeBlock = "%identity"
|
||||
external castNodeBoolean: node => nodeBoolean = "%identity"
|
||||
|
@ -65,6 +67,7 @@ external castNodeModuleIdentifier: node => nodeModuleIdentifier = "%identity"
|
|||
external castNodeString: node => nodeString = "%identity"
|
||||
external castNodeTernary: node => nodeTernary = "%identity"
|
||||
external castNodeTypeIdentifier: node => nodeTypeIdentifier = "%identity"
|
||||
external castNodeVoid: node => nodeVoid = "%identity"
|
||||
|
||||
exception UnsupportedPeggyNodeType(string) // This should never happen; programming error
|
||||
let castNodeType = (node: node) =>
|
||||
|
@ -83,6 +86,7 @@ let castNodeType = (node: node) =>
|
|||
| "String" => node->castNodeString->PgNodeString
|
||||
| "Ternary" => node->castNodeTernary->PgNodeTernary
|
||||
| "TypeIdentifier" => node->castNodeTypeIdentifier->PgNodeTypeIdentifier
|
||||
| "Void" => node->castNodeVoid->PgNodeVoid
|
||||
| _ => raise(UnsupportedPeggyNodeType(node["type"]))
|
||||
}
|
||||
|
||||
|
@ -91,7 +95,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
|
|||
args->Js.Array2.map(arg => PgNodeIdentifier(arg)->pgToString)->Js.Array2.toString
|
||||
|
||||
let nodesToStringUsingSeparator = (nodes: array<node>, separator: string): string =>
|
||||
nodes->Js.Array2.map(toString)->Extra.Array.interperse(separator)->Js.String.concatMany("")
|
||||
nodes->Js.Array2.map(toString)->Extra.Array.intersperse(separator)->Js.String.concatMany("")
|
||||
|
||||
switch peggyNode {
|
||||
| PgNodeBlock(node) => "{" ++ node["statements"]->nodesToStringUsingSeparator("; ") ++ "}"
|
||||
|
@ -116,6 +120,7 @@ let rec pgToString = (peggyNode: peggyNode): string => {
|
|||
" " ++
|
||||
toString(node["falseExpression"]) ++ ")"
|
||||
| PgNodeTypeIdentifier(node) => `#${node["value"]}`
|
||||
| PgNodeVoid(_node) => "()"
|
||||
}
|
||||
}
|
||||
and toString = (node: node): string => node->castNodeType->pgToString
|
||||
|
|
|
@ -48,5 +48,6 @@ let rec fromNode = (node: Parse.node): expression => {
|
|||
)
|
||||
| PgNodeTypeIdentifier(nodeTypeIdentifier) =>
|
||||
ExpressionBuilder.eTypeIdentifier(nodeTypeIdentifier["value"])
|
||||
| PgNodeVoid(_) => ExpressionBuilder.eVoid
|
||||
}
|
||||
}
|
||||
|
|
|
@ -213,3 +213,7 @@ export function nodeTernary(
|
|||
export function nodeTypeIdentifier(typeValue: string) {
|
||||
return { type: "TypeIdentifier", value: typeValue };
|
||||
}
|
||||
|
||||
export function nodeVoid() {
|
||||
return { type: "Void" };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
module ErrorValue = Reducer_ErrorValue
|
||||
module ExpressionT = Reducer_Expression_T
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module Bindings = Reducer_Bindings
|
||||
module T = Reducer_Type_T
|
||||
|
||||
let ievFromTypeExpression = (
|
||||
typeExpressionSourceCode: string,
|
||||
reducerFn: ExpressionT.reducerFn,
|
||||
): result<InternalExpressionValue.t, ErrorValue.t> => {
|
||||
let sIndex = "compiled"
|
||||
let sourceCode = `type ${sIndex}=${typeExpressionSourceCode}`
|
||||
Reducer_Expression.parse(sourceCode)->Belt.Result.flatMap(expr => {
|
||||
let rContext = reducerFn(
|
||||
expr,
|
||||
Bindings.emptyBindings,
|
||||
InternalExpressionValue.defaultEnvironment,
|
||||
)
|
||||
Belt.Result.map(rContext, context =>
|
||||
switch context {
|
||||
| IEvBindings(nameSpace) =>
|
||||
switch Bindings.getType(nameSpace, sIndex) {
|
||||
| Some(value) => value
|
||||
| None => raise(Reducer_Exception.ImpossibleException("Reducer_Type_Compile-none"))
|
||||
}
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_Compile-raise"))
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let fromTypeExpression = (
|
||||
typeExpressionSourceCode: string,
|
||||
reducerFn: ExpressionT.reducerFn,
|
||||
): result<T.t, ErrorValue.t> => {
|
||||
ievFromTypeExpression(
|
||||
(typeExpressionSourceCode: string),
|
||||
(reducerFn: ExpressionT.reducerFn),
|
||||
)->Belt.Result.map(T.fromIEvValue)
|
||||
}
|
||||
|
||||
let fromTypeExpressionExn = (
|
||||
typeExpressionSourceCode: string,
|
||||
reducerFn: ExpressionT.reducerFn,
|
||||
): T.t =>
|
||||
switch fromTypeExpression(typeExpressionSourceCode, reducerFn) {
|
||||
| Ok(value) => value
|
||||
| _ => `Cannot compile ${typeExpressionSourceCode}`->Reducer_Exception.ImpossibleException->raise
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module T = Reducer_Type_T
|
||||
|
||||
let isMin = (modifierArg: InternalExpressionValue.t, aValue: InternalExpressionValue.t): bool => {
|
||||
let pair = (modifierArg, aValue)
|
||||
switch pair {
|
||||
| (IEvNumber(a), IEvNumber(b)) => a <= b
|
||||
| _ => false
|
||||
}
|
||||
}
|
||||
|
||||
let isMax = (modifierArg: InternalExpressionValue.t, aValue: InternalExpressionValue.t): bool => {
|
||||
let pair = (modifierArg, aValue)
|
||||
switch pair {
|
||||
| (IEvNumber(a), IEvNumber(b)) => a >= b
|
||||
| _ => false
|
||||
}
|
||||
}
|
||||
|
||||
let isMemberOf = (
|
||||
modifierArg: InternalExpressionValue.t,
|
||||
aValue: InternalExpressionValue.t,
|
||||
): bool => {
|
||||
let pair = (modifierArg, aValue)
|
||||
switch pair {
|
||||
| (ievA, IEvArray(b)) => Js.Array2.includes(b, ievA)
|
||||
| _ => false
|
||||
}
|
||||
}
|
||||
|
||||
let checkModifier = (
|
||||
key: string,
|
||||
modifierArg: InternalExpressionValue.t,
|
||||
aValue: InternalExpressionValue.t,
|
||||
): bool =>
|
||||
switch key {
|
||||
| "min" => isMin(modifierArg, aValue)
|
||||
| "max" => isMax(modifierArg, aValue)
|
||||
| "isMemberOf" => isMemberOf(modifierArg, aValue)
|
||||
| _ => false
|
||||
}
|
||||
|
||||
let checkModifiers = (
|
||||
contracts: Belt.Map.String.t<InternalExpressionValue.t>,
|
||||
aValue: InternalExpressionValue.t,
|
||||
): bool => {
|
||||
contracts->Belt.Map.String.reduce(true, (acc, key, modifierArg) =>
|
||||
switch acc {
|
||||
| true => checkModifier(key, modifierArg, aValue)
|
||||
| _ => acc
|
||||
}
|
||||
)
|
||||
}
|
|
@ -3,13 +3,42 @@ open InternalExpressionValue
|
|||
|
||||
type rec iType =
|
||||
| ItTypeIdentifier(string)
|
||||
| ItModifiedType({modifiedType: iType})
|
||||
| ItModifiedType({modifiedType: iType, contracts: Belt.Map.String.t<InternalExpressionValue.t>})
|
||||
| ItTypeOr({typeOr: array<iType>})
|
||||
| ItTypeFunction({inputs: array<iType>, output: iType})
|
||||
| ItTypeArray({element: iType})
|
||||
| ItTypeTuple({elements: array<iType>})
|
||||
| ItTypeRecord({properties: Belt.Map.String.t<iType>})
|
||||
|
||||
type t = iType
|
||||
type typeErrorValue = TypeMismatch(t, InternalExpressionValue.t)
|
||||
|
||||
let rec toString = (t: t): string => {
|
||||
switch t {
|
||||
| ItTypeIdentifier(s) => s
|
||||
| ItModifiedType({modifiedType, contracts}) =>
|
||||
`${toString(modifiedType)}${contracts->Belt.Map.String.reduce("", (acc, k, v) =>
|
||||
Js.String2.concatMany(acc, ["<-", k, "(", InternalExpressionValue.toString(v), ")"])
|
||||
)}`
|
||||
| ItTypeOr({typeOr}) => `(${Js.Array2.map(typeOr, toString)->Js.Array2.joinWith(" | ")})`
|
||||
| ItTypeFunction({inputs, output}) =>
|
||||
`(${inputs->Js.Array2.map(toString)->Js.Array2.joinWith(" => ")} => ${toString(output)})`
|
||||
| ItTypeArray({element}) => `[${toString(element)}]`
|
||||
| ItTypeTuple({elements}) => `[${Js.Array2.map(elements, toString)->Js.Array2.joinWith(", ")}]`
|
||||
| ItTypeRecord({properties}) =>
|
||||
`{${properties
|
||||
->Belt.Map.String.toArray
|
||||
->Js.Array2.map(((k, v)) => Js.String2.concatMany(k, [": ", toString(v)]))
|
||||
->Js.Array2.joinWith(", ")}}`
|
||||
}
|
||||
}
|
||||
|
||||
let toStringResult = (rt: result<t, ErrorValue.t>) =>
|
||||
switch rt {
|
||||
| Ok(t) => toString(t)
|
||||
| Error(e) => ErrorValue.errorToString(e)
|
||||
}
|
||||
|
||||
let rec fromTypeMap = typeMap => {
|
||||
let default = IEvString("")
|
||||
let evTypeTag: InternalExpressionValue.t = Belt.Map.String.getWithDefault(
|
||||
|
@ -52,31 +81,39 @@ let rec fromTypeMap = typeMap => {
|
|||
"properties",
|
||||
default,
|
||||
)
|
||||
//TODO: map type modifiers
|
||||
switch evTypeTag {
|
||||
| IEvString("typeIdentifier") => ItModifiedType({modifiedType: fromIEvValue(evTypeIdentifier)})
|
||||
|
||||
let contracts =
|
||||
typeMap->Belt.Map.String.keep((k, _v) => ["min", "max", "memberOf"]->Js.Array2.includes(k))
|
||||
|
||||
let makeIt = switch evTypeTag {
|
||||
| IEvString("typeIdentifier") => fromIEvValue(evTypeIdentifier)
|
||||
| IEvString("typeOr") => ItTypeOr({typeOr: fromIEvArray(evTypeOr)})
|
||||
| IEvString("typeFunction") =>
|
||||
ItTypeFunction({inputs: fromIEvArray(evInputs), output: fromIEvValue(evOutput)})
|
||||
| IEvString("typeArray") => ItTypeArray({element: fromIEvValue(evElement)})
|
||||
| IEvString("typeTuple") => ItTypeTuple({elements: fromIEvArray(evElements)})
|
||||
| IEvString("typeRecord") => ItTypeRecord({properties: fromIEvRecord(evProperties)})
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-evTypeTag"))
|
||||
}
|
||||
|
||||
Belt.Map.String.isEmpty(contracts)
|
||||
? makeIt
|
||||
: ItModifiedType({modifiedType: makeIt, contracts: contracts})
|
||||
}
|
||||
and fromIEvValue = (ievValue: InternalExpressionValue.t) =>
|
||||
|
||||
and fromIEvValue = (ievValue: InternalExpressionValue.t): iType =>
|
||||
switch ievValue {
|
||||
| IEvTypeIdentifier(typeIdentifier) => ItTypeIdentifier({typeIdentifier})
|
||||
| IEvType(typeMap) => fromTypeMap(typeMap)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievValue"))
|
||||
}
|
||||
and fromIEvArray = (ievArray: InternalExpressionValue.t) =>
|
||||
switch ievArray {
|
||||
| IEvArray(array) => array->Belt.Array.map(fromIEvValue)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievArray"))
|
||||
}
|
||||
and fromIEvRecord = (ievRecord: InternalExpressionValue.t) =>
|
||||
switch ievRecord {
|
||||
| IEvRecord(record) => record->Belt.Map.String.map(fromIEvValue)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException("Reducer_Type_T-ievRecord"))
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ let typeFunction = anArray => {
|
|||
|
||||
let typeArray = element => {
|
||||
let newRecord = Belt.Map.String.fromArray([
|
||||
("typeTag", IEvString("typeTuple")),
|
||||
("typeTag", IEvString("typeArray")),
|
||||
("element", element),
|
||||
])
|
||||
newRecord->IEvType->Ok
|
||||
|
@ -64,22 +64,14 @@ let typeArray = element => {
|
|||
|
||||
let typeTuple = anArray => {
|
||||
let newRecord = Belt.Map.String.fromArray([
|
||||
("typeTag", IEvString("typeArray")),
|
||||
("typeTag", IEvString("typeTuple")),
|
||||
("elements", IEvArray(anArray)),
|
||||
])
|
||||
newRecord->IEvType->Ok
|
||||
}
|
||||
|
||||
let typeRecord = arrayOfPairs => {
|
||||
let newProperties =
|
||||
Belt.Array.map(arrayOfPairs, pairValue =>
|
||||
switch pairValue {
|
||||
| IEvArray([IEvString(key), valueValue]) => (key, valueValue)
|
||||
| _ => ("wrong key type", pairValue->toStringWithType->IEvString)
|
||||
}
|
||||
)
|
||||
->Belt.Map.String.fromArray
|
||||
->IEvRecord
|
||||
let typeRecord = propertyMap => {
|
||||
let newProperties = propertyMap->IEvRecord
|
||||
let newRecord = Belt.Map.String.fromArray([
|
||||
("typeTag", IEvString("typeRecord")),
|
||||
("properties", newProperties),
|
||||
|
|
|
@ -1,81 +1,180 @@
|
|||
module ExpressionT = Reducer_Expression_T
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module T = Reducer_Type_T
|
||||
module TypeBuilder = Reducer_Type_TypeBuilder
|
||||
module TypeContracts = Reducer_Type_Contracts
|
||||
open InternalExpressionValue
|
||||
|
||||
type typeErrorValue =
|
||||
| TypeError(T.iType, InternalExpressionValue.t)
|
||||
| TypeErrorWithPosition(T.iType, InternalExpressionValue.t, int)
|
||||
| TypeErrorWithProperty(T.iType, InternalExpressionValue.t, string)
|
||||
|
||||
let rec isOfResolvedIType = (anIType: T.iType, aValue): result<bool, typeErrorValue> => {
|
||||
let rec isITypeOf = (anIType: T.iType, aValue): result<bool, T.typeErrorValue> => {
|
||||
let caseTypeIdentifier = (anUpperTypeName, aValue) => {
|
||||
let aTypeName = anUpperTypeName->Js.String2.toLowerCase
|
||||
switch aTypeName {
|
||||
| "any" => Ok(true)
|
||||
| _ => {
|
||||
let valueTypeName = aValue->valueToValueType->valueTypeToString->Js.String2.toLowerCase
|
||||
switch aTypeName === valueTypeName {
|
||||
switch aTypeName == valueTypeName {
|
||||
| true => Ok(true)
|
||||
| false => TypeError(anIType, aValue)->Error
|
||||
| false => T.TypeMismatch(anIType, aValue)->Error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _caseRecord = (anIType, evValue, propertyMap, map) => {
|
||||
let caseRecord = (anIType, propertyMap: Belt.Map.String.t<T.iType>, evValue) =>
|
||||
switch evValue {
|
||||
| IEvRecord(aRecord) =>
|
||||
if (
|
||||
Js.Array2.length(propertyMap->Belt.Map.String.keysToArray) ==
|
||||
Js.Array2.length(aRecord->Belt.Map.String.keysToArray)
|
||||
) {
|
||||
Belt.Map.String.reduce(propertyMap, Ok(true), (acc, property, propertyType) => {
|
||||
Belt.Result.flatMap(acc, _ =>
|
||||
switch Belt.Map.String.get(map, property) {
|
||||
| Some(propertyValue) => isOfResolvedIType(propertyType, propertyValue)
|
||||
| None => TypeErrorWithProperty(anIType, evValue, property)->Error
|
||||
switch Belt.Map.String.get(aRecord, property) {
|
||||
| Some(propertyValue) => isITypeOf(propertyType, propertyValue)
|
||||
| None => T.TypeMismatch(anIType, evValue)->Error
|
||||
}
|
||||
)
|
||||
})
|
||||
} else {
|
||||
T.TypeMismatch(anIType, evValue)->Error
|
||||
}
|
||||
let _caseArray = (anIType, evValue, elementType, anArray) => {
|
||||
Belt.Array.reduceWithIndex(anArray, Ok(true), (acc, element, index) => {
|
||||
switch isOfResolvedIType(elementType, element) {
|
||||
|
||||
| _ => T.TypeMismatch(anIType, evValue)->Error
|
||||
}
|
||||
|
||||
let caseArray = (anIType, elementType, evValue) =>
|
||||
switch evValue {
|
||||
| IEvArray(anArray) =>
|
||||
Belt.Array.reduce(anArray, Ok(true), (acc, element) =>
|
||||
Belt.Result.flatMap(acc, _ =>
|
||||
switch isITypeOf(elementType, element) {
|
||||
| Ok(_) => Ok(true)
|
||||
| Error(error) => error->Error
|
||||
}
|
||||
)
|
||||
)
|
||||
| _ => T.TypeMismatch(anIType, evValue)->Error
|
||||
}
|
||||
|
||||
let caseTuple = (anIType, elementTypes, evValue) =>
|
||||
switch evValue {
|
||||
| IEvArray(anArray) =>
|
||||
if Js.Array2.length(elementTypes) == Js.Array2.length(anArray) {
|
||||
let zipped = Belt.Array.zip(elementTypes, anArray)
|
||||
Belt.Array.reduce(zipped, Ok(true), (acc, (elementType, element)) =>
|
||||
switch acc {
|
||||
| Ok(_) =>
|
||||
switch isITypeOf(elementType, element) {
|
||||
| Ok(_) => acc
|
||||
| Error(_) => TypeErrorWithPosition(anIType, evValue, index)->Error
|
||||
| Error(error) => Error(error)
|
||||
}
|
||||
| _ => acc
|
||||
}
|
||||
)
|
||||
} else {
|
||||
T.TypeMismatch(anIType, evValue)->Error
|
||||
}
|
||||
| _ => T.TypeMismatch(anIType, evValue)->Error
|
||||
}
|
||||
|
||||
let caseOr = (anIType, anITypeArray, evValue) =>
|
||||
switch Belt.Array.reduce(anITypeArray, Ok(false), (acc, anIType) =>
|
||||
Belt.Result.flatMap(acc, _ =>
|
||||
switch acc {
|
||||
| Ok(false) =>
|
||||
switch isITypeOf(anIType, evValue) {
|
||||
| Ok(_) => Ok(true)
|
||||
| Error(_) => acc
|
||||
}
|
||||
| _ => acc
|
||||
}
|
||||
)
|
||||
) {
|
||||
| Ok(true) => Ok(true)
|
||||
| Ok(false) => T.TypeMismatch(anIType, evValue)->Error
|
||||
| Error(error) => Error(error)
|
||||
}
|
||||
|
||||
let caseModifiedType = (
|
||||
anIType: T.iType,
|
||||
modifiedType: T.iType,
|
||||
contracts: Belt.Map.String.t<InternalExpressionValue.t>,
|
||||
aValue: InternalExpressionValue.t,
|
||||
) => {
|
||||
isITypeOf(modifiedType, aValue)->Belt.Result.flatMap(_result => {
|
||||
if TypeContracts.checkModifiers(contracts, aValue) {
|
||||
Ok(true)
|
||||
} else {
|
||||
T.TypeMismatch(anIType, aValue)->Error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
switch anIType {
|
||||
| ItTypeIdentifier(name) => caseTypeIdentifier(name, aValue)
|
||||
// TODO: Work in progress. Code is commented to make an a release of other features
|
||||
// | ItModifiedType({modifiedType: anIType}) => raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeOr({typeOr: anITypeArray}) => raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeFunction({inputs: anITypeArray, output: anIType}) =>
|
||||
// raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeArray({element: anIType}) => raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeTuple({elements: anITypeArray}) => raise(Reducer_Exception.ImpossibleException)
|
||||
// | ItTypeRecord({properties: anITypeMap}) => raise(Reducer_Exception.ImpossibleException)
|
||||
| _ => raise(Reducer_Exception.ImpossibleException)
|
||||
| ItModifiedType({modifiedType, contracts}) =>
|
||||
caseModifiedType(anIType, modifiedType, contracts, aValue) //{modifiedType: iType, contracts: Belt.Map.String.t<InternalExpressionValue.t>}
|
||||
| ItTypeOr({typeOr}) => caseOr(anIType, typeOr, aValue)
|
||||
| ItTypeFunction(_) =>
|
||||
raise(
|
||||
Reducer_Exception.ImpossibleException(
|
||||
"Reducer_TypeChecker-functions are without a type at the moment",
|
||||
),
|
||||
)
|
||||
| ItTypeArray({element}) => caseArray(anIType, element, aValue)
|
||||
| ItTypeTuple({elements}) => caseTuple(anIType, elements, aValue)
|
||||
| ItTypeRecord({properties}) => caseRecord(anIType, properties, aValue)
|
||||
}
|
||||
}
|
||||
|
||||
let isOfResolvedType = (aType: InternalExpressionValue.t, aValue): result<bool, typeErrorValue> =>
|
||||
aType->T.fromIEvValue->isOfResolvedIType(aValue)
|
||||
let isTypeOf = (
|
||||
typeExpressionSourceCode: string,
|
||||
aValue: InternalExpressionValue.t,
|
||||
reducerFn: ExpressionT.reducerFn,
|
||||
): result<InternalExpressionValue.t, ErrorValue.t> => {
|
||||
switch typeExpressionSourceCode->Reducer_Type_Compile.fromTypeExpression(reducerFn) {
|
||||
| Ok(anIType) =>
|
||||
switch isITypeOf(anIType, aValue) {
|
||||
| Ok(_) => Ok(aValue)
|
||||
| Error(T.TypeMismatch(anIType, evValue)) =>
|
||||
Error(
|
||||
ErrorValue.REExpectedType(anIType->T.toString, evValue->InternalExpressionValue.toString),
|
||||
)
|
||||
}
|
||||
| Error(error) => Error(error) // Directly propagating - err => err - causes type mismatch
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Work in progress. Code is commented to make an a release of other features
|
||||
// let checkArguments = (
|
||||
// evFunctionType: InternalExpressionValue.t,
|
||||
// args: array<InternalExpressionValue.t>,
|
||||
// ) => {
|
||||
// let functionType = switch evFunctionType {
|
||||
// | IEvRecord(functionType) => functionType
|
||||
// | _ => raise(Reducer_Exception.ImpossibleException)
|
||||
// }
|
||||
// let evInputs = functionType->Belt.Map.String.getWithDefault("inputs", []->IEvArray)
|
||||
// let inputs = switch evInputs {
|
||||
// | IEvArray(inputs) => inputs
|
||||
// | _ => raise(Reducer_Exception.ImpossibleException)
|
||||
// }
|
||||
// let rTupleType = TypeBuilder.typeTuple(inputs)
|
||||
// Belt.Result.flatMap(rTupleType, tuppleType => isOfResolvedType(tuppleType, args->IEvArray))
|
||||
// }
|
||||
let checkITypeArguments = (anIType: T.iType, args: array<InternalExpressionValue.t>): result<
|
||||
bool,
|
||||
T.typeErrorValue,
|
||||
> => {
|
||||
switch anIType {
|
||||
| T.ItTypeFunction({inputs}) => isITypeOf(T.ItTypeTuple({elements: inputs}), args->IEvArray)
|
||||
| _ => T.TypeMismatch(anIType, args->IEvArray)->Error
|
||||
}
|
||||
}
|
||||
|
||||
// let compileTypeExpression = (typeExpression: string, bindings: ExpressionT.bindings, reducerFn: ExpressionT.reducerFn) => {
|
||||
// statement = `type compiled=${typeExpression}`
|
||||
let checkITypeArgumentsBool = (anIType: T.iType, args: array<InternalExpressionValue.t>): bool => {
|
||||
switch checkITypeArguments(anIType, args) {
|
||||
| Ok(_) => true
|
||||
| _ => false
|
||||
}
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
//TODO: asGuard
|
||||
let checkArguments = (
|
||||
typeExpressionSourceCode: string,
|
||||
args: array<InternalExpressionValue.t>,
|
||||
reducerFn: ExpressionT.reducerFn,
|
||||
): result<InternalExpressionValue.t, ErrorValue.t> => {
|
||||
switch typeExpressionSourceCode->Reducer_Type_Compile.fromTypeExpression(reducerFn) {
|
||||
| Ok(anIType) =>
|
||||
switch checkITypeArguments(anIType, args) {
|
||||
| Ok(_) => Ok(args->IEvArray)
|
||||
| Error(T.TypeMismatch(anIType, evValue)) =>
|
||||
Error(
|
||||
ErrorValue.REExpectedType(anIType->T.toString, evValue->InternalExpressionValue.toString),
|
||||
)
|
||||
}
|
||||
| Error(error) => Error(error) // Directly propagating - err => err - causes type mismatch
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ module ErrorValue = Reducer_ErrorValue
|
|||
@genType.opaque
|
||||
type internalCode = Object
|
||||
|
||||
@genType.opaque
|
||||
type hiddenNameSpace = Object
|
||||
|
||||
@genType
|
||||
type rec externalExpressionValue =
|
||||
| EvArray(array<externalExpressionValue>)
|
||||
|
@ -25,15 +28,18 @@ type rec externalExpressionValue =
|
|||
| EvTypeIdentifier(string)
|
||||
| EvModule(record)
|
||||
| EvType(record)
|
||||
| EvVoid
|
||||
and record = Js.Dict.t<externalExpressionValue>
|
||||
and externalBindings = record
|
||||
and lambdaValue = {
|
||||
parameters: array<string>,
|
||||
context: externalBindings,
|
||||
context: hiddenNameSpace,
|
||||
body: internalCode,
|
||||
}
|
||||
and lambdaDeclaration = Declaration.declaration<lambdaValue>
|
||||
|
||||
@genType
|
||||
type externalBindings = record
|
||||
|
||||
@genType
|
||||
type t = externalExpressionValue
|
||||
|
||||
|
@ -63,6 +69,7 @@ let rec toString = aValue =>
|
|||
| EvTimeDuration(t) => DateTime.Duration.toString(t)
|
||||
| EvType(t) => `type${t->toStringRecord}`
|
||||
| EvTypeIdentifier(id) => `#${id}`
|
||||
| EvVoid => `()`
|
||||
}
|
||||
and toStringRecord = aRecord => {
|
||||
let pairs =
|
||||
|
|
|
@ -1,40 +1,24 @@
|
|||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
|
||||
type internalExpressionValue = InternalExpressionValue.t
|
||||
|
||||
// module Sample = {
|
||||
// // In real life real libraries should be somewhere else
|
||||
// /*
|
||||
// For an example of mapping polymorphic custom functions. To be deleted after real integration
|
||||
// */
|
||||
// let customAdd = (a: float, b: float): float => {a +. b}
|
||||
// }
|
||||
|
||||
/*
|
||||
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): InternalExpressionValue.functionCall, env) => {
|
||||
FunctionRegistry_Core.Registry.matchAndRun(~registry, ~fnName, ~args, ~env)->E.O2.fmap(
|
||||
E.R2.errMap(_, s => Reducer_ErrorValue.RETodo(s)),
|
||||
)
|
||||
}
|
||||
|
||||
let dispatch = (call: InternalExpressionValue.functionCall, environment, chain): result<
|
||||
internalExpressionValue,
|
||||
'e,
|
||||
> => {
|
||||
let dispatch = (
|
||||
call: InternalExpressionValue.functionCall,
|
||||
environment,
|
||||
reducer: Reducer_Expression_T.reducerFn,
|
||||
chain,
|
||||
): result<internalExpressionValue, 'e> => {
|
||||
E.A.O.firstSomeFn([
|
||||
() => ReducerInterface_GenericDistribution.dispatch(call, environment),
|
||||
() => ReducerInterface_Date.dispatch(call, environment),
|
||||
() => ReducerInterface_Duration.dispatch(call, environment),
|
||||
() => ReducerInterface_Number.dispatch(call, environment),
|
||||
() => tryRegistry(call, environment),
|
||||
])->E.O2.default(chain(call, environment))
|
||||
() => FunctionRegistry_Library.dispatch(call, environment, reducer),
|
||||
])->E.O2.defaultFn(() => chain(call, environment, reducer))
|
||||
}
|
||||
|
||||
/*
|
||||
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.
|
||||
|
||||
|
|
|
@ -233,19 +233,6 @@ let dispatchToGenericOutput = (call: IEV.functionCall, env: GenericDist.env): op
|
|||
| ("inv", [IEvDistribution(dist), IEvNumber(float)]) => Helpers.toFloatFn(#Inv(float), dist, ~env)
|
||||
| ("quantile", [IEvDistribution(dist), IEvNumber(float)]) =>
|
||||
Helpers.toFloatFn(#Inv(float), dist, ~env)
|
||||
| ("toSampleSet", [IEvDistribution(dist), IEvNumber(float)]) =>
|
||||
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist, ~env)
|
||||
| ("toSampleSet", [IEvDistribution(dist)]) =>
|
||||
Helpers.toDistFn(ToSampleSet(env.sampleCount), dist, ~env)
|
||||
| ("toList", [IEvDistribution(SampleSet(dist))]) => Some(FloatArray(SampleSetDist.T.get(dist)))
|
||||
| ("fromSamples", [IEvArray(inputArray)]) => {
|
||||
let _wrapInputErrors = x => SampleSetDist.NonNumericInput(x)
|
||||
let parsedArray = Helpers.parseNumberArray(inputArray)->E.R2.errMap(_wrapInputErrors)
|
||||
switch parsedArray {
|
||||
| Ok(array) => DistributionOperation.run(FromSamples(array), ~env)
|
||||
| Error(e) => GenDistError(SampleSetError(e))
|
||||
}->Some
|
||||
}
|
||||
| ("inspect", [IEvDistribution(dist)]) => Helpers.toDistFn(Inspect, dist, ~env)
|
||||
| ("truncateLeft", [IEvDistribution(dist), IEvNumber(float)]) =>
|
||||
Helpers.toDistFn(Truncate(Some(float), None), dist, ~env)
|
||||
|
|
|
@ -23,6 +23,7 @@ type rec t =
|
|||
| IEvTimeDuration(float)
|
||||
| IEvType(map)
|
||||
| IEvTypeIdentifier(string)
|
||||
| IEvVoid
|
||||
and map = Belt.Map.String.t<t>
|
||||
and nameSpace = NameSpace(Belt.Map.String.t<t>)
|
||||
and lambdaValue = {
|
||||
|
@ -36,6 +37,29 @@ type internalExpressionValue = t
|
|||
|
||||
type functionCall = (string, array<t>)
|
||||
|
||||
module Internal = {
|
||||
module NameSpace = {
|
||||
external castNameSpaceToHidden: nameSpace => ExternalExpressionValue.hiddenNameSpace =
|
||||
"%identity"
|
||||
external castHiddenToNameSpace: ExternalExpressionValue.hiddenNameSpace => nameSpace =
|
||||
"%identity"
|
||||
}
|
||||
module Lambda = {
|
||||
let toInternal = (v: ExternalExpressionValue.lambdaValue): lambdaValue => {
|
||||
let p = v.parameters
|
||||
let c = v.context->NameSpace.castHiddenToNameSpace
|
||||
let b = v.body
|
||||
{parameters: p, context: c, body: b}
|
||||
}
|
||||
and toExternal = (v: lambdaValue): ExternalExpressionValue.lambdaValue => {
|
||||
let p = v.parameters
|
||||
let c = v.context->NameSpace.castNameSpaceToHidden
|
||||
let b = v.body
|
||||
{parameters: p, context: c, body: b}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rec toString = aValue =>
|
||||
switch aValue {
|
||||
| IEvArray(anArray) => {
|
||||
|
@ -60,6 +84,7 @@ let rec toString = aValue =>
|
|||
| IEvType(aMap) => aMap->toStringMap
|
||||
| IEvTimeDuration(t) => DateTime.Duration.toString(t)
|
||||
| IEvTypeIdentifier(id) => `#${id}`
|
||||
| IEvVoid => `()`
|
||||
}
|
||||
and toStringMap = aMap => {
|
||||
let pairs =
|
||||
|
@ -84,7 +109,7 @@ let toStringWithType = aValue =>
|
|||
| IEvDeclaration(_) => `Declaration::${toString(aValue)}`
|
||||
| IEvDistribution(_) => `Distribution::${toString(aValue)}`
|
||||
| IEvLambda(_) => `Lambda::${toString(aValue)}`
|
||||
| IEvBindings(_) => `Module::${toString(aValue)}`
|
||||
| IEvBindings(_) => `Bindings::${toString(aValue)}`
|
||||
| IEvNumber(_) => `Number::${toString(aValue)}`
|
||||
| IEvRecord(_) => `Record::${toString(aValue)}`
|
||||
| IEvString(_) => `String::${toString(aValue)}`
|
||||
|
@ -92,6 +117,7 @@ let toStringWithType = aValue =>
|
|||
| IEvTimeDuration(_) => `Date::${toString(aValue)}`
|
||||
| IEvType(_) => `Type::${toString(aValue)}`
|
||||
| IEvTypeIdentifier(_) => `TypeIdentifier::${toString(aValue)}`
|
||||
| IEvVoid => `Void`
|
||||
}
|
||||
|
||||
let argsToString = (args: array<t>): string => {
|
||||
|
@ -135,6 +161,7 @@ type internalExpressionValueType =
|
|||
| EvtTimeDuration
|
||||
| EvtType
|
||||
| EvtTypeIdentifier
|
||||
| EvtVoid
|
||||
|
||||
type functionCallSignature = CallSignature(string, array<internalExpressionValueType>)
|
||||
type functionDefinitionSignature =
|
||||
|
@ -158,6 +185,28 @@ let valueToValueType = value =>
|
|||
| IEvTimeDuration(_) => EvtTimeDuration
|
||||
| IEvType(_) => EvtType
|
||||
| IEvTypeIdentifier(_) => EvtTypeIdentifier
|
||||
| IEvVoid => EvtVoid
|
||||
}
|
||||
|
||||
let externalValueToValueType = (value: ExternalExpressionValue.t) =>
|
||||
switch value {
|
||||
| EvArray(_) => EvtArray
|
||||
| EvArrayString(_) => EvtArrayString
|
||||
| EvBool(_) => EvtBool
|
||||
| EvCall(_) => EvtCall
|
||||
| EvDate(_) => EvtDate
|
||||
| EvDeclaration(_) => EvtDeclaration
|
||||
| EvDistribution(_) => EvtDistribution
|
||||
| EvLambda(_) => EvtLambda
|
||||
| EvModule(_) => EvtModule
|
||||
| EvNumber(_) => EvtNumber
|
||||
| EvRecord(_) => EvtRecord
|
||||
| EvString(_) => EvtString
|
||||
| EvSymbol(_) => EvtSymbol
|
||||
| EvTimeDuration(_) => EvtTimeDuration
|
||||
| EvType(_) => EvtType
|
||||
| EvTypeIdentifier(_) => EvtTypeIdentifier
|
||||
| EvVoid => EvtVoid
|
||||
}
|
||||
|
||||
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
|
||||
|
@ -183,6 +232,7 @@ let valueTypeToString = (valueType: internalExpressionValueType): string =>
|
|||
| EvtTimeDuration => `Duration`
|
||||
| EvtType => `Type`
|
||||
| EvtTypeIdentifier => `TypeIdentifier`
|
||||
| EvtVoid => `Void`
|
||||
}
|
||||
|
||||
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {
|
||||
|
@ -212,16 +262,12 @@ let rec toExternal = (iev: t): ExternalExpressionValue.t => {
|
|||
| IEvType(v) => v->mapToExternal->EvType
|
||||
| IEvTypeIdentifier(v) => EvTypeIdentifier(v)
|
||||
| IEvBindings(v) => v->nameSpaceToTypeScriptBindings->EvModule
|
||||
| IEvVoid => EvVoid
|
||||
}
|
||||
}
|
||||
and mapToExternal = v =>
|
||||
v->Belt.Map.String.map(e => toExternal(e))->Belt.Map.String.toArray->Js.Dict.fromArray
|
||||
and lambdaValueToExternal = v => {
|
||||
let p = v.parameters
|
||||
let c = v.context->nameSpaceToTypeScriptBindings
|
||||
let b = v.body
|
||||
{parameters: p, context: c, body: b}
|
||||
}
|
||||
and lambdaValueToExternal = Internal.Lambda.toExternal
|
||||
and nameSpaceToTypeScriptBindings = (
|
||||
nameSpace: nameSpace,
|
||||
): ReducerInterface_ExternalExpressionValue.externalBindings => {
|
||||
|
@ -251,16 +297,12 @@ let rec toInternal = (ev: ExternalExpressionValue.t): t => {
|
|||
| EvTimeDuration(v) => IEvTimeDuration(v)
|
||||
| EvType(v) => v->recordToInternal->IEvType
|
||||
| EvTypeIdentifier(v) => IEvTypeIdentifier(v)
|
||||
| EvVoid => IEvVoid
|
||||
}
|
||||
}
|
||||
and recordToInternal = v =>
|
||||
v->Js.Dict.entries->Belt.Map.String.fromArray->Belt.Map.String.map(e => toInternal(e))
|
||||
and lambdaValueToInternal = v => {
|
||||
let p = v.parameters
|
||||
let c = v.context->nameSpaceFromTypeScriptBindings
|
||||
let b = v.body
|
||||
{parameters: p, context: c, body: b}
|
||||
}
|
||||
and lambdaValueToInternal = Internal.Lambda.toInternal
|
||||
and nameSpaceFromTypeScriptBindings = (
|
||||
r: ReducerInterface_ExternalExpressionValue.externalBindings,
|
||||
): nameSpace =>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user