Compare commits

..

3 Commits

Author SHA1 Message Date
Sam Nolan
b3e8253ed7 Add source id to error alert 2022-09-22 17:43:56 +10:00
Sam Nolan
eb86d12d63 Fix some build errors about error values 2022-09-22 17:29:02 +10:00
Sam Nolan
e2e30641b3 Add a source annotation to error objects 2022-09-22 17:07:26 +10:00
235 changed files with 7595 additions and 6914 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,214 @@ on:
branches:
- master
- develop
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: quantified-uncertainty
- reducer-dev
- epic-reducer-project
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@v4.0.0
with:
paths: '["packages/squiggle-lang/**"]'
- id: skip_components_check
name: Check if the changes are about components src files
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["packages/components/**"]'
- id: skip_website_check
name: Check if the changes are about website src files
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["packages/website/**"]'
- id: skip_vscodeext_check
name: Check if the changes are about vscode extension src files
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["packages/vscode-ext/**"]'
- id: skip_cli_check
name: Check if the changes are about cli src files
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["packages/cli/**"]'
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:
name: Components bundle and 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') }}
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
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

View File

@ -18,27 +18,27 @@ jobs:
steps:
- id: skip_lang_check
name: Check if the changes are about squiggle-lang src files
uses: fkirc/skip-duplicate-actions@v5.2.0
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["packages/squiggle-lang/**"]'
- id: skip_components_check
name: Check if the changes are about components src files
uses: fkirc/skip-duplicate-actions@v5.2.0
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["packages/components/**"]'
- id: skip_website_check
name: Check if the changes are about website src files
uses: fkirc/skip-duplicate-actions@v5.2.0
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["packages/website/**"]'
- id: skip_vscodeext_check
name: Check if the changes are about vscode extension src files
uses: fkirc/skip-duplicate-actions@v5.2.0
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["packages/vscode-ext/**"]'
- id: skip_cli_check
name: Check if the changes are about cli src files
uses: fkirc/skip-duplicate-actions@v5.2.0
uses: fkirc/skip-duplicate-actions@v4.0.0
with:
paths: '["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,15 +7,13 @@
"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",
"commander": "^9.4.0",
"fs": "^0.0.1-security",
"glob": "^8.0.3",
"indent-string": "^5.0.0"

View File

@ -1,6 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
setupFilesAfterEnv: ["<rootDir>/test/setup.js"],
testEnvironment: "jsdom",
};

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

@ -1,25 +1,23 @@
{
"name": "@quri/squiggle-components",
"version": "0.5.0",
"version": "0.4.3",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^1.0.0",
"@floating-ui/react-dom-interactions": "^0.10.1",
"@headlessui/react": "^1.7.3",
"@headlessui/react": "^1.7.2",
"@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^2.9.8",
"@quri/squiggle-lang": "^0.5.0",
"@quri/squiggle-lang": "^0.4.2",
"@react-hook/size": "^2.1.2",
"@types/uuid": "^8.3.4",
"clsx": "^1.2.1",
"framer-motion": "^7.5.3",
"framer-motion": "^7.3.5",
"lodash": "^4.17.21",
"react": "^18.1.0",
"react-ace": "^10.1.0",
"react-hook-form": "^7.37.0",
"react-hook-form": "^7.35.0",
"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",
@ -40,17 +38,12 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^27.5.0",
"@types/lodash": "^4.14.186",
"@types/node": "^18.8.3",
"@types/react": "^18.0.21",
"@types/lodash": "^4.14.185",
"@types/node": "^18.7.18",
"@types/react": "^18.0.18",
"@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",
"mini-css-extract-plugin": "^2.6.1",
"postcss-cli": "^10.0.0",
"postcss-import": "^15.0.0",
@ -60,14 +53,13 @@
"react-scripts": "^5.0.1",
"style-loader": "^3.3.1",
"tailwindcss": "^3.1.8",
"ts-jest": "^29.0.3",
"ts-loader": "^9.4.1",
"ts-loader": "^9.4.0",
"tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.8.4",
"web-vitals": "^3.0.3",
"typescript": "^4.8.3",
"web-vitals": "^3.0.2",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
"webpack-dev-server": "^4.11.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18",
@ -83,10 +75,7 @@
"all": "yarn bundle && yarn build",
"lint": "prettier --check .",
"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"
"prepack": "yarn run build:cjs && yarn run bundle"
},
"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

@ -83,32 +83,18 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
}
}
const domain = shapes.value.flatMap((shape) =>
shape.discrete.concat(shape.continuous)
);
const spec = buildVegaSpec(props);
const spec = buildVegaSpec({
...props,
minX: props.minX ?? Math.min(...domain.map((x) => x.x)),
maxX: props.minX ?? Math.max(...domain.map((x) => x.x)),
maxY: Math.max(...domain.map((x) => x.y)),
});
// I think size.width is sometimes not finite due to the component not being in a visible context
// This occurs during testing
let widthProp = width
? width
: Number.isFinite(size.width)
? size.width
: 400;
let widthProp = width ? width : size.width;
if (widthProp < 20) {
console.warn(
`Width of Distribution is set to ${widthProp}, which is too small`
);
widthProp = 20;
}
const vegaData = { data: shapes.value, samples };
const domain = shapes.value.flatMap((shape) =>
shape.discrete.concat(shape.continuous)
);
return (
<div style={{ width: widthProp }}>
@ -119,7 +105,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
) : (
<Vega
spec={spec}
data={vegaData}
data={{ data: shapes.value, domain, samples }}
width={widthProp - 10}
height={height}
actions={actions}

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

@ -172,15 +172,11 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
//TODO: This custom error handling is a bit hacky and should be improved.
let mouseItem: result<SqValue, SqError> = !!mouseOverlay
? fn.call([mouseOverlay])
: {
tag: "Error",
value: SqError.createOtherError(
"Hover x-coordinate returned NaN. Expected a number."
),
};
let showChart =
let showChart: JSX.Element | null = null;
if (!isNaN(mouseOverlay)) {
let mouseItem = fn.call([mouseOverlay]);
showChart =
mouseItem.tag === "Ok" &&
mouseItem.value.tag === SqValueTag.Distribution ? (
<DistributionChart
@ -191,6 +187,7 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
{...distributionPlotSettings}
/>
) : null;
}
let getPercentilesMemoized = React.useMemo(
() => getPercentiles({ chartSettings, fn, environment }),

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()} (Source: {error.getSource()})</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";
@ -135,6 +135,29 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
{() => value.value.toString()}
</VariableBox>
);
case SqValueTag.Symbol:
return (
<VariableBox value={value} heading="Symbol">
{() => (
<>
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
<span className="text-slate-600">{value.value}</span>
</>
)}
</VariableBox>
);
case SqValueTag.Call:
return (
<VariableBox value={value} heading="Call">
{() => value.value}
</VariableBox>
);
case SqValueTag.ArrayString:
return (
<VariableBox value={value} heading="Array String">
{() => value.value.map((r) => `"${r}"`).join(", ")}
</VariableBox>
);
case SqValueTag.Date:
return (
<VariableBox value={value} heading="Date">
@ -219,6 +242,24 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
</VariableBox>
);
}
case SqValueTag.Module: {
return (
<VariableList value={value} heading="Module">
{(_) =>
value.value
.entries()
.filter(([key, _]) => !key.match(/^(__result__)$/))
.map(([key, r]) => (
<ExpressionViewer
key={key}
value={r}
width={width !== undefined ? width - 20 : width}
/>
))
}
</VariableList>
);
}
case SqValueTag.Record:
const plot = makePlot(value.value);
if (plot) {
@ -298,9 +339,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
{() => (
<div>
<span>No display for type: </span>{" "}
<span className="font-semibold text-slate-600">
{(value as { tag: string }).tag}
</span>
<span className="font-semibold text-slate-600">{value.tag}</span>
</div>
)}
</VariableList>

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

@ -26,6 +26,7 @@ export const linearXScale: LinearScale = {
range: "width",
zero: false,
nice: false,
domain: { data: "domain", field: "x" },
};
export const logXScale: LogScale = {
@ -36,6 +37,7 @@ export const logXScale: LogScale = {
base: 10,
nice: false,
clamp: true,
domain: { data: "domain", field: "x" },
};
export const timeXScale: TimeScale = {
@ -44,6 +46,7 @@ export const timeXScale: TimeScale = {
type: "time",
range: "width",
nice: false,
domain: { data: "domain", field: "x" },
};
/** Y Scales */
@ -52,6 +55,7 @@ export const linearYScale: LinearScale = {
type: "linear",
range: "height",
zero: true,
domain: { data: "domain", field: "y" },
};
export const expYScale: PowScale = {
@ -61,6 +65,7 @@ export const expYScale: PowScale = {
range: "height",
zero: true,
nice: false,
domain: { data: "domain", field: "y" },
};
export const defaultTickFormat = ".9~s";
@ -68,17 +73,9 @@ export const timeTickFormat = "%b %d, %Y %H:%M";
const width = 500;
export function buildVegaSpec(
specOptions: DistributionChartSpecOptions & { maxY: number }
specOptions: DistributionChartSpecOptions
): VisualizationSpec {
const {
title,
minX,
maxX,
logX,
expY,
xAxisType = "number",
maxY,
} = specOptions;
const { title, minX, maxX, logX, expY, xAxisType = "number" } = specOptions;
const dateTime = xAxisType === "dateTime";
@ -91,15 +88,13 @@ export function buildVegaSpec(
let xScale = dateTime ? timeXScale : logX ? logXScale : linearXScale;
xScale = {
...xScale,
domain: [minX ?? 0, maxX ?? 1],
domainMin: minX,
domainMax: maxX,
};
if (minX !== undefined && Number.isFinite(minX)) {
xScale = { ...xScale, domainMin: minX };
}
let yScale = expY ? expYScale : linearYScale;
yScale = { ...yScale, domain: [0, maxY ?? 1], domainMin: 0, domainMax: maxY };
if (maxX !== undefined && Number.isFinite(maxX)) {
xScale = { ...xScale, domainMax: maxX };
}
const spec: VisualizationSpec = {
$schema: "https://vega.github.io/schema/vega/v5.json",
@ -133,7 +128,7 @@ export function buildVegaSpec(
],
scales: [
xScale,
yScale,
expY ? expYScale : linearYScale,
{
name: "color",
type: "ordinal",

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

@ -0,0 +1,51 @@
import { SquigglePartial, SquiggleEditor } from "../components/SquiggleEditor";
import { useState } from "react";
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
<Meta title="Squiggle/SquigglePartial" component={SquigglePartial} />
export const Template = (props) => <SquigglePartial {...props} />;
# Squiggle Partial
A Squiggle Partial is an editor that does not return a graph to the user, but
instead returns bindings that can be used by further Squiggle Editors.
<Canvas>
<Story
name="Standalone"
args={{
defaultCode: "x = normal(5,2)",
}}
>
{Template.bind({})}
</Story>
</Canvas>
<Canvas>
<Story
name="With Editor"
args={{
initialPartialString: "x = normal(5,2)",
initialEditorString: "x",
}}
>
{(props) => {
let [bindings, setBindings] = useState({});
return (
<>
<SquigglePartial
{...props}
defaultCode={props.initialPartialString}
onChange={setBindings}
/>
<SquiggleEditor
{...props}
defaultCode={props.initialEditorString}
bindings={bindings}
/>
</>
);
}}
</Story>
</Canvas>

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 +0,0 @@
import { render, screen } 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";
test("Chart logs nothing on render", async () => {
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,8 +0,0 @@
global.console = {
...console,
log: jest.fn(console.log),
debug: jest.fn(console.debug),
info: jest.fn(console.info),
warn: jest.fn(console.warn),
error: jest.fn(console.error),
};

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

@ -0,0 +1,20 @@
open Jest
open Expect
let makeTest = (~only=false, str, item1, item2) =>
only
? Only.test(str, () => expect(item1)->toEqual(item2))
: test(str, () => expect(item1)->toEqual(item2))
describe("Lodash", () =>
describe("Lodash", () => {
makeTest("min", Lodash.min([1, 3, 4]), 1)
makeTest("max", Lodash.max([1, 3, 4]), 4)
makeTest("uniq", Lodash.uniq([1, 3, 4, 4]), [1, 3, 4])
makeTest(
"countBy",
Lodash.countBy([1, 3, 4, 4], r => r),
Js.Dict.fromArray([("1", 1), ("3", 1), ("4", 2)]),
)
})
)

View File

@ -1,50 +1,27 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module Namespace = Reducer_Namespace
open Jest
open Expect
open Expect.Operators
describe("Bindings", () => {
let value = Reducer_T.IEvNumber(1967.0)
let bindings = Bindings.make()->Bindings.set("value", value)
describe("Name Space", () => {
let value = InternalExpressionValue.IEvNumber(1967.0)
let nameSpace = Bindings.emptyNameSpace->Bindings.set("value", value)
test("get", () => {
expect(bindings->Bindings.get("value")) == Some(value)
expect(Bindings.get(nameSpace, "value")) == Some(value)
})
test("get nonexisting value", () => {
expect(bindings->Bindings.get("nosuchvalue")) == None
test("chain and get", () => {
let mainNameSpace = Bindings.emptyNameSpace->Bindings.chainTo([nameSpace])
expect(Bindings.get(mainNameSpace, "value")) == Some(value)
})
test("get on extended", () => {
expect(bindings->Bindings.extend->Bindings.get("value")) == Some(value)
})
test("locals", () => {
expect(bindings->Bindings.locals->Namespace.get("value")) == Some(value)
})
test("locals on extendeed", () => {
expect(bindings->Bindings.extend->Bindings.locals->Namespace.get("value")) == None
})
describe("extend", () => {
let value2 = Reducer_T.IEvNumber(5.)
let extendedBindings = bindings->Bindings.extend->Bindings.set("value", value2)
test(
"get on extended",
() => {
expect(extendedBindings->Bindings.get("value")) == Some(value2)
},
)
test(
"get on original",
() => {
expect(bindings->Bindings.get("value")) == Some(value)
},
)
test("chain and set", () => {
let mainNameSpace0 = Bindings.emptyNameSpace->Bindings.chainTo([nameSpace])
let mainNameSpace =
mainNameSpace0->Bindings.set("value", InternalExpressionValue.IEvNumber(1968.0))
expect(Bindings.get(mainNameSpace, "value")) == Some(InternalExpressionValue.IEvNumber(1968.0))
})
})

View File

@ -0,0 +1,146 @@
open Jest
// open Expect
open Reducer_Expression_ExpressionBuilder
open Reducer_TestMacroHelpers
module ExpressionT = Reducer_Expression_T
let exampleExpression = eNumber(1.)
let exampleExpressionY = eSymbol("y")
let exampleStatementY = eLetStatement("y", eNumber(1.))
let exampleStatementX = eLetStatement("y", eSymbol("x"))
let exampleStatementZ = eLetStatement("z", eSymbol("y"))
// If it is not a macro then it is not expanded
testMacro([], exampleExpression, "Ok(1)")
describe("bindStatement", () => {
// A statement is bound by the bindings created by the previous statement
testMacro(
[],
eBindStatement(eBindings([]), exampleStatementY),
"Ok((:$_setBindings_$ @{} :y 1) context: @{})",
)
// Then it answers the bindings for the next statement when reduced
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok(@{y: 1})")
// Now let's feed a binding to see what happens
testMacro(
[],
eBindStatement(eBindings([("x", IEvNumber(2.))]), exampleStatementX),
"Ok((:$_setBindings_$ @{x: 2} :y 2) context: @{x: 2})",
)
// An expression does not return a binding, thus error
testMacro([], eBindStatement(eBindings([]), exampleExpression), "Assignment expected")
// When bindings from previous statement are missing the context is injected. This must be the first statement of a block
testMacro(
[("z", IEvNumber(99.))],
eBindStatementDefault(exampleStatementY),
"Ok((:$_setBindings_$ @{z: 99} :y 1) context: @{z: 99})",
)
})
describe("bindExpression", () => {
// x is simply bound in the expression
testMacro(
[],
eBindExpression(eBindings([("x", IEvNumber(2.))]), eSymbol("x")),
"Ok(2 context: @{x: 2})",
)
// When an let statement is the end expression then bindings are returned
testMacro(
[],
eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY),
"Ok((:$_exportBindings_$ (:$_setBindings_$ @{x: 2} :y 1)) context: @{x: 2})",
)
// Now let's reduce that expression
testMacroEval(
[],
eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY),
"Ok(@{x: 2,y: 1})",
)
// When bindings are missing the context is injected. This must be the first and last statement of a block
testMacroEval(
[("z", IEvNumber(99.))],
eBindExpressionDefault(exampleStatementY),
"Ok(@{y: 1,z: 99})",
)
})
describe("block", () => {
// Block with a single expression
testMacro([], eBlock(list{exampleExpression}), "Ok((:$$_bindExpression_$$ 1))")
testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)")
// Block with a single statement
testMacro([], eBlock(list{exampleStatementY}), "Ok((:$$_bindExpression_$$ (:$_let_$ :y 1)))")
testMacroEval([], eBlock(list{exampleStatementY}), "Ok(@{y: 1})")
// Block with a statement and an expression
testMacro(
[],
eBlock(list{exampleStatementY, exampleExpressionY}),
"Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) :y))",
)
testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)")
// Block with a statement and another statement
testMacro(
[],
eBlock(list{exampleStatementY, exampleStatementZ}),
"Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) (:$_let_$ :z :y)))",
)
testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok(@{y: 1,z: 1})")
// Block inside a block
testMacro([], eBlock(list{eBlock(list{exampleExpression})}), "Ok((:$$_bindExpression_$$ {1}))")
testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)")
// Block assigned to a variable
testMacro(
[],
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
"Ok((:$$_bindExpression_$$ (:$_let_$ :z {{:y}})))",
)
testMacroEval(
[],
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
"Ok(@{z: :y})",
)
// Empty block
testMacro([], eBlock(list{}), "Ok(:undefined block)") //TODO: should be an error
// :$$_block_$$ (:$$_block_$$ (:$_let_$ :y (:add :x 1)) :y)"
testMacro(
[],
eBlock(list{
eBlock(list{
eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})),
eSymbol("y"),
}),
}),
"Ok((:$$_bindExpression_$$ {(:$_let_$ :y (:add :x 1)); :y}))",
)
testMacroEval(
[("x", IEvNumber(1.))],
eBlock(list{
eBlock(list{
eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})),
eSymbol("y"),
}),
}),
"Ok(2)",
)
})
describe("lambda", () => {
// assign a lambda to a variable
let lambdaExpression = eFunction("$$_lambda_$$", list{eArrayString(["y"]), exampleExpressionY})
testMacro([], lambdaExpression, "Ok(lambda(y=>internal code))")
// call a lambda
let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList
testMacro([], callLambdaExpression, "Ok(((:$$_lambda_$$ [y] :y) 1))")
testMacroEval([], callLambdaExpression, "Ok(1)")
// Parameters shadow the outer scope
testMacroEval([("y", IEvNumber(666.))], callLambdaExpression, "Ok(1)")
// When not shadowed by the parameters, the outer scope variables are available
let lambdaExpression = eFunction(
"$$_lambda_$$",
list{eArrayString(["z"]), eFunction("add", list{eSymbol("y"), eSymbol("z")})},
)
let callLambdaExpression = eList(list{lambdaExpression, eNumber(1.)})
testMacroEval([("y", IEvNumber(666.))], callLambdaExpression, "Ok(667)")
})

View File

@ -0,0 +1,41 @@
module ExpressionValue = ReducerInterface.InternalExpressionValue
module Expression = Reducer_Expression
open Jest
open Expect
let expectEvalToBe = (sourceCode: string, answer: string) =>
Expression.BackCompatible.evaluateString(sourceCode)
->ExpressionValue.toStringResult
->expect
->toBe(answer)
let testEval = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
describe("builtin", () => {
// All MathJs operators and functions are available for string, number and boolean
// .e.g + - / * > >= < <= == /= not and or
// See https://mathjs.org/docs/expressions/syntax.html
// See https://mathjs.org/docs/reference/functions.html
testEval("-1", "Ok(-1)")
testEval("1-1", "Ok(0)")
testEval("2>1", "Ok(true)")
testEval("concat('a','b')", "Ok('ab')")
})
describe("builtin exception", () => {
//It's a pity that MathJs does not return error position
test("MathJs Exception", () =>
expectEvalToBe("testZadanga(1)", "Error(JS Exception: Error: Undefined function testZadanga)")
)
})
describe("error reporting from collection functions", () => {
testEval("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])")
testEval(
"arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)",
"Error(zarathsuzaWasHere is not defined)",
)
// FIXME: returns "Error(Function not found: map(Array,Symbol))"
// Actually this error is correct but not informative
})

View File

@ -0,0 +1,17 @@
// Reducer_Helpers
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module Bindings = Reducer_Bindings
let removeDefaultsInternal = (iev: InternalExpressionValue.t) => {
switch iev {
| InternalExpressionValue.IEvBindings(nameSpace) =>
Bindings.removeOther(
nameSpace,
ReducerInterface.StdLib.internalStdLib,
)->InternalExpressionValue.IEvBindings
| value => value
}
}
let rRemoveDefaultsInternal = r => Belt.Result.map(r, removeDefaultsInternal)

View File

@ -0,0 +1,31 @@
module MathJs = Reducer_MathJs
module ErrorValue = Reducer_ErrorValue
open Jest
open ExpectJs
describe("eval", () => {
test("Number", () => expect(MathJs.Eval.eval("1"))->toEqual(Ok(IEvNumber(1.))))
test("Number expr", () => expect(MathJs.Eval.eval("1-1"))->toEqual(Ok(IEvNumber(0.))))
test("String", () => expect(MathJs.Eval.eval("'hello'"))->toEqual(Ok(IEvString("hello"))))
test("String expr", () =>
expect(MathJs.Eval.eval("concat('hello ','world')"))->toEqual(Ok(IEvString("hello world")))
)
test("Boolean", () => expect(MathJs.Eval.eval("true"))->toEqual(Ok(IEvBool(true))))
test("Boolean expr", () => expect(MathJs.Eval.eval("2>1"))->toEqual(Ok(IEvBool(true))))
})
describe("errors", () => {
// All those errors propagete up and are returned by the resolver
test("unknown function", () =>
expect(MathJs.Eval.eval("testZadanga()"))->toEqual(
Error(ErrorValue.REJavaScriptExn(Some("Undefined function testZadanga"), Some("Error"))),
)
)
test("unknown answer type", () =>
expect(MathJs.Eval.eval("1+1i"))->toEqual(
Error(ErrorValue.RETodo("Unhandled MathJs literal type: object")),
)
)
})

View File

@ -1,62 +0,0 @@
@@warning("-44")
module Namespace = Reducer_Namespace
open Jest
open Expect
open Expect.Operators
let makeValue = (v: float) => v->Reducer_T.IEvNumber
describe("Namespace", () => {
let value = makeValue(5.)
let v2 = makeValue(2.)
let ns = Namespace.make()->Namespace.set("value", value)
test("get", () => {
expect(ns->Namespace.get("value")) == Some(value)
})
test("get nonexisting value", () => {
expect(ns->Namespace.get("nosuchvalue")) == None
})
test("set", () => {
let ns2 = ns->Namespace.set("v2", v2)
expect(ns2->Namespace.get("v2")) == Some(v2)
})
test("immutable", () => {
let _ = ns->Namespace.set("v2", Reducer_T.IEvNumber(2.))
expect(ns->Namespace.get("v2")) == None
})
describe("merge many", () => {
let x1 = makeValue(10.)
let x2 = makeValue(20.)
let x3 = makeValue(30.)
let x4 = makeValue(40.)
let ns1 = Namespace.make()->Namespace.set("x1", x1)->Namespace.set("x2", x2)
let ns2 = Namespace.make()->Namespace.set("x3", x3)->Namespace.set("x4", x4)
let nsMerged = Namespace.mergeMany([ns, ns1, ns2])
test(
"merge many 1",
() => {
expect(nsMerged->Namespace.get("x1")) == Some(x1)
},
)
test(
"merge many 2",
() => {
expect(nsMerged->Namespace.get("x4")) == Some(x4)
},
)
test(
"merge many 3",
() => {
expect(nsMerged->Namespace.get("value")) == Some(value)
},
)
})
})

View File

@ -3,231 +3,346 @@ open Reducer_Peggy_TestHelpers
describe("Peggy parse", () => {
describe("float", () => {
testParse("1.", "{1}")
testParse("1.1", "{1.1}")
testParse(".1", "{0.1}")
testParse("0.1", "{0.1}")
testParse("1e1", "{10}")
testParse("1e-1", "{0.1}")
testParse(".1e1", "{1}")
testParse("0.1e1", "{1}")
testParse("1.", "{(::$_endOfOuterBlock_$ () 1)}")
testParse("1.1", "{(::$_endOfOuterBlock_$ () 1.1)}")
testParse(".1", "{(::$_endOfOuterBlock_$ () 0.1)}")
testParse("0.1", "{(::$_endOfOuterBlock_$ () 0.1)}")
testParse("1e1", "{(::$_endOfOuterBlock_$ () 10)}")
testParse("1e-1", "{(::$_endOfOuterBlock_$ () 0.1)}")
testParse(".1e1", "{(::$_endOfOuterBlock_$ () 1)}")
testParse("0.1e1", "{(::$_endOfOuterBlock_$ () 1)}")
})
describe("literals operators parenthesis", () => {
testParse("1", "{1}")
testParse("'hello'", "{'hello'}")
testParse("true", "{true}")
testParse("1+2", "{(:add 1 2)}")
testParse("add(1,2)", "{(:add 1 2)}")
testParse("(1)", "{1}")
testParse("(1+2)", "{(:add 1 2)}")
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement
testParse("1", "{(::$_endOfOuterBlock_$ () 1)}")
testParse("'hello'", "{(::$_endOfOuterBlock_$ () 'hello')}")
testParse("true", "{(::$_endOfOuterBlock_$ () true)}")
testParse("1+2", "{(::$_endOfOuterBlock_$ () (::add 1 2))}")
testParse("add(1,2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}")
testParse("(1)", "{(::$_endOfOuterBlock_$ () 1)}")
testParse("(1+2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}")
})
describe("unary", () => {
testParse("-1", "{(:unaryMinus 1)}")
testParse("!true", "{(:not true)}")
testParse("1 + -1", "{(:add 1 (:unaryMinus 1))}")
testParse("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}")
testParse("!a[0]", "{(:not (:$_atIndex_$ :a 0))}")
testParse("-1", "{(::$_endOfOuterBlock_$ () (::unaryMinus 1))}")
testParse("!true", "{(::$_endOfOuterBlock_$ () (::not true))}")
testParse("1 + -1", "{(::$_endOfOuterBlock_$ () (::add 1 (::unaryMinus 1)))}")
testParse("-a[0]", "{(::$_endOfOuterBlock_$ () (::unaryMinus (::$_atIndex_$ :a 0)))}")
testParse("!a[0]", "{(::$_endOfOuterBlock_$ () (::not (::$_atIndex_$ :a 0)))}")
})
describe("multiplicative", () => {
testParse("1 * 2", "{(:multiply 1 2)}")
testParse("1 / 2", "{(:divide 1 2)}")
testParse("1 * 2 * 3", "{(:multiply (:multiply 1 2) 3)}")
testParse("1 * 2 / 3", "{(:divide (:multiply 1 2) 3)}")
testParse("1 / 2 * 3", "{(:multiply (:divide 1 2) 3)}")
testParse("1 / 2 / 3", "{(:divide (:divide 1 2) 3)}")
testParse("1 * 2 + 3 * 4", "{(:add (:multiply 1 2) (:multiply 3 4))}")
testParse("1 * 2 - 3 * 4", "{(:subtract (:multiply 1 2) (:multiply 3 4))}")
testParse("1 * 2 .+ 3 * 4", "{(:dotAdd (:multiply 1 2) (:multiply 3 4))}")
testParse("1 * 2 .- 3 * 4", "{(:dotSubtract (:multiply 1 2) (:multiply 3 4))}")
testParse("1 * 2 + 3 .* 4", "{(:add (:multiply 1 2) (:dotMultiply 3 4))}")
testParse("1 * 2 + 3 / 4", "{(:add (:multiply 1 2) (:divide 3 4))}")
testParse("1 * 2 + 3 ./ 4", "{(:add (:multiply 1 2) (:dotDivide 3 4))}")
testParse("1 * 2 - 3 .* 4", "{(:subtract (:multiply 1 2) (:dotMultiply 3 4))}")
testParse("1 * 2 - 3 / 4", "{(:subtract (:multiply 1 2) (:divide 3 4))}")
testParse("1 * 2 - 3 ./ 4", "{(:subtract (:multiply 1 2) (:dotDivide 3 4))}")
testParse("1 * 2 - 3 * 4^5", "{(:subtract (:multiply 1 2) (:multiply 3 (:pow 4 5)))}")
testParse("1 * 2", "{(::$_endOfOuterBlock_$ () (::multiply 1 2))}")
testParse("1 / 2", "{(::$_endOfOuterBlock_$ () (::divide 1 2))}")
testParse("1 * 2 * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::multiply 1 2) 3))}")
testParse("1 * 2 / 3", "{(::$_endOfOuterBlock_$ () (::divide (::multiply 1 2) 3))}")
testParse("1 / 2 * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::divide 1 2) 3))}")
testParse("1 / 2 / 3", "{(::$_endOfOuterBlock_$ () (::divide (::divide 1 2) 3))}")
testParse(
"1 * 2 + 3 * 4",
"{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::multiply 3 4)))}",
)
testParse(
"1 * 2 - 3 * 4",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::multiply 3 4)))}",
)
testParse(
"1 * 2 .+ 3 * 4",
"{(::$_endOfOuterBlock_$ () (::dotAdd (::multiply 1 2) (::multiply 3 4)))}",
)
testParse(
"1 * 2 .- 3 * 4",
"{(::$_endOfOuterBlock_$ () (::dotSubtract (::multiply 1 2) (::multiply 3 4)))}",
)
testParse(
"1 * 2 + 3 .* 4",
"{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::dotMultiply 3 4)))}",
)
testParse(
"1 * 2 + 3 / 4",
"{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::divide 3 4)))}",
)
testParse(
"1 * 2 + 3 ./ 4",
"{(::$_endOfOuterBlock_$ () (::add (::multiply 1 2) (::dotDivide 3 4)))}",
)
testParse(
"1 * 2 - 3 .* 4",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::dotMultiply 3 4)))}",
)
testParse(
"1 * 2 - 3 / 4",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::divide 3 4)))}",
)
testParse(
"1 * 2 - 3 ./ 4",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::dotDivide 3 4)))}",
)
testParse(
"1 * 2 - 3 * 4^5",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::multiply 3 (::pow 4 5))))}",
)
testParse(
"1 * 2 - 3 * 4^5^6",
"{(:subtract (:multiply 1 2) (:multiply 3 (:pow (:pow 4 5) 6)))}",
"{(::$_endOfOuterBlock_$ () (::subtract (::multiply 1 2) (::multiply 3 (::pow (::pow 4 5) 6))))}",
)
testParse(
"1 * -a[-2]",
"{(::$_endOfOuterBlock_$ () (::multiply 1 (::unaryMinus (::$_atIndex_$ :a (::unaryMinus 2)))))}",
)
testParse("1 * -a[-2]", "{(:multiply 1 (:unaryMinus (:$_atIndex_$ :a (:unaryMinus 2))))}")
})
describe("multi-line", () => {
testParse("x=1; 2", "{:x = {1}; 2}")
testParse("x=1; y=2", "{:x = {1}; :y = {2}}")
testParse("x=1; 2", "{:x = {1}; (::$_endOfOuterBlock_$ () 2)}")
testParse("x=1; y=2", "{:x = {1}; :y = {2}; (::$_endOfOuterBlock_$ () ())}")
})
describe("variables", () => {
testParse("x = 1", "{:x = {1}}")
testParse("x", "{:x}")
testParse("x = 1; x", "{:x = {1}; :x}")
testParse("x = 1", "{:x = {1}; (::$_endOfOuterBlock_$ () ())}")
testParse("x", "{(::$_endOfOuterBlock_$ () :x)}")
testParse("x = 1; x", "{:x = {1}; (::$_endOfOuterBlock_$ () :x)}")
})
describe("functions", () => {
testParse("identity(x) = x", "{:identity = {|:x| {:x}}}") // Function definitions become lambda assignments
testParse("identity(x)", "{(:identity :x)}")
testParse("identity(x) = x", "{:identity = {|:x| {:x}}; (::$_endOfOuterBlock_$ () ())}") // Function definitions become lambda assignments
testParse("identity(x)", "{(::$_endOfOuterBlock_$ () (::identity :x))}")
})
describe("arrays", () => {
testParse("[]", "{[]}")
testParse("[0, 1, 2]", "{[0; 1; 2]}")
testParse("['hello', 'world']", "{['hello'; 'world']}")
testParse("([0,1,2])[1]", "{(:$_atIndex_$ [0; 1; 2] 1)}")
testParse("[]", "{(::$_endOfOuterBlock_$ () (::$_constructArray_$))}")
testParse("[0, 1, 2]", "{(::$_endOfOuterBlock_$ () (::$_constructArray_$ 0 1 2))}")
testParse(
"['hello', 'world']",
"{(::$_endOfOuterBlock_$ () (::$_constructArray_$ 'hello' 'world'))}",
)
testParse(
"([0,1,2])[1]",
"{(::$_endOfOuterBlock_$ () (::$_atIndex_$ (::$_constructArray_$ 0 1 2) 1))}",
)
})
describe("records", () => {
testParse("{a: 1, b: 2}", "{{'a': 1, 'b': 2}}")
testParse("{1+0: 1, 2+0: 2}", "{{(:add 1 0): 1, (:add 2 0): 2}}") // key can be any expression
testParse("record.property", "{(:$_atIndex_$ :record 'property')}")
testParse(
"{a: 1, b: 2}",
"{(::$_endOfOuterBlock_$ () (::$_constructRecord_$ ('a': 1 'b': 2)))}",
)
testParse(
"{1+0: 1, 2+0: 2}",
"{(::$_endOfOuterBlock_$ () (::$_constructRecord_$ ((::add 1 0): 1 (::add 2 0): 2)))}",
) // key can be any expression
testParse("record.property", "{(::$_endOfOuterBlock_$ () (::$_atIndex_$ :record 'property'))}")
})
describe("post operators", () => {
//function call, array and record access are post operators with higher priority than unary operators
testParse("a==!b(1)", "{(:equal :a (:not (:b 1)))}")
testParse("a==!b[1]", "{(:equal :a (:not (:$_atIndex_$ :b 1)))}")
testParse("a==!b.one", "{(:equal :a (:not (:$_atIndex_$ :b 'one')))}")
testParse("a==!b(1)", "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::b 1))))}")
testParse("a==!b[1]", "{(::$_endOfOuterBlock_$ () (::equal :a (::not (::$_atIndex_$ :b 1))))}")
testParse(
"a==!b.one",
"{(::$_endOfOuterBlock_$ () (::equal :a (::not (::$_atIndex_$ :b 'one'))))}",
)
})
describe("comments", () => {
testParse("1 # This is a line comment", "{1}")
testParse("1 // This is a line comment", "{1}")
testParse("1 /* This is a multi line comment */", "{1}")
testParse("/* This is a multi line comment */ 1", "{1}")
testParse("1 # This is a line comment", "{(::$_endOfOuterBlock_$ () 1)}")
testParse("1 // This is a line comment", "{(::$_endOfOuterBlock_$ () 1)}")
testParse("1 /* This is a multi line comment */", "{(::$_endOfOuterBlock_$ () 1)}")
testParse("/* This is a multi line comment */ 1", "{(::$_endOfOuterBlock_$ () 1)}")
testParse(
`
/* This is
a multi line
comment */
1`,
"{1}",
"{(::$_endOfOuterBlock_$ () 1)}",
)
})
describe("ternary operator", () => {
testParse("true ? 2 : 3", "{(::$$_ternary_$$ true 2 3)}")
testParse("true ? 2 : 3", "{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ true 2 3))}")
testParse(
"false ? 2 : false ? 4 : 5",
"{(::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5))}",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false 2 (::$$_ternary_$$ false 4 5)))}",
) // nested ternary
})
describe("if then else", () => {
testParse("if true then 2 else 3", "{(::$$_ternary_$$ true {2} {3})}")
testParse("if false then {2} else {3}", "{(::$$_ternary_$$ false {2} {3})}")
testParse(
"if true then 2 else 3",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ true {2} {3}))}",
)
testParse(
"if false then {2} else {3}",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false {2} {3}))}",
)
testParse(
"if false then {2} else if false then {4} else {5}",
"{(::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5}))}",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ false {2} (::$$_ternary_$$ false {4} {5})))}",
) //nested if
})
describe("logical", () => {
testParse("true || false", "{(:or true false)}")
testParse("true && false", "{(:and true false)}")
testParse("a * b + c", "{(:add (:multiply :a :b) :c)}") // for comparison
testParse("a && b || c", "{(:or (:and :a :b) :c)}")
testParse("a && b || c && d", "{(:or (:and :a :b) (:and :c :d))}")
testParse("a && !b || c", "{(:or (:and :a (:not :b)) :c)}")
testParse("a && b==c || d", "{(:or (:and :a (:equal :b :c)) :d)}")
testParse("a && b!=c || d", "{(:or (:and :a (:unequal :b :c)) :d)}")
testParse("a && !(b==c) || d", "{(:or (:and :a (:not (:equal :b :c))) :d)}")
testParse("a && b>=c || d", "{(:or (:and :a (:largerEq :b :c)) :d)}")
testParse("a && !(b>=c) || d", "{(:or (:and :a (:not (:largerEq :b :c))) :d)}")
testParse("a && b<=c || d", "{(:or (:and :a (:smallerEq :b :c)) :d)}")
testParse("a && b>c || d", "{(:or (:and :a (:larger :b :c)) :d)}")
testParse("a && b<c || d", "{(:or (:and :a (:smaller :b :c)) :d)}")
testParse("a && b<c[i] || d", "{(:or (:and :a (:smaller :b (:$_atIndex_$ :c :i))) :d)}")
testParse("a && b<c.i || d", "{(:or (:and :a (:smaller :b (:$_atIndex_$ :c 'i'))) :d)}")
testParse("a && b<c(i) || d", "{(:or (:and :a (:smaller :b (:c :i))) :d)}")
testParse("a && b<1+2 || d", "{(:or (:and :a (:smaller :b (:add 1 2))) :d)}")
testParse("a && b<1+2*3 || d", "{(:or (:and :a (:smaller :b (:add 1 (:multiply 2 3)))) :d)}")
testParse("true || false", "{(::$_endOfOuterBlock_$ () (::or true false))}")
testParse("true && false", "{(::$_endOfOuterBlock_$ () (::and true false))}")
testParse("a * b + c", "{(::$_endOfOuterBlock_$ () (::add (::multiply :a :b) :c))}") // for comparison
testParse("a && b || c", "{(::$_endOfOuterBlock_$ () (::or (::and :a :b) :c))}")
testParse("a && b || c && d", "{(::$_endOfOuterBlock_$ () (::or (::and :a :b) (::and :c :d)))}")
testParse("a && !b || c", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::not :b)) :c))}")
testParse("a && b==c || d", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::equal :b :c)) :d))}")
testParse(
"a && b!=c || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::unequal :b :c)) :d))}",
)
testParse(
"a && !(b==c) || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::not (::equal :b :c))) :d))}",
)
testParse(
"a && b>=c || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::largerEq :b :c)) :d))}",
)
testParse(
"a && !(b>=c) || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::not (::largerEq :b :c))) :d))}",
)
testParse(
"a && b<=c || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smallerEq :b :c)) :d))}",
)
testParse("a && b>c || d", "{(::$_endOfOuterBlock_$ () (::or (::and :a (::larger :b :c)) :d))}")
testParse(
"a && b<c || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b :c)) :d))}",
)
testParse(
"a && b<c[i] || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::$_atIndex_$ :c :i))) :d))}",
)
testParse(
"a && b<c.i || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::$_atIndex_$ :c 'i'))) :d))}",
)
testParse(
"a && b<c(i) || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::c :i))) :d))}",
)
testParse(
"a && b<1+2 || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::add 1 2))) :d))}",
)
testParse(
"a && b<1+2*3 || d",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d))}",
)
testParse(
"a && b<1+2*-3+4 || d",
"{(:or (:and :a (:smaller :b (:add (:add 1 (:multiply 2 (:unaryMinus 3))) 4))) :d)}",
"{(::$_endOfOuterBlock_$ () (::or (::and :a (::smaller :b (::add (::add 1 (::multiply 2 (::unaryMinus 3))) 4))) :d))}",
)
testParse(
"a && b<1+2*3 || d ? true : false",
"{(::$$_ternary_$$ (:or (:and :a (:smaller :b (:add 1 (:multiply 2 3)))) :d) true false)}",
"{(::$_endOfOuterBlock_$ () (::$$_ternary_$$ (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d) true false))}",
)
})
describe("pipe", () => {
testParse("1 -> add(2)", "{(:add 1 2)}")
testParse("-1 -> add(2)", "{(:add (:unaryMinus 1) 2)}")
testParse("-a[1] -> add(2)", "{(:add (:unaryMinus (:$_atIndex_$ :a 1)) 2)}")
testParse("-f(1) -> add(2)", "{(:add (:unaryMinus (:f 1)) 2)}")
testParse("1 + 2 -> add(3)", "{(:add 1 (:add 2 3))}")
testParse("1 -> add(2) * 3", "{(:multiply (:add 1 2) 3)}")
testParse("1 -> subtract(2)", "{(:subtract 1 2)}")
testParse("-1 -> subtract(2)", "{(:subtract (:unaryMinus 1) 2)}")
testParse("1 -> subtract(2) * 3", "{(:multiply (:subtract 1 2) 3)}")
testParse("1 -> add(2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}")
testParse("-1 -> add(2)", "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus 1) 2))}")
testParse(
"-a[1] -> add(2)",
"{(::$_endOfOuterBlock_$ () (::add (::unaryMinus (::$_atIndex_$ :a 1)) 2))}",
)
testParse("-f(1) -> add(2)", "{(::$_endOfOuterBlock_$ () (::add (::unaryMinus (::f 1)) 2))}")
testParse("1 + 2 -> add(3)", "{(::$_endOfOuterBlock_$ () (::add 1 (::add 2 3)))}")
testParse("1 -> add(2) * 3", "{(::$_endOfOuterBlock_$ () (::multiply (::add 1 2) 3))}")
testParse("1 -> subtract(2)", "{(::$_endOfOuterBlock_$ () (::subtract 1 2))}")
testParse("-1 -> subtract(2)", "{(::$_endOfOuterBlock_$ () (::subtract (::unaryMinus 1) 2))}")
testParse(
"1 -> subtract(2) * 3",
"{(::$_endOfOuterBlock_$ () (::multiply (::subtract 1 2) 3))}",
)
})
describe("elixir pipe", () => {
//handled together with -> so there is no need for seperate tests
testParse("1 |> add(2)", "{(:add 1 2)}")
testParse("1 |> add(2)", "{(::$_endOfOuterBlock_$ () (::add 1 2))}")
})
describe("to", () => {
testParse("1 to 2", "{(:credibleIntervalToDistribution 1 2)}")
testParse("-1 to -2", "{(:credibleIntervalToDistribution (:unaryMinus 1) (:unaryMinus 2))}") // lower than unary
testParse("1 to 2", "{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution 1 2))}")
testParse(
"-1 to -2",
"{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2)))}",
) // lower than unary
testParse(
"a[1] to a[2]",
"{(:credibleIntervalToDistribution (:$_atIndex_$ :a 1) (:$_atIndex_$ :a 2))}",
"{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::$_atIndex_$ :a 1) (::$_atIndex_$ :a 2)))}",
) // lower than post
testParse(
"a.p1 to a.p2",
"{(:credibleIntervalToDistribution (:$_atIndex_$ :a 'p1') (:$_atIndex_$ :a 'p2'))}",
"{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::$_atIndex_$ :a 'p1') (::$_atIndex_$ :a 'p2')))}",
) // lower than post
testParse("1 to 2 + 3", "{(:credibleIntervalToDistribution 1 (:add 2 3))}")
testParse(
"1 to 2 + 3",
"{(::$_endOfOuterBlock_$ () (::add (::credibleIntervalToDistribution 1 2) 3))}",
) // higher than binary operators
testParse(
"1->add(2) to 3->add(4) -> add(4)",
"{(:credibleIntervalToDistribution (:add 1 2) (:add (:add 3 4) 4))}",
"{(::$_endOfOuterBlock_$ () (::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4)))}",
) // lower than chain
})
describe("inner block", () => {
// inner blocks are 0 argument lambdas. They can be used whenever a value is required.
// Like lambdas they have a local scope.
testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; :x}")
testParse("x={y=1; y}; x", "{:x = {:y = {1}; :y}; (::$_endOfOuterBlock_$ () :x)}")
})
describe("lambda", () => {
testParse("{|x| x}", "{{|: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
testParse("{|x| x}", "{(::$_endOfOuterBlock_$ () {|:x| {:x}})}")
testParse("f={|x| x}", "{:f = {{|:x| {:x}}}; (::$_endOfOuterBlock_$ () ())}")
testParse("f(x)=x", "{:f = {|:x| {:x}}; (::$_endOfOuterBlock_$ () ())}") // Function definitions are lambda assignments
testParse(
"f(x)=x ? 1 : 0",
"{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}; (::$_endOfOuterBlock_$ () ())}",
) // Function definitions are lambda assignments
})
describe("Using lambda as value", () => {
testParse(
"myadd(x,y)=x+y; z=myadd; z",
"{:myadd = {|:x,:y| {(:add :x :y)}}; :z = {:myadd}; :z}",
"{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; (::$_endOfOuterBlock_$ () :z)}",
)
testParse(
"myadd(x,y)=x+y; z=[myadd]; z",
"{:myadd = {|:x,:y| {(:add :x :y)}}; :z = {[:myadd]}; :z}",
"{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructArray_$ :myadd)}; (::$_endOfOuterBlock_$ () :z)}",
)
testParse(
"myaddd(x,y)=x+y; z={x: myaddd}; z",
"{:myaddd = {|:x,:y| {(:add :x :y)}}; :z = {{'x': :myaddd}}; :z}",
"{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructRecord_$ ('x': :myaddd))}; (::$_endOfOuterBlock_$ () :z)}",
)
testParse("f({|x| x+1})", "{(::$_endOfOuterBlock_$ () (::f {|:x| {(::add :x 1)}}))}")
testParse(
"map(arr, {|x| x+1})",
"{(::$_endOfOuterBlock_$ () (::map :arr {|:x| {(::add :x 1)}}))}",
)
testParse(
"map([1,2,3], {|x| x+1})",
"{(::$_endOfOuterBlock_$ () (::map (::$_constructArray_$ 1 2 3) {|:x| {(::add :x 1)}}))}",
)
testParse(
"[1,2,3]->map({|x| x+1})",
"{(::$_endOfOuterBlock_$ () (::map (::$_constructArray_$ 1 2 3) {|:x| {(::add :x 1)}}))}",
)
testParse("f({|x| x+1})", "{(:f {|:x| (:add :x 1)})}")
testParse("map(arr, {|x| x+1})", "{(:map :arr {|:x| (:add :x 1)})}")
testParse("map([1,2,3], {|x| x+1})", "{(:map [1; 2; 3] {|:x| (:add :x 1)})}")
testParse("[1,2,3]->map({|x| x+1})", "{(:map [1; 2; 3] {|:x| (:add :x 1)})}")
})
describe("unit", () => {
testParse("1m", "{(:fromUnit_m 1)}")
testParse("1M", "{(:fromUnit_M 1)}")
testParse("1m+2cm", "{(:add (:fromUnit_m 1) (:fromUnit_cm 2))}")
testParse("1m", "{(::$_endOfOuterBlock_$ () (::fromUnit_m 1))}")
testParse("1M", "{(::$_endOfOuterBlock_$ () (::fromUnit_M 1))}")
testParse("1m+2cm", "{(::$_endOfOuterBlock_$ () (::add (::fromUnit_m 1) (::fromUnit_cm 2)))}")
})
describe("Module", () => {
testParse("x", "{:x}")
testParse("Math.pi", "{:Math.pi}")
testParse("x", "{(::$_endOfOuterBlock_$ () :x)}")
testParse("Math.pi", "{(::$_endOfOuterBlock_$ () :Math.pi)}")
})
})
@ -236,19 +351,19 @@ describe("parsing new line", () => {
`
a +
b`,
"{(:add :a :b)}",
"{(::$_endOfOuterBlock_$ () (::add :a :b))}",
)
testParse(
`
x=
1`,
"{:x = {1}}",
"{:x = {1}; (::$_endOfOuterBlock_$ () ())}",
)
testParse(
`
x=1
y=2`,
"{:x = {1}; :y = {2}}",
"{:x = {1}; :y = {2}; (::$_endOfOuterBlock_$ () ())}",
)
testParse(
`
@ -256,7 +371,7 @@ describe("parsing new line", () => {
y=2;
y }
x`,
"{:x = {:y = {2}; :y}; :x}",
"{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}",
)
testParse(
`
@ -264,7 +379,7 @@ describe("parsing new line", () => {
y=2
y }
x`,
"{:x = {:y = {2}; :y}; :x}",
"{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}",
)
testParse(
`
@ -273,7 +388,7 @@ describe("parsing new line", () => {
y
}
x`,
"{:x = {:y = {2}; :y}; :x}",
"{:x = {:y = {2}; :y}; (::$_endOfOuterBlock_$ () :x)}",
)
testParse(
`
@ -281,7 +396,7 @@ describe("parsing new line", () => {
y=2
z=3
`,
"{:x = {1}; :y = {2}; :z = {3}}",
"{:x = {1}; :y = {2}; :z = {3}; (::$_endOfOuterBlock_$ () ())}",
)
testParse(
`
@ -292,7 +407,7 @@ describe("parsing new line", () => {
x+y+z
}
`,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}}",
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; (::$_endOfOuterBlock_$ () ())}",
)
testParse(
`
@ -305,7 +420,7 @@ describe("parsing new line", () => {
g=f+4
g
`,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}; :g = {(:add :f 4)}; :g}",
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::$_endOfOuterBlock_$ () :g)}",
)
testParse(
`
@ -327,7 +442,7 @@ describe("parsing new line", () => {
p ->
q
`,
"{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}; :g = {(:add :f 4)}; (:q (:p (:h :g)))}",
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::$_endOfOuterBlock_$ () (::q (::p (::h :g))))}",
)
testParse(
`
@ -336,7 +451,7 @@ describe("parsing new line", () => {
c |>
d
`,
"{(:d (:c (:b :a)))}",
"{(::$_endOfOuterBlock_$ () (::d (::c (::b :a))))}",
)
testParse(
`
@ -346,6 +461,6 @@ describe("parsing new line", () => {
d +
e
`,
"{(:add (:d (:c (:b :a))) :e)}",
"{(::$_endOfOuterBlock_$ () (::add (::d (::c (::b :a))) :e))}",
)
})

View File

@ -1,5 +1,6 @@
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ExpressionValue = ReducerInterface.InternalExpressionValue
module Parse = Reducer_Peggy_Parse
module Result = Belt.Result
module ToExpression = Reducer_Peggy_ToExpression
@ -9,12 +10,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,24 +23,24 @@ 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
->Reducer_Helpers.rRemoveDefaultsInternal
->ExpressionValue.toStringResultOkless
(a1, a2)->expect->toEqual((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

@ -1,12 +1,23 @@
module Bindings = Reducer_Bindings
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Jest
open Reducer_Peggy_TestHelpers
// Note: these tests aren't useful anymore since outer block macro got deleted.
// Probably can be removed or folded into other Peggy tests.
describe("Peggy Outer Block", () => {
testToExpression("1", "1", ~v="1", ())
testToExpression("x=1", "x = {1}", ~v="()", ())
testToExpression("x=1; y=2", "x = {1}; y = {2}", ~v="()", ())
testToExpression("x=1; 2", "x = {1}; 2", ~v="2", ())
testToExpression("x={a=1; a}; x", "x = {a = {1}; a}; x", ~v="1", ())
testToExpression("1", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ())
testToExpression("x=1", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () ())}", ~v="()", ())
testToExpression(
"x=1; y=2",
"{(:$_let_$ :x {1}); (:$_let_$ :y {2}); (:$_endOfOuterBlock_$ () ())}",
~v="()",
(),
)
testToExpression("x=1; 2", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () 2)}", ~v="2", ())
testToExpression(
"x={a=1; a}; x",
"{(:$_let_$ :x {(:$_let_$ :a {1}); :a}); (:$_endOfOuterBlock_$ () :x)}",
~v="1",
(),
)
})

View File

@ -1,4 +1,5 @@
module Bindings = Reducer_Bindings
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Jest
open Reducer_Peggy_TestHelpers
@ -6,121 +7,202 @@ open Reducer_Peggy_TestHelpers
describe("Peggy to Expression", () => {
describe("literals operators parenthesis", () => {
// Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement
testToExpression("1", "1", ~v="1", ())
testToExpression("'hello'", "'hello'", ~v="'hello'", ())
testToExpression("true", "true", ~v="true", ())
testToExpression("1+2", "(add)(1, 2)", ~v="3", ())
testToExpression("add(1,2)", "(add)(1, 2)", ~v="3", ())
testToExpression("(1)", "1", ())
testToExpression("(1+2)", "(add)(1, 2)", ())
testToExpression("1", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ())
testToExpression("'hello'", "{(:$_endOfOuterBlock_$ () 'hello')}", ~v="'hello'", ())
testToExpression("true", "{(:$_endOfOuterBlock_$ () true)}", ~v="true", ())
testToExpression("1+2", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ())
testToExpression("add(1,2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ())
testToExpression("(1)", "{(:$_endOfOuterBlock_$ () 1)}", ())
testToExpression("(1+2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ())
})
describe("unary", () => {
testToExpression("-1", "(unaryMinus)(1)", ~v="-1", ())
testToExpression("!true", "(not)(true)", ~v="false", ())
testToExpression("1 + -1", "(add)(1, (unaryMinus)(1))", ~v="0", ())
testToExpression("-a[0]", "(unaryMinus)(($_atIndex_$)(a, 0))", ())
testToExpression("-1", "{(:$_endOfOuterBlock_$ () (:unaryMinus 1))}", ~v="-1", ())
testToExpression("!true", "{(:$_endOfOuterBlock_$ () (:not true))}", ~v="false", ())
testToExpression("1 + -1", "{(:$_endOfOuterBlock_$ () (:add 1 (:unaryMinus 1)))}", ~v="0", ())
testToExpression("-a[0]", "{(:$_endOfOuterBlock_$ () (:unaryMinus (:$_atIndex_$ :a 0)))}", ())
})
describe("multi-line", () => {
testToExpression("x=1; 2", "x = {1}; 2", ~v="2", ())
testToExpression("x=1; y=2", "x = {1}; y = {2}", ())
testToExpression("x=1; 2", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () 2)}", ~v="2", ())
testToExpression(
"x=1; y=2",
"{(:$_let_$ :x {1}); (:$_let_$ :y {2}); (:$_endOfOuterBlock_$ () ())}",
(),
)
})
describe("variables", () => {
testToExpression("x = 1", "x = {1}", ())
testToExpression("x", "x", ~v="Error(x is not defined)", ()) //TODO: value should return error
testToExpression("x = 1; x", "x = {1}; x", ~v="1", ())
testToExpression("x = 1", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () ())}", ())
testToExpression("x", "{(:$_endOfOuterBlock_$ () :x)}", ~v="Error(x is not defined)", ()) //TODO: value should return error
testToExpression("x = 1; x", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () :x)}", ~v="1", ())
})
describe("functions", () => {
testToExpression("identity(x) = x", "identity = {|x| {x}}", ()) // Function definitions become lambda assignments
testToExpression("identity(x)", "(identity)(x)", ()) // Note value returns error properly
testToExpression(
"identity(x) = x",
"{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())}",
(),
) // Function definitions become lambda assignments
testToExpression("identity(x)", "{(:$_endOfOuterBlock_$ () (:identity :x))}", ()) // Note value returns error properly
testToExpression(
"f(x) = x> 2 ? 0 : 1; f(3)",
"f = {|x| {(larger)(x, 2) ? (0) : (1)}}; (f)(3)",
"{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:$_endOfOuterBlock_$ () (:f 3))}",
~v="0",
(),
)
})
describe("arrays", () => {
testToExpression("[]", "[]", ~v="[]", ())
testToExpression("[0, 1, 2]", "[0, 1, 2]", ~v="[0,1,2]", ())
testToExpression("['hello', 'world']", "['hello', 'world']", ~v="['hello','world']", ())
testToExpression("([0,1,2])[1]", "($_atIndex_$)([0, 1, 2], 1)", ~v="1", ())
testToExpression("[]", "{(:$_endOfOuterBlock_$ () (:$_constructArray_$))}", ~v="[]", ())
testToExpression(
"[0, 1, 2]",
"{(:$_endOfOuterBlock_$ () (:$_constructArray_$ 0 1 2))}",
~v="[0,1,2]",
(),
)
testToExpression(
"['hello', 'world']",
"{(:$_endOfOuterBlock_$ () (:$_constructArray_$ 'hello' 'world'))}",
~v="['hello','world']",
(),
)
testToExpression(
"([0,1,2])[1]",
"{(:$_endOfOuterBlock_$ () (:$_atIndex_$ (:$_constructArray_$ 0 1 2) 1))}",
~v="1",
(),
)
})
describe("records", () => {
testToExpression("{a: 1, b: 2}", "{'a': 1, 'b': 2}", ~v="{a: 1,b: 2}", ())
testToExpression("{1+0: 1, 2+0: 2}", "{(add)(1, 0): 1, (add)(2, 0): 2}", ()) // key can be any expression
testToExpression("record.property", "($_atIndex_$)(record, 'property')", ())
testToExpression(
"{a: 1, b: 2}",
"{(:$_endOfOuterBlock_$ () (:$_constructRecord_$ (('a' 1) ('b' 2))))}",
~v="{a: 1,b: 2}",
(),
)
testToExpression(
"{1+0: 1, 2+0: 2}",
"{(:$_endOfOuterBlock_$ () (:$_constructRecord_$ (((:add 1 0) 1) ((:add 2 0) 2))))}",
(),
) // key can be any expression
testToExpression(
"record.property",
"{(:$_endOfOuterBlock_$ () (:$_atIndex_$ :record 'property'))}",
(),
)
testToExpression(
"record={property: 1}; record.property",
"record = {{'property': 1}}; ($_atIndex_$)(record, 'property')",
"{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_endOfOuterBlock_$ () (:$_atIndex_$ :record 'property'))}",
~v="1",
(),
)
})
describe("comments", () => {
testToExpression("1 # This is a line comment", "1", ~v="1", ())
testToExpression("1 // This is a line comment", "1", ~v="1", ())
testToExpression("1 /* This is a multi line comment */", "1", ~v="1", ())
testToExpression("/* This is a multi line comment */ 1", "1", ~v="1", ())
testToExpression("1 # This is a line comment", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ())
testToExpression("1 // This is a line comment", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ())
testToExpression(
"1 /* This is a multi line comment */",
"{(:$_endOfOuterBlock_$ () 1)}",
~v="1",
(),
)
testToExpression(
"/* This is a multi line comment */ 1",
"{(:$_endOfOuterBlock_$ () 1)}",
~v="1",
(),
)
})
describe("ternary operator", () => {
testToExpression("true ? 1 : 0", "true ? (1) : (0)", ~v="1", ())
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",
() => {
testToExpression(
"true ? 1 : 0",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 1 0))}",
~v="1",
(),
)
testToExpression(
"false ? 1 : 0",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false 1 0))}",
~v="0",
(),
)
testToExpression(
"true ? 1 : false ? 2 : 0",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0)))}",
~v="1",
(),
) // nested ternary
testToExpression(
"false ? 1 : false ? 2 : 0",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0)))}",
~v="0",
(),
) // nested ternary
describe("ternary bindings", () => {
testToExpression(
// expression binding
"f(a) = a > 5 ? 1 : 0; f(6)",
"f = {|a| {(larger)(a, 5) ? (1) : (0)}}; (f)(6)",
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:$_endOfOuterBlock_$ () (:f 6))}",
~v="1",
(),
)
testToExpression(
// when true binding
"f(a) = a > 5 ? a : 0; f(6)",
"f = {|a| {(larger)(a, 5) ? (a) : (0)}}; (f)(6)",
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:$_endOfOuterBlock_$ () (:f 6))}",
~v="6",
(),
)
testToExpression(
// when false binding
"f(a) = a < 5 ? 1 : a; f(6)",
"f = {|a| {(smaller)(a, 5) ? (1) : (a)}}; (f)(6)",
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:$_endOfOuterBlock_$ () (:f 6))}",
~v="6",
(),
)
},
)
})
})
describe("if then else", () => {
testToExpression("if true then 2 else 3", "true ? ({2}) : ({3})", ())
testToExpression("if true then {2} else {3}", "true ? ({2}) : ({3})", ())
testToExpression(
"if true then 2 else 3",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true {2} {3}))}",
(),
)
testToExpression(
"if true then {2} else {3}",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true {2} {3}))}",
(),
)
testToExpression(
"if false then {2} else if false then {4} else {5}",
"false ? ({2}) : (false ? ({4}) : ({5}))",
"{(:$_endOfOuterBlock_$ () (:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5})))}",
(),
) //nested if
})
describe("pipe", () => {
testToExpression("1 -> add(2)", "(add)(1, 2)", ~v="3", ())
testToExpression("-1 -> add(2)", "(add)((unaryMinus)(1), 2)", ~v="1", ()) // note that unary has higher priority naturally
testToExpression("1 -> add(2) * 3", "(multiply)((add)(1, 2), 3)", ~v="9", ())
testToExpression("1 -> add(2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ())
testToExpression(
"-1 -> add(2)",
"{(:$_endOfOuterBlock_$ () (:add (:unaryMinus 1) 2))}",
~v="1",
(),
) // note that unary has higher priority naturally
testToExpression(
"1 -> add(2) * 3",
"{(:$_endOfOuterBlock_$ () (:multiply (:add 1 2) 3))}",
~v="9",
(),
)
})
describe("elixir pipe", () => {
testToExpression("1 |> add(2)", "(add)(1, 2)", ~v="3", ())
testToExpression("1 |> add(2)", "{(:$_endOfOuterBlock_$ () (:add 1 2))}", ~v="3", ())
})
// see testParse for priorities of to and credibleIntervalToDistribution
@ -130,28 +212,44 @@ describe("Peggy to Expression", () => {
// Like lambdas they have a local scope.
testToExpression(
"y=99; x={y=1; y}",
"y = {99}; x = {y = {1}; y}",
// "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y}); (:$_endOfOuterBlock_$ () ())}",
"{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y}); (:$_endOfOuterBlock_$ () ())}",
(),
)
})
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}}", ()) // Function definitions are lambda assignments
testToExpression("f(x)=x ? 1 : 0", "f = {|x| {x ? (1) : (0)}}", ())
testToExpression(
"{|x| x}",
"{(:$_endOfOuterBlock_$ () (:$$_lambda_$$ [x] {:x}))}",
~v="lambda(x=>internal code)",
(),
)
testToExpression(
"f={|x| x}",
"{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})}); (:$_endOfOuterBlock_$ () ())}",
(),
)
testToExpression(
"f(x)=x",
"{(:$_let_$ :f (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())}",
(),
) // Function definitions are lambda assignments
testToExpression(
"f(x)=x ? 1 : 0",
"{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ :x 1 0)})); (:$_endOfOuterBlock_$ () ())}",
(),
)
})
describe("module", () => {
// testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ())
// Only.test("stdlibrary", () => {
// SquiggleLibrary_StdLib.stdLib
// ReducerInterface_StdLib.internalStdLib
// ->IEvBindings
// ->Reducer_Value.toString
// ->InternalExpressionValue.toString
// ->expect
// ->toBe("")
// })
testToExpression("Math.pi", "Math.pi", ~v="3.141592653589793", ())
testToExpression("Math.pi", "{(:$_endOfOuterBlock_$ () :Math.pi)}", ~v="3.141592653589793", ())
})
})

View File

@ -3,17 +3,22 @@ open Reducer_Peggy_TestHelpers
describe("Peggy void", () => {
//literal
testToExpression("()", "()", ~v="()", ())
testToExpression("()", "{(:$_endOfOuterBlock_$ () ())}", ~v="()", ())
testToExpression(
"fn()=1",
"fn = {|_| {1}}",
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () ())}",
// ~v="@{fn: lambda(_=>internal code)}",
(),
)
testToExpression("fn()=1; fn()", "fn = {|_| {1}}; (fn)(())", ~v="1", ())
testToExpression(
"fn()=1; fn()",
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:$_endOfOuterBlock_$ () (:fn ()))}",
~v="1",
(),
)
testToExpression(
"fn(a)=(); call fn(1)",
"fn = {|a| {()}}; _ = {(fn)(1)}",
"{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)}); (:$_endOfOuterBlock_$ () ())}",
// ~v="@{_: (),fn: lambda(a=>internal code)}",
(),
)

View File

@ -1,5 +1,7 @@
module ErrorValue = Reducer_ErrorValue
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
open Jest
open Expect
@ -7,8 +9,8 @@ open Expect
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
| InternalExpressionValue.IEvRecord(aRecord) => Ok(aRecord)
| _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error
}
)
@ -16,13 +18,17 @@ let expectParseToBe = (code: string, answer: string) =>
Expression.BackCompatible.parse(code)->ExpressionT.toStringResult->expect->toBe(answer)
let expectEvalToBe = (code: string, answer: string) =>
Expression.BackCompatible.evaluateString(code)->Reducer_Value.toStringResult->expect->toBe(answer)
Expression.BackCompatible.evaluateString(code)
->Reducer_Helpers.rRemoveDefaultsInternal
->InternalExpressionValue.toStringResult
->expect
->toBe(answer)
let expectEvalError = (code: string) =>
Expression.BackCompatible.evaluateString(code)
->Reducer_Value.toStringResult
->InternalExpressionValue.toStringResult
->expect
->toMatch("Error\\(")
->toMatch("Error\(")
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
let testDescriptionParseToBe = (desc, expr, answer) =>

View File

@ -0,0 +1,89 @@
open Jest
open Expect
module Bindings = Reducer_Bindings
module BindingsReplacer = Reducer_Expression_BindingsReplacer
module Expression = Reducer_Expression
module ExpressionWithContext = Reducer_ExpressionWithContext
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module Macro = Reducer_Expression_Macro
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module T = Reducer_Expression_T
let testMacro_ = (
tester,
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedCode: string,
) => {
let bindings = Bindings.fromArray(bindArray)
tester(expr->T.toString, () =>
expr
->Macro.expandMacroCallRs(
bindings,
ProjectAccessorsT.identityAccessors,
Expression.reduceExpressionInProject,
)
->ExpressionWithContext.toStringResult
->expect
->toEqual(expectedCode)
)
}
let testMacroEval_ = (
tester,
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedValue: string,
) => {
let bindings = Bindings.fromArray(bindArray)
tester(expr->T.toString, () =>
expr
->Macro.doMacroCall(
bindings,
ProjectAccessorsT.identityAccessors,
Expression.reduceExpressionInProject,
)
->Ok
->InternalExpressionValue.toStringResult
->expect
->toEqual(expectedValue)
)
}
let testMacro = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedExpr: string,
) => testMacro_(test, bindArray, expr, expectedExpr)
let testMacroEval = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedValue: string,
) => testMacroEval_(test, bindArray, expr, expectedValue)
module MySkip = {
let testMacro = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedExpr: string,
) => testMacro_(Skip.test, bindArray, expr, expectedExpr)
let testMacroEval = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedValue: string,
) => testMacroEval_(Skip.test, bindArray, expr, expectedValue)
}
module MyOnly = {
let testMacro = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedExpr: string,
) => testMacro_(Only.test, bindArray, expr, expectedExpr)
let testMacroEval = (
bindArray: array<(string, InternalExpressionValue.t)>,
expr: T.expression,
expectedValue: string,
) => testMacroEval_(Only.test, bindArray, expr, expectedValue)
}

View File

@ -0,0 +1,52 @@
module Expression = Reducer_Expression
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeCompile = Reducer_Type_Compile
open Jest
open Expect
let myIevEval = (aTypeSourceCode: string) =>
TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject)
let myIevEvalToString = (aTypeSourceCode: string) =>
myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult
let myIevExpectEqual = (aTypeSourceCode, answer) =>
expect(myIevEvalToString(aTypeSourceCode))->toEqual(answer)
let myIevTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer))
let myTypeEval = (aTypeSourceCode: string) =>
TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject)
let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult
let myTypeExpectEqual = (aTypeSourceCode, answer) =>
expect(myTypeEvalToString(aTypeSourceCode))->toEqual(answer)
let myTypeTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myTypeExpectEqual(aTypeSourceCode, answer))
// | ItTypeIdentifier(string)
myTypeTest(test, "number", "number")
myTypeTest(test, "(number)", "number")
// | ItModifiedType({modifiedType: iType})
myIevTest(test, "number<-min(0)", "Ok({min: 0,typeIdentifier: #number,typeTag: 'typeIdentifier'})")
myTypeTest(test, "number<-min(0)", "number<-min(0)")
// | ItTypeOr({typeOr: array<iType>})
myTypeTest(test, "number | string", "(number | string)")
// | ItTypeFunction({inputs: array<iType>, output: iType})
myTypeTest(test, "number => number => number", "(number => number => number)")
// | ItTypeArray({element: iType})
myIevTest(test, "[number]", "Ok({element: #number,typeTag: 'typeArray'})")
myTypeTest(test, "[number]", "[number]")
// | ItTypeTuple({elements: array<iType>})
myTypeTest(test, "[number, string]", "[number, string]")
// | ItTypeRecord({properties: Belt.Map.String.t<iType>})
myIevTest(
test,
"{age: number, name: string}",
"Ok({properties: {age: #number,name: #string},typeTag: 'typeRecord'})",
)
myTypeTest(test, "{age: number, name: string}", "{age: number, name: string}")

View File

@ -0,0 +1,42 @@
module Bindings = Reducer_Bindings
module ErrorValue = Reducer_ErrorValue
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker
open Jest
open Expect
let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v,
ErrorValue.t,
> => {
let reducerFn = Expression.reduceExpressionInProject
let rResult =
Expression.BackCompatible.parse(sourceCode)->Belt.Result.map(expr =>
reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors)
)
rResult->Belt.Result.flatMap(result =>
switch result {
| IEvArray(args) => TypeChecker.checkArguments(aTypeSourceCode, args, reducerFn)
| _ => Js.Exn.raiseError("Arguments has to be an array")
}
)
}
let myCheckArguments = (aTypeSourceCode: string, sourceCode: string): string =>
switch checkArgumentsSourceCode(aTypeSourceCode, sourceCode) {
| Ok(_) => "Ok"
| Error(error) => ErrorValue.errorToString(error)
}
let myCheckArgumentsExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
expect(myCheckArguments(aTypeSourceCode, sourceCode))->toEqual(answer)
let myCheckArgumentsTest = (test, aTypeSourceCode, sourceCode, answer) =>
test(aTypeSourceCode, () => myCheckArgumentsExpectEqual(aTypeSourceCode, sourceCode, answer))
myCheckArgumentsTest(test, "number=>number=>number", "[1,2]", "Ok")

View File

@ -0,0 +1,73 @@
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
open Jest
open Expect
// In development, you are expected to use TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn).
// isTypeOfSourceCode is written to use strings instead of expression values.
let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v,
ErrorValue.t,
> => {
let reducerFn = Expression.reduceExpressionInProject
let rResult =
Expression.BackCompatible.parse(sourceCode)->Belt.Result.map(expr =>
reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors)
)
rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn))
}
let myTypeCheck = (aTypeSourceCode: string, sourceCode: string): string =>
switch isTypeOfSourceCode(aTypeSourceCode, sourceCode) {
| Ok(_) => "Ok"
| Error(error) => ErrorValue.errorToString(error)
}
let myTypeCheckExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
expect(myTypeCheck(aTypeSourceCode, sourceCode))->toEqual(answer)
let myTypeCheckTest = (test, aTypeSourceCode, sourceCode, answer) =>
test(aTypeSourceCode, () => myTypeCheckExpectEqual(aTypeSourceCode, sourceCode, answer))
myTypeCheckTest(test, "number", "1", "Ok")
myTypeCheckTest(test, "number", "'2'", "Expected type: number but got: '2'")
myTypeCheckTest(test, "string", "3", "Expected type: string but got: 3")
myTypeCheckTest(test, "string", "'a'", "Ok")
myTypeCheckTest(test, "[number]", "[1,2,3]", "Ok")
myTypeCheckTest(test, "[number]", "['a','a','a']", "Expected type: number but got: 'a'")
myTypeCheckTest(test, "[number]", "[1,'a',3]", "Expected type: number but got: 'a'")
myTypeCheckTest(test, "[number, string]", "[1,'a']", "Ok")
myTypeCheckTest(test, "[number, string]", "[1, 2]", "Expected type: string but got: 2")
myTypeCheckTest(
test,
"[number, string, string]",
"[1,'a']",
"Expected type: [number, string, string] but got: [1,'a']",
)
myTypeCheckTest(
test,
"[number, string]",
"[1,'a', 3]",
"Expected type: [number, string] but got: [1,'a',3]",
)
myTypeCheckTest(test, "{age: number, name: string}", "{age: 1, name: 'a'}", "Ok")
myTypeCheckTest(
test,
"{age: number, name: string}",
"{age: 1, name: 'a', job: 'IT'}",
"Expected type: {age: number, name: string} but got: {age: 1,job: 'IT',name: 'a'}",
)
myTypeCheckTest(test, "number | string", "1", "Ok")
myTypeCheckTest(test, "date | string", "1", "Expected type: (date | string) but got: 1")
myTypeCheckTest(test, "number<-min(10)", "10", "Ok")
myTypeCheckTest(test, "number<-min(10)", "0", "Expected type: number<-min(10) but got: 0")
myTypeCheckTest(test, "any", "0", "Ok")
myTypeCheckTest(test, "any", "'a'", "Ok")

View File

@ -0,0 +1,127 @@
open Jest
open Expect
module DispatchT = Reducer_Dispatch_T
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module ProjectReducerFnT = ReducerProject_ReducerFn_T
module TypeChecker = Reducer_Type_TypeChecker
module TypeCompile = Reducer_Type_Compile
open ReducerInterface_InternalExpressionValue
type errorValue = Reducer_ErrorValue.errorValue
// Let's build a function to replace switch statements
// In dispatchChainPiece, we execute an return the result of execution if there is a type match.
// Otherwise we return None so that the call chain can continue.
// So we want to build a function like
// dispatchChainPiece = (call: functionCall, accessors): option<result<internalExpressionValue, errorValue>>
// Use accessors.environment to get the environment finally.
// Now lets make the dispatchChainPiece itself.
// Note that I am not passing the reducer to the dispatchChainPiece as an argument because it is in the context anyway.
// Keep in mind that reducerFn is necessary for map/reduce so dispatchChainPiece should have a reducerFn in context.
let makeMyDispatchChainPiece = (reducer: ProjectReducerFnT.t): DispatchT.dispatchChainPiece => {
// Let's have a pure implementations
module Implementation = {
let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b)
let arrayConcat = (
a: Js.Array2.t<internalExpressionValue>,
b: Js.Array2.t<internalExpressionValue>,
): Js.Array2.t<internalExpressionValue> => Js.Array2.concat(a, b)
let plot = _r => "yey, plotted"
}
let extractStringString = args =>
switch args {
| [IEvString(a), IEvString(b)] => (a, b)
| _ => raise(Reducer_Exception.ImpossibleException("extractStringString developer error"))
}
let extractArrayArray = args =>
switch args {
| [IEvArray(a), IEvArray(b)] => (a, b)
| _ => raise(Reducer_Exception.ImpossibleException("extractArrayArray developer error"))
}
// Let's bridge the pure implementation to expression values
module Bridge = {
let stringConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
let (a, b) = extractStringString(args)
Implementation.stringConcat(a, b)->IEvString->Ok
}
let arrayConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
let (a, b) = extractArrayArray(args)
Implementation.arrayConcat(a, b)->IEvArray->Ok
}
let plot: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
switch args {
// Just assume that we are doing the business of extracting and converting the deep record
| [IEvRecord(_)] => Implementation.plot({"title": "This is a plot"})->IEvString->Ok
| _ => raise(Reducer_Exception.ImpossibleException("plot developer error"))
}
}
}
// concat functions are to illustrate polymoprhism. And the plot function is to illustrate complex types
let jumpTable = [
(
"concat",
TypeCompile.fromTypeExpressionExn("string=>string=>string", reducer),
Bridge.stringConcat,
),
(
"concat",
TypeCompile.fromTypeExpressionExn("[any]=>[any]=>[any]", reducer),
Bridge.arrayConcat,
),
(
"plot",
TypeCompile.fromTypeExpressionExn(
// Nested complex types are available
// records {property: type}
// arrays [type]
// tuples [type, type]
// <- type contracts are available naturally and they become part of dispatching
// Here we are not enumerating the possibilities because type checking has a dedicated test
"{title: string, line: {width: number, color: string}}=>string",
reducer,
),
Bridge.plot,
),
]
//Here we are creating a dispatchChainPiece function that will do the actual dispatch from the jumpTable
Reducer_Dispatch_ChainPiece.makeFromTypes(jumpTable)
}
// And finally, let's write a library dispatch for our external library
// Exactly the same as the one used in real life
let _dispatch = (
call: functionCall,
accessors: ProjectAccessorsT.t,
reducer: ProjectReducerFnT.t,
chain,
): result<internalExpressionValue, 'e> => {
let dispatchChainPiece = makeMyDispatchChainPiece(reducer)
dispatchChainPiece(call, accessors)->E.O2.defaultFn(() => chain(call, accessors, reducer))
}
// What is important about this implementation?
// A) Exactly the same function jump table can be used to create type guarded lambda functions
// Guarded lambda functions will be the basis of the next version of Squiggle
// B) Complicated recursive record types are not a problem.
describe("Type Dispatch", () => {
let reducerFn = Expression.reduceExpressionInProject
let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn)
test("stringConcat", () => {
let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")])
let result = dispatchChainPiece(call, ProjectAccessorsT.identityAccessors)
expect(result)->toEqual(Some(Ok(IEvString("helloworld"))))
})
})

View File

@ -1,14 +0,0 @@
open Jest
open Expect
describe("ExpressionValue", () => {
test("argsToString", () =>
expect([IEvNumber(1.), IEvString("a")]->Reducer_Value.argsToString)->toBe("1,'a'")
)
test("toStringFunctionCall", () =>
expect(("fn", [IEvNumber(1.), IEvString("a")])->Reducer_Value.toStringFunctionCall)->toBe(
"fn(1,'a')",
)
)
})

View File

@ -2,11 +2,11 @@ open Jest
open Reducer_Peggy_TestHelpers
describe("Construct Array", () => {
testToExpression("[1,2]", "[1, 2]", ~v="[1,2]", ())
testToExpression("[]", "[]", ~v="[]", ())
testToExpression("[1,2]", "{(:$_endOfOuterBlock_$ () (:$_constructArray_$ 1 2))}", ~v="[1,2]", ())
testToExpression("[]", "{(:$_endOfOuterBlock_$ () (:$_constructArray_$))}", ~v="[]", ())
testToExpression(
"f(x)=x; g(x)=x; [f, g]",
"f = {|x| {x}}; g = {|x| {x}}; [f, g]",
"{(:$_let_$ :f (:$$_lambda_$$ [x] {:x})); (:$_let_$ :g (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () (:$_constructArray_$ :f :g))}",
~v="[lambda(x=>internal code),lambda(x=>internal code)]",
(),
)

View File

@ -2,15 +2,17 @@ open Jest
open Reducer_TestHelpers
describe("Parse function assignment", () => {
testParseToBe("f(x)=x", "Ok(f = {|x| {x}})")
testParseToBe("f(x)=2*x", "Ok(f = {|x| {(multiply)(2, x)}})")
testParseToBe(
"f(x)=x",
"Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())})",
)
testParseToBe(
"f(x)=2*x",
"Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)})); (:$_endOfOuterBlock_$ () ())})",
)
//MathJs does not allow blocks in function definitions
})
describe("Evaluate function assignment", () => {
testEvalToBe("f(x)=x; f(1)", "Ok(1)")
})
describe("Shadowing", () => {
testEvalToBe("x = 5; f(y) = x*y; x = 6; f(2)", "Ok(10)")
})

View File

@ -34,7 +34,7 @@ describe("symbol not defined", () => {
testEvalToBe("f(x)=x(y); f(f)", "Error(y is not defined)")
testEvalToBe("f(x)=x; f(f)", "Ok(lambda(x=>internal code))")
testEvalToBe("f(x)=x(y); f(z)", "Error(z is not defined)")
testEvalToBe("f(x)=x(y); f(2)", "Error(y is not defined)")
testEvalToBe("f(x)=x(y); f(2)", "Error(2 is not a function)")
testEvalToBe("f(x)=x(1); f(2)", "Error(2 is not a function)")
})
@ -46,7 +46,10 @@ describe("call and bindings", () => {
testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)")
testEvalToBe("f(x)=x+1; y=f(1); z=f(1); z", "Ok(2)")
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(0)", "Ok(2)")
testParseToBe("f=99; g(x)=f; g(2)", "Ok(f = {99}; g = {|x| {f}}; (g)(2))")
testParseToBe(
"f=99; g(x)=f; g(2)",
"Ok({(:$_let_$ :f {99}); (:$_let_$ :g (:$$_lambda_$$ [x] {:f})); (:$_endOfOuterBlock_$ () (:g 2))})",
)
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; y=g(2); y", "Ok(4)")

View File

@ -5,7 +5,3 @@ Skip.describe("map reduce (sam)", () => {
testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???")
testEvalToBe("addone(x)=x+1; map(2, {x: addone})", "Error???")
})
describe("map", () => {
testEvalToBe("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])")
})

View File

@ -2,7 +2,10 @@ open Jest
open Reducer_TestHelpers
describe("Parse ternary operator", () => {
testParseToBe("true ? 'YES' : 'NO'", "Ok(true ? ('YES') : ('NO'))")
testParseToBe(
"true ? 'YES' : 'NO'",
"Ok({(:$_endOfOuterBlock_$ () (:$$_ternary_$$ true 'YES' 'NO'))})",
)
})
describe("Evaluate ternary operator", () => {

View File

@ -2,29 +2,19 @@ open Jest
open Reducer_TestHelpers
describe("eval", () => {
// All MathJs operators and functions are builtin for string, float and boolean
// .e.g + - / * > >= < <= == /= not and or
// See https://mathjs.org/docs/reference/functions.html
describe("expressions", () => {
testEvalToBe("1", "Ok(1)")
testEvalToBe("-1", "Ok(-1)")
testEvalToBe("1-1", "Ok(0)")
testEvalToBe("1+2", "Ok(3)")
testEvalToBe("(1+2)*3", "Ok(9)")
testEvalToBe("2>1", "Ok(true)")
testEvalToBe("concat('a ', 'b')", "Ok('a b')")
testEvalToBe("concat([3,4], [5,6,7])", "Ok([3,4,5,6,7])")
testEvalToBe("log(10)", "Ok(2.302585092994046)")
testEvalToBe("Math.cos(10)", "Ok(-0.8390715290764524)")
testEvalToBe("cos(10)", "Ok(-0.8390715290764524)")
// TODO more built ins
})
describe("missing function", () => {
testEvalToBe("testZadanga(1)", "Error(testZadanga is not defined)")
testEvalToBe(
"arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)",
"Error(zarathsuzaWasHere is not defined)",
)
})
describe("arrays", () => {
test("empty array", () => expectEvalToBe("[]", "Ok([])"))
testEvalToBe("[1, 2, 3]", "Ok([1,2,3])")
@ -37,16 +27,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})",
),
)
)
})
@ -62,17 +50,13 @@ describe("eval", () => {
testEvalError("1; 1")
testEvalToBe("x=1; x=1; x", "Ok(1)")
})
describe("blocks", () => {
testEvalToBe("x = { y = { z = 5; z * 2 }; y + 3 }; x", "Ok(13)")
})
})
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 +64,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

@ -23,7 +23,7 @@ describe("eval on distribution functions", () => {
testEval("-normal(5,2)", "Ok(Normal(-5,2))")
})
describe("to", () => {
testEval("5 to 2", "Error(Error: Low value must be less than high value.)")
testEval("5 to 2", "Error(TODO: Low value must be less than high value.)")
testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.27853260523016377))")
testEval("to(-2,2)", "Ok(Normal(0,1.2159136638235384))")
})
@ -98,7 +98,6 @@ describe("eval on distribution functions", () => {
"log(normal(5,2), normal(10,1))",
"Error(Distribution Math Error: Logarithm of input error: First input must be completely greater than 0)",
)
testEval("log(2, SampleSet.fromDist(0.0001 to 5))", "Ok(Sample Set Distribution)") // log with low values, see https://github.com/quantified-uncertainty/squiggle/issues/1098
testEval("log(uniform(5,8))", "Ok(Sample Set Distribution)")
testEval("log10(uniform(5,8))", "Ok(Sample Set Distribution)")
})
@ -120,28 +119,40 @@ describe("eval on distribution functions", () => {
describe("parse on distribution functions", () => {
describe("power", () => {
testParse("normal(5,2) ^ normal(5,1)", "Ok((pow)((normal)(5, 2), (normal)(5, 1)))")
testParse("3 ^ normal(5,1)", "Ok((pow)(3, (normal)(5, 1)))")
testParse("normal(5,2) ^ 3", "Ok((pow)((normal)(5, 2), 3))")
testParse(
"normal(5,2) ^ normal(5,1)",
"Ok({(:$_endOfOuterBlock_$ () (:pow (:normal 5 2) (:normal 5 1)))})",
)
testParse("3 ^ normal(5,1)", "Ok({(:$_endOfOuterBlock_$ () (:pow 3 (:normal 5 1)))})")
testParse("normal(5,2) ^ 3", "Ok({(:$_endOfOuterBlock_$ () (:pow (:normal 5 2) 3))})")
})
describe("subtraction", () => {
testParse("10 - normal(5,1)", "Ok((subtract)(10, (normal)(5, 1)))")
testParse("normal(5,1) - 10", "Ok((subtract)((normal)(5, 1), 10))")
testParse("10 - normal(5,1)", "Ok({(:$_endOfOuterBlock_$ () (:subtract 10 (:normal 5 1)))})")
testParse("normal(5,1) - 10", "Ok({(:$_endOfOuterBlock_$ () (:subtract (:normal 5 1) 10))})")
})
describe("pointwise arithmetic expressions", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
testParse(
~skip=true,
"normal(5,2) .- normal(5,1)",
"Ok((:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1))))",
"Ok((:$_endOfOuterBlock_$ () (:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1)))))",
// TODO: !!! returns "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})"
)
testParse("normal(5,2) .* normal(5,1)", "Ok((dotMultiply)((normal)(5, 2), (normal)(5, 1)))")
testParse("normal(5,2) ./ normal(5,1)", "Ok((dotDivide)((normal)(5, 2), (normal)(5, 1)))")
testParse("normal(5,2) .^ normal(5,1)", "Ok((dotPow)((normal)(5, 2), (normal)(5, 1)))")
testParse(
"normal(5,2) .* normal(5,1)",
"Ok({(:$_endOfOuterBlock_$ () (:dotMultiply (:normal 5 2) (:normal 5 1)))})",
)
testParse(
"normal(5,2) ./ normal(5,1)",
"Ok({(:$_endOfOuterBlock_$ () (:dotDivide (:normal 5 2) (:normal 5 1)))})",
)
testParse(
"normal(5,2) .^ normal(5,1)",
"Ok({(:$_endOfOuterBlock_$ () (:dotPow (:normal 5 2) (:normal 5 1)))})",
)
})
describe("equality", () => {
testParse("5 == normal(5,2)", "Ok((equal)(5, (normal)(5, 2)))")
testParse("5 == normal(5,2)", "Ok({(:$_endOfOuterBlock_$ () (:equal 5 (:normal 5 2)))})")
})
describe("pointwise adding two normals", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")

View File

@ -0,0 +1,11 @@
open ReducerInterface.InternalExpressionValue
open Jest
open Expect
describe("ExpressionValue", () => {
test("argsToString", () => expect([IEvNumber(1.), IEvString("a")]->argsToString)->toBe("1,'a'"))
test("toStringFunctionCall", () =>
expect(("fn", [IEvNumber(1.), IEvString("a")])->toStringFunctionCall)->toBe("fn(1,'a')")
)
})

View File

@ -1,5 +1,7 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
open Jest
open Expect
@ -25,14 +27,15 @@ 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.error->Reducer_ErrorValue.errorToString)
}
})
let internalProject = project->Project.T.Private.castToInternalProject
test("past chain", () => {
expect(project->Project.getPastChain("main")) == ["common"]
expect(Project.Private.getPastChain(internalProject, "main")) == ["common"]
})
test("import as variables", () => {
expect(project->Project.Private.getIncludesAsVariables("main")) == []
expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == []
})
})
@ -60,20 +63,24 @@ 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.error->Reducer_ErrorValue.errorToString)
}
})
let internalProject = project->Project.T.Private.castToInternalProject
test("direct past chain", () => {
expect(project->Project.Private.getPastChain("main")) == ["common"]
expect(Project.Private.getPastChain(internalProject, "main")) == ["common"]
})
test("direct includes", () => {
expect(project->Project.Private.getDirectIncludes("main")) == ["common"]
expect(Project.Private.getDirectIncludes(internalProject, "main")) == ["common"]
})
test("include as variables", () => {
expect(project->Project.Private.getIncludesAsVariables("main")) == [("myVariable", "myModule")]
expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [
("myVariable", "myModule"),
]
})
})
@ -99,13 +106,16 @@ 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.error->Reducer_ErrorValue.errorToString)
}
})
let internalProject = project->Project.T.Private.castToInternalProject
test("direct past chain", () => {
expect(Project.getPastChain(project, "main")) == ["common", "common2"]
})
test("include as variables", () => {
expect(project->Project.Private.getIncludesAsVariables("main")) == [("myVariable", "myModule")]
expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [
("myVariable", "myModule"),
]
})
})

View File

@ -1,4 +1,5 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
@ -6,16 +7,29 @@ open Jest
open Expect
open Expect.Operators
// test("", () => expect(1)->toBe(1))
let runFetchResult = (project, sourceId) => {
Project.run(project, sourceId)
Project.getResult(project, sourceId)->Reducer_Value.toStringResult
Project.getResult(project, sourceId)->InternalExpressionValue.toStringWithSourceResult
}
let runFetchFlatBindings = (project, sourceId) => {
Project.run(project, sourceId)
Project.getBindings(project, sourceId)->Reducer_Value.toStringRecord
Project.getBindings(project, sourceId)
->Bindings.removeResult
->InternalExpressionValue.toStringBindings
}
test("setting continuation", () => {
let project = Project.createProject()
let privateProject = project->Project.T.Private.castToInternalProject
let sampleBindings = Bindings.emptyBindings->Bindings.set("test", IEvVoid)
Project.Private.setContinuation(privateProject, "main", sampleBindings)
let answer = Project.Private.getContinuation(privateProject, "main")
expect(answer)->toBe(sampleBindings)
})
test("test result true", () => {
let project = Project.createProject()
Project.setSource(project, "main", "true")
@ -37,7 +51,7 @@ test("test library", () => {
test("test bindings", () => {
let project = Project.createProject()
Project.setSource(project, "variables", "myVariable=666")
runFetchFlatBindings(project, "variables")->expect->toBe("{myVariable: 666}")
runFetchFlatBindings(project, "variables")->expect->toBe("@{myVariable: 666}")
})
describe("project1", () => {
@ -45,6 +59,7 @@ describe("project1", () => {
Project.setSource(project, "first", "x=1")
Project.setSource(project, "main", "x")
Project.setContinues(project, "main", ["first"])
let internalProject = project->Project.T.Private.castToInternalProject
test("runOrder", () => {
expect(Project.getRunOrder(project)) == ["first", "main"]
@ -63,17 +78,17 @@ describe("project1", () => {
})
test("past chain first", () => {
expect(ReducerProject.getPastChain(project, "first")) == []
expect(Project.Private.getPastChain(internalProject, "first")) == []
})
test("past chain main", () => {
expect(ReducerProject.getPastChain(project, "main")) == ["first"]
expect(Project.Private.getPastChain(internalProject, "main")) == ["first"]
})
test("test result", () => {
runFetchResult(project, "main")->expect->toBe("Ok(1)")
})
test("test bindings", () => {
runFetchFlatBindings(project, "first")->expect->toBe("{x: 1}")
runFetchFlatBindings(project, "first")->expect->toBe("@{x: 1}")
})
})
@ -83,7 +98,7 @@ describe("project2", () => {
Project.setContinues(project, "second", ["first"])
Project.setSource(project, "first", "x=1")
Project.setSource(project, "second", "y=2")
Project.setSource(project, "main", "z=3;y")
Project.setSource(project, "main", "y")
test("runOrder", () => {
expect(Project.getRunOrder(project)) == ["first", "second", "main"]
@ -107,27 +122,7 @@ describe("project2", () => {
runFetchResult(project, "main")->expect->toBe("Ok(2)")
})
test("test bindings", () => {
// bindings from continues are not exposed!
runFetchFlatBindings(project, "main")->expect->toBe("{z: 3}")
})
})
describe("removing sources", () => {
let project = Project.createProject()
Project.setContinues(project, "main", ["second"])
Project.setContinues(project, "second", ["first"])
Project.setSource(project, "first", "x=1")
Project.setSource(project, "second", "y=2")
Project.setSource(project, "main", "y")
Project.removeSource(project, "main")
test("project doesn't have source", () => {
expect(Project.getSource(project, "main")) == None
})
test("dependents get updated", () => {
expect(Project.getDependents(project, "second")) == []
runFetchFlatBindings(project, "main")->expect->toBe("@{x: 1,y: 2}")
})
})
@ -157,7 +152,7 @@ describe("project with include", () => {
)
Project.parseIncludes(project, "second") //The only way of setting includes
Project.setSource(project, "main", "z=3; y")
Project.setSource(project, "main", "y")
test("runOrder", () => {
expect(Project.getRunOrder(project)) == ["common", "first", "second", "main"]
@ -183,8 +178,7 @@ describe("project with include", () => {
runFetchResult(project, "main")->expect->toBe("Ok(2)")
})
test("test bindings", () => {
// bindings from continues are not exposed!
runFetchFlatBindings(project, "main")->expect->toBe("{z: 3}")
runFetchFlatBindings(project, "main")->expect->toBe("@{common: 0,x: 1,y: 2}")
})
})

View File

@ -1,4 +1,5 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
@ -11,19 +12,17 @@ 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.
You can run any source in the project. It will be compiled and run if it hasn't happened already; otherwise already existing results will be presented.
You can run any source in the project. It will be compiled and run if it is not already done else already existing results will be presented.
The dependencies will be automatically compiled and run. So you don't need to worry about that in a multi source project.
In summary you issue a run command on the whole project or on a specific source to ensure that there is a result for that source.
*/
let project = Project.createProject()
/* Every source has a name. This is used for debugging, dependencies and error messages. */
project->Project.setSource("main", "1 + 2")
Project.setSource(project, "main", "1 + 2")
/* Let's run "main" source. */
project->Project.run("main")
/* Now you have a result for "main" source.
@ -47,58 +46,52 @@ Case "Running a single source".
Getting None means you have forgotten to run the source.
*/
let result = project->Project.getResult("main")
let bindings = project->Project.getBindings("main")
let bindings = project->Project.getBindings("main")->Bindings.removeResult
/* Let's display the result and bindings */
(result->Reducer_Value.toStringResult, bindings->Reducer_Value.toStringRecord)->expect ==
("Ok(3)", "{}")
(
result->InternalExpressionValue.toStringWithSourceResult,
bindings->InternalExpressionValue.toStringBindings,
)->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
let result = project->Project.getResult("main")
let bindings = project->Project.getBindings("main")
Project.setSource(project, "main", "1 + 2")
Project.runAll(project)
let result = Project.getResult(project, "main")
let bindings = Project.getBindings(project, "main")->Bindings.removeResult
/* Now you have external bindings and external result. */
(
result->Reducer_Value.toStringResult,
bindings->Reducer_T.IEvRecord->Reducer_Value.toString,
)->expect == ("Ok(3)", "{}")
},
)
result->InternalExpressionValue.toStringWithSourceResult,
bindings->InternalExpressionValue.IEvBindings->InternalExpressionValue.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()
/* Optional. Set your custom environment anytime before running */
project->Project.setEnvironment(Reducer_Context.defaultEnvironment)
Project.setEnvironment(project, InternalExpressionValue.defaultEnvironment)
project->Project.setSource("main", "1 + 2")
project->Project.runAll
let result = project->Project.getResult("main")
let _bindings = project->Project.getBindings("main")
result->Reducer_Value.toStringResult->expect == "Ok(3)"
},
)
Project.setSource(project, "main", "1 + 2")
Project.runAll(project)
let result = Project.getResult(project, "main")
let _bindings = Project.getBindings(project, "main")
result->InternalExpressionValue.toStringWithSourceResult->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)", "{}")
},
)
(
result->InternalExpressionValue.toStringWithSourceResult,
bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(3)", "@{}")
})
})
})

View File

@ -1,4 +1,5 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
@ -10,93 +11,92 @@ 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")
Project.setSource(project, "source1", "x=1")
project->Project.setSource("source2", "y=x+1")
Project.setSource(project, "source2", "y=2")
/* To run, source2 depends on source1 */
project->Project.setContinues("source2", ["source1"])
Project.setContinues(project, "source2", ["source1"])
project->Project.setSource("source3", "z=y+1")
Project.setSource(project, "source3", "z=3")
/* To run, source3 depends on source2 */
project->Project.setContinues("source3", ["source2"])
Project.setContinues(project, "source3", ["source2"])
/* Now we can run the project */
project->Project.runAll
Project.runAll(project)
/* And let's check the result and bindings of source3 */
let result3 = project->Project.getResult("source3")
let bindings3 = project->Project.getBindings("source3")
let result3 = Project.getResult(project, "source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult
(result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect ==
("Ok(())", "{z: 3}")
},
)
(
result3->InternalExpressionValue.toStringWithSourceResult,
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,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()
/* This time source1 and source2 are not depending on anything */
project->Project.setSource("source1", "x=1")
project->Project.setSource("source2", "y=2")
Project.setSource(project, "source1", "x=1")
Project.setSource(project, "source2", "y=2")
project->Project.setSource("source3", "z=x+y")
Project.setSource(project, "source3", "z=3")
/* To run, source3 depends on source1 and source3 together */
project->Project.setContinues("source3", ["source1", "source2"])
Project.setContinues(project, "source3", ["source1", "source2"])
/* Now we can run the project */
project->Project.runAll
Project.runAll(project)
/* And let's check the result and bindings of source3 */
let result3 = project->Project.getResult("source3")
let bindings3 = project->Project.getBindings("source3")
let result3 = Project.getResult(project, "source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult
(result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect ==
("Ok(())", "{z: 3}")
},
)
(
result3->InternalExpressionValue.toStringWithSourceResult,
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,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. */
let project = Project.createProject()
/* This time source1 and source2 are not depending on anything */
project->Project.setSource("source1", "x=1")
project->Project.setSource("source2", "y=2")
Project.setSource(project, "source1", "x=1")
Project.setSource(project, "source2", "y=2")
project->Project.setSource(
Project.setSource(
project,
"source3",
`
#include "source1"
#include "source2"
z=x+y`,
z=3`,
)
/* We need to parse the includes to set the dependencies */
project->Project.parseIncludes("source3")
Project.parseIncludes(project, "source3")
/* Now we can run the project */
project->Project.runAll
Project.runAll(project)
/* And let's check the result and bindings of source3
This time you are getting all the variables because we are including the other sources
Behind the scenes parseIncludes is setting the dependencies */
let result3 = project->Project.getResult("source3")
let bindings3 = project->Project.getBindings("source3")
let result3 = Project.getResult(project, "source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult
(result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect ==
("Ok(())", "{z: 3}")
(
result3->InternalExpressionValue.toStringWithSourceResult,
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
/*
Doing it like this is too verbose for a storybook
But I hope you have seen the relation of setContinues and parseIncludes */
@ -107,7 +107,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

@ -1,4 +1,5 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
@ -15,7 +16,8 @@ Here we will finally proceed to a real life scenario. */
/* Here we investigate the details about parseIncludes, before setting up a real life scenario in the next section. */
/* Everything happens inside a project, so let's have a project */
let project = Project.createProject()
project->Project.setSource(
Project.setSource(
project,
"main",
`
#include "common"
@ -23,47 +25,36 @@ 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",
() => {
Project.parseIncludes(project, "main")
test("getDependencies", () => {
/* Parse includes has set the dependencies */
project->Project.getDependencies("main")->expect == ["common"]
Project.getDependencies(project, "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") {
switch Project.getIncludes(project, "main") {
| Ok(includes) => includes->expect == ["common"]
| Error(err) => err->SqError.toString->fail
| Error(err) => err.error->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 == []
Project.getDependents(project, "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.
@ -85,32 +76,30 @@ Here we will finally proceed to a real life scenario. */
/* let's recursively load the sources */
let rec loadIncludesRecursively = (project, sourceName, visited) => {
if visited->Js.Array2.includes(sourceName) {
if Js.Array2.includes(visited, sourceName) {
/* Oh we have already visited this source. There is an include cycle */
"Cyclic include ${sourceName}"->Js.Exn.raiseError
} else {
let newVisited = Js.Array2.copy(visited)
let _ = newVisited->Js.Array2.push(sourceName)
let _ = Js.Array2.push(newVisited, sourceName)
/* Let's parse the includes and dive into them */
Project.parseIncludes(project, sourceName)
let rIncludes = project->Project.getIncludes(sourceName)
let rIncludes = Project.getIncludes(project, sourceName)
switch rIncludes {
/* Maybe there is an include syntax error */
| Error(err) => err->SqError.toString->Js.Exn.raiseError
| Error(err) => err.error->Reducer_ErrorValue.errorToString->Js.Exn.raiseError
| Ok(includes) =>
includes->Belt.Array.forEach(
newIncludeName => {
Belt.Array.forEach(includes, newIncludeName => {
/* We have got one of the new includes.
Let's load it and add it to the project */
let newSource = loadSource(newIncludeName)
project->Project.setSource(newIncludeName, newSource)
Project.setSource(project, newIncludeName, newSource)
/* The new source is loaded and added to the project. */
/* Of course the new source might have includes too. */
/* Let's recursively load them */
project->loadIncludesRecursively(newIncludeName, newVisited)
},
)
loadIncludesRecursively(project, newIncludeName, newVisited)
})
}
}
}
@ -121,53 +110,48 @@ Here we will finally proceed to a real life scenario. */
let project = Project.createProject()
project->Project.setSource(
/* main includes source3 which includes source2 which includes source1 */
Project.setSource(
project,
"main",
`
#include "source1"
#include "source2"
#include "source3"
a = x+y+z
b = doubleX
a
x+y+z
`,
)
/* Setting source requires parsing and loading the includes recursively */
project->loadIncludesRecursively("main", []) // Not visited yet
loadIncludesRecursively(project, "main", []) //No visited yet
/* Let's salt it more. Let's have another source in the project which also has includes */
/* doubleX includes source1 which is eventually included by main as well */
project->Project.setSource(
Project.setSource(
project,
"doubleX",
`
#include "source1"
doubleX = x * 2
`,
)
project->loadIncludesRecursively("doubleX", [])
loadIncludesRecursively(project, "doubleX", [])
/* Remember, any time you set a source, you need to load includes recursively */
/* As doubleX is not included by main, it is not loaded recursively.
So we link it to the project as a dependency */
project->Project.setContinues("main", ["doubleX"])
Project.setContinues(project, "main", ["doubleX"])
/* Let's run the project */
project->Project.runAll
let result = project->Project.getResult("main")
let bindings = project->Project.getBindings("main")
Project.runAll(project)
let result = Project.getResult(project, "main")
let bindings = Project.getBindings(project, "main")
/* And see the result and bindings.. */
test(
"recursive includes",
() => {
test("recursive includes", () => {
(
result->Reducer_Value.toStringResult,
bindings->Reducer_Value.toStringRecord,
)->expect == ("Ok(6)", "{a: 6,b: 2}")
result->InternalExpressionValue.toStringWithSourceResult,
bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(6)", "@{doubleX: 2,x: 1,y: 2,z: 3}")
/* Everything as expected */
},
)
},
)
})
})
})
describe("Includes myFile as myVariable", () => {
@ -182,20 +166,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.error->Reducer_ErrorValue.errorToString->fail
}
},
)
})
})
})

View File

@ -1,5 +1,7 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
open Jest
open Expect
@ -28,7 +30,7 @@ describe("ReducerProject Tutorial", () => {
/* We can now run the project */
Project.runAll(project)
let result = Project.getResult(project, "main")
result->Reducer_Value.toStringResult->expect == "Ok(6)"
result->InternalExpressionValue.toStringWithSourceResult->expect == "Ok(6)"
})
})

View File

@ -1,4 +1,5 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
@ -30,9 +31,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->InternalExpressionValue.toStringWithSourceResult
)
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

@ -3,17 +3,19 @@ open Expect
open Reducer_TestHelpers
let expectEvalToBeOk = (code: string) =>
Reducer_Expression.BackCompatible.evaluateString(code)->E.R.isOk->expect->toBe(true)
Reducer_Expression.BackCompatible.evaluateString(code)
->Reducer_Helpers.rRemoveDefaultsInternal
->E.R.isOk
->expect
->toBe(true)
let registry = FunctionRegistry_Library.registry
let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry))
describe("FunctionRegistry Library", () => {
describe("Regular tests", () => {
testEvalToBe("List.length([3,5,8])", "Ok(3)")
testEvalToBe("List.length([])", "Ok(0)")
testEvalToBe("List.make(3, 'HI')", "Ok(['HI','HI','HI'])")
testEvalToBe("make(3, 'HI')", "Error(make is not defined)")
testEvalToBe("make(3, 'HI')", "Error(Function not found: make(Number,String))")
testEvalToBe("List.upTo(1,3)", "Ok([1,2,3])")
testEvalToBe("List.first([3,5,8])", "Ok(3)")
testEvalToBe("List.last([3,5,8])", "Ok(8)")
@ -82,43 +84,25 @@ 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(
"Dict.mergeMany([{a: 1, b: 2}, {c: 3, d: 4}, {c: 5, e: 6}])",
"Ok({a: 1,b: 2,c: 5,d: 4,e: 6})",
)
testEvalToBe("Dict.keys({a: 1, b: 2})", "Ok(['a','b'])")
testEvalToBe("Dict.values({a: 1, b: 2})", "Ok([1,2])")
testEvalToBe("Dict.toList({a: 1, b: 2})", "Ok([['a',1],['b',2]])")
testEvalToBe("Dict.fromList([['a', 1], ['b', 2]])", "Ok({a: 1,b: 2})")
})
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)) => {
let responseType =
example
->Reducer_Expression.BackCompatible.evaluateString
->E.R2.fmap(Reducer_Value.valueToValueType)
->E.R2.fmap(ReducerInterface_InternalExpressionValue.valueToValueType)
let expectedOutputType = fn.output |> E.O.toExn("")
expect(responseType)->toEqual(Ok(expectedOutputType))
},

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

@ -1,80 +0,0 @@
module Map: Benchmark_Helpers.BenchmarkTopic = {
let arraySize = 1000
let iterations = 300_000
let beltArray = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Belt.Array.map(v => v)
})
}
let jsArray2 = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Js.Array2.map(v => v)
})
}
let ocamlArray = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Array.map(v => v, _)
})
}
let runAll = () => {
Js.log(
`Mapping identity function over arrays of size ${arraySize->Js.Int.toString} (${iterations->Js.Int.toString} iterations)`,
)
Benchmark_Helpers.measure("Belt.Array.map", beltArray)
Benchmark_Helpers.measure("Js.Array2.map", jsArray2)
Benchmark_Helpers.measure("Array.map", ocamlArray)
}
}
module Sort: Benchmark_Helpers.BenchmarkTopic = {
let arraySize = 1000
let iterations = 30000
let jsArray2 = () => {
let x = Belt.Array.make(arraySize, 0.)
let compare = (a: float, b: float) => {
if a < b {
-1
} else {
1
}
}
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Js.Array2.sortInPlaceWith(compare)
})
}
let jsArray2withOcamlCompare = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Js.Array2.sortInPlaceWith(Pervasives.compare)
})
}
let ocamlArray = () => {
let x = Belt.Array.make(arraySize, 0.)
Belt.Range.forEach(1, iterations, _ => {
let _ = x->Array.fast_sort(compare, _)
})
}
let runAll = () => {
Js.log(
`Sorting arrays of size ${arraySize->Js.Int.toString} (${iterations->Js.Int.toString} iterations)`,
)
Benchmark_Helpers.measure("Js.Array2.sort", jsArray2)
Benchmark_Helpers.measure("Js.Array2.sort with Ocaml compare", jsArray2withOcamlCompare)
Benchmark_Helpers.measure("Array.fast_sort", ocamlArray)
}
}
Map.runAll()
Sort.runAll()

View File

@ -1,11 +0,0 @@
module type BenchmarkTopic = {
let runAll: unit => unit
}
let measure = (name: string, f: unit => unit) => {
let start = Js.Date.make()->Js.Date.valueOf
f()
let end = Js.Date.make()->Js.Date.valueOf
let duration = (end -. start) /. 1000.
Js.log2(duration, name)
}

View File

@ -1,63 +0,0 @@
module StringMap: Benchmark_Helpers.BenchmarkTopic = {
let size = 1000
let iterations = 10_000
let kv = Belt.Array.range(1, size)->Belt.Array.map(v => ("key" ++ v->Belt.Int.toString, v))
let beltMap = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Belt.Map.String.empty
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => acc->Belt.Map.String.set(k, v))
})
}
let beltMutableMap = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Belt.MutableMap.String.make()
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => {
acc->Belt.MutableMap.String.set(k, v)
acc
})
})
}
let beltHashMap = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Belt.HashMap.String.make(~hintSize=100)
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => {
acc->Belt.HashMap.String.set(k, v)
acc
})
})
}
let jsDict = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Js.Dict.empty()
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => {
acc->Js.Dict.set(k, v)
acc
})
})
}
let jsMap = () => {
Belt.Range.forEach(1, iterations, _ => {
let m = Js_map.make()
let _ = Belt.Array.reduce(kv, m, (acc, (k, v)) => acc->Js_map.set(k, v))
})
}
let runAll = () => {
Js.log(
`Filling a map with ("key{i}" => "i") key-value pairs, size ${size->Js.Int.toString} (${iterations->Js.Int.toString} iterations)`,
)
Benchmark_Helpers.measure("Belt.Map.String", beltMap)
Benchmark_Helpers.measure("Belt.MutableMap.String", beltMutableMap)
Benchmark_Helpers.measure("Belt.HashMap.String", beltHashMap)
Benchmark_Helpers.measure("Js.Dict", jsDict)
Benchmark_Helpers.measure("Js.Map", jsMap)
}
}
let runAll = StringMap.runAll()

View File

@ -9,11 +9,6 @@
"dir": "__tests__",
"type": "dev",
"subdirs": true
},
{
"dir": "benchmark",
"type": "dev",
"subdirs": true
}
],
"bsc-flags": ["-bs-super-errors", "-bs-no-version-header", "-bs-g"],
@ -25,12 +20,8 @@
],
"suffix": ".bs.js",
"namespace": true,
"bs-dev-dependencies": [
"@glennsl/rescript-jest",
"rescript-fast-check",
"rescript-js-map",
"rescript-js-iterator"
],
"bs-dependencies": ["bisect_ppx"],
"bs-dev-dependencies": ["@glennsl/rescript-jest", "rescript-fast-check"],
"gentypeconfig": {
"language": "typescript",
"module": "commonjs",
@ -44,5 +35,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,12 +2,13 @@
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/",
".*Helpers.bs.js",
".*Helpers.ts",
".*Reducer_Type.*",
".*_type_test.bs.js",
],
};

View File

@ -1,6 +1,6 @@
{
"name": "@quri/squiggle-lang",
"version": "0.5.0",
"version": "0.4.2",
"homepage": "https://squiggle-language.com",
"license": "MIT",
"scripts": {
@ -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,23 +50,26 @@
},
"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",
"fast-check": "^3.1.3",
"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-loader": "^9.4.1",
"ts-jest": "^27.1.4",
"ts-loader": "^9.4.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"typescript": "^4.8.3",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
},

View File

@ -13,7 +13,7 @@ export const measure = (cb, times = 1) => {
export const red = (str) => `\x1b[31m${str}\x1b[0m`;
export const green = (str) => `\x1b[32m${str}\x1b[0m`;
export const run = (src, { output, sampleCount } = {}) => {
export const run = (src, { output, sampleCount }) => {
const project = SqProject.create();
if (sampleCount) {
project.setEnvironment({
@ -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,19 +1,10 @@
#!/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;
run(src, { output: true, sampleCount });
run(src);

View File

@ -1,48 +1,13 @@
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.reducerErrorValueWithSource) {}
toString() {
return RSError.toString(this._value);
return RSErrorValue.toString(this._value);
}
toStringWithStackTrace() {
return RSError.toStringWithStackTrace(this._value);
}
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);
getSource() {
return RSErrorValue.getSource(this._value);
}
}

View File

@ -0,0 +1,30 @@
import * as RSModuleValue from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Module.gen";
import { SqModuleValue, wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
export class SqModule {
constructor(
private _value: RSModuleValue.squiggleValue_Module,
public location: SqValueLocation
) {}
entries() {
return RSModuleValue.getKeyValuePairs(this._value).map(
([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
);
}
asValue() {
return new SqModuleValue(
RSModuleValue.toSquiggleValue(this._value),
this.location
);
}
get(k: string) {
const v = RSModuleValue.get(this._value, k);
return v === undefined || v === null
? undefined
: wrapValue(v, this.location.extend(k));
}
}

View File

@ -1,4 +1,4 @@
import zipWith from "lodash/zipWith";
import * as _ from "lodash";
import { wrapDistribution } from "./SqDistribution";
import * as RSPointSetDist from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_PointSetDistribution.gen";
import { pointSetDistributionTag as Tag } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_PointSetDistribution_tag";
@ -16,7 +16,7 @@ const shapePoints = (
): SqPoint[] => {
let xs = x.xyShape.xs;
let ys = x.xyShape.ys;
return zipWith(xs, ys, (x, y) => ({ x, y }));
return _.zipWith(xs, ys, (x, y) => ({ x, y }));
};
export const wrapPointSetDist = (value: T) => {

View File

@ -1,8 +1,8 @@
import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen";
import * as RSError from "../rescript/SqError.gen";
import { reducerErrorValueWithSource } 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";
import { SqModule } from "./SqModule";
import { wrapValue } from "./SqValue";
import { resultMap2 } from "./types";
import { SqValueLocation } from "./SqValueLocation";
@ -22,10 +22,6 @@ export class SqProject {
return RSProject.setSource(this._value, sourceId, value);
}
removeSource(sourceId: string) {
RSProject.removeSource(this._value, sourceId);
}
getSource(sourceId: string) {
return RSProject.getSource(this._value, sourceId);
}
@ -54,7 +50,7 @@ export class SqProject {
return resultMap2(
RSProject.getIncludes(this._value, sourceId),
(a) => a,
(v: RSError.t) => new SqError(v)
(v: reducerErrorValueWithSource) => new SqError(v)
);
}
@ -87,7 +83,7 @@ export class SqProject {
}
getBindings(sourceId: string) {
return new SqRecord(
return new SqModule(
RSProject.getBindings(this._value, sourceId),
new SqValueLocation(this, sourceId, {
root: "bindings",
@ -108,15 +104,11 @@ export class SqProject {
items: [],
})
),
(v: RSError.t) => new SqError(v)
(v: reducerErrorValueWithSource) => new SqError(v)
);
}
setEnvironment(environment: environment) {
RSProject.setEnvironment(this._value, environment);
}
getEnvironment(): environment {
return RSProject.getEnvironment(this._value);
}
}

View File

@ -1,5 +1,5 @@
import * as RSRecord from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Record.gen";
import { SqRecordValue, wrapValue } from "./SqValue";
import { wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
type T = RSRecord.squiggleValue_Record;
@ -12,15 +12,4 @@ export class SqRecord {
([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
);
}
toString() {
return RSRecord.toString(this._value);
}
asValue() {
return new SqRecordValue(
RSRecord.toSquiggleValue(this._value),
this.location
);
}
}

View File

@ -0,0 +1,7 @@
import * as RSType from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Type.gen";
type T = RSType.squiggleValue_Type;
export class SqType {
constructor(private _value: T) {}
}

View File

@ -3,8 +3,11 @@ import { squiggleValueTag as Tag } from "../rescript/ForTS/ForTS_SquiggleValue/F
import { wrapDistribution } from "./SqDistribution";
import { SqLambda } from "./SqLambda";
import { SqLambdaDeclaration } from "./SqLambdaDeclaration";
import { SqModule } from "./SqModule";
import { SqRecord } from "./SqRecord";
import { SqArray } from "./SqArray";
import { SqType } from "./SqType";
import { SqProject } from "./SqProject";
import { SqValueLocation } from "./SqValueLocation";
export { Tag as SqValueTag };
@ -43,6 +46,14 @@ export class SqArrayValue extends SqAbstractValue {
}
}
export class SqArrayStringValue extends SqAbstractValue {
tag = Tag.ArrayString as const;
get value() {
return this.valueMethod(RSValue.getArrayString);
}
}
export class SqBoolValue extends SqAbstractValue {
tag = Tag.Bool as const;
@ -51,6 +62,14 @@ export class SqBoolValue extends SqAbstractValue {
}
}
export class SqCallValue extends SqAbstractValue {
tag = Tag.Call as const;
get value() {
return this.valueMethod(RSValue.getCall);
}
}
export class SqDateValue extends SqAbstractValue {
tag = Tag.Date as const;
@ -83,6 +102,14 @@ export class SqLambdaValue extends SqAbstractValue {
}
}
export class SqModuleValue extends SqAbstractValue {
tag = Tag.Module as const;
get value() {
return new SqModule(this.valueMethod(RSValue.getModule), this.location);
}
}
export class SqNumberValue extends SqAbstractValue {
tag = Tag.Number as const;
@ -107,6 +134,14 @@ export class SqStringValue extends SqAbstractValue {
}
}
export class SqSymbolValue extends SqAbstractValue {
tag = Tag.Symbol as const;
get value(): string {
return this.valueMethod(RSValue.getSymbol);
}
}
export class SqTimeDurationValue extends SqAbstractValue {
tag = Tag.TimeDuration as const;
@ -115,6 +150,22 @@ export class SqTimeDurationValue extends SqAbstractValue {
}
}
export class SqTypeValue extends SqAbstractValue {
tag = Tag.Type as const;
get value() {
return new SqType(this.valueMethod(RSValue.getType));
}
}
export class SqTypeIdentifierValue extends SqAbstractValue {
tag = Tag.TypeIdentifier as const;
get value() {
return this.valueMethod(RSValue.getTypeIdentifier);
}
}
export class SqVoidValue extends SqAbstractValue {
tag = Tag.Void as const;
@ -125,15 +176,21 @@ export class SqVoidValue extends SqAbstractValue {
const tagToClass = {
[Tag.Array]: SqArrayValue,
[Tag.ArrayString]: SqArrayStringValue,
[Tag.Bool]: SqBoolValue,
[Tag.Call]: SqCallValue,
[Tag.Date]: SqDateValue,
[Tag.Declaration]: SqDeclarationValue,
[Tag.Distribution]: SqDistributionValue,
[Tag.Lambda]: SqLambdaValue,
[Tag.Module]: SqModuleValue,
[Tag.Number]: SqNumberValue,
[Tag.Record]: SqRecordValue,
[Tag.String]: SqStringValue,
[Tag.Symbol]: SqSymbolValue,
[Tag.TimeDuration]: SqTimeDurationValue,
[Tag.Type]: SqTypeValue,
[Tag.TypeIdentifier]: SqTypeIdentifierValue,
[Tag.Void]: SqVoidValue,
} as const;
@ -141,13 +198,19 @@ const tagToClass = {
// type SqValue = typeof tagToClass[keyof typeof tagToClass];
export type SqValue =
| SqArrayValue
| SqArrayStringValue
| SqBoolValue
| SqCallValue
| SqDateValue
| SqDeclarationValue
| SqDistributionValue
| SqLambdaValue
| SqModuleValue
| SqNumberValue
| SqRecordValue
| SqStringValue
| SqSymbolValue
| SqTimeDurationValue
| SqTypeValue
| SqTypeIdentifierValue
| SqVoidValue;

View File

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

View File

@ -1,4 +1,4 @@
import { environment } from "../rescript/ForTS/ForTS__Types.gen";
import { environment } from "../rescript/ForTS/ForTS_ReducerProject.gen";
import { SqProject } from "./SqProject";
import { SqValue, SqValueTag } from "./SqValue";
export { SqValueLocation } from "./SqValueLocation";
@ -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

@ -86,7 +86,6 @@ let toFloatOperation = (
| (SampleSet(sampleSet), #Inv(r)) => SampleSetDist.percentile(sampleSet, r)->Some
| (SampleSet(sampleSet), #Min) => SampleSetDist.min(sampleSet)->Some
| (SampleSet(sampleSet), #Max) => SampleSetDist.max(sampleSet)->Some
| (SampleSet(sampleSet), #Cdf(r)) => SampleSetDist.cdf(sampleSet, r)->Some
| _ => None
}
@ -99,7 +98,6 @@ let toFloatOperation = (
}
}
}
| (#Stdev | #Variance | #Mode) as op =>
switch t {
| SampleSet(s) =>
@ -130,7 +128,7 @@ let toPointSet = (
SampleSetDist.toPointSetDist(
~samples=r,
~samplingInputs={
sampleCount,
sampleCount: sampleCount,
outputXYPoints: xyPointLength,
pointSetDistLength: xyPointLength,
kernelWidth: None,
@ -279,14 +277,22 @@ module AlgebraicCombination = {
Right now we don't yet have a way of getting probability mass, so I'll leave this for later.
*/
let getLogarithmInputError = (t1: t, t2: t, ~toPointSetFn: toPointSetFn): option<error> => {
let isDistGreaterThanZero = t =>
let firstOperandIsGreaterThanZero =
toFloatOperation(
t,
t1,
~toPointSetFn,
~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
)->E.R2.fmap(r => r > 0.)
let items = E.A.R.firstErrorOrOpen([isDistGreaterThanZero(t1), isDistGreaterThanZero(t2)])
) |> E.R.fmap(r => r > 0.)
let secondOperandIsGreaterThanZero =
toFloatOperation(
t2,
~toPointSetFn,
~distToFloatOperation=#Cdf(MagicNumbers.Epsilon.ten),
) |> E.R.fmap(r => r > 0.)
let items = E.A.R.firstErrorOrOpen([
firstOperandIsGreaterThanZero,
secondOperandIsGreaterThanZero,
])
switch items {
| Error(r) => Some(r)
| Ok([true, _]) =>
@ -428,7 +434,6 @@ module AlgebraicCombination = {
~toSampleSetFn,
)
}
| (None, AsMonteCarlo) =>
StrategyCallOnValidatedInputs.monteCarlo(toSampleSetFn, arithmeticOperation, t1, t2)
| (None, AsSymbolic) =>
@ -445,7 +450,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

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