Compare commits
No commits in common. "develop" and "danger-adjustments" have entirely different histories.
develop
...
danger-adj
20
.github/CODEOWNERS
vendored
20
.github/CODEOWNERS
vendored
|
@ -9,22 +9,22 @@
|
|||
# This also holds true for GitHub teams.
|
||||
|
||||
# Rescript
|
||||
*.res @berekuk @OAGr
|
||||
*.resi @berekuk @OAGr
|
||||
*.res @OAGr
|
||||
*.resi @OAGr
|
||||
|
||||
# Typescript
|
||||
*.tsx @Hazelfire @berekuk @OAGr
|
||||
*.ts @Hazelfire @berekuk @OAGr
|
||||
*.tsx @Hazelfire @OAGr
|
||||
*.ts @Hazelfire @OAGr
|
||||
|
||||
# Javascript
|
||||
*.js @Hazelfire @berekuk @OAGr
|
||||
*.js @Hazelfire @OAGr
|
||||
|
||||
# Any opsy files
|
||||
.github/** @quinn-dougherty @berekuk @OAGr
|
||||
*.json @quinn-dougherty @Hazelfire @berekuk @OAGr
|
||||
*.y*ml @quinn-dougherty @berekuk @OAGr
|
||||
*.config.js @Hazelfire @berekuk @OAGr
|
||||
vercel.json @OAGr @berekuk @Hazelfire
|
||||
.github/** @quinn-dougherty @OAGr
|
||||
*.json @quinn-dougherty @Hazelfire @OAGr
|
||||
*.y*ml @quinn-dougherty @OAGr
|
||||
*.config.js @Hazelfire @OAGr
|
||||
netlify.toml @quinn-dougherty @OAGr @Hazelfire
|
||||
|
||||
# Documentation
|
||||
*.md @quinn-dougherty @OAGr @Hazelfire
|
||||
|
|
228
.github/workflows/ci.yml
vendored
228
.github/workflows/ci.yml
vendored
|
@ -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:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3
|
||||
- id: skip_lang_check
|
||||
name: Check if the changes are about squiggle-lang src files
|
||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Turbo run
|
||||
run: npx turbo run build test lint bundle
|
||||
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/**"]'
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
# 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: 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:
|
||||
# dry: true
|
||||
# prettier_options: --check packages/squiggle-lang
|
||||
|
||||
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
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
- name: Coverage
|
||||
run: npx turbo run coverage
|
||||
- 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:
|
||||
# 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
|
||||
|
|
10
.github/workflows/release-please.yml
vendored
10
.github/workflows/release-please.yml
vendored
|
@ -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/**"]'
|
||||
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -7,9 +7,4 @@ yarn-error.log
|
|||
**/.sync.ffs_db
|
||||
.direnv
|
||||
.log
|
||||
|
||||
.vscode
|
||||
todo.txt
|
||||
result
|
||||
shell.nix
|
||||
.turbo
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"packages/cli": "0.0.3",
|
||||
"packages/components": "0.4.1",
|
||||
"packages/squiggle-lang": "0.4.1",
|
||||
"packages/vscode-ext": "0.4.1",
|
||||
"packages/website": "0.0.0"
|
||||
"packages/components": "0.3.1",
|
||||
"packages/squiggle-lang": "0.3.0",
|
||||
"packages/vscode-ext": "0.3.1",
|
||||
"packages/website": "0.3.0"
|
||||
}
|
||||
|
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rescript.settings.autoRunCodeAnalysis": true
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
See the [Changelog.mdx page](./packages/website/docs/Changelog.mdx) for the changelog.
|
|
@ -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.
|
||||
|
||||
|
|
28
README.md
28
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -65,7 +75,6 @@ rec {
|
|||
# custom gitignore so that the flake keeps build artefacts
|
||||
mv .gitignore GITIGNORE
|
||||
sed -i /Reducer_Peggy_GeneratedParser.js/d GITIGNORE
|
||||
sed -i /ReducerProject_IncludeParser.js/d GITIGNORE
|
||||
sed -i /\*.bs.js/d GITIGNORE
|
||||
sed -i /\*.gen.ts/d GITIGNORE
|
||||
sed -i /\*.gen.tsx/d GITIGNORE
|
||||
|
|
18
nixos.sh
Executable file
18
nixos.sh
Executable 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
|
|
@ -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/*"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
setupFilesAfterEnv: ["<rootDir>/test/setup.js"],
|
||||
testEnvironment: "jsdom",
|
||||
};
|
8
packages/components/netlify.toml
Normal file
8
packages/components/netlify.toml
Normal 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"
|
|
@ -1,73 +1,64 @@
|
|||
{
|
||||
"name": "@quri/squiggle-components",
|
||||
"version": "0.5.0",
|
||||
"version": "0.4.0-alpha.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react-dom": "^1.0.0",
|
||||
"@floating-ui/react-dom-interactions": "^0.10.1",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@floating-ui/react-dom-interactions": "^0.9.3",
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@hookform/resolvers": "^2.9.8",
|
||||
"@quri/squiggle-lang": "^0.5.0",
|
||||
"@hookform/resolvers": "^2.9.7",
|
||||
"@quri/squiggle-lang": "^0.4.0-alpha.0",
|
||||
"@react-hook/size": "^2.1.2",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"clsx": "^1.2.1",
|
||||
"framer-motion": "^7.5.3",
|
||||
"framer-motion": "^7.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^18.1.0",
|
||||
"react-ace": "^10.1.0",
|
||||
"react-hook-form": "^7.37.0",
|
||||
"react-hook-form": "^7.34.2",
|
||||
"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",
|
||||
"vscode-uri": "^3.0.6",
|
||||
"vscode-uri": "^3.0.3",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
|
||||
"@storybook/addon-actions": "^6.5.12",
|
||||
"@storybook/addon-essentials": "^6.5.12",
|
||||
"@storybook/addon-links": "^6.5.12",
|
||||
"@storybook/builder-webpack5": "^6.5.12",
|
||||
"@storybook/manager-webpack5": "^6.5.12",
|
||||
"@storybook/addon-actions": "^6.5.9",
|
||||
"@storybook/addon-essentials": "^6.5.10",
|
||||
"@storybook/addon-links": "^6.5.10",
|
||||
"@storybook/builder-webpack5": "^6.5.10",
|
||||
"@storybook/manager-webpack5": "^6.5.10",
|
||||
"@storybook/node-logger": "^6.5.9",
|
||||
"@storybook/preset-create-react-app": "^4.1.2",
|
||||
"@storybook/react": "^6.5.12",
|
||||
"@storybook/react": "^6.5.10",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/react": "^13.3.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.184",
|
||||
"@types/node": "^18.7.13",
|
||||
"@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",
|
||||
"postcss-import": "^14.1.0",
|
||||
"postcss-loader": "^7.0.1",
|
||||
"postcss-nesting": "^10.2.0",
|
||||
"react": "^18.1.0",
|
||||
"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.3.0",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"typescript": "^4.8.4",
|
||||
"web-vitals": "^3.0.3",
|
||||
"typescript": "^4.8.2",
|
||||
"web-vitals": "^3.0.0",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
"webpack-dev-server": "^4.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18",
|
||||
|
@ -75,7 +66,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
||||
"build:cjs": "rm -rf dist/src && rm -f dist/tsconfig.tsbuildinfo && tsc -b",
|
||||
"build:cjs": "rm -rf dist/src && tsc -b",
|
||||
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
|
||||
"build:storybook": "build-storybook -s public",
|
||||
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
|
||||
|
@ -83,10 +74,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": [
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
resultMap,
|
||||
SqRecord,
|
||||
environment,
|
||||
SqDistributionTag,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { Vega } from "react-vega";
|
||||
import { ErrorAlert } from "./Alert";
|
||||
|
@ -32,7 +31,6 @@ export type DistributionChartProps = {
|
|||
environment: environment;
|
||||
width?: number;
|
||||
height: number;
|
||||
xAxisType?: "number" | "dateTime";
|
||||
} & DistributionPlottingSettings;
|
||||
|
||||
export function defaultPlot(distribution: SqDistribution): Plot {
|
||||
|
@ -58,15 +56,14 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
} = props;
|
||||
const [sized] = useSize((size) => {
|
||||
const shapes = flattenResult(
|
||||
plot.distributions.map((x) =>
|
||||
resultMap(x.distribution.pointSet(environment), (pointSet) => ({
|
||||
plot.distributions.map((x) => {
|
||||
return resultMap(x.distribution.pointSet(environment), (pointSet) => ({
|
||||
...pointSet.asShape(),
|
||||
name: x.name,
|
||||
// color: x.color, // not supported yet
|
||||
...pointSet.asShape(),
|
||||
}))
|
||||
)
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
if (shapes.tag === "Error") {
|
||||
return (
|
||||
<ErrorAlert heading="Distribution Error">
|
||||
|
@ -75,40 +72,18 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
);
|
||||
}
|
||||
|
||||
// if this is a sample set, include the samples
|
||||
const samples: number[] = [];
|
||||
for (const { distribution } of plot?.distributions) {
|
||||
if (distribution.tag === SqDistributionTag.SampleSet) {
|
||||
samples.push(...distribution.value());
|
||||
}
|
||||
}
|
||||
const spec = buildVegaSpec(props);
|
||||
|
||||
const domain = shapes.value.flatMap((shape) =>
|
||||
shape.discrete.concat(shape.continuous)
|
||||
);
|
||||
|
||||
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 +94,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
|||
) : (
|
||||
<Vega
|
||||
spec={spec}
|
||||
data={vegaData}
|
||||
data={{ data: shapes.value, domain }}
|
||||
width={widthProp - 10}
|
||||
height={height}
|
||||
actions={actions}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -46,35 +48,24 @@ export type SquiggleChartProps = {
|
|||
minX?: number;
|
||||
/** Specify the upper bound of the x scale */
|
||||
maxX?: number;
|
||||
/** Whether the x-axis should be dates or numbers */
|
||||
xAxisType?: "number" | "dateTime";
|
||||
/** 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,
|
||||
|
@ -85,72 +76,47 @@ export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
|
|||
maxX,
|
||||
color,
|
||||
title,
|
||||
xAxisType = "number",
|
||||
distributionChartActions,
|
||||
} = props;
|
||||
|
||||
const distributionPlotSettings = {
|
||||
showSummary,
|
||||
logX,
|
||||
expY,
|
||||
format: tickFormat,
|
||||
minX,
|
||||
maxX,
|
||||
color,
|
||||
title,
|
||||
xAxisType,
|
||||
actions: distributionChartActions,
|
||||
};
|
||||
|
||||
const chartSettings = {
|
||||
start: diagramStart,
|
||||
stop: diagramStop,
|
||||
count: diagramCount,
|
||||
};
|
||||
|
||||
return { distributionPlotSettings, chartSettings };
|
||||
};
|
||||
|
||||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||
(props) => {
|
||||
const { distributionPlotSettings, chartSettings } =
|
||||
splitSquiggleChartSettings(props);
|
||||
|
||||
const {
|
||||
enableLocalSettings = false,
|
||||
}) => {
|
||||
const { result, bindings } = useSquiggle({
|
||||
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 distributionPlotSettings = {
|
||||
showSummary,
|
||||
logX,
|
||||
expY,
|
||||
format: tickFormat,
|
||||
minX,
|
||||
maxX,
|
||||
color,
|
||||
title,
|
||||
actions: distributionChartActions,
|
||||
};
|
||||
|
||||
const chartSettings = {
|
||||
start: diagramStart,
|
||||
stop: diagramStop,
|
||||
count: diagramCount,
|
||||
};
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SqError, SqFrame } from "@quri/squiggle-lang";
|
||||
import { SqError } from "@quri/squiggle-lang";
|
||||
import React from "react";
|
||||
import { ErrorAlert } from "./Alert";
|
||||
|
||||
|
@ -6,39 +6,6 @@ type Props = {
|
|||
error: SqError;
|
||||
};
|
||||
|
||||
const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => {
|
||||
const location = frame.location();
|
||||
return (
|
||||
<div>
|
||||
{frame.name()}
|
||||
{location
|
||||
? ` at line ${location.start.line}, column ${location.start.column}`
|
||||
: ""}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const StackTrace: React.FC<Props> = ({ error }) => {
|
||||
const frames = error.getFrameArray();
|
||||
return frames.length ? (
|
||||
<div>
|
||||
<div className="font-medium">Stack trace:</div>
|
||||
<div className="ml-4">
|
||||
{frames.map((frame, i) => (
|
||||
<StackTraceFrame frame={frame} key={i} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
|
||||
return (
|
||||
<ErrorAlert heading="Error">
|
||||
<div className="space-y-4">
|
||||
<div>{error.toString()}</div>
|
||||
<StackTrace error={error} />
|
||||
</div>
|
||||
</ErrorAlert>
|
||||
);
|
||||
return <ErrorAlert heading="Error">{error.toString()}</ErrorAlert>;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { VisualizationSpec } from "react-vega";
|
||||
import type { LogScale, LinearScale, PowScale, TimeScale } from "vega";
|
||||
import type { LogScale, LinearScale, PowScale } from "vega";
|
||||
|
||||
export type DistributionChartSpecOptions = {
|
||||
/** Set the x scale to be logarithmic by deault */
|
||||
|
@ -14,21 +14,26 @@ export type DistributionChartSpecOptions = {
|
|||
title?: string;
|
||||
/** The formatting of the ticks */
|
||||
format?: string;
|
||||
/** Whether the x-axis should be dates or numbers */
|
||||
xAxisType?: "number" | "dateTime";
|
||||
};
|
||||
|
||||
/** X Scales */
|
||||
export const linearXScale: LinearScale = {
|
||||
export let linearXScale: LinearScale = {
|
||||
name: "xscale",
|
||||
clamp: true,
|
||||
type: "linear",
|
||||
range: "width",
|
||||
zero: false,
|
||||
nice: false,
|
||||
domain: { data: "domain", field: "x" },
|
||||
};
|
||||
export let linearYScale: LinearScale = {
|
||||
name: "yscale",
|
||||
type: "linear",
|
||||
range: "height",
|
||||
zero: true,
|
||||
domain: { data: "domain", field: "y" },
|
||||
};
|
||||
|
||||
export const logXScale: LogScale = {
|
||||
export let logXScale: LogScale = {
|
||||
name: "xscale",
|
||||
type: "log",
|
||||
range: "width",
|
||||
|
@ -36,104 +41,60 @@ export const logXScale: LogScale = {
|
|||
base: 10,
|
||||
nice: false,
|
||||
clamp: true,
|
||||
domain: { data: "domain", field: "x" },
|
||||
};
|
||||
|
||||
export const timeXScale: TimeScale = {
|
||||
name: "xscale",
|
||||
clamp: true,
|
||||
type: "time",
|
||||
range: "width",
|
||||
nice: false,
|
||||
};
|
||||
|
||||
/** Y Scales */
|
||||
export const linearYScale: LinearScale = {
|
||||
name: "yscale",
|
||||
type: "linear",
|
||||
range: "height",
|
||||
zero: true,
|
||||
};
|
||||
|
||||
export const expYScale: PowScale = {
|
||||
export let expYScale: PowScale = {
|
||||
name: "yscale",
|
||||
type: "pow",
|
||||
exponent: 0.1,
|
||||
range: "height",
|
||||
zero: true,
|
||||
nice: false,
|
||||
domain: { data: "domain", field: "y" },
|
||||
};
|
||||
|
||||
export const defaultTickFormat = ".9~s";
|
||||
export const timeTickFormat = "%b %d, %Y %H:%M";
|
||||
const width = 500;
|
||||
|
||||
export function buildVegaSpec(
|
||||
specOptions: DistributionChartSpecOptions & { maxY: number }
|
||||
specOptions: DistributionChartSpecOptions
|
||||
): VisualizationSpec {
|
||||
const {
|
||||
format = defaultTickFormat,
|
||||
title,
|
||||
minX,
|
||||
maxX,
|
||||
logX,
|
||||
expY,
|
||||
xAxisType = "number",
|
||||
maxY,
|
||||
} = specOptions;
|
||||
|
||||
const dateTime = xAxisType === "dateTime";
|
||||
let xScale = logX ? logXScale : linearXScale;
|
||||
if (minX !== undefined && Number.isFinite(minX)) {
|
||||
xScale = { ...xScale, domainMin: minX };
|
||||
}
|
||||
|
||||
// some fallbacks
|
||||
const format = specOptions?.format
|
||||
? specOptions.format
|
||||
: dateTime
|
||||
? timeTickFormat
|
||||
: defaultTickFormat;
|
||||
if (maxX !== undefined && Number.isFinite(maxX)) {
|
||||
xScale = { ...xScale, domainMax: maxX };
|
||||
}
|
||||
|
||||
let xScale = dateTime ? timeXScale : logX ? logXScale : linearXScale;
|
||||
|
||||
xScale = {
|
||||
...xScale,
|
||||
domain: [minX ?? 0, maxX ?? 1],
|
||||
domainMin: minX,
|
||||
domainMax: maxX,
|
||||
};
|
||||
|
||||
let yScale = expY ? expYScale : linearYScale;
|
||||
yScale = { ...yScale, domain: [0, maxY ?? 1], domainMin: 0, domainMax: maxY };
|
||||
|
||||
const spec: VisualizationSpec = {
|
||||
let spec: VisualizationSpec = {
|
||||
$schema: "https://vega.github.io/schema/vega/v5.json",
|
||||
description: "Squiggle plot chart",
|
||||
width: width,
|
||||
width: 500,
|
||||
height: 100,
|
||||
padding: 5,
|
||||
data: [{ name: "data" }, { name: "domain" }, { name: "samples" }],
|
||||
signals: [
|
||||
data: [
|
||||
{
|
||||
name: "hover",
|
||||
value: null,
|
||||
on: [
|
||||
{ events: "mouseover", update: "datum" },
|
||||
{ events: "mouseout", update: "null" },
|
||||
],
|
||||
name: "data",
|
||||
},
|
||||
{
|
||||
name: "position",
|
||||
value: "[0, 0]",
|
||||
on: [
|
||||
{ events: "mousemove", update: "xy() " },
|
||||
{ events: "mouseout", update: "null" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "position_scaled",
|
||||
value: null,
|
||||
update: "isArray(position) ? invert('xscale', position[0]) : ''",
|
||||
name: "domain",
|
||||
},
|
||||
],
|
||||
signals: [],
|
||||
scales: [
|
||||
xScale,
|
||||
yScale,
|
||||
expY ? expYScale : linearYScale,
|
||||
{
|
||||
name: "color",
|
||||
type: "ordinal",
|
||||
|
@ -154,7 +115,7 @@ export function buildVegaSpec(
|
|||
domainColor: "#fff",
|
||||
domainOpacity: 0.0,
|
||||
format: format,
|
||||
tickCount: dateTime ? 3 : 10,
|
||||
tickCount: 10,
|
||||
labelOverlap: "greedy",
|
||||
},
|
||||
],
|
||||
|
@ -271,16 +232,13 @@ export function buildVegaSpec(
|
|||
},
|
||||
size: [{ value: 100 }],
|
||||
tooltip: {
|
||||
signal: dateTime
|
||||
? "{ probability: datum.y, value: datetime(datum.x) }"
|
||||
: "{ probability: datum.y, value: datum.x }",
|
||||
signal: "{ probability: datum.y, value: datum.x }",
|
||||
},
|
||||
},
|
||||
update: {
|
||||
x: {
|
||||
scale: "xscale",
|
||||
field: "x",
|
||||
offset: 0.5, // if this is not included, the circles are slightly left of center.
|
||||
},
|
||||
y: {
|
||||
scale: "yscale",
|
||||
|
@ -297,69 +255,6 @@ export function buildVegaSpec(
|
|||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "sampleset",
|
||||
type: "rect",
|
||||
from: { data: "samples" },
|
||||
encode: {
|
||||
enter: {
|
||||
x: { scale: "xscale", field: "data" },
|
||||
width: { value: 0.1 },
|
||||
|
||||
y: { value: 25, offset: { signal: "height" } },
|
||||
height: { value: 5 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "announcer",
|
||||
interactive: false,
|
||||
encode: {
|
||||
enter: {
|
||||
x: { signal: String(width), offset: 1 }, // vega would prefer its internal ` "width" ` variable, but that breaks the squiggle playground. Just setting it to the same var as used elsewhere in the spec achieves the same result.
|
||||
fill: { value: "black" },
|
||||
fontSize: { value: 20 },
|
||||
align: { value: "right" },
|
||||
},
|
||||
update: {
|
||||
text: {
|
||||
signal: dateTime
|
||||
? "position_scaled ? utcyear(position_scaled) + '-' + utcmonth(position_scaled) + '-' + utcdate(position_scaled) + 'T' + utchours(position_scaled)+':' +utcminutes(position_scaled) : ''"
|
||||
: "position_scaled ? format(position_scaled, ',.4r') : ''",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "rule",
|
||||
interactive: false,
|
||||
encode: {
|
||||
enter: {
|
||||
x: { value: 0 },
|
||||
y: { scale: "yscale", value: 0 },
|
||||
|
||||
y2: {
|
||||
signal: "height",
|
||||
offset: 2,
|
||||
},
|
||||
strokeDash: { value: [5, 5] },
|
||||
},
|
||||
|
||||
update: {
|
||||
x: {
|
||||
signal:
|
||||
"position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null",
|
||||
},
|
||||
|
||||
opacity: {
|
||||
signal:
|
||||
"position ? position[0] < 0 ? 0 : position[0] > width ? 0 : 1 : 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
legends: [
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
import * as yup from "yup";
|
||||
import {
|
||||
SqValue,
|
||||
SqValueTag,
|
||||
SqDistribution,
|
||||
result,
|
||||
SqRecord,
|
||||
} from "@quri/squiggle-lang";
|
||||
import { SqDistribution, result, SqRecord } from "@quri/squiggle-lang";
|
||||
|
||||
export type LabeledDistribution = {
|
||||
name: string;
|
||||
|
@ -27,55 +21,48 @@ function ok<a, b>(x: a): result<a, b> {
|
|||
|
||||
const schema = yup
|
||||
.object()
|
||||
.noUnknown()
|
||||
.strict()
|
||||
.noUnknown()
|
||||
.shape({
|
||||
distributions: yup
|
||||
.array()
|
||||
.required()
|
||||
.of(
|
||||
yup.object().required().shape({
|
||||
name: yup.string().required(),
|
||||
distribution: yup.mixed().required(),
|
||||
})
|
||||
),
|
||||
distributions: yup.object().shape({
|
||||
tag: yup.mixed().oneOf(["array"]),
|
||||
value: yup
|
||||
.array()
|
||||
.of(
|
||||
yup.object().shape({
|
||||
tag: yup.mixed().oneOf(["record"]),
|
||||
value: yup.object({
|
||||
name: yup.object().shape({
|
||||
tag: yup.mixed().oneOf(["string"]),
|
||||
value: yup.string().required(),
|
||||
}),
|
||||
// color: yup
|
||||
// .object({
|
||||
// tag: yup.mixed().oneOf(["string"]),
|
||||
// value: yup.string().required(),
|
||||
// })
|
||||
// .default(undefined),
|
||||
distribution: yup.object({
|
||||
tag: yup.mixed().oneOf(["distribution"]),
|
||||
value: yup.mixed(),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
}),
|
||||
});
|
||||
|
||||
type JsonObject =
|
||||
| string
|
||||
| { [key: string]: JsonObject }
|
||||
| JsonObject[]
|
||||
| SqDistribution;
|
||||
|
||||
function toJson(val: SqValue): JsonObject {
|
||||
if (val.tag === SqValueTag.String) {
|
||||
return val.value;
|
||||
} else if (val.tag === SqValueTag.Record) {
|
||||
return toJsonRecord(val.value);
|
||||
} else if (val.tag === SqValueTag.Array) {
|
||||
return val.value.getValues().map(toJson);
|
||||
} else if (val.tag === SqValueTag.Distribution) {
|
||||
return val.value;
|
||||
} else {
|
||||
throw new Error("Could not parse object of type " + val.tag);
|
||||
}
|
||||
}
|
||||
|
||||
function toJsonRecord(val: SqRecord): JsonObject {
|
||||
let recordObject: JsonObject = {};
|
||||
val.entries().forEach(([key, value]) => (recordObject[key] = toJson(value)));
|
||||
return recordObject;
|
||||
}
|
||||
|
||||
export function parsePlot(record: SqRecord): result<Plot, string> {
|
||||
try {
|
||||
const plotRecord = schema.validateSync(toJsonRecord(record));
|
||||
if (plotRecord.distributions) {
|
||||
return ok({ distributions: plotRecord.distributions.map((x) => x) });
|
||||
} else {
|
||||
// I have no idea why yup's typings thinks this is possible
|
||||
return error("no distributions field. Should never get here");
|
||||
}
|
||||
const plotRecord = schema.validateSync(record);
|
||||
return ok({
|
||||
distributions: plotRecord.distributions.value.map((x) => ({
|
||||
name: x.value.name.value,
|
||||
// color: x.value.color?.value, // not supported yet
|
||||
distribution: x.value.distribution.value,
|
||||
})),
|
||||
});
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : "Unknown error";
|
||||
return error(message);
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,22 +79,6 @@ could be continuous, discrete or mixed.
|
|||
</Story>
|
||||
</Canvas>
|
||||
|
||||
### Date Distribution
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Date Distribution"
|
||||
args={{
|
||||
code: "mx(1661819770311, 1661829770311, 1661839770311)",
|
||||
width,
|
||||
xAxisType: "dateTime",
|
||||
width,
|
||||
}}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Mixed distributions
|
||||
|
||||
<Canvas>
|
||||
|
|
51
packages/components/src/stories/SquigglePartial.stories.mdx
Normal file
51
packages/components/src/stories/SquigglePartial.stories.mdx
Normal 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>
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
*/
|
||||
});
|
|
@ -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");
|
||||
});
|
|
@ -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([]);
|
||||
});
|
|
@ -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),
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"buildCommand": "cd ../.. && npx turbo run build --filter=@quri/squiggle-components",
|
||||
"outputDirectory": "storybook-static"
|
||||
}
|
1
packages/squiggle-lang/.gitignore
vendored
1
packages/squiggle-lang/.gitignore
vendored
|
@ -23,4 +23,3 @@ coverage
|
|||
.nyc_output/
|
||||
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
||||
src/rescript/Reducer/Reducer_Peggy/helpers.js
|
||||
src/rescript/ReducerProject/ReducerProject_IncludeParser.js
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,29 +32,25 @@ describe("dotSubtract", () => {
|
|||
*/
|
||||
Skip.test("mean of normal minus exponential (property)", () => {
|
||||
assert_(
|
||||
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,
|
||||
mkNormal(mean, 1.0),
|
||||
mkExponential(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,
|
||||
mkNormal(mean, 1.0),
|
||||
mkExponential(rate),
|
||||
)
|
||||
let meanResult = E.R2.bind(DistributionOperation.Constructors.mean(~env), dotDifference)
|
||||
// according to algebra or random variables,
|
||||
let meanAnalytical =
|
||||
mean -.
|
||||
SymbolicDist.Exponential.mean({rate: rate})->E.R2.toExn(
|
||||
"On trusted input this should never happen",
|
||||
)
|
||||
let meanResult = E.R2.bind(DistributionOperation.Constructors.mean(~env), dotDifference)
|
||||
// according to algebra or random variables,
|
||||
let meanAnalytical =
|
||||
mean -.
|
||||
SymbolicDist.Exponential.mean({rate: rate})->E.R2.toExn(
|
||||
"On trusted input this should never happen",
|
||||
)
|
||||
switch meanResult {
|
||||
| Ok(meanValue) => abs_float(meanValue -. meanAnalytical) /. abs_float(meanValue) < 1e-2 // 1% relative error
|
||||
| Error(err) => err === DistributionTypes.OperationError(DivisionByZeroError)
|
||||
}
|
||||
},
|
||||
),
|
||||
switch meanResult {
|
||||
| Ok(meanValue) => abs_float(meanValue -. meanAnalytical) /. abs_float(meanValue) < 1e-2 // 1% relative error
|
||||
| Error(err) => err === DistributionTypes.OperationError(DivisionByZeroError)
|
||||
}
|
||||
}),
|
||||
)
|
||||
pass
|
||||
})
|
||||
|
|
|
@ -40,60 +40,51 @@ let algebraicPower = algebraicPower(~env)
|
|||
|
||||
describe("(Algebraic) addition of distributions", () => {
|
||||
describe("mean", () => {
|
||||
test(
|
||||
"normal(mean=5) + normal(mean=20)",
|
||||
() => {
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist20)
|
||||
test("normal(mean=5) + normal(mean=20)", () => {
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist20)
|
||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
->expect
|
||||
->toBe(Some(2.5e1))
|
||||
})
|
||||
|
||||
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 =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
->expect
|
||||
->toBe(Some(2.5e1))
|
||||
},
|
||||
)
|
||||
|
||||
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 =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// 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)",
|
||||
() => {
|
||||
// let uniformMean = (9.0 +. 10.0) /. 2.0
|
||||
// let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0)
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=2.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(9.784290207736126, ~digits=1) // (uniformMean +. betaMean)
|
||||
}
|
||||
},
|
||||
)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// 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)", () => {
|
||||
// let uniformMean = (9.0 +. 10.0) /. 2.0
|
||||
// let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0)
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// 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,282 +122,247 @@ describe("(Algebraic) addition of distributions", () => {
|
|||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(normal(mean=10) + normal(mean=10)).pdf(1.9e1)",
|
||||
() => {
|
||||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=1)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).pdf(10)",
|
||||
() => {
|
||||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
test("(normal(mean=10) + normal(mean=10)).pdf(1.9e1)", () => {
|
||||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=4.
|
||||
// This value was calculated by a python script
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=1)
|
||||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).pdf(10)",
|
||||
() => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).pdf(10)", () => {
|
||||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=4.
|
||||
// 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)", () => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// 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 => {
|
||||
let received =
|
||||
normalDist10
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist5)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
testAll("(normal(mean=5) + normal(mean=5)).cdf (imprecise)", list{6e0, 8e0, 1e1, 1.2e1}, x => {
|
||||
let received =
|
||||
normalDist10
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist5)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=0)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(normal(mean=10) + normal(mean=10)).cdf(1.25e1)",
|
||||
() => {
|
||||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=2)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).cdf(10)",
|
||||
() => {
|
||||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// The value was calculated externally using a python script
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=0)
|
||||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).cdf(10)",
|
||||
() => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
}
|
||||
})
|
||||
test("(normal(mean=10) + normal(mean=10)).cdf(1.25e1)", () => {
|
||||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// The value was calculated externally using a python script
|
||||
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=2)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).cdf(10)", () => {
|
||||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// 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)", () => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// 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 => {
|
||||
let received =
|
||||
normalDist10
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist5)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
testAll("(normal(mean=5) + normal(mean=5)).inv (imprecise)", list{5e-2, 4.2e-3, 9e-3}, x => {
|
||||
let received =
|
||||
normalDist10
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist5
|
||||
->algebraicAdd(normalDist5)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(normal(mean=10) + normal(mean=10)).inv(1e-1)",
|
||||
() => {
|
||||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).inv(2e-2)",
|
||||
() => {
|
||||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=2.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(9.179319623146968, ~digits=0)
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
|
||||
}
|
||||
},
|
||||
)
|
||||
test(
|
||||
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).inv(2e-2)",
|
||||
() => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
}
|
||||
})
|
||||
test("(normal(mean=10) + normal(mean=10)).inv(1e-1)", () => {
|
||||
let received =
|
||||
normalDist20
|
||||
->Ok
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
let calculated =
|
||||
normalDist10
|
||||
->algebraicAdd(normalDist10)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toOption
|
||||
->E.O.flatten
|
||||
switch received {
|
||||
| None =>
|
||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||
->expect
|
||||
->toBe("never")
|
||||
| Some(x) =>
|
||||
switch calculated {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=2.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(9.190872365862756, ~digits=0)
|
||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).inv(2e-2)", () => {
|
||||
let received =
|
||||
uniformDist
|
||||
->algebraicAdd(betaDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// 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)", () => {
|
||||
let received =
|
||||
betaDist
|
||||
->algebraicAdd(uniformDist)
|
||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
||||
->E.R2.fmap(run)
|
||||
->E.R2.fmap(toFloat)
|
||||
->E.R.toExn("Expected float", _)
|
||||
switch received {
|
||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||
// sometimes it works with ~digits=2.
|
||||
| Some(x) => x->expect->toBeSoCloseTo(9.190872365862756, ~digits=0)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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 => {
|
||||
testAddInvariant(dist, dist)
|
||||
},
|
||||
)
|
||||
testAll("with two of the same distribution", distributions, dist => {
|
||||
testAddInvariant(dist, dist)
|
||||
})
|
||||
|
||||
testAll(
|
||||
"with two different distributions",
|
||||
pairsOfDifferentDistributions,
|
||||
dists => {
|
||||
let (dist1, dist2) = dists
|
||||
testAddInvariant(dist1, dist2)
|
||||
},
|
||||
)
|
||||
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 => {
|
||||
testSubtractInvariant(dist, dist)
|
||||
},
|
||||
)
|
||||
testAll("with two of the same distribution", distributions, dist => {
|
||||
testSubtractInvariant(dist, dist)
|
||||
})
|
||||
|
||||
testAll(
|
||||
"with two different distributions",
|
||||
pairsOfDifferentDistributions,
|
||||
dists => {
|
||||
let (dist1, dist2) = dists
|
||||
testSubtractInvariant(dist1, dist2)
|
||||
},
|
||||
)
|
||||
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 => {
|
||||
testMultiplicationInvariant(dist, dist)
|
||||
},
|
||||
)
|
||||
testAll("with two of the same distribution", distributions, dist => {
|
||||
testMultiplicationInvariant(dist, dist)
|
||||
})
|
||||
|
||||
testAll(
|
||||
"with two different distributions",
|
||||
pairsOfDifferentDistributions,
|
||||
dists => {
|
||||
let (dist1, dist2) = dists
|
||||
testMultiplicationInvariant(dist1, dist2)
|
||||
},
|
||||
)
|
||||
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
|
||||
let (dist1, dist2) = dists
|
||||
testMultiplicationInvariant(dist1, dist2)
|
||||
})
|
||||
|
||||
testAll(
|
||||
"with two different distributions in swapped order",
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -9,28 +9,22 @@ let prepareInputs = (ar, minWeight) =>
|
|||
describe("Continuous and discrete splits", () => {
|
||||
makeTest(
|
||||
"is empty, with no common elements",
|
||||
prepareInputs([1.33455, 1.432, 2.0], 2),
|
||||
prepareInputs([1.432, 1.33455, 2.0], 2),
|
||||
([1.33455, 1.432, 2.0], []),
|
||||
)
|
||||
|
||||
makeTest(
|
||||
"only stores 3.5 as discrete when minWeight is 3",
|
||||
prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
|
||||
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
|
||||
([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]),
|
||||
)
|
||||
|
||||
makeTest(
|
||||
"doesn't store 3.5 as discrete when minWeight is 5",
|
||||
prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 5),
|
||||
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 5),
|
||||
([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], []),
|
||||
)
|
||||
|
||||
makeTest(
|
||||
"more general test",
|
||||
prepareInputs([10., 10., 11., 11., 11., 12., 13., 13., 13., 13., 13., 14.], 3),
|
||||
([10., 10., 12., 14.], [(11., 3.), (13., 5.)]),
|
||||
)
|
||||
|
||||
let makeDuplicatedArray = count => {
|
||||
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
|
||||
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
|
||||
|
|
20
packages/squiggle-lang/__tests__/Lodash_test.res
Normal file
20
packages/squiggle-lang/__tests__/Lodash_test.res
Normal 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)]),
|
||||
)
|
||||
})
|
||||
)
|
|
@ -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))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)")
|
||||
})
|
|
@ -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
|
||||
})
|
17
packages/squiggle-lang/__tests__/Reducer/Reducer_Helpers.res
Normal file
17
packages/squiggle-lang/__tests__/Reducer/Reducer_Helpers.res
Normal 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)
|
|
@ -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")),
|
||||
)
|
||||
)
|
||||
})
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
|
@ -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))}",
|
||||
)
|
||||
})
|
||||
|
|
|
@ -14,29 +14,29 @@ describe("Peggy parse type", () => {
|
|||
describe("type or", () => {
|
||||
testParse(
|
||||
"answer: number|string",
|
||||
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ #number #string))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string)))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
})
|
||||
describe("type function", () => {
|
||||
testParse(
|
||||
"f: number=>number=>number",
|
||||
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ #number #number #number))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number)))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
})
|
||||
describe("high priority contract", () => {
|
||||
testParse(
|
||||
"answer: number<-min<-max(100)|string",
|
||||
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string)))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
testParse(
|
||||
"answer: number<-memberOf([1,3,5])",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ 1 3 5))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5)))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
})
|
||||
describe("low priority contract", () => {
|
||||
testParse(
|
||||
"answer: number | string $ opaque",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ #number #string)))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string))))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
})
|
||||
describe("type array", () => {
|
||||
|
@ -54,32 +54,32 @@ describe("Peggy parse type", () => {
|
|||
describe("type constructor", () => {
|
||||
testParse(
|
||||
"answer: Age(number)",
|
||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ #number))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number)))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
testParse(
|
||||
"answer: Complex(number, number)",
|
||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ #number #number))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number)))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
testParse(
|
||||
"answer: Person({age: number, name: string})",
|
||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ (::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ ((::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string))))))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
testParse(
|
||||
"weekend: Saturday | Sunday",
|
||||
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ (::$_typeConstructor_$ #Saturday (::$_constructArray_$)) (::$_typeConstructor_$ #Sunday (::$_constructArray_$))))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ())))))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
})
|
||||
describe("type parenthesis", () => {
|
||||
//$ is introduced to avoid parenthesis
|
||||
testParse(
|
||||
"answer: (number|string)<-opaque",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ #number #string)))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string))))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
})
|
||||
describe("squiggle expressions in type contracts", () => {
|
||||
testParse(
|
||||
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
|
||||
"{:odds1 = {(::$_constructArray_$ 1 3 5)}; :odds2 = {(::$_constructArray_$ 7 9)}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2))); (::$_endOfOuterBlock_$ () ())}",
|
||||
"{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2))); (::$_endOfOuterBlock_$ () ())}",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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, ()))
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
(),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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", ())
|
||||
})
|
||||
|
||||
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(
|
||||
// expression binding
|
||||
"f(a) = a > 5 ? 1 : 0; f(6)",
|
||||
"f = {|a| {(larger)(a, 5) ? (1) : (0)}}; (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)",
|
||||
~v="6",
|
||||
(),
|
||||
)
|
||||
testToExpression(
|
||||
// when false binding
|
||||
"f(a) = a < 5 ? 1 : a; f(6)",
|
||||
"f = {|a| {(smaller)(a, 5) ? (1) : (a)}}; (f)(6)",
|
||||
~v="6",
|
||||
(),
|
||||
)
|
||||
},
|
||||
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",
|
||||
"{(:$_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)",
|
||||
"{(:$_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)",
|
||||
"{(:$_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)",
|
||||
"{(:$_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", ())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -21,7 +21,7 @@ describe("Peggy Types to Expression", () => {
|
|||
describe("type or", () => {
|
||||
testToExpression(
|
||||
"answer: number|string|distribution",
|
||||
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ #number #string #distribution))); (:$_endOfOuterBlock_$ () ())}",
|
||||
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution)))); (:$_endOfOuterBlock_$ () ())}",
|
||||
// ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}",
|
||||
(),
|
||||
)
|
||||
|
@ -29,13 +29,13 @@ describe("Peggy Types to Expression", () => {
|
|||
describe("type function", () => {
|
||||
testToExpression(
|
||||
"f: number=>number=>number",
|
||||
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ #number #number #number))); (:$_endOfOuterBlock_$ () ())}",
|
||||
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number)))); (:$_endOfOuterBlock_$ () ())}",
|
||||
// ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}",
|
||||
(),
|
||||
)
|
||||
testToExpression(
|
||||
"f: number=>number",
|
||||
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ #number #number))); (:$_endOfOuterBlock_$ () ())}",
|
||||
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number)))); (:$_endOfOuterBlock_$ () ())}",
|
||||
// ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}",
|
||||
(),
|
||||
)
|
||||
|
@ -43,13 +43,13 @@ describe("Peggy Types to Expression", () => {
|
|||
describe("high priority contract", () => {
|
||||
testToExpression(
|
||||
"answer: number<-min(1)<-max(100)|string",
|
||||
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))); (:$_endOfOuterBlock_$ () ())}",
|
||||
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string)))); (:$_endOfOuterBlock_$ () ())}",
|
||||
// ~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}",
|
||||
(),
|
||||
)
|
||||
testToExpression(
|
||||
"answer: number<-memberOf([1,3,5])",
|
||||
"{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ 1 3 5))); (:$_endOfOuterBlock_$ () ())}",
|
||||
"{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ (1 3 5)))); (:$_endOfOuterBlock_$ () ())}",
|
||||
// ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
||||
(),
|
||||
)
|
||||
|
@ -81,7 +81,7 @@ describe("Peggy Types to Expression", () => {
|
|||
describe("low priority contract", () => {
|
||||
testToExpression(
|
||||
"answer: number | string $ opaque",
|
||||
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ #number #string)))); (:$_endOfOuterBlock_$ () ())}",
|
||||
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string))))); (:$_endOfOuterBlock_$ () ())}",
|
||||
// ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}",
|
||||
(),
|
||||
)
|
||||
|
@ -89,7 +89,7 @@ describe("Peggy Types to Expression", () => {
|
|||
describe("squiggle expressions in type contracts", () => {
|
||||
testToExpression(
|
||||
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
|
||||
"{(:$_let_$ :odds1 {(:$_constructArray_$ 1 3 5)}); (:$_let_$ :odds2 {(:$_constructArray_$ 7 9)}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2))); (:$_endOfOuterBlock_$ () ())}",
|
||||
"{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2))); (:$_endOfOuterBlock_$ () ())}",
|
||||
// ~v="@{_typeAliases_: {odds: {memberOf: [1,3,5,7,9],typeIdentifier: #number,typeTag: 'typeIdentifier'}},odds1: [1,3,5],odds2: [7,9]}",
|
||||
(),
|
||||
)
|
||||
|
|
|
@ -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)}",
|
||||
(),
|
||||
)
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
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.expandMacroCall(
|
||||
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,
|
||||
)
|
||||
->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)
|
||||
}
|
|
@ -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}")
|
|
@ -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.flatMap(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")
|
|
@ -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.flatMap(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")
|
|
@ -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"))))
|
||||
})
|
||||
})
|
|
@ -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')",
|
||||
)
|
||||
)
|
||||
})
|
|
@ -1,13 +0,0 @@
|
|||
open Jest
|
||||
open Reducer_Peggy_TestHelpers
|
||||
|
||||
describe("Construct Array", () => {
|
||||
testToExpression("[1,2]", "[1, 2]", ~v="[1,2]", ())
|
||||
testToExpression("[]", "[]", ~v="[]", ())
|
||||
testToExpression(
|
||||
"f(x)=x; g(x)=x; [f, g]",
|
||||
"f = {|x| {x}}; g = {|x| {x}}; [f, g]",
|
||||
~v="[lambda(x=>internal code),lambda(x=>internal code)]",
|
||||
(),
|
||||
)
|
||||
})
|
|
@ -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)")
|
||||
})
|
||||
|
|
|
@ -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)")
|
||||
|
|
|
@ -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])")
|
||||
})
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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",
|
||||
() =>
|
||||
expectEvalToBe(
|
||||
`{
|
||||
test("always the same property ending", () =>
|
||||
expectEvalToBe(
|
||||
`{
|
||||
a: 1,
|
||||
b: 2,
|
||||
}`,
|
||||
"Ok({a: 1,b: 2})",
|
||||
),
|
||||
"Ok({a: 1,b: 2})",
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -62,10 +50,6 @@ 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", () => {
|
||||
|
@ -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
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)))")
|
|
@ -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')")
|
||||
)
|
||||
})
|
|
@ -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->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->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->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"),
|
||||
]
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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.toStringResult
|
||||
}
|
||||
|
||||
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}")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@@warning("-44")
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module Project = ForTS_ReducerProject
|
||||
module Bindings = Reducer_Bindings
|
||||
|
||||
|
@ -11,34 +12,32 @@ describe("ReducerProject Tutorial", () => {
|
|||
/*
|
||||
Case "Running a single source".
|
||||
*/
|
||||
test(
|
||||
"run",
|
||||
() => {
|
||||
/* Let's start with running a single source and getting Result as well as the Bindings
|
||||
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")
|
||||
/* Let's run "main" source. */
|
||||
project->Project.run("main")
|
||||
/* Now you have a result for "main" source.
|
||||
let project = Project.createProject()
|
||||
/* Every source has a name. This is used for debugging, dependencies and error messages. */
|
||||
Project.setSource(project, "main", "1 + 2")
|
||||
/* Let's run "main" source. */
|
||||
project->Project.run("main")
|
||||
/* Now you have a result for "main" source.
|
||||
Running one by one is necessary for UI to navigate among the sources and to see the results by source.
|
||||
And you're free to run any source you want.
|
||||
You will look at the results of this source and you don't want to run the others if not required.
|
||||
*/
|
||||
|
||||
/* However, you could also run the whole project.
|
||||
/* However, you could also run the whole project.
|
||||
If you have all the sources, you can always run the whole project.
|
||||
Dependencies and recompiling on demand will be taken care of by the project.
|
||||
*/
|
||||
project->Project.runAll
|
||||
project->Project.runAll
|
||||
|
||||
/* Either with run or runAll you executed the project.
|
||||
/* Either with run or runAll you executed the project.
|
||||
You can get the result of a specific source by calling getResult for that source.
|
||||
You can get the bindings of a specific source by calling getBindings for that source.
|
||||
If there is any runtime error, getResult will return the error.
|
||||
|
@ -46,59 +45,53 @@ Case "Running a single source".
|
|||
Note that getResult returns None if the source has not been run.
|
||||
Getting None means you have forgotten to run the source.
|
||||
*/
|
||||
let result = project->Project.getResult("main")
|
||||
let bindings = project->Project.getBindings("main")
|
||||
let result = project->Project.getResult("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)", "{}")
|
||||
/* You've got 3 with empty bindings. */
|
||||
},
|
||||
)
|
||||
/* Let's display the result and bindings */
|
||||
(
|
||||
result->InternalExpressionValue.toStringResult,
|
||||
bindings->InternalExpressionValue.toStringBindings,
|
||||
)->expect == ("Ok(3)", "@{}")
|
||||
/* You've got 3 with empty bindings. */
|
||||
})
|
||||
|
||||
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")
|
||||
/* Now you have external bindings and external result. */
|
||||
(
|
||||
result->Reducer_Value.toStringResult,
|
||||
bindings->Reducer_T.IEvRecord->Reducer_Value.toString,
|
||||
)->expect == ("Ok(3)", "{}")
|
||||
},
|
||||
)
|
||||
test("run summary", () => {
|
||||
let project = Project.createProject()
|
||||
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->InternalExpressionValue.toStringResult,
|
||||
bindings->InternalExpressionValue.IEvBindings->InternalExpressionValue.toString,
|
||||
)->expect == ("Ok(3)", "@{}")
|
||||
})
|
||||
|
||||
test(
|
||||
"run with an environment",
|
||||
() => {
|
||||
/* Running the source code like above allows you to set a custom environment */
|
||||
let project = Project.createProject()
|
||||
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)
|
||||
/* Optional. Set your custom environment anytime before running */
|
||||
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.toStringResult->expect == "Ok(3)"
|
||||
})
|
||||
|
||||
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)", "{}")
|
||||
},
|
||||
)
|
||||
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->InternalExpressionValue.toStringResult,
|
||||
bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings,
|
||||
)->expect == ("Ok(3)", "@{}")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@@warning("-44")
|
||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
||||
module Project = ForTS_ReducerProject
|
||||
module Bindings = Reducer_Bindings
|
||||
|
||||
|
@ -10,104 +11,102 @@ describe("ReducerProject Tutorial", () => {
|
|||
describe("Multi source", () => {
|
||||
/*
|
||||
Case "Running multiple sources" */
|
||||
test(
|
||||
"Chaining",
|
||||
() => {
|
||||
let project = Project.createProject()
|
||||
/* This time let's add 3 sources and chain them together */
|
||||
project->Project.setSource("source1", "x=1")
|
||||
test("Chaining", () => {
|
||||
let project = Project.createProject()
|
||||
/* This time let's add 3 sources and chain them together */
|
||||
Project.setSource(project, "source1", "x=1")
|
||||
|
||||
project->Project.setSource("source2", "y=x+1")
|
||||
/* To run, source2 depends on source1 */
|
||||
project->Project.setContinues("source2", ["source1"])
|
||||
Project.setSource(project, "source2", "y=2")
|
||||
/* To run, source2 depends on source1 */
|
||||
Project.setContinues(project, "source2", ["source1"])
|
||||
|
||||
project->Project.setSource("source3", "z=y+1")
|
||||
/* To run, source3 depends on source2 */
|
||||
project->Project.setContinues("source3", ["source2"])
|
||||
Project.setSource(project, "source3", "z=3")
|
||||
/* To run, source3 depends on source2 */
|
||||
Project.setContinues(project, "source3", ["source2"])
|
||||
|
||||
/* Now we can run the project */
|
||||
project->Project.runAll
|
||||
/* Now we can run the project */
|
||||
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")
|
||||
/* And let's check the result and bindings of 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.toStringResult,
|
||||
bindings3->InternalExpressionValue.toStringBindings,
|
||||
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
|
||||
})
|
||||
|
||||
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()
|
||||
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")
|
||||
/* This time source1 and source2 are not depending on anything */
|
||||
Project.setSource(project, "source1", "x=1")
|
||||
Project.setSource(project, "source2", "y=2")
|
||||
|
||||
project->Project.setSource("source3", "z=x+y")
|
||||
/* To run, source3 depends on source1 and source3 together */
|
||||
project->Project.setContinues("source3", ["source1", "source2"])
|
||||
Project.setSource(project, "source3", "z=3")
|
||||
/* To run, source3 depends on source1 and source3 together */
|
||||
Project.setContinues(project, "source3", ["source1", "source2"])
|
||||
|
||||
/* Now we can run the project */
|
||||
project->Project.runAll
|
||||
/* Now we can run the project */
|
||||
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")
|
||||
/* And let's check the result and bindings of 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.toStringResult,
|
||||
bindings3->InternalExpressionValue.toStringBindings,
|
||||
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
|
||||
})
|
||||
|
||||
test(
|
||||
"Intro to including",
|
||||
() => {
|
||||
/* Though it would not be practical for a storybook,
|
||||
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()
|
||||
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")
|
||||
/* This time source1 and source2 are not depending on anything */
|
||||
Project.setSource(project, "source1", "x=1")
|
||||
Project.setSource(project, "source2", "y=2")
|
||||
|
||||
project->Project.setSource(
|
||||
"source3",
|
||||
`
|
||||
Project.setSource(
|
||||
project,
|
||||
"source3",
|
||||
`
|
||||
#include "source1"
|
||||
#include "source2"
|
||||
z=x+y`,
|
||||
)
|
||||
/* We need to parse the includes to set the dependencies */
|
||||
project->Project.parseIncludes("source3")
|
||||
z=3`,
|
||||
)
|
||||
/* We need to parse the includes to set the dependencies */
|
||||
Project.parseIncludes(project, "source3")
|
||||
|
||||
/* Now we can run the project */
|
||||
project->Project.runAll
|
||||
/* Now we can run the project */
|
||||
Project.runAll(project)
|
||||
|
||||
/* And let's check the result and bindings of source3
|
||||
/* 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.toStringResult,
|
||||
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 */
|
||||
/*
|
||||
/*
|
||||
Dealing with includes needs more.
|
||||
- There are parse errors
|
||||
- There are cyclic includes
|
||||
- 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. */
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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,151 +25,133 @@ 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",
|
||||
() => {
|
||||
/* Parse includes has set the dependencies */
|
||||
project->Project.getDependencies("main")->expect == ["common"]
|
||||
/* If there were no includes than there would be no dependencies */
|
||||
/* However if there was a syntax error at includes then would be no dependencies also */
|
||||
/* Therefore looking at dependencies is not the right way to load includes */
|
||||
/* getDependencies does not distinguish between setContinues or parseIncludes */
|
||||
},
|
||||
)
|
||||
test(
|
||||
"getIncludes",
|
||||
() => {
|
||||
/* Parse includes has set the includes */
|
||||
switch project->Project.getIncludes("main") {
|
||||
| Ok(includes) => includes->expect == ["common"]
|
||||
| Error(err) => err->SqError.toString->fail
|
||||
}
|
||||
/* If the includes cannot be parsed then you get a syntax error.
|
||||
Project.parseIncludes(project, "main")
|
||||
test("getDependencies", () => {
|
||||
/* Parse includes has set the dependencies */
|
||||
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", () => {
|
||||
/* Parse includes has set the includes */
|
||||
switch Project.getIncludes(project, "main") {
|
||||
| Ok(includes) => includes->expect == ["common"]
|
||||
| Error(err) => err->Reducer_ErrorValue.errorToString->fail
|
||||
}
|
||||
/* If the includes cannot be parsed then you get a syntax error.
|
||||
Otherwise you get the includes.
|
||||
If there is no syntax error then you can load that file and use setSource to add it to the project.
|
||||
And so on recursively... */
|
||||
},
|
||||
)
|
||||
test(
|
||||
"getDependents",
|
||||
() => {
|
||||
/* For any reason, you are able to query what other sources
|
||||
})
|
||||
test("getDependents", () => {
|
||||
/* For any reason, you are able to query what other sources
|
||||
include or depend on the current source.
|
||||
But you don't need to use this to execute the projects.
|
||||
It is provided for completeness of information. */
|
||||
project->Project.getDependents("main")->expect == []
|
||||
/* Nothing is depending on or including main */
|
||||
},
|
||||
)
|
||||
Project.getDependents(project, "main")->expect == []
|
||||
/* Nothing is depending on or including main */
|
||||
})
|
||||
|
||||
describe(
|
||||
"Real Like",
|
||||
() => {
|
||||
/* Now let's look at recursive and possibly cyclic includes */
|
||||
/* There is no function provided to load the include files.
|
||||
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.
|
||||
Therefore one has to write a function to load sources recursively and and setSources
|
||||
while checking for dependencies */
|
||||
|
||||
/* Let's make a dummy loader */
|
||||
let loadSource = (sourceName: string) =>
|
||||
switch sourceName {
|
||||
| "source1" => "x=1"
|
||||
| "source2" => `
|
||||
/* Let's make a dummy loader */
|
||||
let loadSource = (sourceName: string) =>
|
||||
switch sourceName {
|
||||
| "source1" => "x=1"
|
||||
| "source2" => `
|
||||
#include "source1"
|
||||
y=2`
|
||||
| "source3" => `
|
||||
| "source3" => `
|
||||
#include "source2"
|
||||
z=3`
|
||||
| _ => `source ${sourceName} not found`->Js.Exn.raiseError
|
||||
}
|
||||
| _ => `source ${sourceName} not found`->Js.Exn.raiseError
|
||||
}
|
||||
|
||||
/* let's recursively load the sources */
|
||||
let rec loadIncludesRecursively = (project, sourceName, visited) => {
|
||||
if visited->Js.Array2.includes(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's parse the includes and dive into them */
|
||||
Project.parseIncludes(project, sourceName)
|
||||
let rIncludes = project->Project.getIncludes(sourceName)
|
||||
switch rIncludes {
|
||||
/* Maybe there is an include syntax error */
|
||||
| Error(err) => err->SqError.toString->Js.Exn.raiseError
|
||||
/* let's recursively load the sources */
|
||||
let rec loadIncludesRecursively = (project, sourceName, visited) => {
|
||||
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 _ = Js.Array2.push(newVisited, sourceName)
|
||||
/* Let's parse the includes and dive into them */
|
||||
Project.parseIncludes(project, sourceName)
|
||||
let rIncludes = Project.getIncludes(project, sourceName)
|
||||
switch rIncludes {
|
||||
/* Maybe there is an include syntax error */
|
||||
| Error(err) => err->Reducer_ErrorValue.errorToString->Js.Exn.raiseError
|
||||
|
||||
| Ok(includes) =>
|
||||
includes->Belt.Array.forEach(
|
||||
newIncludeName => {
|
||||
/* We have got one of the new includes.
|
||||
Let's load it and add it to the project */
|
||||
let newSource = loadSource(newIncludeName)
|
||||
project->Project.setSource(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)
|
||||
},
|
||||
)
|
||||
}
|
||||
| Ok(includes) =>
|
||||
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.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 */
|
||||
loadIncludesRecursively(project, newIncludeName, newVisited)
|
||||
})
|
||||
}
|
||||
}
|
||||
/* As we have a fake source loader and a recursive include handler,
|
||||
We can not set up a real project */
|
||||
}
|
||||
/* As we have a fake source loader and a recursive include handler,
|
||||
We can not set up a real project */
|
||||
|
||||
/* * Here starts our real life project! * */
|
||||
/* * Here starts our real life project! * */
|
||||
|
||||
let project = Project.createProject()
|
||||
let project = Project.createProject()
|
||||
|
||||
project->Project.setSource(
|
||||
"main",
|
||||
`
|
||||
#include "source1"
|
||||
#include "source2"
|
||||
/* main includes source3 which includes source2 which includes source1 */
|
||||
Project.setSource(
|
||||
project,
|
||||
"main",
|
||||
`
|
||||
#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
|
||||
)
|
||||
/* Setting source requires parsing and loading the includes recursively */
|
||||
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(
|
||||
"doubleX",
|
||||
`
|
||||
/* 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.setSource(
|
||||
project,
|
||||
"doubleX",
|
||||
`
|
||||
#include "source1"
|
||||
doubleX = x * 2
|
||||
`,
|
||||
)
|
||||
project->loadIncludesRecursively("doubleX", [])
|
||||
/* Remember, any time you set a source, you need to load includes recursively */
|
||||
)
|
||||
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"])
|
||||
/* As doubleX is not included by main, it is not loaded recursively.
|
||||
So we link it to the project as a dependency */
|
||||
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")
|
||||
/* And see the result and bindings.. */
|
||||
test(
|
||||
"recursive includes",
|
||||
() => {
|
||||
(
|
||||
result->Reducer_Value.toStringResult,
|
||||
bindings->Reducer_Value.toStringRecord,
|
||||
)->expect == ("Ok(6)", "{a: 6,b: 2}")
|
||||
/* Everything as expected */
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
/* Let's run the project */
|
||||
Project.runAll(project)
|
||||
let result = Project.getResult(project, "main")
|
||||
let bindings = Project.getBindings(project, "main")
|
||||
/* And see the result and bindings.. */
|
||||
test("recursive includes", () => {
|
||||
(
|
||||
result->InternalExpressionValue.toStringResult,
|
||||
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",
|
||||
() => {
|
||||
Project.getDependencies(project, "main")->expect == ["common"]
|
||||
},
|
||||
)
|
||||
test(
|
||||
"getIncludes",
|
||||
() => {
|
||||
switch Project.getIncludes(project, "main") {
|
||||
| Ok(includes) => includes->expect == ["common"]
|
||||
| Error(err) => err->SqError.toString->fail
|
||||
}
|
||||
},
|
||||
)
|
||||
test("getDependencies", () => {
|
||||
Project.getDependencies(project, "main")->expect == ["common"]
|
||||
})
|
||||
test("getIncludes", () => {
|
||||
switch Project.getIncludes(project, "main") {
|
||||
| Ok(includes) => includes->expect == ["common"]
|
||||
| Error(err) => err->Reducer_ErrorValue.errorToString->fail
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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.toStringResult->expect == "Ok(6)"
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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.toStringResult
|
||||
)
|
||||
userResultsAsString->expect == ["Ok(2)", "Ok(4)", "Ok(6)", "Ok(8)", "Ok(10)"]
|
||||
})
|
||||
|
|
|
@ -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
|
||||
`)
|
||||
})
|
||||
})
|
|
@ -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 => {
|
||||
expectEvalToBeOk(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))
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
|
@ -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$$"]
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@quri/squiggle-lang",
|
||||
"version": "0.5.0",
|
||||
"version": "0.4.0-alpha.0",
|
||||
"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,32 +41,35 @@
|
|||
],
|
||||
"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",
|
||||
"mathjs": "^11.2.1",
|
||||
"mathjs": "^11.1.0",
|
||||
"pdfast": "^0.2.0"
|
||||
},
|
||||
"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.2",
|
||||
"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.3.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.4",
|
||||
"typescript": "^4.8.2",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0"
|
||||
},
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
Various scripts used for development, benchmarking and testing.
|
||||
|
||||
None of these are bundled in the NPM package yet.
|
||||
|
||||
# run.mjs
|
||||
|
||||
`scripts/run.mjs` allows quick testing of Squiggle programs:
|
||||
|
||||
```
|
||||
$ ./scripts/run.mjs '2+2'
|
||||
Running 2+2
|
||||
Ok 4
|
||||
@{__result__: 4}
|
||||
```
|
||||
|
||||
# run-file.mjs
|
||||
|
||||
`scripts/run-file.mjs` can be used to run and benchmark squiggle scripts stored in files:
|
||||
|
||||
```
|
||||
$ ./scripts/run-file.mjs ./path/to/file.squiggle
|
||||
Time: 3.18 Ok
|
||||
```
|
||||
|
||||
To see the result and bindings, add the `-o` or `--output` flag.
|
|
@ -1,18 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import { SqProject } from "@quri/squiggle-lang";
|
||||
import { measure } from "./lib.mjs";
|
||||
|
||||
const maxP = 5;
|
||||
|
||||
for (let p = 0; p <= maxP; p++) {
|
||||
const size = Math.pow(10, p);
|
||||
const project = SqProject.create();
|
||||
project.setSource(
|
||||
"main",
|
||||
`List.upTo(1, ${size}) |> map({|x| List.upTo(1, 100) |> reduce(0, {|a,b|a+b})})`
|
||||
);
|
||||
const time = measure(() => {
|
||||
project.run("main");
|
||||
});
|
||||
console.log(`1e${p}`, "\t", time);
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import { SqProject } from "@quri/squiggle-lang";
|
||||
import { measure } from "./lib.mjs";
|
||||
|
||||
const maxP = 7;
|
||||
|
||||
for (let p = 0; p <= maxP; p++) {
|
||||
const size = Math.pow(10, p);
|
||||
const project = SqProject.create();
|
||||
project.setSource("list", `l = List.upTo(1,${size})`);
|
||||
project.run("list");
|
||||
project.setSource("map", "l |> map({|x| x})");
|
||||
project.setContinues("map", ["list"]);
|
||||
const time = measure(() => {
|
||||
project.run("map");
|
||||
});
|
||||
console.log(`1e${p}`, "\t", time);
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import { SqProject } from "@quri/squiggle-lang";
|
||||
import { measure } from "./lib.mjs";
|
||||
|
||||
const maxP = 3;
|
||||
|
||||
const sampleCount = process.env.SAMPLE_COUNT;
|
||||
|
||||
for (let p = 0; p <= maxP; p++) {
|
||||
const size = Math.pow(10, p);
|
||||
const project = SqProject.create();
|
||||
if (sampleCount) {
|
||||
project.setEnvironment({
|
||||
sampleCount: Number(sampleCount),
|
||||
xyPointLength: Number(sampleCount),
|
||||
});
|
||||
}
|
||||
project.setSource(
|
||||
"main",
|
||||
`
|
||||
List.upTo(1, ${size}) -> map(
|
||||
{ |x| normal(x,2) -> SampleSet.fromDist -> PointSet.fromDist }
|
||||
)->List.last
|
||||
`
|
||||
);
|
||||
const time = measure(() => {
|
||||
project.run("main");
|
||||
});
|
||||
const result = project.getResult("main");
|
||||
if (result.tag != "Ok") {
|
||||
throw new Error("Code failed: " + result.value.toString());
|
||||
}
|
||||
console.log(`1e${p}`, "\t", time);
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import { SqProject } from "@quri/squiggle-lang";
|
||||
|
||||
export const measure = (cb, times = 1) => {
|
||||
const t1 = new Date();
|
||||
|
||||
for (let i = 1; i <= times; i++) {
|
||||
cb();
|
||||
}
|
||||
const t2 = new Date();
|
||||
return (t2 - t1) / 1000;
|
||||
};
|
||||
|
||||
export const red = (str) => `\x1b[31m${str}\x1b[0m`;
|
||||
export const green = (str) => `\x1b[32m${str}\x1b[0m`;
|
||||
|
||||
export const run = (src, { output, sampleCount } = {}) => {
|
||||
const project = SqProject.create();
|
||||
if (sampleCount) {
|
||||
project.setEnvironment({
|
||||
sampleCount: Number(sampleCount),
|
||||
xyPointLength: Number(sampleCount),
|
||||
});
|
||||
}
|
||||
project.setSource("main", src);
|
||||
const time = measure(() => project.run("main"));
|
||||
|
||||
const bindings = project.getBindings("main");
|
||||
const result = project.getResult("main");
|
||||
|
||||
if (output) {
|
||||
console.log("Result:", result.tag, result.value.toString());
|
||||
console.log("Bindings:", bindings.toString());
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Time:",
|
||||
String(time),
|
||||
result.tag === "Error" ? red(result.tag) : green(result.tag),
|
||||
result.tag === "Error" ? result.value.toStringWithFrameStack() : ""
|
||||
);
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import fs from "fs";
|
||||
|
||||
import { Command } from "commander";
|
||||
|
||||
import { run } from "./lib.mjs";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program.option("-o, --output");
|
||||
program.arguments("<string>");
|
||||
|
||||
const options = program.parse(process.argv);
|
||||
|
||||
const sampleCount = process.env.SAMPLE_COUNT;
|
||||
|
||||
const src = fs.readFileSync(program.args[0], "utf-8");
|
||||
if (!src) {
|
||||
throw new Error("Expected src");
|
||||
}
|
||||
|
||||
run(src, { output: options.output, sampleCount });
|
|
@ -1,19 +0,0 @@
|
|||
#!/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];
|
||||
if (!src) {
|
||||
throw new Error("Expected src");
|
||||
}
|
||||
|
||||
const sampleCount = process.env.SAMPLE_COUNT;
|
||||
|
||||
run(src, { output: true, sampleCount });
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user