Compare commits

..

No commits in common. "develop" and "epic-0.5.0" have entirely different histories.

140 changed files with 2463 additions and 3125 deletions

2
.github/CODEOWNERS vendored
View File

@ -24,7 +24,7 @@
*.json @quinn-dougherty @Hazelfire @berekuk @OAGr
*.y*ml @quinn-dougherty @berekuk @OAGr
*.config.js @Hazelfire @berekuk @OAGr
vercel.json @OAGr @berekuk @Hazelfire
netlify.toml @quinn-dougherty @OAGr @berekuk @Hazelfire
# Documentation
*.md @quinn-dougherty @OAGr @Hazelfire

View File

@ -1,4 +1,4 @@
name: Squiggle packages checks
name: Squiggle packages check
on:
push:
@ -9,40 +9,217 @@ on:
branches:
- master
- develop
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: quantified-uncertainty
- reducer-dev
- epic-reducer-project
- epic-0.5.0
jobs:
build-test-lint:
name: Build, test, lint
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@v5.2.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@v5.2.0
with:
paths: '["packages/components/**"]'
- id: skip_website_check
name: Check if the changes are about website src files
uses: fkirc/skip-duplicate-actions@v5.2.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@v5.2.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@v5.2.0
with:
paths: '["packages/cli/**"]'
lang-lint:
name: Language lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/squiggle-lang
steps:
- uses: actions/checkout@v3
- name: Setup Node.js environment
uses: actions/setup-node@v3
- name: Install Dependencies
run: cd ../../ && yarn
- name: Check rescript lint
run: yarn lint:rescript
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
node-version: 16
cache: 'yarn'
- name: Install dependencies
run: yarn --frozen-lockfile
- name: Turbo run
run: npx turbo run build test lint bundle
dry: true
prettier_options: --check packages/squiggle-lang
coverage:
name: Coverage
lang-build-test-bundle:
name: Language build, test, and bundle
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/squiggle-lang
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Setup Node.js environment
uses: actions/setup-node@v2
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build rescript codebase
run: yarn build
- name: Run rescript tests
run: yarn test:rescript
- name: Run typescript tests
run: yarn test:ts
- name: Run webpack
run: yarn bundle
- name: Upload rescript coverage report
run: yarn coverage:rescript:ci
- name: Upload typescript coverage report
run: yarn coverage:ts:ci
components-lint:
name: Components lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/components
steps:
- uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
node-version: 16
cache: 'yarn'
- name: Install dependencies
run: yarn
- name: Coverage
run: npx turbo run coverage
dry: true
prettier_options: --check packages/components --ignore-path packages/components/.prettierignore
components-bundle-build-test:
name: Components bundle, build and test
runs-on: ubuntu-latest
needs: pre_check
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }}
defaults:
run:
shell: bash
working-directory: packages/components
steps:
- uses: actions/checkout@v3
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build rescript codebase in squiggle-lang
run: cd ../squiggle-lang && yarn build
- name: Run webpack
run: yarn bundle
- name: Build storybook
run: yarn build
- name: Test components
run: yarn test
website-lint:
name: Website lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/website
steps:
- uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
dry: true
prettier_options: --check packages/website
website-build:
name: Website build
runs-on: ubuntu-latest
needs: pre_check
if: ${{ (needs.pre_check.outputs.should_skip_website != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') || (needs.pre_check.outputs.should_skip_components != 'true') }}
defaults:
run:
shell: bash
working-directory: packages/website
steps:
- uses: actions/checkout@v3
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build rescript in squiggle-lang
run: cd ../squiggle-lang && yarn build
- name: Build components
run: cd ../components && yarn build
- name: Build website assets
run: yarn build
vscode-ext-lint:
name: VS Code extension lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/vscode-ext
steps:
- uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
dry: true
prettier_options: --check packages/vscode-ext
vscode-ext-build:
name: VS Code extension build
runs-on: ubuntu-latest
needs: pre_check
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }} || (needs.pre_check.outputs.should_skip_vscodeext != 'true') }}
defaults:
run:
shell: bash
working-directory: packages/vscode-ext
steps:
- uses: actions/checkout@v3
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Build
run: yarn compile
cli-lint:
name: CLI lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/cli
steps:
- uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2
with:
dry: true
prettier_options: --check packages/cli

2
.gitignore vendored
View File

@ -11,5 +11,3 @@ yarn-error.log
.vscode
todo.txt
result
shell.nix
.turbo

View File

@ -1 +0,0 @@
See the [Changelog.mdx page](./packages/website/docs/Changelog.mdx) for the changelog.

View File

@ -16,7 +16,7 @@ Squiggle is currently pre-alpha.
# Bug reports
Anyone (with a github account) can file an issue at any time. Please allow Slava, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates.
Anyone (with a github account) can file an issue at any time. Please allow Quinn, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates.
# Project structure
@ -28,7 +28,7 @@ Squiggle is a **monorepo** with three **packages**.
# Deployment ops
We use Vercel, and it should only concern Slava, Sam, and Ozzie.
We use netlify, and it should only concern Quinn, Sam, and Ozzie.
# Development environment, building, testing, dev server
@ -56,9 +56,9 @@ If you absolutely must, please prefix your commit message with `hotfix: `.
Please work against `develop` branch. **Do not** work against `master`.
- For rescript code: Slava and Ozzie are reviewers
- For rescript code: Quinn and Ozzie are reviewers
- For js or typescript code: Sam and Ozzie are reviewers
- For ops code (i.e. yaml, package.json): Slava and Sam are reviewers
- For ops code (i.e. yaml, package.json): Quinn and Sam are reviewers
Autopings are set up: if you are not autopinged, you are welcome to comment, but please do not use the formal review feature, send approvals, rejections, or merges.

View File

@ -21,10 +21,10 @@ _An estimation language_.
## Our deployments
- **website/docs prod**: https://squiggle-language.com
- **website/docs staging**: https://preview.squiggle-language.com
- **components storybook prod**: https://components.squiggle-language.com
- **components storybook staging**: https://preview-components.squiggle-language.com
- **website/docs prod**: https://squiggle-language.com [![Netlify Status](https://api.netlify.com/api/v1/badges/2139af5c-671d-473d-a9f6-66c96077d8a1/deploy-status)](https://app.netlify.com/sites/squiggle-documentation/deploys)
- **website/docs staging**: https://develop--squiggle-documentation.netlify.app/
- **components storybook prod**: https://squiggle-components.netlify.app/ [![Netlify Status](https://api.netlify.com/api/v1/badges/b7f724aa-6b20-4d0e-bf86-3fcd1a3e9a70/deploy-status)](https://app.netlify.com/sites/squiggle-components/deploys)
- **components storybook staging**: https://develop--squiggle-components.netlify.app/
- **legacy (2020) playground**: https://playground.squiggle-language.com
## Packages
@ -51,25 +51,7 @@ For any project in the repo, begin by running `yarn` in the top level
yarn
```
Then use `turbo` to build the specific packages or the entire monorepo:
```sh
turbo run build
```
Or:
```sh
turbo run build --filter=@quri/squiggle-components
```
You can also run specific npm scripts for the package you're working on. See `packages/*/README.md` for the details.
# NixOS users
This repository requires the use of bundled binaries from node_modules, which
are not linked statically. The easiest way to get them working is to enable
[nix-ld](https://github.com/Mic92/nix-ld).
See `packages/*/README.md` to work with whatever project you're interested in.
# Contributing

View File

@ -30,6 +30,16 @@ rec {
patchelf --replace-needed libstdc++.so.6 $THE_SO linux/ninja.exe && echo "- replaced needed for linux/ninja.exe"
'';
};
bisect_ppx = {
buildInputs = common.which;
postInstall = ''
echo "PATCHELF'ING BISECT_PPX EXECUTABLE"
THE_LD=$(patchelf --print-interpreter $(which mkdir))
patchelf --set-interpreter $THE_LD bin/linux/ppx
patchelf --set-interpreter $THE_LD bin/linux/bisect-ppx-report
cp bin/linux/ppx ppx
'';
};
gentype = {
postInstall = ''
mv gentype.exe ELFLESS-gentype.exe

18
nixos.sh Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
# This script is only relevant if you're rolling nixos.
# Esy (a bisect_ppx dependency/build tool) is borked on nixos without using an FHS shell. https://github.com/esy/esy/issues/858
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
set -x
fhsShellName="squiggle-fhs-development"
fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn pkgs.glibc]; runScript = \"yarn\"; }).env"
nix-shell - <<<"$fhsShellDotNix"
theLd=$(patchelf --print-interpreter $(which mkdir))
patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe
patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe
patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/ppx
patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/bisect-ppx-report
theSo=$(find /nix/store/*$fhsShellName*/lib64 -name libstdc++.so.6 | head -n 1)
patchelf --replace-needed libstdc++.so.6 $theSo ./node_modules/rescript/linux/ninja.exe

View File

@ -2,11 +2,12 @@
"private": true,
"name": "squiggle",
"scripts": {
"nodeclean": "rm -r node_modules && rm -r packages/*/node_modules"
"nodeclean": "rm -r node_modules && rm -r packages/*/node_modules",
"format:all": "prettier --write . && cd packages/squiggle-lang && yarn format",
"lint:all": "prettier --check . && cd packages/squiggle-lang && yarn lint:rescript"
},
"devDependencies": {
"prettier": "^2.7.1",
"turbo": "^1.5.5"
"prettier": "^2.7.1"
},
"workspaces": [
"packages/*"

View File

@ -20,30 +20,3 @@ Runs compilation in the current directory and all of its subdirectories.
### `npx squiggle-cli-experimental watch`
Watches `.squiggleU` files in the current directory (and subdirectories) and rebuilds them when they are saved. Note that this will _not_ rebuild files when their dependencies are changed, just when they are changed directly.
## Further instructions
The above requires having node, npm and npx. To install the first two, see [here](https://nodejs.org/en/), to install npx, run:
```
npm install -g npx
```
Alternatively, you can run the following without the need for npx:
```
npm install squiggle-cli-experimental
node node_modules/squiggle-cli-experimental/index.js compile
```
or you can add a script to your `package.json`, like:
```
...
scripts: {
"compile": "squiggle-cli-experimental compile"
}
...
```
This can be run with `npm run compile`. `npm` knows how to reach into the node_modules directly, so it's not necessary to specify that.

View File

@ -7,13 +7,11 @@
"bin": "index.js",
"type": "module",
"scripts": {
"start": "node .",
"lint": "prettier --check .",
"format": "prettier --write ."
"start": "node ."
},
"license": "MIT",
"dependencies": {
"chalk": "^5.1.0",
"chalk": "^5.0.1",
"chokidar": "^3.5.3",
"commander": "^9.4.1",
"fs": "^0.0.1-security",

View File

@ -0,0 +1,8 @@
[build]
base = "packages/components/"
command = "cd ../squiggle-lang && yarn build && cd ../components && yarn build"
publish = "storybook-static/"
ignore = "node -e 'process.exitCode = process.env.BRANCH.includes(\"dependabot\") ? 0 : 1' && git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../squiggle-lang"
[build.environment]
NETLIFY_USE_YARN = "true"

View File

@ -10,16 +10,14 @@
"@hookform/resolvers": "^2.9.8",
"@quri/squiggle-lang": "^0.5.0",
"@react-hook/size": "^2.1.2",
"@types/uuid": "^8.3.4",
"clsx": "^1.2.1",
"framer-motion": "^7.5.3",
"framer-motion": "^7.5.1",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-ace": "^10.1.0",
"react-hook-form": "^7.37.0",
"react-hook-form": "^7.36.1",
"react-use": "^17.4.0",
"react-vega": "^7.6.0",
"uuid": "^9.0.0",
"vega": "^5.22.1",
"vega-embed": "^6.21.0",
"vega-lite": "^5.5.0",
@ -41,16 +39,15 @@
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^27.5.0",
"@types/lodash": "^4.14.186",
"@types/node": "^18.8.3",
"@types/node": "^18.8.0",
"@types/react": "^18.0.21",
"@types/styled-components": "^5.1.26",
"@types/uuid": "^8.3.4",
"@types/webpack": "^5.28.0",
"canvas": "^2.10.1",
"cross-env": "^7.0.3",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.1.2",
"jsdom": "^20.0.1",
"jest-environment-jsdom": "^29.0.3",
"jsdom": "^20.0.0",
"mini-css-extract-plugin": "^2.6.1",
"postcss-cli": "^10.0.0",
"postcss-import": "^15.0.0",
@ -60,11 +57,11 @@
"react-scripts": "^5.0.1",
"style-loader": "^3.3.1",
"tailwindcss": "^3.1.8",
"ts-jest": "^29.0.3",
"ts-jest": "^29.0.2",
"ts-loader": "^9.4.1",
"tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.8.4",
"web-vitals": "^3.0.3",
"web-vitals": "^3.0.2",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
@ -85,8 +82,7 @@
"format": "prettier --write .",
"prepack": "yarn run build:cjs && yarn run bundle",
"test": "jest",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"test:profile": "node --cpu-prof node_modules/.bin/jest --runInBand"
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand"
},
"eslintConfig": {
"extends": [

View File

@ -24,13 +24,13 @@ export const Alert: React.FC<{
children,
}) => {
return (
<div className={clsx("rounded-md p-4", backgroundColor)} role="status">
<div className={clsx("rounded-md p-4", backgroundColor)}>
<div className="flex">
<Icon
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
aria-hidden="true"
/>
<div className="ml-3 grow">
<div className="ml-3">
<header className={clsx("text-sm font-medium", headingColor)}>
{heading}
</header>

View File

@ -5,8 +5,6 @@ import AceEditor from "react-ace";
import "ace-builds/src-noconflict/mode-golang";
import "ace-builds/src-noconflict/theme-github";
import { SqLocation } from "@quri/squiggle-lang";
interface CodeEditorProps {
value: string;
onChange: (value: string) => void;
@ -15,17 +13,15 @@ interface CodeEditorProps {
width?: number;
height: number;
showGutter?: boolean;
errorLocations?: SqLocation[];
}
export const CodeEditor: FC<CodeEditorProps> = ({
value,
onChange,
onSubmit,
height,
oneLine = false,
showGutter = false,
errorLocations = [],
height,
}) => {
const lineCount = value.split("\n").length;
const id = useMemo(() => _.uniqueId(), []);
@ -34,11 +30,8 @@ export const CodeEditor: FC<CodeEditorProps> = ({
const onSubmitRef = useRef<typeof onSubmit | null>(null);
onSubmitRef.current = onSubmit;
const editorEl = useRef<AceEditor | null>(null);
return (
<AceEditor
ref={editorEl}
value={value}
mode="golang"
theme="github"
@ -55,7 +48,10 @@ export const CodeEditor: FC<CodeEditorProps> = ({
editorProps={{
$blockScrolling: true,
}}
setOptions={{}}
setOptions={{
enableBasicAutocompletion: false,
enableLiveAutocompletion: false,
}}
commands={[
{
name: "submit",
@ -63,14 +59,6 @@ export const CodeEditor: FC<CodeEditorProps> = ({
exec: () => onSubmitRef.current?.(),
},
]}
markers={errorLocations?.map((location) => ({
startRow: location.start.line - 1,
startCol: location.start.column - 1,
endRow: location.end.line - 1,
endCol: location.end.column - 1,
className: "ace-error-marker",
type: "text",
}))}
/>
);
};

View File

@ -1,15 +1,9 @@
import * as React from "react";
import {
SqLambda,
environment,
SqValueTag,
SqError,
} from "@quri/squiggle-lang";
import { SqLambda, environment, SqValueTag } from "@quri/squiggle-lang";
import { FunctionChart1Dist } from "./FunctionChart1Dist";
import { FunctionChart1Number } from "./FunctionChart1Number";
import { DistributionPlottingSettings } from "./DistributionChart";
import { MessageAlert } from "./Alert";
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
import { ErrorAlert, MessageAlert } from "./Alert";
export type FunctionChartSettings = {
start: number;
@ -25,25 +19,6 @@ interface FunctionChartProps {
height: number;
}
const FunctionCallErrorAlert = ({ error }: { error: SqError }) => {
const [expanded, setExpanded] = React.useState(false);
if (expanded) {
}
return (
<MessageAlert heading="Function Display Failed">
<div className="space-y-2">
<span
className="underline decoration-dashed cursor-pointer"
onClick={() => setExpanded(!expanded)}
>
{expanded ? "Hide" : "Show"} error details
</span>
{expanded ? <SquiggleErrorAlert error={error} /> : null}
</div>
</MessageAlert>
);
};
export const FunctionChart: React.FC<FunctionChartProps> = ({
fn,
chartSettings,
@ -51,8 +26,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
distributionPlotSettings,
height,
}) => {
console.log(fn.parameters().length);
if (fn.parameters().length !== 1) {
if (fn.parameters.length > 1) {
return (
<MessageAlert heading="Function Display Not Supported">
Only functions with one parameter are displayed.
@ -73,7 +47,9 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
const validResult = getValidResult();
if (validResult.tag === "Error") {
return <FunctionCallErrorAlert error={validResult.value} />;
return (
<ErrorAlert heading="Error">{validResult.value.toString()}</ErrorAlert>
);
}
switch (validResult.value.tag) {

View File

@ -2,21 +2,23 @@ import * as React from "react";
import {
SqValue,
environment,
SqProject,
defaultEnvironment,
resultMap,
SqValueTag,
} from "@quri/squiggle-lang";
import { useSquiggle } from "../lib/hooks";
import { SquiggleViewer } from "./SquiggleViewer";
import { JsImports } from "../lib/jsImports";
import { getValueToRender } from "../lib/utility";
export type SquiggleChartProps = {
export interface SquiggleChartProps {
/** The input string for squiggle */
code: string;
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 */
environment?: environment;
/** If the result is a function, where the function domain starts */
diagramStart?: number;
/** If the result is a function, where the function domain ends */
@ -24,7 +26,7 @@ export type SquiggleChartProps = {
/** If the result is a function, the amount of stops sampled */
diagramCount?: number;
/** When the squiggle code gets reevaluated */
onChange?(expr: SqValue | undefined, sourceName: string): void;
onChange?(expr: SqValue | undefined): void;
/** CSS width of the element */
width?: number;
height?: number;
@ -51,30 +53,21 @@ export type SquiggleChartProps = {
/** Whether to show vega actions to the user, so they can copy the chart spec */
distributionChartActions?: boolean;
enableLocalSettings?: boolean;
} & (StandaloneExecutionProps | ProjectExecutionProps);
}
// Props needed for a standalone execution
type StandaloneExecutionProps = {
project?: undefined;
continues?: undefined;
/** The amount of points returned to draw the distribution, not needed if using a project */
environment?: environment;
};
// Props needed when executing inside a project.
type ProjectExecutionProps = {
environment?: undefined;
/** The project that this execution is part of */
project: SqProject;
/** What other squiggle sources from the project to continue. Default [] */
continues?: string[];
};
const defaultOnChange = () => {};
const defaultImports: JsImports = {};
export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
const {
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,
jsImports = defaultImports,
showSummary = false,
width,
logX = false,
expY = false,
diagramStart = 0,
@ -87,7 +80,15 @@ export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
title,
xAxisType = "number",
distributionChartActions,
} = props;
enableLocalSettings = false,
}) => {
const { result, bindings } = useSquiggle({
code,
environment,
jsImports,
onChange,
executionId,
});
const distributionPlotSettings = {
showSummary,
@ -108,49 +109,18 @@ export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
count: diagramCount,
};
return { distributionPlotSettings, chartSettings };
};
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
(props) => {
const { distributionPlotSettings, chartSettings } =
splitSquiggleChartSettings(props);
const {
code,
jsImports = defaultImports,
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
executionId = 0,
width,
height = 200,
enableLocalSettings = false,
continues,
project,
environment,
} = props;
const resultAndBindings = useSquiggle({
environment,
continues,
project,
code,
jsImports,
onChange,
executionId,
});
const valueToRender = getValueToRender(resultAndBindings);
const resultToRender = resultMap(result, (value) =>
value.tag === SqValueTag.Void ? bindings.asValue() : value
);
return (
<SquiggleViewer
result={valueToRender}
result={resultToRender}
width={width}
height={height}
distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings}
environment={
project ? project.getEnvironment() : environment ?? defaultEnvironment
}
environment={environment ?? defaultEnvironment}
enableLocalSettings={enableLocalSettings}
/>
);

View File

@ -1,29 +1,20 @@
import React from "react";
import { CodeEditor } from "./CodeEditor";
import { SquiggleContainer } from "./SquiggleContainer";
import {
splitSquiggleChartSettings,
SquiggleChartProps,
} from "./SquiggleChart";
import { useMaybeControlledValue, useSquiggle } from "../lib/hooks";
import { JsImports } from "../lib/jsImports";
import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang";
import { SquiggleViewer } from "./SquiggleViewer";
import { getErrorLocations, getValueToRender } from "../lib/utility";
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart";
import { useMaybeControlledValue } from "../lib/hooks";
const WrappedCodeEditor: React.FC<{
code: string;
setCode: (code: string) => void;
errorLocations?: SqLocation[];
}> = ({ code, setCode, errorLocations }) => (
<div className="border border-grey-200 p-2 m-4" data-testid="squiggle-editor">
}> = ({ code, setCode }) => (
<div className="border border-grey-200 p-2 m-4">
<CodeEditor
value={code}
onChange={setCode}
oneLine={true}
showGutter={false}
height={20}
errorLocations={errorLocations}
/>
</div>
);
@ -33,9 +24,6 @@ export type SquiggleEditorProps = SquiggleChartProps & {
onCodeChange?: (code: string) => void;
};
const defaultOnChange = () => {};
const defaultImports: JsImports = {};
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
const [code, setCode] = useMaybeControlledValue({
value: props.code,
@ -43,50 +31,11 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
onChange: props.onCodeChange,
});
const { distributionPlotSettings, chartSettings } =
splitSquiggleChartSettings(props);
const {
environment,
jsImports = defaultImports,
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
executionId = 0,
width,
height = 200,
enableLocalSettings = false,
continues,
project,
} = props;
const resultAndBindings = useSquiggle({
environment,
continues,
code,
project,
jsImports,
onChange,
executionId,
});
const valueToRender = getValueToRender(resultAndBindings);
const errorLocations = getErrorLocations(resultAndBindings.result);
let chartProps = { ...props, code };
return (
<SquiggleContainer>
<WrappedCodeEditor
code={code}
setCode={setCode}
errorLocations={errorLocations}
/>
<SquiggleViewer
result={valueToRender}
width={width}
height={height}
distributionPlotSettings={distributionPlotSettings}
chartSettings={chartSettings}
environment={environment ?? defaultEnvironment}
enableLocalSettings={enableLocalSettings}
/>
<WrappedCodeEditor code={code} setCode={setCode} />
<SquiggleChart {...chartProps} />
</SquiggleContainer>
);
};

View File

@ -1,4 +1,4 @@
import { SqError, SqFrame } from "@quri/squiggle-lang";
import { SqError } from "@quri/squiggle-lang";
import React from "react";
import { ErrorAlert } from "./Alert";
@ -6,39 +6,6 @@ type Props = {
error: SqError;
};
const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => {
const location = frame.location();
return (
<div>
{frame.name()}
{location
? ` at line ${location.start.line}, column ${location.start.column}`
: ""}
</div>
);
};
const StackTrace: React.FC<Props> = ({ error }) => {
const frames = error.getFrameArray();
return frames.length ? (
<div>
<div className="font-medium">Stack trace:</div>
<div className="ml-4">
{frames.map((frame, i) => (
<StackTraceFrame frame={frame} key={i} />
))}
</div>
</div>
) : null;
};
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
return (
<ErrorAlert heading="Error">
<div className="space-y-4">
<div>{error.toString()}</div>
<StackTrace error={error} />
</div>
</ErrorAlert>
);
return <ErrorAlert heading="Error">{error.toString()}</ErrorAlert>;
};

View File

@ -8,11 +8,7 @@ import React, {
} from "react";
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
import * as yup from "yup";
import {
useMaybeControlledValue,
useRunnerState,
useSquiggle,
} from "../lib/hooks";
import { useMaybeControlledValue, useRunnerState } from "../lib/hooks";
import { yupResolver } from "@hookform/resolvers/yup";
import {
ChartSquareBarIcon,
@ -28,9 +24,9 @@ import {
} from "@heroicons/react/solid";
import clsx from "clsx";
import { environment, SqProject } from "@quri/squiggle-lang";
import { environment } from "@quri/squiggle-lang";
import { SquiggleChartProps } from "./SquiggleChart";
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart";
import { CodeEditor } from "./CodeEditor";
import { JsonEditor } from "./JsonEditor";
import { ErrorAlert, SuccessAlert } from "./Alert";
@ -44,8 +40,6 @@ import { HeadedSection } from "./ui/HeadedSection";
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
import { Button } from "./ui/Button";
import { JsImports } from "../lib/jsImports";
import { getErrorLocations, getValueToRender } from "../lib/utility";
import { SquiggleViewer } from "./SquiggleViewer";
type PlaygroundProps = SquiggleChartProps & {
/** The initial squiggle string to put in the playground */
@ -182,7 +176,7 @@ const RunControls: React.FC<{
const CurrentPlayIcon = isRunning ? RefreshIcon : PlayIcon;
return (
<div className="flex space-x-1 items-center" data-testid="autorun-controls">
<div className="flex space-x-1 items-center">
{autorunMode ? null : (
<button onClick={run}>
<CurrentPlayIcon
@ -251,8 +245,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
onSettingsChange,
showEditor = true,
showShareButton = false,
continues,
project,
}) => {
const [code, setCode] = useMaybeControlledValue({
value: controlledCode,
@ -290,7 +282,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
onSettingsChange?.(vars);
}, [vars, onSettingsChange]);
const environment: environment = useMemo(
const env: environment = useMemo(
() => ({
sampleCount: Number(vars.sampleCount),
xyPointLength: Number(vars.xyPointLength),
@ -307,53 +299,26 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
executionId,
} = useRunnerState(code);
const resultAndBindings = useSquiggle({
environment,
continues,
code: renderedCode,
project,
jsImports: imports,
executionId,
});
const valueToRender = getValueToRender(resultAndBindings);
const squiggleChart =
renderedCode === "" ? null : (
<div className="relative">
{isRunning ? (
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
) : null}
<SquiggleViewer
result={valueToRender}
environment={environment}
height={vars.chartHeight || 150}
distributionPlotSettings={{
showSummary: vars.showSummary ?? false,
logX: vars.logX ?? false,
expY: vars.expY ?? false,
format: vars.tickFormat,
minX: vars.minX,
maxX: vars.maxX,
title: vars.title,
actions: vars.distributionChartActions,
}}
chartSettings={{
start: vars.diagramStart ?? 0,
stop: vars.diagramStop ?? 10,
count: vars.diagramCount ?? 20,
}}
<SquiggleChart
code={renderedCode}
executionId={executionId}
environment={env}
{...vars}
jsImports={imports}
enableLocalSettings={true}
/>
</div>
);
const errorLocations = getErrorLocations(resultAndBindings.result);
const firstTab = vars.showEditor ? (
<div className="border border-slate-200" data-testid="squiggle-editor">
<div className="border border-slate-200">
<CodeEditor
errorLocations={errorLocations}
value={code}
onChange={setCode}
onSubmit={run}
@ -403,9 +368,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
>
{tabs}
</div>
<div className="w-1/2 p-2 pl-4" data-testid="playground-result">
{squiggleChart}
</div>
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
</div>
);

View File

@ -2,7 +2,7 @@ import React, { useContext } from "react";
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
import { NumberShower } from "../NumberShower";
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
import { FunctionChart } from "../FunctionChart";
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
import clsx from "clsx";
import { VariableBox } from "./VariableBox";
import { ItemSettingsMenu } from "./ItemSettingsMenu";

View File

@ -1,4 +1,4 @@
import { SqValue } from "@quri/squiggle-lang";
import { SqValue, SqValueLocation } from "@quri/squiggle-lang";
import React, { useContext, useReducer } from "react";
import { Tooltip } from "../ui/Tooltip";
import { LocalItemSettings, MergedItemSettings } from "./utils";
@ -45,7 +45,7 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
: location.path.items[location.path.items.length - 1];
return (
<div role={isTopLevel ? "status" : undefined}>
<div>
<header className="inline-flex space-x-1">
<Tooltip text={heading}>
<span
@ -70,7 +70,7 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
<div className="flex w-full">
{location.path.items.length ? (
<div
className="shrink-0 border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
onClick={toggleCollapsed}
></div>
) : null}

View File

@ -1,4 +1,3 @@
export { SqProject } from "@quri/squiggle-lang/";
export { SquiggleChart } from "./components/SquiggleChart";
export { SquiggleEditor } from "./components/SquiggleEditor";
export { SquigglePlayground } from "./components/SquigglePlayground";

View File

@ -1,97 +1,42 @@
import {
result,
SqError,
SqProject,
SqRecord,
SqValue,
environment,
} from "@quri/squiggle-lang";
import { environment, SqProject, SqValue } from "@quri/squiggle-lang";
import { useEffect, useMemo } from "react";
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
import * as uuid from "uuid";
type SquiggleArgs = {
environment?: environment;
code: string;
executionId?: number;
jsImports?: JsImports;
project?: SqProject;
continues?: string[];
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
environment?: environment;
onChange?: (expr: SqValue | undefined) => void;
};
export type ResultAndBindings = {
result: result<SqValue, SqError>;
bindings: SqRecord;
};
const importSourceName = (sourceName: string) => "imports-" + sourceName;
const defaultContinues = [];
export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
const project = useMemo(() => {
if (args.project) {
return args.project;
} else {
const p = SqProject.create();
if (args.environment) {
p.setEnvironment(args.environment);
}
return p;
}
}, [args.project, args.environment]);
const sourceName = useMemo(() => uuid.v4(), []);
const env = project.getEnvironment();
const continues = args.continues || defaultContinues;
export const useSquiggle = (args: SquiggleArgs) => {
const result = useMemo(
() => {
project.setSource(sourceName, args.code);
let fullContinues = continues;
const project = SqProject.create();
project.setSource("main", args.code);
if (args.environment) {
project.setEnvironment(args.environment);
}
if (args.jsImports && Object.keys(args.jsImports).length) {
const importsSource = jsImportsToSquiggleCode(args.jsImports);
project.setSource(importSourceName(sourceName), importsSource);
fullContinues = continues.concat(importSourceName(sourceName));
project.setSource("imports", importsSource);
project.setContinues("main", ["imports"]);
}
project.setContinues(sourceName, fullContinues);
project.run(sourceName);
const result = project.getResult(sourceName);
const bindings = project.getBindings(sourceName);
project.run("main");
const result = project.getResult("main");
const bindings = project.getBindings("main");
return { result, bindings };
},
// This complains about executionId not being used inside the function body.
// This is on purpose, as executionId simply allows you to run the squiggle
// code again
// eslint-disable-next-line react-hooks/exhaustive-deps
[
args.code,
args.jsImports,
args.executionId,
sourceName,
continues,
project,
env,
]
[args.code, args.environment, args.jsImports, args.executionId]
);
const { onChange } = args;
useEffect(() => {
onChange?.(
result.result.tag === "Ok" ? result.result.value : undefined,
sourceName
);
}, [result, onChange, sourceName]);
useEffect(() => {
return () => {
project.removeSource(sourceName);
if (project.getSource(importSourceName(sourceName)))
project.removeSource(importSourceName(sourceName));
};
}, [project, sourceName]);
onChange?.(result.result.tag === "Ok" ? result.result.value : undefined);
}, [result, onChange]);
return result;
};

View File

@ -1,5 +1,4 @@
import { result, resultMap, SqValueTag } from "@quri/squiggle-lang";
import { ResultAndBindings } from "./hooks/useSquiggle";
import { result } from "@quri/squiggle-lang";
export function flattenResult<a, b>(x: result<a, b>[]): result<a[], b> {
if (x.length === 0) {
@ -36,18 +35,3 @@ export function all(arr: boolean[]): boolean {
export function some(arr: boolean[]): boolean {
return arr.reduce((x, y) => x || y, false);
}
export function getValueToRender({ result, bindings }: ResultAndBindings) {
return resultMap(result, (value) =>
value.tag === SqValueTag.Void ? bindings.asValue() : value
);
}
export function getErrorLocations(result: ResultAndBindings["result"]) {
if (result.tag === "Error") {
const location = result.value.location();
return location ? [location] : [];
} else {
return [];
}
}

View File

@ -22,8 +22,3 @@ but this line is still necessary for proper initialization of `--tw-*` variables
.ace_cursor {
border-left: 2px solid !important;
}
.ace-error-marker {
position: absolute;
border-bottom: 1px solid red;
}

View File

@ -1,55 +0,0 @@
import { render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import * as React from "react";
import "@testing-library/jest-dom";
import { SquigglePlayground } from "../src/index";
test("Autorun is default", async () => {
render(<SquigglePlayground code="70*30" />);
await waitFor(() =>
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100")
);
});
test("Autorun can be switched off", async () => {
const user = userEvent.setup();
render(<SquigglePlayground code="70*30" />);
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Autorun");
await waitFor(() =>
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100")
);
await user.click(screen.getByText("Autorun")); // disable
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Paused");
expect(screen.getByTestId("autorun-controls")).not.toHaveTextContent(
"Autorun"
);
await user.click(screen.getByText("Paused")); // enable autorun again
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Autorun");
// we should replace the code here, but it's hard to update react-ace state via user events: https://github.com/securingsincity/react-ace/issues/923
// ...or replace react-ace with something else
// TODO:
/*
const editor = screen
.getByTestId("squiggle-editor")
.querySelector(".ace_editor") as HTMLElement;
editor.focus();
// await user.clear(editor);
await userEvent.paste("40*40"); // https://github.com/securingsincity/react-ace/issues/923#issuecomment-755502696
screen.debug(editor);
// this makes the tests slower, but it's hard to test otherwise that the code _didn't_ execute
await new Promise((r) => setTimeout(r, 300));
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100"); // still the old value
await waitFor(() =>
expect(screen.getByTestId("playground-result")).toHaveTextContent("1600")
);
*/
});

View File

@ -1,53 +1,13 @@
import { render, screen } from "@testing-library/react";
import { render } from "@testing-library/react";
import React from "react";
import "@testing-library/jest-dom";
import {
SquiggleChart,
SquiggleEditor,
SquigglePlayground,
} from "../src/index";
import { SqProject } from "@quri/squiggle-lang";
import { SquiggleChart } from "../src/index";
test("Chart logs nothing on render", async () => {
test("Logs no warnings or errors", async () => {
debugger;
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
unmount();
expect(console.log).not.toBeCalled();
expect(console.warn).not.toBeCalled();
expect(console.error).not.toBeCalled();
});
test("Editor logs nothing on render", async () => {
const { unmount } = render(<SquiggleEditor code={"normal(0, 1)"} />);
unmount();
expect(console.log).not.toBeCalled();
expect(console.warn).not.toBeCalled();
expect(console.error).not.toBeCalled();
});
test("Project dependencies work in editors", async () => {
const project = SqProject.create();
render(<SquiggleEditor code={"x = 1"} project={project} />);
const source = project.getSourceIds()[0];
const { container } = render(
<SquiggleEditor code={"x + 1"} project={project} continues={[source]} />
);
expect(container).toHaveTextContent("2");
});
test("Project dependencies work in playgrounds", async () => {
const project = SqProject.create();
project.setSource("depend", "x = 1");
render(
<SquigglePlayground
code={"x + 1"}
project={project}
continues={["depend"]}
/>
);
// We must await here because SquigglePlayground loads results asynchronously
expect(await screen.findByRole("status")).toHaveTextContent("2");
});

View File

@ -1,39 +0,0 @@
import { render } from "@testing-library/react";
import React from "react";
import "@testing-library/jest-dom";
import { SquiggleChart } from "../src/index";
import { SqProject } from "@quri/squiggle-lang";
test("Creates and cleans up source", async () => {
const project = SqProject.create();
const { unmount } = render(
<SquiggleChart code={"normal(0, 1)"} project={project} />
);
expect(project.getSourceIds().length).toBe(1);
const sourceId = project.getSourceIds()[0];
expect(project.getSource(sourceId)).toBe("normal(0, 1)");
unmount();
expect(project.getSourceIds().length).toBe(0);
expect(project.getSource(sourceId)).toBe(undefined);
});
test("Creates and cleans up source and imports", async () => {
const project = SqProject.create();
const { unmount } = render(
<SquiggleChart
code={"normal($x, 1)"}
project={project}
jsImports={{ x: 3 }}
/>
);
expect(project.getSourceIds().length).toBe(2);
unmount();
expect(project.getSourceIds()).toStrictEqual([]);
});

View File

@ -1,4 +0,0 @@
{
"buildCommand": "cd ../.. && npx turbo run build --filter=@quri/squiggle-components",
"outputDirectory": "storybook-static"
}

View File

@ -3,8 +3,6 @@ lib
*.bs.js
*.gen.tsx
.nyc_output/
coverage/
_coverage/
.cache/
Reducer_Peggy_GeneratedParser.js
ReducerProject_IncludeParser.js
src/rescript/Reducer/Reducer_Peggy/helpers.js

View File

@ -32,10 +32,7 @@ describe("dotSubtract", () => {
*/
Skip.test("mean of normal minus exponential (property)", () => {
assert_(
property2(
float_(),
floatRange(1e-5, 1e5),
(mean, rate) => {
property2(float_(), floatRange(1e-5, 1e5), (mean, rate) => {
// We limit ourselves to stdev=1 so that the integral is trivial
let dotDifference = DistributionOperation.Constructors.pointwiseSubtract(
~env,
@ -53,8 +50,7 @@ describe("dotSubtract", () => {
| Ok(meanValue) => abs_float(meanValue -. meanAnalytical) /. abs_float(meanValue) < 1e-2 // 1% relative error
| Error(err) => err === DistributionTypes.OperationError(DivisionByZeroError)
}
},
),
}),
)
pass
})

View File

@ -40,9 +40,7 @@ let algebraicPower = algebraicPower(~env)
describe("(Algebraic) addition of distributions", () => {
describe("mean", () => {
test(
"normal(mean=5) + normal(mean=20)",
() => {
test("normal(mean=5) + normal(mean=20)", () => {
normalDist5
->algebraicAdd(normalDist20)
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
@ -51,12 +49,9 @@ describe("(Algebraic) addition of distributions", () => {
->E.R.toExn("Expected float", _)
->expect
->toBe(Some(2.5e1))
},
)
})
test(
"uniform(low=9, high=10) + beta(alpha=2, beta=5)",
() => {
test("uniform(low=9, high=10) + beta(alpha=2, beta=5)", () => {
// let uniformMean = (9.0 +. 10.0) /. 2.0
// let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0)
let received =
@ -72,11 +67,8 @@ describe("(Algebraic) addition of distributions", () => {
// sometimes it works with ~digits=2.
| Some(x) => x->expect->toBeSoCloseTo(9.786831807237022, ~digits=1) // (uniformMean +. betaMean)
}
},
)
test(
"beta(alpha=2, beta=5) + uniform(low=9, high=10)",
() => {
})
test("beta(alpha=2, beta=5) + uniform(low=9, high=10)", () => {
// let uniformMean = (9.0 +. 10.0) /. 2.0
// let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0)
let received =
@ -92,8 +84,7 @@ describe("(Algebraic) addition of distributions", () => {
// sometimes it works with ~digits=2.
| Some(x) => x->expect->toBeSoCloseTo(9.784290207736126, ~digits=1) // (uniformMean +. betaMean)
}
},
)
})
})
describe("pdf", () => {
// TEST IS WRONG. SEE STDEV ADDITION EXPRESSION.
@ -131,9 +122,7 @@ describe("(Algebraic) addition of distributions", () => {
}
},
)
test(
"(normal(mean=10) + normal(mean=10)).pdf(1.9e1)",
() => {
test("(normal(mean=10) + normal(mean=10)).pdf(1.9e1)", () => {
let received =
normalDist20
->Ok
@ -161,11 +150,8 @@ describe("(Algebraic) addition of distributions", () => {
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=1)
}
}
},
)
test(
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).pdf(10)",
() => {
})
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).pdf(10)", () => {
let received =
uniformDist
->algebraicAdd(betaDist)
@ -180,11 +166,8 @@ describe("(Algebraic) addition of distributions", () => {
// This value was calculated by a python script
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
}
},
)
test(
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).pdf(10)",
() => {
})
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).pdf(10)", () => {
let received =
betaDist
->algebraicAdd(uniformDist)
@ -197,14 +180,10 @@ describe("(Algebraic) addition of distributions", () => {
// This is nondeterministic.
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
}
},
)
})
})
describe("cdf", () => {
testAll(
"(normal(mean=5) + normal(mean=5)).cdf (imprecise)",
list{6e0, 8e0, 1e1, 1.2e1},
x => {
testAll("(normal(mean=5) + normal(mean=5)).cdf (imprecise)", list{6e0, 8e0, 1e1, 1.2e1}, x => {
let received =
normalDist10
->Ok
@ -233,11 +212,8 @@ describe("(Algebraic) addition of distributions", () => {
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=0)
}
}
},
)
test(
"(normal(mean=10) + normal(mean=10)).cdf(1.25e1)",
() => {
})
test("(normal(mean=10) + normal(mean=10)).cdf(1.25e1)", () => {
let received =
normalDist20
->Ok
@ -265,11 +241,8 @@ describe("(Algebraic) addition of distributions", () => {
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=2)
}
}
},
)
test(
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).cdf(10)",
() => {
})
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).cdf(10)", () => {
let received =
uniformDist
->algebraicAdd(betaDist)
@ -283,11 +256,8 @@ describe("(Algebraic) addition of distributions", () => {
// The value was calculated externally using a python script
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
}
},
)
test(
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).cdf(10)",
() => {
})
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).cdf(10)", () => {
let received =
betaDist
->algebraicAdd(uniformDist)
@ -301,15 +271,11 @@ describe("(Algebraic) addition of distributions", () => {
// The value was calculated externally using a python script
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
}
},
)
})
})
describe("inv", () => {
testAll(
"(normal(mean=5) + normal(mean=5)).inv (imprecise)",
list{5e-2, 4.2e-3, 9e-3},
x => {
testAll("(normal(mean=5) + normal(mean=5)).inv (imprecise)", list{5e-2, 4.2e-3, 9e-3}, x => {
let received =
normalDist10
->Ok
@ -338,11 +304,8 @@ describe("(Algebraic) addition of distributions", () => {
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
}
}
},
)
test(
"(normal(mean=10) + normal(mean=10)).inv(1e-1)",
() => {
})
test("(normal(mean=10) + normal(mean=10)).inv(1e-1)", () => {
let received =
normalDist20
->Ok
@ -370,11 +333,8 @@ describe("(Algebraic) addition of distributions", () => {
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
}
}
},
)
test(
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).inv(2e-2)",
() => {
})
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).inv(2e-2)", () => {
let received =
uniformDist
->algebraicAdd(betaDist)
@ -388,11 +348,8 @@ describe("(Algebraic) addition of distributions", () => {
// sometimes it works with ~digits=2.
| Some(x) => x->expect->toBeSoCloseTo(9.179319623146968, ~digits=0)
}
},
)
test(
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).inv(2e-2)",
() => {
})
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).inv(2e-2)", () => {
let received =
betaDist
->algebraicAdd(uniformDist)
@ -406,7 +363,6 @@ describe("(Algebraic) addition of distributions", () => {
// sometimes it works with ~digits=2.
| Some(x) => x->expect->toBeSoCloseTo(9.190872365862756, ~digits=0)
}
},
)
})
})
})

View File

@ -3,7 +3,7 @@ This is the most basic file in our invariants family of tests.
Validate that the addition of means equals the mean of the addition, similar for subtraction and multiplication.
Details in https://squiggle-language.com/docs/internal/invariants/
Details in https://develop--squiggle-documentation.netlify.app/docs/internal/invariants/
Note: epsilon of 1e3 means the invariants are, in general, not being satisfied.
*/
@ -87,22 +87,14 @@ describe("Means are invariant", () => {
let testAddInvariant = (t1, t2) =>
E.R.liftM2(testAdditionMean, t1, t2)->E.R.toExn("Means were not invariant", _)
testAll(
"with two of the same distribution",
distributions,
dist => {
testAll("with two of the same distribution", distributions, dist => {
testAddInvariant(dist, dist)
},
)
})
testAll(
"with two different distributions",
pairsOfDifferentDistributions,
dists => {
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
let (dist1, dist2) = dists
testAddInvariant(dist1, dist2)
},
)
})
testAll(
"with two different distributions in swapped order",
@ -124,22 +116,14 @@ describe("Means are invariant", () => {
let testSubtractInvariant = (t1, t2) =>
E.R.liftM2(testSubtractionMean, t1, t2)->E.R.toExn("Means were not invariant", _)
testAll(
"with two of the same distribution",
distributions,
dist => {
testAll("with two of the same distribution", distributions, dist => {
testSubtractInvariant(dist, dist)
},
)
})
testAll(
"with two different distributions",
pairsOfDifferentDistributions,
dists => {
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
let (dist1, dist2) = dists
testSubtractInvariant(dist1, dist2)
},
)
})
testAll(
"with two different distributions in swapped order",
@ -161,22 +145,14 @@ describe("Means are invariant", () => {
let testMultiplicationInvariant = (t1, t2) =>
E.R.liftM2(testMultiplicationMean, t1, t2)->E.R.toExn("Means were not invariant", _)
testAll(
"with two of the same distribution",
distributions,
dist => {
testAll("with two of the same distribution", distributions, dist => {
testMultiplicationInvariant(dist, dist)
},
)
})
testAll(
"with two different distributions",
pairsOfDifferentDistributions,
dists => {
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
let (dist1, dist2) = dists
testMultiplicationInvariant(dist1, dist2)
},
)
})
testAll(
"with two different distributions in swapped order",

View File

@ -17,9 +17,10 @@ describe("klDivergence: continuous -> continuous -> float", () => {
let answer =
uniformMakeR(lowAnswer, highAnswer)->E.R2.errMap(s => DistributionTypes.ArgumentError(s))
let prediction =
uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap(
s => DistributionTypes.ArgumentError(s),
)
uniformMakeR(
lowPrediction,
highPrediction,
)->E.R2.errMap(s => DistributionTypes.ArgumentError(s))
// integral along the support of the answer of answer.pdf(x) times log of prediction.pdf(x) divided by answer.pdf(x) dx
let analyticalKl = Js.Math.log((highPrediction -. lowPrediction) /. (highAnswer -. lowAnswer))
let kl = E.R.liftJoin2(klDivergence, prediction, answer)
@ -182,9 +183,9 @@ describe("combineAlongSupportOfSecondArgument0", () => {
let answer =
uniformMakeR(lowAnswer, highAnswer)->E.R2.errMap(s => DistributionTypes.ArgumentError(s))
let prediction =
uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap(
s => DistributionTypes.ArgumentError(s),
)
uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap(s => DistributionTypes.ArgumentError(
s,
))
let answerWrapped = E.R.fmap(a => run(FromDist(#ToDist(ToPointSet), a)), answer)
let predictionWrapped = E.R.fmap(a => run(FromDist(#ToDist(ToPointSet), a)), prediction)

View File

@ -3,7 +3,7 @@ open Expect
open TestHelpers
// TODO: use Normal.make (but preferably after teh new validation dispatch is in)
let mkNormal = (mean, stdev) => DistributionTypes.Symbolic(#Normal({mean, stdev}))
let mkNormal = (mean, stdev) => DistributionTypes.Symbolic(#Normal({mean: mean, stdev: stdev}))
describe("(Symbolic) normalize", () => {
testAll("has no impact on normal distributions", list{-1e8, -1e-2, 0.0, 1e-4, 1e16}, mean => {
@ -47,7 +47,10 @@ describe("(Symbolic) mean", () => {
tup => {
let (low, medium, high) = tup
let meanValue = run(
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Triangular({low, medium, high}))),
FromDist(
#ToFloat(#Mean),
DistributionTypes.Symbolic(#Triangular({low: low, medium: medium, high: high})),
),
)
meanValue->unpackFloat->expect->toBeCloseTo((low +. medium +. high) /. 3.0) // https://www.statology.org/triangular-distribution/
},
@ -60,7 +63,7 @@ describe("(Symbolic) mean", () => {
tup => {
let (alpha, beta) = tup
let meanValue = run(
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha, beta}))),
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha: alpha, beta: beta}))),
)
meanValue->unpackFloat->expect->toBeCloseTo(1.0 /. (1.0 +. beta /. alpha)) // https://en.wikipedia.org/wiki/Beta_distribution#Mean
},
@ -81,8 +84,8 @@ describe("(Symbolic) mean", () => {
let (mean, stdev) = tup
let betaDistribution = SymbolicDist.Beta.fromMeanAndStdev(mean, stdev)
let meanValue =
betaDistribution->E.R2.fmap(
d => run(FromDist(#ToFloat(#Mean), d->DistributionTypes.Symbolic)),
betaDistribution->E.R2.fmap(d =>
run(FromDist(#ToFloat(#Mean), d->DistributionTypes.Symbolic))
)
switch meanValue {
| Ok(value) => value->unpackFloat->expect->toBeCloseTo(mean)
@ -97,7 +100,7 @@ describe("(Symbolic) mean", () => {
tup => {
let (mu, sigma) = tup
let meanValue = run(
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Lognormal({mu, sigma}))),
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Lognormal({mu: mu, sigma: sigma}))),
)
meanValue->unpackFloat->expect->toBeCloseTo(Js.Math.exp(mu +. sigma ** 2.0 /. 2.0)) // https://brilliant.org/wiki/log-normal-distribution/
},
@ -109,7 +112,7 @@ describe("(Symbolic) mean", () => {
tup => {
let (low, high) = tup
let meanValue = run(
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Uniform({low, high}))),
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Uniform({low: low, high: high}))),
)
meanValue->unpackFloat->expect->toBeCloseTo((low +. high) /. 2.0) // https://en.wikipedia.org/wiki/Continuous_uniform_distribution#Moments
},

View File

@ -33,18 +33,12 @@ describe("Bindings", () => {
let value2 = Reducer_T.IEvNumber(5.)
let extendedBindings = bindings->Bindings.extend->Bindings.set("value", value2)
test(
"get on extended",
() => {
test("get on extended", () => {
expect(extendedBindings->Bindings.get("value")) == Some(value2)
},
)
})
test(
"get on original",
() => {
test("get on original", () => {
expect(bindings->Bindings.get("value")) == Some(value)
},
)
})
})
})

View File

@ -40,23 +40,14 @@ describe("Namespace", () => {
let nsMerged = Namespace.mergeMany([ns, ns1, ns2])
test(
"merge many 1",
() => {
test("merge many 1", () => {
expect(nsMerged->Namespace.get("x1")) == Some(x1)
},
)
test(
"merge many 2",
() => {
})
test("merge many 2", () => {
expect(nsMerged->Namespace.get("x4")) == Some(x4)
},
)
test(
"merge many 3",
() => {
})
test("merge many 3", () => {
expect(nsMerged->Namespace.get("value")) == Some(value)
},
)
})
})
})

View File

@ -197,7 +197,7 @@ describe("Peggy parse", () => {
describe("lambda", () => {
testParse("{|x| x}", "{{|:x| :x}}")
testParse("f={|x| x}", "{:f = {|:x| :x}}")
testParse("f={|x| x}", "{:f = {{|:x| :x}}}")
testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments
testParse("f(x)=x ? 1 : 0", "{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}}") // Function definitions are lambda assignments
})

View File

@ -9,12 +9,12 @@ open Jest
open Expect
let expectParseToBe = (expr, answer) =>
Parse.parse(expr, "test")->Parse.toStringResult->expect->toBe(answer)
Parse.parse(expr)->Parse.toStringResult->expect->toBe(answer)
let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
let expectExpressionToBe = (expr, answer, ~v="_", ()) => {
let rExpr = Parse.parse(expr, "test")->Result.map(ToExpression.fromNode)
let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode)
let a1 = rExpr->ExpressionT.toStringResultOkless
if v == "_" {
@ -22,7 +22,6 @@ let expectExpressionToBe = (expr, answer, ~v="_", ()) => {
} else {
let a2 =
rExpr
->E.R2.errMap(e => e->SqError.fromParseError)
->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr))
->Reducer_Value.toStringResultOkless
(a1, a2)->expect->toEqual((answer, v))
@ -30,16 +29,16 @@ let expectExpressionToBe = (expr, answer, ~v="_", ()) => {
}
let testToExpression = (expr, answer, ~v="_", ()) =>
test(expr, () => expectExpressionToBe(expr, answer, ~v, ()))
test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
module MyOnly = {
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
let testToExpression = (expr, answer, ~v="_", ()) =>
Only.test(expr, () => expectExpressionToBe(expr, answer, ~v, ()))
Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
}
module MySkip = {
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
let testToExpression = (expr, answer, ~v="_", ()) =>
Skip.test(expr, () => expectExpressionToBe(expr, answer, ~v, ()))
Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
}

View File

@ -75,9 +75,7 @@ describe("Peggy to Expression", () => {
testToExpression("false ? 1 : 0", "false ? (1) : (0)", ~v="0", ())
testToExpression("true ? 1 : false ? 2 : 0", "true ? (1) : (false ? (2) : (0))", ~v="1", ()) // nested ternary
testToExpression("false ? 1 : false ? 2 : 0", "false ? (1) : (false ? (2) : (0))", ~v="0", ()) // nested ternary
describe(
"ternary bindings",
() => {
describe("ternary bindings", () => {
testToExpression(
// expression binding
"f(a) = a > 5 ? 1 : 0; f(6)",
@ -99,8 +97,7 @@ describe("Peggy to Expression", () => {
~v="6",
(),
)
},
)
})
})
describe("if then else", () => {
@ -138,7 +135,7 @@ describe("Peggy to Expression", () => {
describe("lambda", () => {
testToExpression("{|x| x}", "{|x| x}", ~v="lambda(x=>internal code)", ())
testToExpression("f={|x| x}", "f = {|x| x}", ())
testToExpression("f={|x| x}", "f = {{|x| x}}", ())
testToExpression("f(x)=x", "f = {|x| {x}}", ()) // Function definitions are lambda assignments
testToExpression("f(x)=x ? 1 : 0", "f = {|x| {x ? (1) : (0)}}", ())
})

View File

@ -1,3 +1,4 @@
module ErrorValue = Reducer_ErrorValue
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
@ -8,7 +9,7 @@ let unwrapRecord = rValue =>
rValue->Belt.Result.flatMap(value =>
switch value {
| Reducer_T.IEvRecord(aRecord) => Ok(aRecord)
| _ => SqError.Message.RETodo("TODO: Internal bindings must be returned")->Error
| _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error
}
)
@ -22,7 +23,7 @@ let expectEvalError = (code: string) =>
Expression.BackCompatible.evaluateString(code)
->Reducer_Value.toStringResult
->expect
->toMatch("Error\\(")
->toMatch("Error\(")
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
let testDescriptionParseToBe = (desc, expr, answer) =>

View File

@ -37,16 +37,14 @@ 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",
() =>
test("always the same property ending", () =>
expectEvalToBe(
`{
a: 1,
b: 2,
}`,
"Ok({a: 1,b: 2})",
),
)
)
})
@ -72,7 +70,7 @@ describe("test exceptions", () => {
testDescriptionEvalToBe(
"javascript exception",
"javascriptraise('div by 0')",
"Error(JS Exception: Error: 'div by 0')",
"Error(Error: 'div by 0')",
)
// testDescriptionEvalToBe(
// "rescript exception",
@ -80,33 +78,3 @@ describe("test exceptions", () => {
// "Error(TODO: unhandled rescript exception)",
// )
})
describe("stacktraces", () => {
test("nested calls", () => {
open Expect
let error =
Expression.BackCompatible.evaluateString(`
f(x) = {
y = "a"
x + y
}
g = {|x| f(x)}
h(x) = g(x)
h(5)
`)
->E.R.getError
->E.O2.toExn("oops")
->SqError.toStringWithStackTrace
expect(
error,
)->toBe(`Error: There are function matches for add(), but with different arguments: [add(number, number)]; [add(distribution, number)]; [add(number, distribution)]; [add(distribution, distribution)]; [add(date, duration)]; [add(duration, duration)]
Stack trace:
f at line 4, column 5
g at line 6, column 12
h at line 7, column 10
<top> at line 8, column 3
`)
})
})

View File

@ -25,7 +25,7 @@ x=1`,
let mainIncludes = Project.getIncludes(project, "main")
switch mainIncludes {
| Ok(includes) => expect(includes) == ["common"]
| Error(error) => fail(error->SqError.toString)
| Error(error) => fail(error->Reducer_ErrorValue.errorToString)
}
})
test("past chain", () => {
@ -60,7 +60,7 @@ x=1`,
let mainIncludes = Project.getIncludes(project, "main")
switch mainIncludes {
| Ok(includes) => expect(includes) == ["common", "myModule"]
| Error(error) => fail(error->SqError.toString)
| Error(error) => fail(error->Reducer_ErrorValue.errorToString)
}
})
@ -99,7 +99,7 @@ x=1`,
let mainIncludes = Project.getIncludes(project, "main")
switch mainIncludes {
| Ok(includes) => expect(includes) == ["common", "common2", "myModule"]
| Error(error) => fail(error->SqError.toString)
| Error(error) => fail(error->Reducer_ErrorValue.errorToString)
}
})
test("direct past chain", () => {

View File

@ -11,9 +11,7 @@ describe("ReducerProject Tutorial", () => {
/*
Case "Running a single source".
*/
test(
"run",
() => {
test("run", () => {
/* Let's start with running a single source and getting Result as well as the Bindings
First you need to create a project. A project is a collection of sources.
Project takes care of the dependencies between the sources, correct compilation and run order.
@ -53,12 +51,9 @@ Case "Running a single source".
(result->Reducer_Value.toStringResult, bindings->Reducer_Value.toStringRecord)->expect ==
("Ok(3)", "{}")
/* You've got 3 with empty bindings. */
},
)
})
test(
"run summary",
() => {
test("run summary", () => {
let project = Project.createProject()
project->Project.setSource("main", "1 + 2")
project->Project.runAll
@ -69,12 +64,9 @@ Case "Running a single source".
result->Reducer_Value.toStringResult,
bindings->Reducer_T.IEvRecord->Reducer_Value.toString,
)->expect == ("Ok(3)", "{}")
},
)
})
test(
"run with an environment",
() => {
test("run with an environment", () => {
/* Running the source code like above allows you to set a custom environment */
let project = Project.createProject()
@ -86,19 +78,15 @@ Case "Running a single source".
let result = project->Project.getResult("main")
let _bindings = project->Project.getBindings("main")
result->Reducer_Value.toStringResult->expect == "Ok(3)"
},
)
})
test(
"shortcut",
() => {
test("shortcut", () => {
/* If you are running single source without includes and you don't need a custom environment, you can use the shortcut. */
/* Examples above was to prepare you for the multi source tutorial. */
let (result, bindings) = Project.evaluate("1+2")
(result->Reducer_Value.toStringResult, bindings->Reducer_Value.toStringRecord)->expect ==
("Ok(3)", "{}")
},
)
})
})
})

View File

@ -10,9 +10,7 @@ describe("ReducerProject Tutorial", () => {
describe("Multi source", () => {
/*
Case "Running multiple sources" */
test(
"Chaining",
() => {
test("Chaining", () => {
let project = Project.createProject()
/* This time let's add 3 sources and chain them together */
project->Project.setSource("source1", "x=1")
@ -34,12 +32,9 @@ describe("ReducerProject Tutorial", () => {
(result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect ==
("Ok(())", "{z: 3}")
},
)
})
test(
"Depending",
() => {
test("Depending", () => {
/* Instead of chaining the sources, we could have a dependency tree */
/* The point here is that any source can depend on multiple sources */
let project = Project.createProject()
@ -61,12 +56,9 @@ describe("ReducerProject Tutorial", () => {
(result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect ==
("Ok(())", "{z: 3}")
},
)
})
test(
"Intro to including",
() => {
test("Intro to including", () => {
/* Though it would not be practical for a storybook,
let's write the same project above with includes.
You will see that parsing includes is setting the dependencies the same way as before. */
@ -107,7 +99,6 @@ describe("ReducerProject Tutorial", () => {
- And the depended source1 and source2 is not already there in the project
- If you knew the includes before hand there would not be point of the include directive.
More on those on the next section. */
},
)
})
})
})

View File

@ -24,46 +24,35 @@ Here we will finally proceed to a real life scenario. */
)
/* We need to parse includes after changing the source */
project->Project.parseIncludes("main")
test(
"getDependencies",
() => {
test("getDependencies", () => {
/* Parse includes has set the dependencies */
project->Project.getDependencies("main")->expect == ["common"]
/* If there were no includes than there would be no dependencies */
/* However if there was a syntax error at includes then would be no dependencies also */
/* Therefore looking at dependencies is not the right way to load includes */
/* getDependencies does not distinguish between setContinues or parseIncludes */
},
)
test(
"getIncludes",
() => {
})
test("getIncludes", () => {
/* Parse includes has set the includes */
switch project->Project.getIncludes("main") {
| Ok(includes) => includes->expect == ["common"]
| Error(err) => err->SqError.toString->fail
| Error(err) => err->Reducer_ErrorValue.errorToString->fail
}
/* If the includes cannot be parsed then you get a syntax error.
Otherwise you get the includes.
If there is no syntax error then you can load that file and use setSource to add it to the project.
And so on recursively... */
},
)
test(
"getDependents",
() => {
})
test("getDependents", () => {
/* For any reason, you are able to query what other sources
include or depend on the current source.
But you don't need to use this to execute the projects.
It is provided for completeness of information. */
project->Project.getDependents("main")->expect == []
/* Nothing is depending on or including main */
},
)
})
describe(
"Real Like",
() => {
describe("Real Like", () => {
/* Now let's look at recursive and possibly cyclic includes */
/* There is no function provided to load the include files.
Because we have no idea if will it be an ordinary function or will it use promises.
@ -96,11 +85,10 @@ Here we will finally proceed to a real life scenario. */
let rIncludes = project->Project.getIncludes(sourceName)
switch rIncludes {
/* Maybe there is an include syntax error */
| Error(err) => err->SqError.toString->Js.Exn.raiseError
| Error(err) => err->Reducer_ErrorValue.errorToString->Js.Exn.raiseError
| Ok(includes) =>
includes->Belt.Array.forEach(
newIncludeName => {
includes->Belt.Array.forEach(newIncludeName => {
/* We have got one of the new includes.
Let's load it and add it to the project */
let newSource = loadSource(newIncludeName)
@ -109,8 +97,7 @@ Here we will finally proceed to a real life scenario. */
/* Of course the new source might have includes too. */
/* Let's recursively load them */
project->loadIncludesRecursively(newIncludeName, newVisited)
},
)
})
}
}
}
@ -156,18 +143,12 @@ Here we will finally proceed to a real life scenario. */
let result = project->Project.getResult("main")
let bindings = project->Project.getBindings("main")
/* And see the result and bindings.. */
test(
"recursive includes",
() => {
(
result->Reducer_Value.toStringResult,
bindings->Reducer_Value.toStringRecord,
)->expect == ("Ok(6)", "{a: 6,b: 2}")
test("recursive includes", () => {
(result->Reducer_Value.toStringResult, bindings->Reducer_Value.toStringRecord)->expect ==
("Ok(6)", "{a: 6,b: 2}")
/* Everything as expected */
},
)
},
)
})
})
})
describe("Includes myFile as myVariable", () => {
@ -182,20 +163,14 @@ Here we will finally proceed to a real life scenario. */
`,
)
Project.parseIncludes(project, "main")
test(
"getDependencies",
() => {
test("getDependencies", () => {
Project.getDependencies(project, "main")->expect == ["common"]
},
)
test(
"getIncludes",
() => {
})
test("getIncludes", () => {
switch Project.getIncludes(project, "main") {
| Ok(includes) => includes->expect == ["common"]
| Error(err) => err->SqError.toString->fail
| Error(err) => err->Reducer_ErrorValue.errorToString->fail
}
},
)
})
})
})

View File

@ -30,9 +30,8 @@ describe("ReducerProject Tutorial", () => {
})
test("userResults", () => {
let userResultsAsString = Belt.Array.map(
userResults,
aResult => aResult->Reducer_Value.toStringResult,
let userResultsAsString = Belt.Array.map(userResults, aResult =>
aResult->Reducer_Value.toStringResult
)
userResultsAsString->expect == ["Ok(2)", "Ok(4)", "Ok(6)", "Ok(8)", "Ok(10)"]
})

View File

@ -1,41 +0,0 @@
open Jest
open Expect
describe("SqError.Message", () => {
test("toString", () =>
expect(SqError.Message.REOther("test error")->SqError.Message.toString)->toBe(
"Error: test error",
)
)
})
describe("SqError", () => {
test("fromMessage", () =>
expect(SqError.Message.REOther("test error")->SqError.fromMessage->SqError.toString)->toBe(
"Error: test error",
)
)
test("toStringWithStackTrace with empty stacktrace", () =>
expect(
SqError.Message.REOther("test error")->SqError.fromMessage->SqError.toStringWithStackTrace,
)->toBe("Error: test error")
)
test("toStringWithStackTrace", () => {
let frameStack =
Reducer_FrameStack.make()
->Reducer_FrameStack.extend("frame1", None)
->Reducer_FrameStack.extend("frame2", None)
expect(
SqError.Message.REOther("test error")
->SqError.fromMessageWithFrameStack(frameStack)
->SqError.toStringWithStackTrace,
)->toBe(`Error: test error
Stack trace:
frame2
frame1
`)
})
})

View File

@ -82,10 +82,6 @@ describe("FunctionRegistry Library", () => {
"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])",
)
testEvalToBe(
"SampleSet.fromList([1, 2, 3])",
"Error(Error: Too few samples when constructing sample set)",
)
testEvalToBe("Dict.merge({a: 1, b: 2}, {b: 3, c: 4, d: 5})", "Ok({a: 1,b: 3,c: 4,d: 5})")
testEvalToBe(
@ -99,19 +95,15 @@ describe("FunctionRegistry Library", () => {
})
describe("Fn auto-testing", () => {
testAll(
"tests of validity",
examples,
r => {
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),
FunctionRegistry_Core.Registry.allExamplesWithFns(registry)->E.A2.filter(((fn, _)) =>
E.O.isSome(fn.output)
),
),
((fn, example)) => {

View File

@ -45,12 +45,12 @@ let toExtDist: option<DistributionTypes.genericDist> => DistributionTypes.generi
let unpackFloat = x => x->toFloat->toExtFloat
let unpackDist = y => y->toDist->toExtDist
let mkNormal = (mean, stdev) => DistributionTypes.Symbolic(#Normal({mean, stdev}))
let mkBeta = (alpha, beta) => DistributionTypes.Symbolic(#Beta({alpha, beta}))
let mkNormal = (mean, stdev) => DistributionTypes.Symbolic(#Normal({mean: mean, stdev: stdev}))
let mkBeta = (alpha, beta) => DistributionTypes.Symbolic(#Beta({alpha: alpha, beta: beta}))
let mkExponential = rate => DistributionTypes.Symbolic(#Exponential({rate: rate}))
let mkUniform = (low, high) => DistributionTypes.Symbolic(#Uniform({low, high}))
let mkCauchy = (local, scale) => DistributionTypes.Symbolic(#Cauchy({local, scale}))
let mkLognormal = (mu, sigma) => DistributionTypes.Symbolic(#Lognormal({mu, sigma}))
let mkUniform = (low, high) => DistributionTypes.Symbolic(#Uniform({low: low, high: high}))
let mkCauchy = (local, scale) => DistributionTypes.Symbolic(#Cauchy({local: local, scale: scale}))
let mkLognormal = (mu, sigma) => DistributionTypes.Symbolic(#Lognormal({mu: mu, sigma: sigma}))
let mkDelta = x => DistributionTypes.Symbolic(#Float(x))
let normalMake = SymbolicDist.Normal.make

View File

@ -25,6 +25,7 @@
],
"suffix": ".bs.js",
"namespace": true,
"bs-dependencies": ["bisect_ppx"],
"bs-dev-dependencies": [
"@glennsl/rescript-jest",
"rescript-fast-check",
@ -44,5 +45,8 @@
"refmt": 3,
"warnings": {
"number": "+A-42-48-9-30-4"
}
},
"ppx-flags": [
["../../node_modules/bisect_ppx/ppx", "--exclude-files", ".*_test\\.res$$"]
]
}

View File

@ -2,6 +2,9 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
setupFilesAfterEnv: [
"<rootdir>/../../node_modules/bisect_ppx/src/runtime/js/jest.bs.js",
],
testPathIgnorePatterns: [
".*Fixtures.bs.js",
"/node_modules/",

View File

@ -22,8 +22,10 @@
"test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*",
"test:watch": "jest --watchAll",
"test:fnRegistry": "jest __tests__/SquiggleLibrary/SquiggleLibrary_FunctionRegistryLibrary_test.bs.js",
"coverage:local": "jest --coverage && echo && echo 'Open ./coverage/lcov-report/index.html to see the detailed report.'",
"coverage": "jest --coverage && codecov",
"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",
"coverage:ts:ci": "yarn coverage:ts && codecov",
"lint:rescript": "./lint.sh",
"lint:prettier": "prettier --check .",
"lint": "yarn lint:rescript && yarn lint:prettier",
@ -39,7 +41,7 @@
],
"author": "Quantified Uncertainty Research Institute",
"dependencies": {
"@rescript/std": "^10.0.0",
"@rescript/std": "^9.1.4",
"@stdlib/stats": "^0.0.13",
"jstat": "^1.9.5",
"lodash": "^4.17.21",
@ -48,20 +50,24 @@
},
"devDependencies": {
"@glennsl/rescript-jest": "^0.9.2",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/jest": "^27.5.0",
"chalk": "^5.1.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"bisect_ppx": "^2.7.1",
"chalk": "^5.0.1",
"codecov": "^3.8.3",
"fast-check": "^3.1.4",
"gentype": "^4.5.0",
"jest": "^27.5.1",
"moduleserve": "^0.9.1",
"nyc": "^15.1.0",
"peggy": "^2.0.1",
"prettier": "^2.7.1",
"reanalyze": "^2.23.0",
"rescript": "^10.0.0",
"rescript": "^9.1.4",
"rescript-fast-check": "^1.1.1",
"rescript-js-map": "^1.1.0",
"ts-jest": "^29.0.3",
"ts-jest": "^27.1.4",
"ts-loader": "^9.4.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",

View File

@ -36,6 +36,6 @@ export const run = (src, { output, sampleCount } = {}) => {
"Time:",
String(time),
result.tag === "Error" ? red(result.tag) : green(result.tag),
result.tag === "Error" ? result.value.toStringWithFrameStack() : ""
result.tag === "Error" ? result.value.toString() : ""
);
};

View File

@ -1,18 +1,11 @@
#!/usr/bin/env node
import { run } from "./lib.mjs";
import { Command } from "commander";
const program = new Command();
program.arguments("<string>");
const options = program.parse(process.argv);
const src = program.args[0];
const src = process.argv[2];
if (!src) {
throw new Error("Expected src");
}
console.log(`Running ${src}`);
const sampleCount = process.env.SAMPLE_COUNT;

View File

@ -1,48 +1,17 @@
import * as RSError from "../rescript/SqError.gen";
import * as RSReducerT from "../rescript/Reducer/Reducer_T.gen";
import * as RSFrameStack from "../rescript/Reducer/Reducer_FrameStack.gen";
export { location as SqLocation } from "../rescript/Reducer/Reducer_Peggy/Reducer_Peggy_Parse.gen";
import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen";
export class SqError {
constructor(private _value: RSError.t) {}
constructor(private _value: RSErrorValue.reducerErrorValue) {}
toString() {
return RSError.toString(this._value);
return RSErrorValue.toString(this._value);
}
toStringWithStackTrace() {
return RSError.toStringWithStackTrace(this._value);
static createTodoError(v: string) {
return new SqError(RSErrorValue.createTodoError(v));
}
static createOtherError(v: string) {
return new SqError(RSError.createOtherError(v));
}
getTopFrame(): SqFrame | undefined {
const frame = RSFrameStack.getTopFrame(RSError.getFrameStack(this._value));
return frame ? new SqFrame(frame) : undefined;
}
getFrameArray(): SqFrame[] {
const frames = RSError.getFrameArray(this._value);
return frames.map((frame) => new SqFrame(frame));
}
location() {
return this.getTopFrame()?.location();
}
}
export class SqFrame {
constructor(private _value: RSReducerT.frame) {}
name(): string {
return RSFrameStack.Frame.getName(this._value);
}
location() {
return RSFrameStack.Frame.getLocation(this._value);
return new SqError(RSErrorValue.createOtherError(v));
}
}

View File

@ -1,5 +1,5 @@
import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen";
import * as RSError from "../rescript/SqError.gen";
import { reducerErrorValue } from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen";
import { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen";
import { SqError } from "./SqError";
import { SqRecord } from "./SqRecord";
@ -54,7 +54,7 @@ export class SqProject {
return resultMap2(
RSProject.getIncludes(this._value, sourceId),
(a) => a,
(v: RSError.t) => new SqError(v)
(v: reducerErrorValue) => new SqError(v)
);
}
@ -108,7 +108,7 @@ export class SqProject {
items: [],
})
),
(v: RSError.t) => new SqError(v)
(v: reducerErrorValue) => new SqError(v)
);
}

View File

@ -1,3 +1,4 @@
import { isParenthesisNode } from "mathjs";
import { SqProject } from "./SqProject";
type PathItem = string | number;

View File

@ -13,7 +13,7 @@ export {
environment,
defaultEnvironment,
} from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen";
export { SqError, SqFrame, SqLocation } from "./SqError";
export { SqError } from "./SqError";
export { SqShape } from "./SqPointSetDist";
export { resultMap } from "./types";

View File

@ -141,7 +141,6 @@ let rec run = (~env: env, functionCallInfo: functionCallInfo): outputType => {
Js.log2("Console log requested: ", dist)
Dist(dist)
}
| #ToDist(Normalize) => dist->GenericDist.normalize->Dist
| #ToScore(LogScore(answer, prior)) =>
GenericDist.Score.logScore(~estimate=dist, ~answer, ~prior, ~env)

View File

@ -99,7 +99,6 @@ let toFloatOperation = (
}
}
}
| (#Stdev | #Variance | #Mode) as op =>
switch t {
| SampleSet(s) =>
@ -130,7 +129,7 @@ let toPointSet = (
SampleSetDist.toPointSetDist(
~samples=r,
~samplingInputs={
sampleCount,
sampleCount: sampleCount,
outputXYPoints: xyPointLength,
pointSetDistLength: xyPointLength,
kernelWidth: None,
@ -428,7 +427,6 @@ module AlgebraicCombination = {
~toSampleSetFn,
)
}
| (None, AsMonteCarlo) =>
StrategyCallOnValidatedInputs.monteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
| (None, AsSymbolic) =>
@ -445,7 +443,6 @@ module AlgebraicCombination = {
)}`
Error(RequestedStrategyInvalidError(errString))
}
| Some(convOp) => StrategyCallOnValidatedInputs.convolution(toPointSetFn, convOp, t1, t2)
}
}

View File

@ -69,7 +69,7 @@ let toDiscretePointMassesFromTriangulars = (
()
}
{n: n - 2, masses, means, variances}
{n: n - 2, masses: masses, means: means, variances: variances}
} else {
for i in 1 to n - 2 {
// area of triangle = width * height / 2
@ -91,7 +91,7 @@ let toDiscretePointMassesFromTriangulars = (
) |> ignore
()
}
{n: n - 2, masses, means, variances}
{n: n - 2, masses: masses, means: means, variances: variances}
}
}
@ -184,7 +184,7 @@ let toDiscretePointMassesFromDiscrete = (s: PointSetTypes.xyShape): pointMassesW
let means: array<float> = Belt.Array.makeBy(n, i => xs[i])
let variances: array<float> = Belt.Array.makeBy(n, _ => 0.0)
{n, masses, means, variances}
{n: n, masses: masses, means: means, variances: variances}
}
type argumentPosition = First | Second

View File

@ -45,16 +45,16 @@ module Analysis = {
let getShape = (t: t) => t.xyShape
let interpolation = (t: t) => t.interpolation
let make = (~interpolation=#Linear, ~integralSumCache=None, ~integralCache=None, xyShape): t => {
xyShape,
interpolation,
integralSumCache,
integralCache,
xyShape: xyShape,
interpolation: interpolation,
integralSumCache: integralSumCache,
integralCache: integralCache,
}
let shapeMap = (fn, {xyShape, interpolation, integralSumCache, integralCache}: t): t => {
xyShape: fn(xyShape),
interpolation,
integralSumCache,
integralCache,
interpolation: interpolation,
integralSumCache: integralSumCache,
integralCache: integralCache,
}
let lastY = (t: t) => t |> getShape |> XYShape.T.lastY
let oShapeMap = (fn, {xyShape, interpolation, integralSumCache, integralCache}: t): option<
@ -135,10 +135,10 @@ let shapeFn = (fn, t: t) => t |> getShape |> fn
let updateIntegralSumCache = (integralSumCache, t: t): t => {
...t,
integralSumCache,
integralSumCache: integralSumCache,
}
let updateIntegralCache = (integralCache, t: t): t => {...t, integralCache}
let updateIntegralCache = (integralCache, t: t): t => {...t, integralCache: integralCache}
let sum = (
~integralSumCachesFn: (float, float) => option<float>=(_, _) => None,

View File

@ -4,14 +4,14 @@ open Distributions
type t = PointSetTypes.discreteShape
let make = (~integralSumCache=None, ~integralCache=None, xyShape): t => {
xyShape,
integralSumCache,
integralCache,
xyShape: xyShape,
integralSumCache: integralSumCache,
integralCache: integralCache,
}
let shapeMap = (fn, {xyShape, integralSumCache, integralCache}: t): t => {
xyShape: fn(xyShape),
integralSumCache,
integralCache,
integralSumCache: integralSumCache,
integralCache: integralCache,
}
let getShape = (t: t) => t.xyShape
let oShapeMap = (fn, {xyShape, integralSumCache, integralCache}: t): option<t> =>
@ -63,12 +63,12 @@ let reduce = (
let updateIntegralSumCache = (integralSumCache, t: t): t => {
...t,
integralSumCache,
integralSumCache: integralSumCache,
}
let updateIntegralCache = (integralCache, t: t): t => {
...t,
integralCache,
integralCache: integralCache,
}
/* This multiples all of the data points together and creates a new discrete distribution from the results.

View File

@ -4,10 +4,10 @@ open Distributions
type t = PointSetTypes.mixedShape
let make = (~integralSumCache=None, ~integralCache=None, ~continuous, ~discrete): t => {
continuous,
discrete,
integralSumCache,
integralCache,
continuous: continuous,
discrete: discrete,
integralSumCache: integralSumCache,
integralCache: integralCache,
}
let totalLength = (t: t): int => {
@ -35,7 +35,7 @@ let toDiscrete = ({discrete}: t) => Some(discrete)
let updateIntegralCache = (integralCache, t: t): t => {
...t,
integralCache,
integralCache: integralCache,
}
let combinePointwise = (

View File

@ -79,8 +79,8 @@ module MixedPoint = {
type t = mixedPoint
let toContinuousValue = (t: t) => t.continuous
let toDiscreteValue = (t: t) => t.discrete
let makeContinuous = (continuous: float): t => {continuous, discrete: 0.0}
let makeDiscrete = (discrete: float): t => {continuous: 0.0, discrete}
let makeContinuous = (continuous: float): t => {continuous: continuous, discrete: 0.0}
let makeDiscrete = (discrete: float): t => {continuous: 0.0, discrete: discrete}
let fmap = (fn: float => float, t: t) => {
continuous: fn(t.continuous),

View File

@ -4,6 +4,13 @@ module Error = {
type sampleSetError =
TooFewSamples | NonNumericInput(string) | OperationError(Operation.operationError)
let sampleSetErrorToString = (err: sampleSetError): string =>
switch err {
| TooFewSamples => "Too few samples when constructing sample set"
| NonNumericInput(err) => `Found a non-number in input: ${err}`
| OperationError(err) => Operation.Error.toString(err)
}
@genType
type pointsetConversionError = TooFewSamplesForConversionToPointSet

View File

@ -7,7 +7,7 @@ module Normal = {
type t = normal
let make = (mean: float, stdev: float): result<symbolicDist, string> =>
stdev > 0.0
? Ok(#Normal({mean, stdev}))
? Ok(#Normal({mean: mean, stdev: stdev}))
: Error("Standard deviation of normal distribution must be larger than 0")
let pdf = (x, t: t) => Jstat.Normal.pdf(x, t.mean, t.stdev)
let cdf = (x, t: t) => Jstat.Normal.cdf(x, t.mean, t.stdev)
@ -15,7 +15,7 @@ module Normal = {
let from90PercentCI = (low, high) => {
let mean = E.A.Floats.mean([low, high])
let stdev = (high -. low) /. (2. *. normal95confidencePoint)
#Normal({mean, stdev})
#Normal({mean: mean, stdev: stdev})
}
let inv = (p, t: t) => Jstat.Normal.inv(p, t.mean, t.stdev)
let sample = (t: t) => Jstat.Normal.sample(t.mean, t.stdev)
@ -25,12 +25,12 @@ module Normal = {
let add = (n1: t, n2: t) => {
let mean = n1.mean +. n2.mean
let stdev = Js.Math.sqrt(n1.stdev ** 2. +. n2.stdev ** 2.)
#Normal({mean, stdev})
#Normal({mean: mean, stdev: stdev})
}
let subtract = (n1: t, n2: t) => {
let mean = n1.mean -. n2.mean
let stdev = Js.Math.sqrt(n1.stdev ** 2. +. n2.stdev ** 2.)
#Normal({mean, stdev})
#Normal({mean: mean, stdev: stdev})
}
// TODO: is this useful here at all? would need the integral as well ...
@ -38,7 +38,7 @@ module Normal = {
let mean =
(n1.mean *. n2.stdev ** 2. +. n2.mean *. n1.stdev ** 2.) /. (n1.stdev ** 2. +. n2.stdev ** 2.)
let stdev = 1. /. (1. /. n1.stdev ** 2. +. 1. /. n2.stdev ** 2.)
#Normal({mean, stdev})
#Normal({mean: mean, stdev: stdev})
}
let operate = (operation: Operation.Algebraic.t, n1: t, n2: t) =>
@ -88,7 +88,7 @@ module Cauchy = {
type t = cauchy
let make = (local, scale): result<symbolicDist, string> =>
scale > 0.0
? Ok(#Cauchy({local, scale}))
? Ok(#Cauchy({local: local, scale: scale}))
: Error("Cauchy distribution scale parameter must larger than 0.")
let pdf = (x, t: t) => Jstat.Cauchy.pdf(x, t.local, t.scale)
let cdf = (x, t: t) => Jstat.Cauchy.cdf(x, t.local, t.scale)
@ -102,7 +102,7 @@ module Triangular = {
type t = triangular
let make = (low, medium, high): result<symbolicDist, string> =>
low < medium && medium < high
? Ok(#Triangular({low, medium, high}))
? Ok(#Triangular({low: low, medium: medium, high: high}))
: Error("Triangular values must be increasing order.")
let pdf = (x, t: t) => Jstat.Triangular.pdf(x, t.low, t.high, t.medium) // not obvious in jstat docs that high comes before medium?
let cdf = (x, t: t) => Jstat.Triangular.cdf(x, t.low, t.high, t.medium)
@ -116,7 +116,7 @@ module Beta = {
type t = beta
let make = (alpha, beta) =>
alpha > 0.0 && beta > 0.0
? Ok(#Beta({alpha, beta}))
? Ok(#Beta({alpha: alpha, beta: beta}))
: Error("Beta distribution parameters must be positive")
let pdf = (x, t: t) => Jstat.Beta.pdf(x, t.alpha, t.beta)
let cdf = (x, t: t) => Jstat.Beta.cdf(x, t.alpha, t.beta)
@ -150,7 +150,7 @@ module Lognormal = {
type t = lognormal
let make = (mu, sigma) =>
sigma > 0.0
? Ok(#Lognormal({mu, sigma}))
? Ok(#Lognormal({mu: mu, sigma: sigma}))
: Error("Lognormal standard deviation must be larger than 0")
let pdf = (x, t: t) => Jstat.Lognormal.pdf(x, t.mu, t.sigma)
let cdf = (x, t: t) => Jstat.Lognormal.cdf(x, t.mu, t.sigma)
@ -164,7 +164,7 @@ module Lognormal = {
let logHigh = Js.Math.log(high)
let mu = E.A.Floats.mean([logLow, logHigh])
let sigma = (logHigh -. logLow) /. (2.0 *. normal95confidencePoint)
#Lognormal({mu, sigma})
#Lognormal({mu: mu, sigma: sigma})
}
let fromMeanAndStdev = (mean, stdev) => {
// https://math.stackexchange.com/questions/2501783/parameters-of-a-lognormal-distribution
@ -174,7 +174,7 @@ module Lognormal = {
let meanSquared = mean ** 2.
let mu = 2. *. Js.Math.log(mean) -. 0.5 *. Js.Math.log(variance +. meanSquared)
let sigma = Js.Math.sqrt(Js.Math.log(variance /. meanSquared +. 1.))
Ok(#Lognormal({mu, sigma}))
Ok(#Lognormal({mu: mu, sigma: sigma}))
} else {
Error("Lognormal standard deviation must be larger than 0")
}
@ -184,14 +184,14 @@ module Lognormal = {
// https://wikiless.org/wiki/Log-normal_distribution?lang=en#Multiplication_and_division_of_independent,_log-normal_random_variables
let mu = l1.mu +. l2.mu
let sigma = Js.Math.sqrt(l1.sigma ** 2. +. l2.sigma ** 2.)
#Lognormal({mu, sigma})
#Lognormal({mu: mu, sigma: sigma})
}
let divide = (l1, l2) => {
let mu = l1.mu -. l2.mu
// We believe the ratiands will have covariance zero.
// See here https://stats.stackexchange.com/questions/21735/what-are-the-mean-and-variance-of-the-ratio-of-two-lognormal-variables for details
let sigma = Js.Math.sqrt(l1.sigma ** 2. +. l2.sigma ** 2.)
#Lognormal({mu, sigma})
#Lognormal({mu: mu, sigma: sigma})
}
let operate = (operation: Operation.Algebraic.t, n1: t, n2: t) =>
switch operation {
@ -220,7 +220,7 @@ module Lognormal = {
module Uniform = {
type t = uniform
let make = (low, high) =>
high > low ? Ok(#Uniform({low, high})) : Error("High must be larger than low")
high > low ? Ok(#Uniform({low: low, high: high})) : Error("High must be larger than low")
let pdf = (x, t: t) => Jstat.Uniform.pdf(x, t.low, t.high)
let cdf = (x, t: t) => Jstat.Uniform.cdf(x, t.low, t.high)
@ -239,7 +239,9 @@ module Uniform = {
module Logistic = {
type t = logistic
let make = (location, scale) =>
scale > 0.0 ? Ok(#Logistic({location, scale})) : Error("Scale must be positive")
scale > 0.0
? Ok(#Logistic({location: location, scale: scale}))
: Error("Scale must be positive")
let pdf = (x, t: t) => Stdlib.Logistic.pdf(x, t.location, t.scale)
let cdf = (x, t: t) => Stdlib.Logistic.cdf(x, t.location, t.scale)
@ -283,7 +285,7 @@ module Gamma = {
let make = (shape: float, scale: float) => {
if shape > 0. {
if scale > 0. {
Ok(#Gamma({shape, scale}))
Ok(#Gamma({shape: shape, scale: scale}))
} else {
Error("scale must be larger than 0")
}
@ -541,6 +543,6 @@ module T = {
| _ =>
let xs = interpolateXs(~xSelection, d, sampleCount)
let ys = xs |> E.A.fmap(x => pdf(x, d))
Continuous(Continuous.make(~integralSumCache=Some(1.0), {xs, ys}))
Continuous(Continuous.make(~integralSumCache=Some(1.0), {xs: xs, ys: ys}))
}
}

View File

@ -5,7 +5,7 @@ let nameSpace = "" // no namespaced versions
type simpleDefinition = {
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorMessage>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
}
let makeFnMany = (name: string, definitions: array<simpleDefinition>) =>
@ -22,8 +22,8 @@ let makeFnMany = (name: string, definitions: array<simpleDefinition>) =>
let makeFn = (
name: string,
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorMessage>,
) => makeFnMany(name, [{inputs, fn}])
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
) => makeFnMany(name, [{inputs: inputs, fn: fn}])
let library = [
Make.ff2f(~name="add", ~fn=(x, y) => x +. y, ()), // infix + (see Reducer/Reducer_Peggy/helpers.ts)
@ -62,7 +62,6 @@ let library = [
let answer = Js.String2.concat(a, b)
answer->Reducer_T.IEvString->Ok
}
| _ => Error(impossibleError)
}
}),
@ -73,7 +72,6 @@ let library = [
let _ = Js.Array2.pushMany(a, b)
a->Reducer_T.IEvArray->Ok
}
| _ => Error(impossibleError)
}
}),
@ -83,7 +81,6 @@ let library = [
Js.log(value->Reducer_Value.toString)
value->Ok
}
| _ => Error(impossibleError)
}
}),
@ -93,7 +90,6 @@ let library = [
Js.log(`${label}: ${value->Reducer_Value.toString}`)
value->Ok
}
| _ => Error(impossibleError)
}
}),

View File

@ -67,7 +67,7 @@ module Integration = {
let applyFunctionAtFloatToFloatOption = (point: float) => {
// Defined here so that it has access to environment, reducer
let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point)
let resultAsInternalExpression = Reducer_Lambda.doLambdaCall(
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall(
aLambda,
[pointAsInternalExpression],
environment,
@ -77,7 +77,7 @@ module Integration = {
| Reducer_T.IEvNumber(x) => Ok(x)
| _ =>
Error(
"Error 1 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->SqError.Message.REOther,
"Error 1 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->Reducer_ErrorValue.REOther,
)
}
result
@ -135,18 +135,16 @@ module Integration = {
let wrappedResult = result->Reducer_T.IEvNumber->Ok
wrappedResult
}
| (Error(b), _) => Error(b)
| (_, Error(b)) => Error(b)
}
resultWithOuterPoints
}
| Error(b) =>
("Integration error 2 in Danger.integrate. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead." ++
"Original error: " ++
b->SqError.Message.toString)
->SqError.Message.REOther
b->Reducer_ErrorValue.errorToString)
->Reducer_ErrorValue.REOther
->Error
}
result
@ -171,7 +169,7 @@ module Integration = {
let result = switch inputs {
| [_, _, _, IEvNumber(0.0)] =>
"Integration error 4 in Danger.integrate: Increment can't be 0."
->SqError.Message.REOther
->Reducer_ErrorValue.REOther
->Error
| [
IEvLambda(aLambda),
@ -189,7 +187,7 @@ module Integration = {
)
| _ =>
Error(
SqError.Message.REOther(
Reducer_ErrorValue.REOther(
"Integration error 5 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))",
),
)
@ -215,7 +213,7 @@ module Integration = {
let result = switch inputs {
| [_, _, _, IEvNumber(0.0)] =>
"Integration error in Danger.integrate: Increment can't be 0."
->SqError.Message.REOther
->Reducer_ErrorValue.REOther
->Error
| [IEvLambda(aLambda), IEvNumber(min), IEvNumber(max), IEvNumber(epsilon)] =>
Helpers.integrateFunctionBetweenWithNumIntegrationPoints(
@ -227,11 +225,11 @@ module Integration = {
reducer,
)->E.R2.errMap(b =>
("Integration error 7 in Danger.integrate. Something went wrong along the way: " ++
b->SqError.Message.toString)->SqError.Message.REOther
b->Reducer_ErrorValue.errorToString)->Reducer_ErrorValue.REOther
)
| _ =>
"Integration error 8 in Danger.integrate. Remember that inputs are (function, number (min), number (max), number(increment))"
->SqError.Message.REOther
->Reducer_ErrorValue.REOther
->Error
}
result
@ -248,7 +246,7 @@ module DiminishingReturns = {
module Helpers = {
type diminishingReturnsAccumulatorInner = {
optimalAllocations: array<float>,
currentMarginalReturns: result<array<float>, errorMessage>,
currentMarginalReturns: result<array<float>, errorValue>,
}
let findBiggestElementIndex = (xs: array<float>) =>
E.A.reducei(xs, 0, (acc, newElement, index) => {
@ -257,7 +255,7 @@ module DiminishingReturns = {
| false => acc
}
})
type diminishingReturnsAccumulator = result<diminishingReturnsAccumulatorInner, errorMessage>
type diminishingReturnsAccumulator = result<diminishingReturnsAccumulatorInner, errorValue>
// TODO: This is so complicated, it probably should be its own file. It might also make sense to have it work in Rescript directly, taking in a function rather than a reducer; then something else can wrap that function in the reducer/lambdas/environment.
/*
The key idea for this function is that
@ -292,25 +290,25 @@ module DiminishingReturns = {
) {
| (false, _, _, _) =>
Error(
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1."->SqError.Message.REOther,
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, number of functions should be greater than 1."->Reducer_ErrorValue.REOther,
)
| (_, false, _, _) =>
Error(
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0."->SqError.Message.REOther,
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, funds should be greater than 0."->Reducer_ErrorValue.REOther,
)
| (_, _, false, _) =>
Error(
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0."->SqError.Message.REOther,
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be greater than 0."->Reducer_ErrorValue.REOther,
)
| (_, _, _, false) =>
Error(
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount."->SqError.Message.REOther,
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions, approximateIncrement should be smaller than funds amount."->Reducer_ErrorValue.REOther,
)
| (true, true, true, true) => {
let applyFunctionAtPoint = (lambda, point: float) => {
// Defined here so that it has access to environment, reducer
let pointAsInternalExpression = FunctionRegistry_Helpers.Wrappers.evNumber(point)
let resultAsInternalExpression = Reducer_Lambda.doLambdaCall(
let resultAsInternalExpression = Reducer_Expression_Lambda.doLambdaCall(
lambda,
[pointAsInternalExpression],
environment,
@ -320,7 +318,7 @@ module DiminishingReturns = {
| Reducer_T.IEvNumber(x) => Ok(x)
| _ =>
Error(
"Error 1 in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->SqError.Message.REOther,
"Error 1 in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. It's possible that your function doesn't return a number, try definining auxiliaryFunction(x) = mean(yourFunction(x)) and integrate auxiliaryFunction instead"->Reducer_ErrorValue.REOther,
)
}
}
@ -364,7 +362,6 @@ module DiminishingReturns = {
result[indexOfBiggestDMR] = value
Ok(result)
}
| Error(b) => Error(b)
}
@ -374,12 +371,10 @@ module DiminishingReturns = {
}
Ok(newAcc)
}
| Error(b) => Error(b)
}
newAccWrapped
}
| Error(b) => Error(b)
}
})
@ -416,7 +411,7 @@ module DiminishingReturns = {
| Reducer_T.IEvLambda(lambda) => Ok(lambda)
| _ =>
"Error in Danger.optimalAllocationGivenDiminishingMarginalReturnsForManyFunctions. A member of the array wasn't a function"
->SqError.Message.REOther
->Reducer_ErrorValue.REOther
->Error
}
}, innerlambdas)
@ -432,15 +427,13 @@ module DiminishingReturns = {
)
result
}
| Error(b) => Error(b)
}
result
}
| _ =>
"Error in Danger.diminishingMarginalReturnsForTwoFunctions"
->SqError.Message.REOther
->Reducer_ErrorValue.REOther
->Error
},
(),

View File

@ -4,7 +4,7 @@ open FunctionRegistry_Helpers
let makeFn = (
name: string,
inputs: array<frType>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorMessage>,
fn: array<Reducer_T.value> => result<Reducer_T.value, errorValue>,
) =>
Function.make(
~name,
@ -66,7 +66,7 @@ let library = [
| [IEvNumber(year)] =>
switch DateTime.Date.makeFromYear(year) {
| Ok(t) => IEvDate(t)->Ok
| Error(e) => SqError.Message.RETodo(e)->Error
| Error(e) => Reducer_ErrorValue.RETodo(e)->Error
}
| _ => Error(impossibleError)
}

View File

@ -17,7 +17,7 @@ module Internals = {
->E.A2.fmap(((key, value)) => Wrappers.evArray([IEvString(key), value]))
->Wrappers.evArray
let fromList = (items: array<Reducer_T.value>): result<Reducer_T.value, errorMessage> =>
let fromList = (items: array<Reducer_T.value>): result<Reducer_T.value, errorValue> =>
items
->E.A2.fmap(item => {
switch (item: Reducer_T.value) {
@ -76,7 +76,7 @@ let library = [
->Belt.Array.map(dictValue =>
switch dictValue {
| IEvRecord(dict) => dict
| _ => impossibleError->SqError.Message.throw
| _ => impossibleError->Reducer_ErrorValue.toException
}
)
->Internals.mergeMany

View File

@ -16,14 +16,13 @@ module DistributionCreation = {
r
->E.R.bind(Process.DistOrNumberToDist.twoValuesUsingSymbolicDist(~fn, ~values=_, ~env))
->E.R2.fmap(Wrappers.evDistribution)
->E.R2.errMap(e => SqError.Message.REOther(e))
->E.R2.errMap(e => Reducer_ErrorValue.REOther(e))
let make = (name, fn) => {
FnDefinition.make(
~name,
~inputs=[FRTypeDistOrNumber, FRTypeDistOrNumber],
~run=(inputs, context, _) =>
inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env=context.environment),
~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.twoDistOrNumber->process(~fn, ~env),
(),
)
}
@ -32,10 +31,8 @@ module DistributionCreation = {
FnDefinition.make(
~name,
~inputs=[FRTypeRecord([("p5", FRTypeDistOrNumber), ("p95", FRTypeDistOrNumber)])],
~run=(inputs, context, _) =>
inputs
->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95"))
->process(~fn, ~env=context.environment),
~run=(inputs, env, _) =>
inputs->Prepare.ToValueTuple.Record.twoDistOrNumber(("p5", "p95"))->process(~fn, ~env),
(),
)
}
@ -44,10 +41,10 @@ module DistributionCreation = {
FnDefinition.make(
~name,
~inputs=[FRTypeRecord([("mean", FRTypeDistOrNumber), ("stdev", FRTypeDistOrNumber)])],
~run=(inputs, context, _) =>
~run=(inputs, env, _) =>
inputs
->Prepare.ToValueTuple.Record.twoDistOrNumber(("mean", "stdev"))
->process(~fn, ~env=context.environment),
->process(~fn, ~env),
(),
)
}
@ -58,14 +55,13 @@ module DistributionCreation = {
r
->E.R.bind(Process.DistOrNumberToDist.oneValueUsingSymbolicDist(~fn, ~value=_, ~env))
->E.R2.fmap(Wrappers.evDistribution)
->E.R2.errMap(e => SqError.Message.REOther(e))
->E.R2.errMap(e => Reducer_ErrorValue.REOther(e))
let make = (name, fn) =>
FnDefinition.make(
~name,
~inputs=[FRTypeDistOrNumber],
~run=(inputs, context, _) =>
inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env=context.environment),
~run=(inputs, env, _) => inputs->Prepare.ToValueTuple.oneDistOrNumber->process(~fn, ~env),
(),
)
}

View File

@ -20,7 +20,6 @@ module Declaration = {
->E.A.R.firstErrorOrOpen
->E.R2.fmap(args => Reducer_T.IEvDeclaration(Declaration.make(lambda, args)))
}
| Error(r) => Error(r)
| Ok(_) => Error(impossibleErrorString)
}

View File

@ -140,7 +140,6 @@ module Old = {
| Error(err) => error(err)
}
}
| Some(IEvNumber(_))
| Some(IEvDistribution(_)) =>
switch parseDistributionArray(args) {
@ -193,7 +192,6 @@ module Old = {
}
Helpers.toFloatFn(fn, dist, ~env)
}
| ("integralSum", [IEvDistribution(dist)]) => Helpers.toFloatFn(#IntegralSum, dist, ~env)
| ("toString", [IEvDistribution(dist)]) => Helpers.toStringFn(ToString, dist, ~env)
| ("sparkline", [IEvDistribution(dist)]) =>
@ -298,7 +296,7 @@ module Old = {
let genericOutputToReducerValue = (o: DistributionOperation.outputType): result<
Reducer_T.value,
SqError.Message.t,
Reducer_ErrorValue.errorValue,
> =>
switch o {
| Dist(d) => Ok(Reducer_T.IEvDistribution(d))
@ -313,9 +311,9 @@ module Old = {
switch dispatchToGenericOutput(call, environment) {
| Some(o) => genericOutputToReducerValue(o)
| None =>
SqError.Message.REOther(
"Internal error in FR_GenericDist implementation",
)->SqError.Message.throw
Reducer_ErrorValue.REOther("Internal error in FR_GenericDist implementation")
->Reducer_ErrorValue.ErrorException
->raise
}
}
@ -328,7 +326,7 @@ let makeProxyFn = (name: string, inputs: array<frType>) => {
FnDefinition.make(
~name,
~inputs,
~run=(inputs, context, _) => Old.dispatch((name, inputs), context.environment),
~run=(inputs, env, _) => Old.dispatch((name, inputs), env),
(),
),
],
@ -404,9 +402,9 @@ let library = E.A.concatMany([
])
// FIXME - impossible to implement with FR due to arbitrary parameters length;
let mxLambda = Reducer_Lambda.makeFFILambda("mx", (inputs, context, _) => {
switch Old.dispatch(("mx", inputs), context.environment) {
let mxLambda = Reducer_Expression_Lambda.makeFFILambda((inputs, env, _) => {
switch Old.dispatch(("mx", inputs), env) {
| Ok(value) => value
| Error(e) => e->SqError.Message.throw
| Error(e) => e->Reducer_ErrorValue.ErrorException->raise
}
})

View File

@ -30,11 +30,11 @@ module Internals = {
let map = (
array: array<Reducer_T.value>,
eLambdaValue,
context: Reducer_T.context,
env: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
): Reducer_T.value => {
Belt.Array.map(array, elem =>
Reducer_Lambda.doLambdaCall(eLambdaValue, [elem], context, reducer)
Reducer_Expression_Lambda.doLambdaCall(eLambdaValue, [elem], env, reducer)
)->Wrappers.evArray
}
@ -42,11 +42,11 @@ module Internals = {
aValueArray,
initialValue,
aLambdaValue,
context: Reducer_T.context,
env: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
) => {
aValueArray->E.A.reduce(initialValue, (acc, elem) =>
Reducer_Lambda.doLambdaCall(aLambdaValue, [acc, elem], context, reducer)
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer)
)
}
@ -54,22 +54,22 @@ module Internals = {
aValueArray,
initialValue,
aLambdaValue,
context: Reducer_T.context,
env: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
) => {
aValueArray->Belt.Array.reduceReverse(initialValue, (acc, elem) =>
Reducer_Lambda.doLambdaCall(aLambdaValue, [acc, elem], context, reducer)
Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [acc, elem], env, reducer)
)
}
let filter = (
aValueArray,
aLambdaValue,
context: Reducer_T.context,
env: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
) => {
Js.Array2.filter(aValueArray, elem => {
let result = Reducer_Lambda.doLambdaCall(aLambdaValue, [elem], context, reducer)
let result = Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, [elem], env, reducer)
switch result {
| IEvBool(true) => true
| _ => false

View File

@ -16,31 +16,30 @@ let inputsToDist = (inputs: array<Reducer_T.value>, xyShapeToPointSetDist) => {
let yValue = map->Belt.Map.String.get("y")
switch (xValue, yValue) {
| (Some(IEvNumber(x)), Some(IEvNumber(y))) => (x, y)
| _ => impossibleError->SqError.Message.throw
| _ => impossibleError->Reducer_ErrorValue.toException
}
}
| _ => impossibleError->SqError.Message.throw
| _ => impossibleError->Reducer_ErrorValue.toException
}
)
->Ok
->E.R.bind(r => r->XYShape.T.makeFromZipped->E.R2.errMap(XYShape.Error.toString))
->E.R2.fmap(r => Reducer_T.IEvDistribution(PointSet(r->xyShapeToPointSetDist)))
| _ => impossibleError->SqError.Message.throw
| _ => impossibleError->Reducer_ErrorValue.toException
}
}
module Internal = {
type t = PointSetDist.t
let toType = (r): result<Reducer_T.value, SqError.Message.t> =>
let toType = (r): result<Reducer_T.value, Reducer_ErrorValue.errorValue> =>
switch r {
| Ok(r) => Ok(Wrappers.evDistribution(PointSet(r)))
| Error(err) => Error(REOperationError(err))
}
let doLambdaCall = (aLambdaValue, list, env, reducer) =>
switch Reducer_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) {
switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) {
| Reducer_T.IEvNumber(f) => Ok(f)
| _ => Error(Operation.SampleMapNeedsNtoNFunction)
}
@ -62,18 +61,18 @@ let library = [
FnDefinition.make(
~name="fromDist",
~inputs=[FRTypeDist],
~run=(inputs, context, _) =>
~run=(inputs, env, _) =>
switch inputs {
| [IEvDistribution(dist)] =>
GenericDist.toPointSet(
dist,
~xyPointLength=context.environment.xyPointLength,
~sampleCount=context.environment.sampleCount,
~xyPointLength=env.xyPointLength,
~sampleCount=env.sampleCount,
(),
)
->E.R2.fmap(Wrappers.pointSet)
->E.R2.fmap(Wrappers.evDistribution)
->E.R2.errMap(e => SqError.Message.REDistributionError(e))
->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e))
| _ => Error(impossibleError)
},
(),

View File

@ -10,40 +10,41 @@ module Internal = {
let doLambdaCall = (
aLambdaValue,
list,
context: Reducer_T.context,
env: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
) =>
switch Reducer_Lambda.doLambdaCall(aLambdaValue, list, context, reducer) {
switch Reducer_Expression_Lambda.doLambdaCall(aLambdaValue, list, env, reducer) {
| IEvNumber(f) => Ok(f)
| _ => Error(Operation.SampleMapNeedsNtoNFunction)
}
let toType = (r): result<Reducer_T.value, SqError.Message.t> =>
let toType = (r): result<Reducer_T.value, 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, context: Reducer_T.context, reducer: Reducer_T.reducerFn) => {
let sampleCount = context.environment.sampleCount
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], context, reducer)
let fromFn = (aLambdaValue, environment: Reducer_T.environment, reducer: Reducer_T.reducerFn) => {
let sampleCount = environment.sampleCount
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer)
Belt_Array.makeBy(sampleCount, r => fn(r->Js.Int.toFloat))->E.A.R.firstErrorOrOpen
}
let map1 = (sampleSetDist: t, aLambdaValue, context: Reducer_T.context, reducer) => {
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], context, reducer)
let map1 = (sampleSetDist: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let fn = r => doLambdaCall(aLambdaValue, [IEvNumber(r)], environment, reducer)
SampleSetDist.samplesMap(~fn, sampleSetDist)->toType
}
let map2 = (t1: t, t2: t, aLambdaValue, context: Reducer_T.context, reducer) => {
let fn = (a, b) => doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b)], context, reducer)
let map2 = (t1: t, t2: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let fn = (a, b) =>
doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b)], environment, reducer)
SampleSetDist.map2(~fn, ~t1, ~t2)->toType
}
let map3 = (t1: t, t2: t, t3: t, aLambdaValue, context: Reducer_T.context, reducer) => {
let map3 = (t1: t, t2: t, t3: t, aLambdaValue, environment: Reducer_T.environment, reducer) => {
let fn = (a, b, c) =>
doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b), IEvNumber(c)], context, reducer)
doLambdaCall(aLambdaValue, [IEvNumber(a), IEvNumber(b), IEvNumber(c)], environment, reducer)
SampleSetDist.map3(~fn, ~t1, ~t2, ~t3)->toType
}
@ -59,7 +60,7 @@ module Internal = {
let mapN = (
aValueArray: array<Reducer_T.value>,
aLambdaValue,
context: Reducer_T.context,
environment: Reducer_T.environment,
reducer,
) => {
switch parseSampleSetArray(aValueArray) {
@ -68,7 +69,7 @@ module Internal = {
doLambdaCall(
aLambdaValue,
[IEvArray(E.A.fmap(x => Wrappers.evNumber(x), a))],
context,
environment,
reducer,
)
SampleSetDist.mapN(~fn, ~t1)->toType
@ -88,13 +89,13 @@ let libaryBase = [
FnDefinition.make(
~name="fromDist",
~inputs=[FRTypeDist],
~run=(inputs, context, _) =>
~run=(inputs, environment, _) =>
switch inputs {
| [IEvDistribution(dist)] =>
GenericDist.toSampleSetDist(dist, context.environment.sampleCount)
GenericDist.toSampleSetDist(dist, environment.sampleCount)
->E.R2.fmap(Wrappers.sampleSet)
->E.R2.fmap(Wrappers.evDistribution)
->E.R2.errMap(e => SqError.Message.REDistributionError(e))
->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e))
| _ => Error(impossibleError)
},
(),
@ -115,7 +116,7 @@ let libaryBase = [
~run=(inputs, _, _) => {
let sampleSet =
inputs->Prepare.ToTypedArray.numbers
|> E.R2.bind(r => SampleSetDist.make(r)->E.R2.errMap(SampleSetDist.Error.toString))
|> 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)
@ -162,7 +163,7 @@ let libaryBase = [
| [IEvLambda(lambda)] =>
switch Internal.fromFn(lambda, environment, reducer) {
| Ok(r) => Ok(r->Wrappers.sampleSet->Wrappers.evDistribution)
| Error(e) => e->SqError.Message.REOperationError->Error
| Error(e) => e->Reducer_ErrorValue.REOperationError->Error
}
| _ => Error(impossibleError)
},
@ -289,7 +290,7 @@ module Comparison = {
r
->E.R2.fmap(r => r->Wrappers.sampleSet->Wrappers.evDistribution)
->E.R2.errMap(e =>
e->DistributionTypes.Error.sampleErrorToDistErr->SqError.Message.REDistributionError
e->DistributionTypes.Error.sampleErrorToDistErr->Reducer_ErrorValue.REDistributionError
)
let mkBig = (name, withDist, withFloat) =>

View File

@ -6,7 +6,7 @@ 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(e => SqError.Message.REDistributionError(e))
->E.R2.errMap(e => Reducer_ErrorValue.REDistributionError(e))
}
let library = [
@ -30,15 +30,15 @@ let library = [
("prior", FRTypeDist),
]),
],
~run=(inputs, context, _) => {
~run=(inputs, environment, _) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.threeArgs(
inputs,
("estimate", "answer", "prior"),
) {
| Ok([IEvDistribution(estimate), IEvDistribution(d), IEvDistribution(prior)]) =>
runScoring(estimate, Score_Dist(d), Some(prior), context.environment)
runScoring(estimate, Score_Dist(d), Some(prior), environment)
| Ok([IEvDistribution(estimate), IEvNumber(d), IEvDistribution(prior)]) =>
runScoring(estimate, Score_Scalar(d), Some(prior), context.environment)
runScoring(estimate, Score_Scalar(d), Some(prior), environment)
| Error(e) => Error(e->FunctionRegistry_Helpers.wrapError)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
@ -48,15 +48,15 @@ let library = [
FnDefinition.make(
~name="logScore",
~inputs=[FRTypeRecord([("estimate", FRTypeDist), ("answer", FRTypeDistOrNumber)])],
~run=(inputs, context, _) => {
~run=(inputs, environment, _) => {
switch FunctionRegistry_Helpers.Prepare.ToValueArray.Record.twoArgs(
inputs,
("estimate", "answer"),
) {
| Ok([IEvDistribution(estimate), IEvDistribution(d)]) =>
runScoring(estimate, Score_Dist(d), None, context.environment)
runScoring(estimate, Score_Dist(d), None, environment)
| Ok([IEvDistribution(estimate), IEvNumber(d)]) =>
runScoring(estimate, Score_Scalar(d), None, context.environment)
runScoring(estimate, Score_Scalar(d), None, environment)
| Error(e) => Error(e->FunctionRegistry_Helpers.wrapError)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
@ -76,10 +76,10 @@ let library = [
FnDefinition.make(
~name="klDivergence",
~inputs=[FRTypeDist, FRTypeDist],
~run=(inputs, context, _) => {
~run=(inputs, environment, _) => {
switch inputs {
| [IEvDistribution(estimate), IEvDistribution(d)] =>
runScoring(estimate, Score_Dist(d), None, context.environment)
runScoring(estimate, Score_Dist(d), None, environment)
| _ => Error(FunctionRegistry_Helpers.impossibleError)
}
},

View File

@ -1,7 +1,6 @@
@genType type reducerProject = ReducerProject_T.project //re-export
type error = SqError.t //use
type errorMessage = SqError.Message.t //use
type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use
type squiggleValue = ForTS_SquiggleValue.squiggleValue //use
type squiggleValue_Record = ForTS_SquiggleValue.squiggleValue_Record //use
@ -104,8 +103,10 @@ let cleanAllResults = (project: reducerProject): unit => project->Private.cleanA
To set the includes one first has to call "parseIncludes". The parsed includes or the parser error is returned.
*/
@genType
let getIncludes = (project: reducerProject, sourceId: string): result<array<string>, error> =>
project->Private.getIncludes(sourceId)
let getIncludes = (project: reducerProject, sourceId: string): result<
array<string>,
reducerErrorValue,
> => project->Private.getIncludes(sourceId)
/* Other sources contributing to the global namespace of this source. */
@genType
@ -197,8 +198,10 @@ let getBindings = (project: reducerProject, sourceId: string): squiggleValue_Rec
Get the result after running this source file or the project
*/
@genType
let getResult = (project: reducerProject, sourceId: string): result<squiggleValue, error> =>
project->Private.getResult(sourceId)
let getResult = (project: reducerProject, sourceId: string): result<
squiggleValue,
reducerErrorValue,
> => project->Private.getResult(sourceId)
/*
This is a convenience function to get the result of a single source without creating a project.
@ -206,8 +209,10 @@ However, without a project, you cannot handle include directives.
The source has to be include free
*/
@genType
let evaluate = (sourceCode: string): (result<squiggleValue, error>, squiggleValue_Record) =>
Private.evaluate(sourceCode)
let evaluate = (sourceCode: string): (
result<squiggleValue, reducerErrorValue>,
squiggleValue_Record,
) => Private.evaluate(sourceCode)
@genType
let setEnvironment = (project: reducerProject, environment: environment): unit =>
@ -215,3 +220,24 @@ let setEnvironment = (project: reducerProject, environment: environment): unit =
@genType
let getEnvironment = (project: reducerProject): environment => project->Private.getEnvironment
/*
Foreign function interface is intentionally demolished.
There is another way to do that: Umur.
Also there is no more conversion from javascript to squiggle values currently.
If the conversion to the new project is too difficult, I can add it later.
*/
// let foreignFunctionInterface = (
// lambdaValue: squiggleValue_Lambda,
// argArray: array<squiggleValue>,
// environment: environment,
// ): result<squiggleValue, reducerErrorValue> => {
// let accessors = ReducerProject_ProjectAccessors_T.identityAccessorsWithEnvironment(environment)
// Reducer_Expression_Lambda.foreignFunctionInterface(
// lambdaValue,
// argArray,
// accessors,
// Reducer_Expression.reduceExpressionInProject,
// )
// }

View File

@ -0,0 +1,18 @@
@genType type reducerErrorValue = Reducer_ErrorValue.errorValue //alias
@genType type syntaxErrorLocation = Reducer_ErrorValue.syntaxErrorLocation //alias
@genType
let toString = (e: reducerErrorValue): string => Reducer_ErrorValue.errorToString(e)
@genType
let getLocation = (e: reducerErrorValue): option<syntaxErrorLocation> =>
switch e {
| RESyntaxError(_, optionalLocation) => optionalLocation
| _ => None
}
@genType
let createTodoError = (v: string) => Reducer_ErrorValue.RETodo(v)
@genType
let createOtherError = (v: string) => Reducer_ErrorValue.REOther(v)

View File

@ -1,5 +1,5 @@
@genType type squiggleValue = Reducer_T.value //re-export
type error = SqError.t //use
type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //use
@genType type squiggleValue_Array = Reducer_T.arrayValue //re-export recursive type
@genType type squiggleValue_Record = Reducer_T.map //re-export recursive type
@ -69,7 +69,7 @@ let toString = (variant: squiggleValue) => Reducer_Value.toString(variant)
// This is a useful method for unit tests.
// Convert the result along with the error message to a string.
@genType
let toStringResult = (variantResult: result<squiggleValue, error>) =>
let toStringResult = (variantResult: result<squiggleValue, reducerErrorValue>) =>
Reducer_Value.toStringResult(variantResult)
@genType

View File

@ -1,7 +1,9 @@
@genType type squiggleValue_Lambda = Reducer_T.lambdaValue //re-export
@genType
let toString = (v: squiggleValue_Lambda): string => Reducer_Value.toStringLambda(v)
let toString = (v: squiggleValue_Lambda): string => Reducer_Value.toStringFunction(v)
@genType
let parameters = (v: squiggleValue_Lambda): array<string> => Reducer_Lambda.parameters(v)
let parameters = (v: squiggleValue_Lambda): array<string> => {
v.parameters
}

View File

@ -1,3 +1,6 @@
@genType type reducerErrorValue = ForTS_Reducer_ErrorValue.reducerErrorValue //re-export
@genType type syntaxErrorLocation = ForTS_Reducer_ErrorValue.syntaxErrorLocation //re-export
@genType type reducerProject = ForTS_ReducerProject.reducerProject //re-export
@genType type squiggleValue = ForTS_SquiggleValue.squiggleValue //re-export
@genType type squiggleValue_Array = ForTS_SquiggleValue_Array.squiggleValue_Array //re-export

View File

@ -1,5 +1,5 @@
type internalExpressionValueType = Reducer_Value.internalExpressionValueType
type errorMessage = SqError.Message.t
type errorValue = Reducer_ErrorValue.errorValue
/*
Function Registry "Type". A type, without any other information.
@ -30,9 +30,9 @@ type fnDefinition = {
inputs: array<frType>,
run: (
array<Reducer_T.value>,
Reducer_T.context,
Reducer_T.environment,
Reducer_T.reducerFn,
) => result<Reducer_T.value, errorMessage>,
) => result<Reducer_T.value, errorValue>,
}
type function = {
@ -61,7 +61,6 @@ module FRType = {
let input = ((name, frType): frTypeRecordParam) => `${name}: ${toString(frType)}`
`{${r->E.A2.fmap(input)->E.A2.joinWith(", ")}}`
}
| FRTypeArray(r) => `list(${toString(r)})`
| FRTypeLambda => `lambda`
| FRTypeString => `string`
@ -123,19 +122,19 @@ module FnDefinition = {
let run = (
t: t,
args: array<Reducer_T.value>,
context: Reducer_T.context,
env: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
) => {
switch t->isMatch(args) {
| true => t.run(args, context, reducer)
| true => t.run(args, env, reducer)
| false => REOther("Incorrect Types")->Error
}
}
let make = (~name, ~inputs, ~run, ()): t => {
name,
inputs,
run,
name: name,
inputs: inputs,
run: run,
}
}
@ -161,14 +160,14 @@ module Function = {
~isExperimental=false,
(),
): t => {
name,
nameSpace,
definitions,
output,
examples: examples->E.O2.default([]),
isExperimental,
requiresNamespace,
description,
name: name,
nameSpace: nameSpace,
definitions: definitions,
output: output,
examples: examples |> E.O.default([]),
isExperimental: isExperimental,
requiresNamespace: requiresNamespace,
description: description,
}
let toJson = (t: t): functionJson => {
@ -204,19 +203,15 @@ module Registry = {
fn.requiresNamespace ? [] : [def.name],
]->E.A.concatMany
names->Belt.Array.reduce(
acc,
(acc, name) => {
names->Belt.Array.reduce(acc, (acc, name) => {
switch acc->Belt.Map.String.get(name) {
| Some(fns) => {
let _ = fns->Js.Array2.push(def) // mutates the array, no need to update acc
acc
}
| None => acc->Belt.Map.String.set(name, [def])
}
},
)
})
})
)
}
@ -230,9 +225,9 @@ module Registry = {
registry,
fnName: string,
args: array<Reducer_T.value>,
context: Reducer_T.context,
env: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
): result<Reducer_T.value, errorMessage> => {
): result<Reducer_T.value, errorValue> => {
switch Belt.Map.String.get(registry.fnNameDict, fnName) {
| Some(definitions) => {
let showNameMatchDefinitions = () => {
@ -246,11 +241,10 @@ module Registry = {
let match = definitions->Js.Array2.find(def => def->FnDefinition.isMatch(args))
switch match {
| Some(def) => def->FnDefinition.run(args, context, reducer)
| Some(def) => def->FnDefinition.run(args, env, reducer)
| None => REOther(showNameMatchDefinitions())->Error
}
}
| None => RESymbolNotFound(fnName)->Error
}
}

View File

@ -2,8 +2,8 @@ open FunctionRegistry_Core
open Reducer_T
let impossibleErrorString = "Wrong inputs / Logically impossible"
let impossibleError: errorMessage = impossibleErrorString->SqError.Message.REOther
let wrapError = e => SqError.Message.REOther(e)
let impossibleError: errorValue = impossibleErrorString->Reducer_ErrorValue.REOther
let wrapError = e => Reducer_ErrorValue.REOther(e)
module Wrappers = {
let symbolic = r => DistributionTypes.Symbolic(r)
@ -34,7 +34,6 @@ module Prepare = {
let n2 = map->Belt.Map.String.getExn(arg2)
Ok([n1, n2])
}
| _ => Error(impossibleErrorString)
}
@ -46,7 +45,6 @@ module Prepare = {
let n3 = map->Belt.Map.String.getExn(arg3)
Ok([n1, n2, n3])
}
| _ => Error(impossibleErrorString)
}
}

View File

@ -44,4 +44,4 @@ let removeResult = ({namespace} as bindings: t): t => {
let locals = ({namespace}: t): Reducer_T.namespace => namespace
let fromNamespace = (namespace: Reducer_Namespace.t): t => {namespace, parent: None}
let fromNamespace = (namespace: Reducer_Namespace.t): t => {namespace: namespace, parent: None}

View File

@ -4,13 +4,9 @@ let defaultEnvironment: Reducer_T.environment = DistributionOperation.defaultEnv
let createContext = (stdLib: Reducer_Namespace.t, environment: Reducer_T.environment): t => {
{
frameStack: list{},
bindings: stdLib->Reducer_Bindings.fromNamespace->Reducer_Bindings.extend,
environment,
inFunction: None,
environment: environment,
}
}
let currentFunctionName = (t: t): string => {
t.inFunction->E.O2.fmap(Reducer_Lambda_T.name)->E.O2.default(Reducer_T.topFrameName)
}
let createDefaultContext = (): t => createContext(SquiggleLibrary_StdLib.stdLib, defaultEnvironment)

View File

@ -0,0 +1,27 @@
// types are disabled until review and rewrite for 0.5 interpreter compatibility
/*
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module T = Reducer_Dispatch_T
module TypeChecker = Reducer_Type_TypeChecker
open Reducer_Value
type errorValue = Reducer_ErrorValue.errorValue
let makeFromTypes = jumpTable => {
let dispatchChainPiece: T.dispatchChainPiece = (
(fnName, fnArgs): functionCall,
accessors: ProjectAccessorsT.t,
) => {
let jumpTableEntry = jumpTable->Js.Array2.find(elem => {
let (candidName, candidType, _) = elem
candidName == fnName && TypeChecker.checkITypeArgumentsBool(candidType, fnArgs)
})
switch jumpTableEntry {
| Some((_, _, bridgeFn)) => bridgeFn(fnArgs, accessors)->Some
| _ => None
}
}
dispatchChainPiece
}
*/

View File

@ -0,0 +1,21 @@
// module ExpressionT = Reducer_Expression_T
// module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
// // Each piece of the dispatch chain computes the result or returns None so that the chain can continue
// type dispatchChainPiece = (
// Reducer_Value.functionCall,
// ProjectAccessorsT.t,
// ) => option<result<Reducer_T.value, Reducer_ErrorValue.errorValue>>
// type dispatchChainPieceWithReducer = (
// Reducer_Value.functionCall,
// ProjectAccessorsT.t,
// Reducer_T.reducerFn,
// ) => option<result<Reducer_T.value, Reducer_ErrorValue.errorValue>>
// // This is a switch statement case implementation: get the arguments and compute the result
// type genericIEvFunction = (
// array<Reducer_T.value>,
// ProjectAccessorsT.t,
// ) => result<Reducer_T.value, Reducer_ErrorValue.errorValue>

View File

@ -0,0 +1,83 @@
//TODO: Do not export here but in ForTS__Types
@gentype.import("peggy") @genType.as("LocationRange")
type syntaxErrorLocation
@genType.opaque
type errorValue =
| REArityError(option<string>, int, int)
| REArrayIndexNotFound(string, int)
| REAssignmentExpected
| REDistributionError(DistributionTypes.error)
| REExpectedType(string, string)
| REExpressionExpected
| REFunctionExpected(string)
| REFunctionNotFound(string)
| REJavaScriptExn(option<string>, option<string>) // Javascript Exception
| REMacroNotFound(string)
| RENotAFunction(string)
| REOperationError(Operation.operationError)
| RERecordPropertyNotFound(string, string)
| RESymbolNotFound(string)
| RESyntaxError(string, option<syntaxErrorLocation>)
| RETodo(string) // To do
| REUnitNotFound(string)
| RENeedToRun
| REOther(string)
type t = errorValue
exception ErrorException(errorValue)
let errorToString = err =>
switch err {
| REArityError(_oFnName, arity, usedArity) =>
`${Js.String.make(arity)} arguments expected. Instead ${Js.String.make(
usedArity,
)} argument(s) were passed.`
| REArrayIndexNotFound(msg, index) => `${msg}: ${Js.String.make(index)}`
| REAssignmentExpected => "Assignment expected"
| REExpressionExpected => "Expression expected"
| REFunctionExpected(msg) => `Function expected: ${msg}`
| REFunctionNotFound(msg) => `Function not found: ${msg}`
| REDistributionError(err) => `Distribution Math Error: ${DistributionTypes.Error.toString(err)}`
| REOperationError(err) => `Math Error: ${Operation.Error.toString(err)}`
| REJavaScriptExn(omsg, oname) => {
let answer = "JS Exception:"
let answer = switch oname {
| Some(name) => `${answer} ${name}`
| _ => answer
}
let answer = switch omsg {
| Some(msg) => `${answer}: ${msg}`
| _ => answer
}
answer
}
| REMacroNotFound(macro) => `Macro not found: ${macro}`
| RENotAFunction(valueString) => `${valueString} is not a function`
| RERecordPropertyNotFound(msg, index) => `${msg}: ${index}`
| RESymbolNotFound(symbolName) => `${symbolName} is not defined`
| RESyntaxError(desc, _) => `Syntax Error: ${desc}`
| RETodo(msg) => `TODO: ${msg}`
| REExpectedType(typeName, valueString) => `Expected type: ${typeName} but got: ${valueString}`
| REUnitNotFound(unitName) => `Unit not found: ${unitName}`
| RENeedToRun => "Need to run"
| REOther(msg) => `Error: ${msg}`
}
let fromException = exn =>
switch exn {
| ErrorException(e) => e
| Js.Exn.Error(e) =>
switch Js.Exn.message(e) {
| Some(message) => REOther(message)
| None =>
switch Js.Exn.name(e) {
| Some(name) => REOther(name)
| None => REOther("Unknown error")
}
}
| _e => REOther("Unknown error")
}
let toException = (errorValue: t) => raise(ErrorException(errorValue))

View File

@ -0,0 +1,3 @@
// There are switch statement cases in the code which are impossible to reach by design.
// ImpossibleException is a sign of programming error.
exception ImpossibleException(string)

View File

@ -1,25 +1,16 @@
module Bindings = Reducer_Bindings
module Lambda = Reducer_Expression_Lambda
module Result = Belt.Result
module T = Reducer_T
let toLocation = (expression: T.expression): Reducer_Peggy_Parse.location => {
expression.ast.location
}
let throwFrom = (error: SqError.Message.t, expression: T.expression, context: T.context) =>
error->SqError.throwMessageWithFrameStack(
context.frameStack->Reducer_FrameStack.extend(
context->Reducer_Context.currentFunctionName,
Some(expression->toLocation),
),
)
type errorValue = Reducer_ErrorValue.errorValue
/*
Recursively evaluate the expression
*/
let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
// Js.log(`reduce: ${expression->Reducer_Expression_T.toString}`)
switch expression.content {
switch expression {
| T.EBlock(statements) => {
let innerContext = {...context, bindings: context.bindings->Bindings.extend}
let (value, _) =
@ -58,7 +49,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
let (key, _) = eKey->evaluate(context)
let keyString = switch key {
| IEvString(s) => s
| _ => REOther("Record keys must be strings")->throwFrom(expression, context)
| _ => REOther("Record keys must be strings")->Reducer_ErrorValue.ErrorException->raise
}
let (value, _) = eValue->evaluate(context)
(keyString, value)
@ -82,7 +73,7 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
| T.ESymbol(name) =>
switch context.bindings->Bindings.get(name) {
| Some(v) => (v, context)
| None => RESymbolNotFound(name)->throwFrom(expression, context)
| None => Reducer_ErrorValue.RESymbolNotFound(name)->Reducer_ErrorValue.ErrorException->raise
}
| T.EValue(value) => (value, context)
@ -91,40 +82,28 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
let (predicateResult, _) = predicate->evaluate(context)
switch predicateResult {
| T.IEvBool(value) => (value ? trueCase : falseCase)->evaluate(context)
| _ => REExpectedType("Boolean", "")->throwFrom(expression, context)
| _ => REExpectedType("Boolean", "")->Reducer_ErrorValue.ErrorException->raise
}
}
| T.ELambda(parameters, body, name) => (
Reducer_Lambda.makeLambda(
name,
parameters,
context.bindings,
body,
expression->toLocation,
)->T.IEvLambda,
| T.ELambda(parameters, body) => (
Lambda.makeLambda(parameters, context.bindings, body)->T.IEvLambda,
context,
)
| T.ECall(fn, args) => {
let (lambda, _) = fn->evaluate(context)
let argValues = Belt.Array.map(args, arg => {
let argValues = Js.Array2.map(args, arg => {
let (argValue, _) = arg->evaluate(context)
argValue
})
switch lambda {
| T.IEvLambda(lambda) => {
let result = Reducer_Lambda.doLambdaCallFrom(
lambda,
argValues,
| T.IEvLambda(lambda) => (
Lambda.doLambdaCall(lambda, argValues, context.environment, evaluate),
context,
evaluate,
Some(expression->toLocation), // we have to pass the location of a current expression here, to put it on frameStack
)
(result, context)
}
| _ => RENotAFunction(lambda->Reducer_Value.toString)->throwFrom(expression, context)
| _ =>
RENotAFunction(lambda->Reducer_Value.toString)->Reducer_ErrorValue.ErrorException->raise
}
}
}
@ -133,22 +112,19 @@ let rec evaluate: T.reducerFn = (expression, context): (T.value, T.context) => {
module BackCompatible = {
// Those methods are used to support the existing tests
// If they are used outside limited testing context, error location reporting will fail
let parse = (peggyCode: string): result<T.expression, Reducer_Peggy_Parse.parseError> =>
peggyCode->Reducer_Peggy_Parse.parse("main")->Result.map(Reducer_Peggy_ToExpression.fromNode)
let parse = (peggyCode: string): result<T.expression, errorValue> =>
peggyCode->Reducer_Peggy_Parse.parse->Result.map(Reducer_Peggy_ToExpression.fromNode)
let createDefaultContext = () =>
Reducer_Context.createContext(SquiggleLibrary_StdLib.stdLib, Reducer_Context.defaultEnvironment)
let evaluate = (expression: T.expression): result<T.value, SqError.t> => {
let context = createDefaultContext()
let evaluate = (expression: T.expression): result<T.value, errorValue> => {
let context = Reducer_Context.createDefaultContext()
try {
let (value, _) = expression->evaluate(context)
value->Ok
} catch {
| exn => exn->SqError.fromException->Error
| exn => Reducer_ErrorValue.fromException(exn)->Error
}
}
let evaluateString = (peggyCode: string): result<T.value, SqError.t> =>
parse(peggyCode)->E.R2.errMap(e => e->SqError.fromParseError)->Result.flatMap(evaluate)
let evaluateString = (peggyCode: string): result<T.value, errorValue> =>
parse(peggyCode)->Result.flatMap(evaluate)
}

View File

@ -1,19 +1,16 @@
module BErrorValue = Reducer_ErrorValue
module T = Reducer_T
type errorValue = BErrorValue.errorValue
type expression = Reducer_T.expression
type expressionContent = Reducer_T.expressionContent
let eArray = (anArray: array<T.expression>): expressionContent => anArray->T.EArray
let eArray = (anArray: array<T.expression>) => anArray->T.EArray
let eBool = aBool => aBool->T.IEvBool->T.EValue
let eCall = (fn: expression, args: array<expression>): expressionContent => T.ECall(fn, args)
let eCall = (fn: expression, args: array<expression>): expression => T.ECall(fn, args)
let eLambda = (
parameters: array<string>,
expr: expression,
name: option<string>,
): expressionContent => T.ELambda(parameters, expr, name)
let eLambda = (parameters: array<string>, expr: expression) => T.ELambda(parameters, expr)
let eNumber = aNumber => aNumber->T.IEvNumber->T.EValue
@ -21,13 +18,13 @@ let eRecord = (aMap: array<(T.expression, T.expression)>) => aMap->T.ERecord
let eString = aString => aString->T.IEvString->T.EValue
let eSymbol = (name: string): expressionContent => T.ESymbol(name)
let eSymbol = (name: string): expression => T.ESymbol(name)
let eBlock = (exprs: array<expression>): expressionContent => T.EBlock(exprs)
let eBlock = (exprs: array<expression>): expression => T.EBlock(exprs)
let eProgram = (exprs: array<expression>): expressionContent => T.EProgram(exprs)
let eProgram = (exprs: array<expression>): expression => T.EProgram(exprs)
let eLetStatement = (symbol: string, valueExpression: expression): expressionContent => T.EAssign(
let eLetStatement = (symbol: string, valueExpression: expression): expression => T.EAssign(
symbol,
valueExpression,
)
@ -36,8 +33,11 @@ let eTernary = (
predicate: expression,
trueCase: expression,
falseCase: expression,
): expressionContent => T.ETernary(predicate, trueCase, falseCase)
): expression => T.ETernary(predicate, trueCase, falseCase)
let eIdentifier = (name: string): expressionContent => name->T.ESymbol
let eIdentifier = (name: string): expression => name->T.ESymbol
let eVoid: expressionContent = T.IEvVoid->T.EValue
// let eTypeIdentifier = (name: string): expression =>
// name->T.IEvTypeIdentifier->T.EValue
let eVoid: expression = T.IEvVoid->T.EValue

View File

@ -0,0 +1,60 @@
module ErrorValue = Reducer_ErrorValue
let doLambdaCall = (
lambdaValue: Reducer_T.lambdaValue,
args,
environment: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
): Reducer_T.value => {
lambdaValue.body(args, environment, reducer)
}
let makeLambda = (
parameters: array<string>,
bindings: Reducer_T.bindings,
body: Reducer_T.expression,
): Reducer_T.lambdaValue => {
// TODO - clone bindings to avoid later redefinitions affecting lambdas?
// Note: with this implementation, FFI lambdas (created by other methods than calling `makeLambda`) are allowed to violate the rules, pollute the bindings, etc.
// Not sure yet if that's a bug or a feature.
// FunctionRegistry functions are unaffected by this, their API is too limited.
let lambda = (
arguments: array<Reducer_T.value>,
environment: Reducer_T.environment,
reducer: Reducer_T.reducerFn,
) => {
let argsLength = arguments->Js.Array2.length
let parametersLength = parameters->Js.Array2.length
if argsLength !== parametersLength {
ErrorValue.REArityError(None, parametersLength, argsLength)->ErrorValue.ErrorException->raise
}
let localBindings = bindings->Reducer_Bindings.extend
let localBindingsWithParameters = parameters->Belt.Array.reduceWithIndex(localBindings, (
currentBindings,
parameter,
index,
) => {
currentBindings->Reducer_Bindings.set(parameter, arguments[index])
})
let (value, _) = reducer(
body,
{bindings: localBindingsWithParameters, environment: environment},
)
value
}
{
// context: bindings,
body: lambda,
parameters: parameters,
}
}
let makeFFILambda = (body: Reducer_T.lambdaBody): Reducer_T.lambdaValue => {
body: body,
parameters: ["..."],
}

View File

@ -12,7 +12,7 @@ let semicolonJoin = values =>
Converts the expression to String
*/
let rec toString = (expression: t) =>
switch expression.content {
switch expression {
| EBlock(statements) =>
`{${Js.Array2.map(statements, aValue => toString(aValue))->semicolonJoin}}`
| EProgram(statements) => Js.Array2.map(statements, aValue => toString(aValue))->semicolonJoin
@ -24,23 +24,37 @@ let rec toString = (expression: t) =>
`${predicate->toString} ? (${trueCase->toString}) : (${falseCase->toString})`
| EAssign(name, value) => `${name} = ${value->toString}`
| ECall(fn, args) => `(${fn->toString})(${args->Js.Array2.map(toString)->commaJoin})`
| ELambda(parameters, body, _) => `{|${parameters->commaJoin}| ${body->toString}}`
| ELambda(parameters, body) => `{|${parameters->commaJoin}| ${body->toString}}`
| EValue(aValue) => Reducer_Value.toString(aValue)
}
let toStringResult = codeResult =>
switch codeResult {
| Ok(a) => `Ok(${toString(a)})`
| Error(m) => `Error(${Reducer_Peggy_Parse.toStringError(m)})`
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
}
let toStringResultOkless = codeResult =>
switch codeResult {
| Ok(a) => toString(a)
| Error(m) => `Error(${Reducer_Peggy_Parse.toStringError(m)})`
| Error(m) => `Error(${Reducer_ErrorValue.errorToString(m)})`
}
let inspect = (expr: t): t => {
Js.log(toString(expr))
expr
}
let inspectResult = (r: result<t, Reducer_ErrorValue.errorValue>): result<
t,
Reducer_ErrorValue.errorValue,
> => {
Js.log(toStringResult(r))
r
}
let resultToValue = (rExpression: result<t, Reducer_ErrorValue.t>): t =>
switch rExpression {
| Ok(expression) => expression
| Error(errorValue) => Reducer_ErrorValue.toException(errorValue)
}

View File

@ -1,51 +0,0 @@
// This is called "frameStack" and not "callStack", because the last frame in errors is often not a function call.
// A "frame" is a pair of a scope (function or top-level scope, currently stored as a string) and a location inside it.
// See this comment to deconfuse about what a frame is: https://github.com/quantified-uncertainty/squiggle/pull/1172#issuecomment-1264115038
type t = Reducer_T.frameStack
module Frame = {
let toString = ({name, location}: Reducer_T.frame) =>
name ++
switch location {
| Some(location) =>
` at line ${location.start.line->Js.Int.toString}, column ${location.start.column->Js.Int.toString}` // TODO - source id?
| None => ""
}
@genType
let getLocation = (t: Reducer_T.frame): option<Reducer_Peggy_Parse.location> => t.location
@genType
let getName = (t: Reducer_T.frame): string => t.name
}
let make = (): t => list{}
let extend = (t: t, name: string, location: option<Reducer_Peggy_Parse.location>) =>
t->Belt.List.add({
name,
location,
})
// this is useful for SyntaxErrors
let makeSingleFrameStack = (location: Reducer_Peggy_Parse.location): t =>
make()->extend(Reducer_T.topFrameName, Some(location))
// this includes the left offset because it's mostly used in SqError.toStringWithStackTrace
let toString = (t: t) =>
t
->Belt.List.map(s => " " ++ s->Frame.toString ++ "\n")
->Belt.List.toArray
->Js.Array2.joinWith("")
@genType
let toFrameArray = (t: t): array<Reducer_T.frame> => t->Belt.List.toArray
@genType
let getTopFrame = (t: t): option<Reducer_T.frame> => t->Belt.List.head
let isEmpty = (t: t): bool =>
switch t->Belt.List.head {
| Some(_) => true
| None => false
}

View File

@ -1,95 +0,0 @@
type t = Reducer_T.lambdaValue
// user-defined functions, i.e. `add2 = {|x, y| x + y}`, are built by this method
let makeLambda = (
name: option<string>,
parameters: array<string>,
bindings: Reducer_T.bindings,
body: Reducer_T.expression,
location: Reducer_Peggy_Parse.location,
): t => {
let lambda = (
arguments: array<Reducer_T.value>,
context: Reducer_T.context,
reducer: Reducer_T.reducerFn,
) => {
let argsLength = arguments->E.A.length
let parametersLength = parameters->E.A.length
if argsLength !== parametersLength {
SqError.Message.REArityError(None, parametersLength, argsLength)->SqError.Message.throw
}
// create new bindings scope - technically not necessary, since bindings are immutable, but might help with debugging/new features in the future
let localBindings = bindings->Reducer_Bindings.extend
let localBindingsWithParameters = parameters->Belt.Array.reduceWithIndex(localBindings, (
currentBindings,
parameter,
index,
) => {
currentBindings->Reducer_Bindings.set(parameter, arguments[index])
})
let lambdaContext: Reducer_T.context = {
bindings: localBindingsWithParameters, // based on bindings at the moment of lambda creation
environment: context.environment, // environment at the moment when lambda is called
frameStack: context.frameStack, // already extended in `doLambdaCall`
inFunction: context.inFunction, // already updated in `doLambdaCall`
}
let (value, _) = reducer(body, lambdaContext)
value
}
FnLambda({
// context: bindings,
name,
body: lambda,
parameters,
location,
})
}
// stdlib functions (everything in FunctionRegistry) are built by this method. Body is generated in SquiggleLibrary_StdLib.res
let makeFFILambda = (name: string, body: Reducer_T.lambdaBody): t => FnBuiltin({
// Note: current bindings could be accidentally exposed here through context (compare with native lambda implementation above, where we override them with local bindings).
// But FunctionRegistry API is too limited for that to matter. Please take care not to violate that in the future by accident.
body,
name,
})
// this function doesn't scale to FunctionRegistry's polymorphic functions
let parameters = (t: t): array<string> => {
switch t {
| FnLambda({parameters}) => parameters
| FnBuiltin(_) => ["..."]
}
}
let doLambdaCallFrom = (
t: t,
args: array<Reducer_T.value>,
context: Reducer_T.context,
reducer,
location: option<Reducer_Peggy_Parse.location>,
) => {
let newContext = {
...context,
frameStack: context.frameStack->Reducer_FrameStack.extend(
context->Reducer_Context.currentFunctionName,
location,
),
inFunction: Some(t),
}
SqError.rethrowWithFrameStack(() => {
switch t {
| FnLambda({body}) => body(args, newContext, reducer)
| FnBuiltin({body}) => body(args, newContext, reducer)
}
}, newContext.frameStack)
}
let doLambdaCall = (t: t, args, context, reducer) => {
doLambdaCallFrom(t, args, context, reducer, None)
}

View File

@ -1,8 +0,0 @@
type t = Reducer_T.lambdaValue
let name = (t: t): string => {
switch t {
| FnLambda({name}) => name->E.O2.default("<anonymous>")
| FnBuiltin({name}) => name
}
}

Some files were not shown because too many files have changed in this diff Show More