Merge branch 'drop-bisect-ppx' into experiment-10.0rc1
This commit is contained in:
commit
666524a36a
20
.github/CODEOWNERS
vendored
20
.github/CODEOWNERS
vendored
|
@ -9,22 +9,22 @@
|
||||||
# This also holds true for GitHub teams.
|
# This also holds true for GitHub teams.
|
||||||
|
|
||||||
# Rescript
|
# Rescript
|
||||||
*.res @OAGr
|
*.res @berekuk @OAGr
|
||||||
*.resi @OAGr
|
*.resi @berekuk @OAGr
|
||||||
|
|
||||||
# Typescript
|
# Typescript
|
||||||
*.tsx @Hazelfire @OAGr
|
*.tsx @Hazelfire @berekuk @OAGr
|
||||||
*.ts @Hazelfire @OAGr
|
*.ts @Hazelfire @berekuk @OAGr
|
||||||
|
|
||||||
# Javascript
|
# Javascript
|
||||||
*.js @Hazelfire @OAGr
|
*.js @Hazelfire @berekuk @OAGr
|
||||||
|
|
||||||
# Any opsy files
|
# Any opsy files
|
||||||
.github/** @quinn-dougherty @OAGr
|
.github/** @quinn-dougherty @berekuk @OAGr
|
||||||
*.json @quinn-dougherty @Hazelfire @OAGr
|
*.json @quinn-dougherty @Hazelfire @berekuk @OAGr
|
||||||
*.y*ml @quinn-dougherty @OAGr
|
*.y*ml @quinn-dougherty @berekuk @OAGr
|
||||||
*.config.js @Hazelfire @OAGr
|
*.config.js @Hazelfire @berekuk @OAGr
|
||||||
netlify.toml @quinn-dougherty @OAGr @Hazelfire
|
vercel.json @OAGr @berekuk @Hazelfire
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
*.md @quinn-dougherty @OAGr @Hazelfire
|
*.md @quinn-dougherty @OAGr @Hazelfire
|
||||||
|
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
|
@ -12,9 +12,13 @@ updates:
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "⬆️"
|
prefix: "⬆️"
|
||||||
open-pull-requests-limit: 100
|
open-pull-requests-limit: 100
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "⬆️"
|
prefix: "⬆️"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
|
87
.github/workflows/ci-cachix.yml
vendored
Normal file
87
.github/workflows/ci-cachix.yml
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
name: Nix build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- reducer-dev
|
||||||
|
- epic-reducer-project
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flake-lints:
|
||||||
|
name: All lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install nix
|
||||||
|
uses: cachix/install-nix-action@v17
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-22.05
|
||||||
|
- name: Use cachix
|
||||||
|
uses: cachix/cachix-action@v10
|
||||||
|
with:
|
||||||
|
name: quantified-uncertainty
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Check that lang lints
|
||||||
|
run: nix build .#lang-lint
|
||||||
|
- name: Check that components lints
|
||||||
|
run: nix build .#components-lint
|
||||||
|
- name: Check that website lints
|
||||||
|
run: nix build .#docusaurus-lint
|
||||||
|
- name: Check that vscode extension lints
|
||||||
|
run: nix build .#vscode-lint
|
||||||
|
- name: Check that cli lints
|
||||||
|
run: nix build .#cli-lint
|
||||||
|
|
||||||
|
flake-packages:
|
||||||
|
name: Builds, tests, and bundles
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: flake-lints
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install nix
|
||||||
|
uses: cachix/install-nix-action@v17
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-22.05
|
||||||
|
- name: Use cachix
|
||||||
|
uses: cachix/cachix-action@v10
|
||||||
|
with:
|
||||||
|
name: quantified-uncertainty
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Check all lang tests
|
||||||
|
run: nix build .#lang-test
|
||||||
|
- name: Check that lang bundles
|
||||||
|
run: nix build .#lang-bundle
|
||||||
|
- name: Check that components builds
|
||||||
|
run: nix build .#components
|
||||||
|
- name: Check that components bundles
|
||||||
|
run: nix build .#components-bundle
|
||||||
|
|
||||||
|
flake-devshells:
|
||||||
|
name: Development shell environment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install nix
|
||||||
|
uses: cachix/install-nix-action@v17
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-22.05
|
||||||
|
- name: Use cachix
|
||||||
|
uses: cachix/cachix-action@v10
|
||||||
|
with:
|
||||||
|
name: quantified-uncertainty
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
- name: Build js devshell
|
||||||
|
run: nix develop .#js --profile just-js
|
||||||
|
- name: Build js & wasm devshell
|
||||||
|
run: nix develop --profile full-shell
|
223
.github/workflows/ci.yml
vendored
223
.github/workflows/ci.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
name: Squiggle packages check
|
name: Squiggle packages checks
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@ -9,213 +9,40 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
- reducer-dev
|
|
||||||
|
env:
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: quantified-uncertainty
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre_check:
|
build-test-lint:
|
||||||
name: Precheck for skipping redundant jobs
|
name: Build, test, lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
|
||||||
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
|
|
||||||
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
|
|
||||||
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
|
|
||||||
should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }}
|
|
||||||
should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }}
|
|
||||||
steps:
|
|
||||||
- id: skip_lang_check
|
|
||||||
name: Check if the changes are about squiggle-lang src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/squiggle-lang/**"]'
|
|
||||||
- id: skip_components_check
|
|
||||||
name: Check if the changes are about components src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/components/**"]'
|
|
||||||
- id: skip_website_check
|
|
||||||
name: Check if the changes are about website src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/website/**"]'
|
|
||||||
- id: skip_vscodeext_check
|
|
||||||
name: Check if the changes are about vscode extension src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/vscode-ext/**"]'
|
|
||||||
- id: skip_cli_check
|
|
||||||
name: Check if the changes are about cli src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
|
||||||
with:
|
|
||||||
paths: '["packages/cli/**"]'
|
|
||||||
|
|
||||||
lang-lint:
|
|
||||||
name: Language lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/squiggle-lang
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Install Dependencies
|
- name: Setup Node.js environment
|
||||||
run: cd ../../ && yarn
|
uses: actions/setup-node@v3
|
||||||
- name: Check rescript lint
|
|
||||||
run: yarn lint:rescript
|
|
||||||
- name: Check javascript, typescript, and markdown lint
|
|
||||||
uses: creyD/prettier_action@v4.2
|
|
||||||
with:
|
with:
|
||||||
dry: true
|
node-version: 16
|
||||||
prettier_options: --check packages/squiggle-lang
|
cache: 'yarn'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn --frozen-lockfile
|
||||||
|
- name: Turbo run
|
||||||
|
run: npx turbo run build test lint bundle
|
||||||
|
|
||||||
lang-build-test-bundle:
|
coverage:
|
||||||
name: Language build, test, and bundle
|
name: Coverage
|
||||||
runs-on: ubuntu-latest
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Install dependencies from monorepo level
|
- name: Setup Node.js environment
|
||||||
run: cd ../../ && yarn
|
uses: actions/setup-node@v2
|
||||||
- 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:
|
with:
|
||||||
dry: true
|
node-version: 16
|
||||||
prettier_options: --check packages/components --ignore-path packages/components/.prettierignore
|
cache: 'yarn'
|
||||||
|
- name: Install dependencies
|
||||||
components-bundle-build:
|
run: yarn
|
||||||
name: Components bundle and build
|
- name: Coverage
|
||||||
runs-on: ubuntu-latest
|
run: npx turbo run coverage
|
||||||
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: Install dependencies from monorepo level
|
|
||||||
run: cd ../../ && yarn
|
|
||||||
- name: Lint the VSCode Extension source code
|
|
||||||
run: yarn lint
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
71
.github/workflows/release-please.yml
vendored
71
.github/workflows/release-please.yml
vendored
|
@ -3,7 +3,7 @@ name: Run Release Please
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre_check:
|
pre_check:
|
||||||
|
@ -18,27 +18,27 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- id: skip_lang_check
|
- id: skip_lang_check
|
||||||
name: Check if the changes are about squiggle-lang src files
|
name: Check if the changes are about squiggle-lang src files
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
with:
|
with:
|
||||||
paths: '["packages/squiggle-lang/**"]'
|
paths: '["packages/squiggle-lang/**"]'
|
||||||
- id: skip_components_check
|
- id: skip_components_check
|
||||||
name: Check if the changes are about components src files
|
name: Check if the changes are about components src files
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
with:
|
with:
|
||||||
paths: '["packages/components/**"]'
|
paths: '["packages/components/**"]'
|
||||||
- id: skip_website_check
|
- id: skip_website_check
|
||||||
name: Check if the changes are about website src files
|
name: Check if the changes are about website src files
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
with:
|
with:
|
||||||
paths: '["packages/website/**"]'
|
paths: '["packages/website/**"]'
|
||||||
- id: skip_vscodeext_check
|
- id: skip_vscodeext_check
|
||||||
name: Check if the changes are about vscode extension src files
|
name: Check if the changes are about vscode extension src files
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
with:
|
with:
|
||||||
paths: '["packages/vscode-ext/**"]'
|
paths: '["packages/vscode-ext/**"]'
|
||||||
- id: skip_cli_check
|
- id: skip_cli_check
|
||||||
name: Check if the changes are about cli src files
|
name: Check if the changes are about cli src files
|
||||||
uses: fkirc/skip-duplicate-actions@v4.0.0
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
with:
|
with:
|
||||||
paths: '["packages/cli/**"]'
|
paths: '["packages/cli/**"]'
|
||||||
|
|
||||||
|
@ -55,21 +55,22 @@ jobs:
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
command: manifest-pr
|
command: manifest-pr
|
||||||
path: packages/squiggle-lang
|
path: packages/squiggle-lang
|
||||||
bump-patch-for-minor-pre-major: true
|
# bump-patch-for-minor-pre-major: true
|
||||||
skip-github-release: true
|
skip-github-release: true
|
||||||
# - name: Publish: Checkout source
|
- name: Publish- Checkout source
|
||||||
# uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
# # these if statements ensure that a publication only occurs when
|
# these if statements ensure that a publication only occurs when
|
||||||
# # a new release is created:
|
# a new release is created:
|
||||||
# if: ${{ steps.release.outputs.release_created }}
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
# - name: Publish: Install dependencies
|
- name: Publish- Install dependencies
|
||||||
# run: yarn
|
run: yarn
|
||||||
# if: ${{ steps.release.outputs.release_created }}
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
# - name: Publish
|
- name: Publish
|
||||||
# run: cd packages/squiggle-lang && yarn publish
|
run: cd packages/squiggle-lang && yarn publish
|
||||||
# env:
|
env:
|
||||||
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||||
# if: ${{ steps.release.outputs.release_created }}
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
|
|
||||||
relplz-components:
|
relplz-components:
|
||||||
name: for components
|
name: for components
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -82,20 +83,20 @@ jobs:
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
command: manifest-pr
|
command: manifest-pr
|
||||||
path: packages/components
|
path: packages/components
|
||||||
bump-patch-for-minor-pre-major: true
|
# bump-patch-for-minor-pre-major: true
|
||||||
skip-github-release: true
|
skip-github-release: true
|
||||||
# - name: Publish: Checkout source
|
- name: Publish- Checkout source
|
||||||
# uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
# # these if statements ensure that a publication only occurs when
|
# these if statements ensure that a publication only occurs when
|
||||||
# # a new release is created:
|
# a new release is created:
|
||||||
# if: ${{ steps.release.outputs.release_created }}
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
# - name: Publish: Install dependencies
|
- name: Publish- Install dependencies
|
||||||
# run: yarn
|
run: yarn
|
||||||
# if: ${{ steps.release.outputs.release_created }}
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
# - name: Publish
|
- name: Publish
|
||||||
# run: cd packages/components && yarn publish
|
run: cd packages/components && yarn publish
|
||||||
# env:
|
env:
|
||||||
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||||
relplz-website:
|
relplz-website:
|
||||||
name: for website
|
name: for website
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -108,7 +109,7 @@ jobs:
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
command: manifest-pr
|
command: manifest-pr
|
||||||
path: packages/website
|
path: packages/website
|
||||||
bump-patch-for-minor-pre-major: true
|
# bump-patch-for-minor-pre-major: true
|
||||||
skip-github-release: true
|
skip-github-release: true
|
||||||
relplz-vscodeext:
|
relplz-vscodeext:
|
||||||
name: for vscode-ext
|
name: for vscode-ext
|
||||||
|
@ -122,7 +123,7 @@ jobs:
|
||||||
token: ${{secrets.GITHUB_TOKEN}}
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
command: manifest-pr
|
command: manifest-pr
|
||||||
path: packages/vscode-ext
|
path: packages/vscode-ext
|
||||||
bump-patch-for-minor-pre-major: true
|
# bump-patch-for-minor-pre-major: true
|
||||||
skip-github-release: true
|
skip-github-release: true
|
||||||
relplz-cl:
|
relplz-cl:
|
||||||
name: for cli
|
name: for cli
|
||||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -7,3 +7,9 @@ yarn-error.log
|
||||||
**/.sync.ffs_db
|
**/.sync.ffs_db
|
||||||
.direnv
|
.direnv
|
||||||
.log
|
.log
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
todo.txt
|
||||||
|
result
|
||||||
|
shell.nix
|
||||||
|
.turbo
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
.direnv
|
.direnv
|
||||||
*.bs.js
|
*.bs.js
|
||||||
*.gen.tsx
|
*.gen.tsx
|
||||||
packages/*/dist
|
|
||||||
packages/components/storybook-static
|
packages/components/storybook-static
|
||||||
node_modules
|
node_modules
|
||||||
packages/*/node_modules
|
packages/*/node_modules
|
||||||
packages/website/.docusaurus
|
packages/website/.docusaurus
|
||||||
packages/squiggle-lang/lib
|
packages/squiggle-lang/lib
|
||||||
packages/squiggle-lang/.nyc_output/
|
|
||||||
packages/squiggle-lang/coverage/
|
packages/squiggle-lang/coverage/
|
||||||
packages/squiggle-lang/.cache/
|
packages/squiggle-lang/.cache/
|
||||||
packages/website/build/
|
packages/website/build/
|
||||||
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
||||||
packages/vscode-ext/media/vendor/
|
packages/vscode-ext/media/vendor/
|
||||||
|
packages/squiggle-lang/.nyc_output/
|
||||||
|
packages/*/dist
|
||||||
|
result
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"packages/cli": "0.0.3",
|
"packages/cli": "0.0.3",
|
||||||
"packages/components": "0.3.1",
|
"packages/components": "0.4.1",
|
||||||
"packages/squiggle-lang": "0.3.0",
|
"packages/squiggle-lang": "0.4.1",
|
||||||
"packages/vscode-ext": "0.3.1",
|
"packages/vscode-ext": "0.4.1",
|
||||||
"packages/website": "0.3.0"
|
"packages/website": "0.0.0"
|
||||||
}
|
}
|
||||||
|
|
1
CHANGELOG.md
Normal file
1
CHANGELOG.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
See the [Changelog.mdx page](./packages/website/docs/Changelog.mdx) for the changelog.
|
|
@ -16,7 +16,7 @@ Squiggle is currently pre-alpha.
|
||||||
|
|
||||||
# Bug reports
|
# Bug reports
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
# Project structure
|
# Project structure
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ Squiggle is a **monorepo** with three **packages**.
|
||||||
|
|
||||||
# Deployment ops
|
# Deployment ops
|
||||||
|
|
||||||
We use netlify, and it should only concern Quinn, Sam, and Ozzie.
|
We use Vercel, and it should only concern Slava, Sam, and Ozzie.
|
||||||
|
|
||||||
# Development environment, building, testing, dev server
|
# 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`.
|
Please work against `develop` branch. **Do not** work against `master`.
|
||||||
|
|
||||||
- For rescript code: Quinn and Ozzie are reviewers
|
- For rescript code: Slava and Ozzie are reviewers
|
||||||
- For js or typescript code: Sam and Ozzie are reviewers
|
- For js or typescript code: Sam and Ozzie are reviewers
|
||||||
- For ops code (i.e. yaml, package.json): Quinn and Sam are reviewers
|
- For ops code (i.e. yaml, package.json): Slava 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.
|
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.
|
||||||
|
|
||||||
|
|
22
README.md
22
README.md
|
@ -21,10 +21,10 @@ _An estimation language_.
|
||||||
|
|
||||||
## Our deployments
|
## Our deployments
|
||||||
|
|
||||||
- **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 prod**: https://squiggle-language.com
|
||||||
- **website/docs staging**: https://develop--squiggle-documentation.netlify.app/
|
- **website/docs staging**: https://preview.squiggle-language.com
|
||||||
- **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 prod**: https://components.squiggle-language.com
|
||||||
- **components storybook staging**: https://develop--squiggle-components.netlify.app/
|
- **components storybook staging**: https://preview-components.squiggle-language.com
|
||||||
- **legacy (2020) playground**: https://playground.squiggle-language.com
|
- **legacy (2020) playground**: https://playground.squiggle-language.com
|
||||||
|
|
||||||
## Packages
|
## Packages
|
||||||
|
@ -51,7 +51,19 @@ For any project in the repo, begin by running `yarn` in the top level
|
||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
See `packages/*/README.md` to work with whatever project you're interested in.
|
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.
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|
79
flake.lock
Normal file
79
flake.lock
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gentype": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1661855866,
|
||||||
|
"narHash": "sha256-+q0OOTyaq8eOn9BOWdPOCtSDOISW4A59v3mq3JOZyug=",
|
||||||
|
"owner": "rescript-association",
|
||||||
|
"repo": "genType",
|
||||||
|
"rev": "6b5f164b4f6ced456019b7579a0ab7e0a86518ad",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rescript-association",
|
||||||
|
"repo": "genType",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1661617163,
|
||||||
|
"narHash": "sha256-NN9Ky47j8ohgPhA9JZyfkYIbbAo6RJkGz+7h8/exVpE=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "0ba2543f8c855d7be8e90ef6c8dc89c1617e8a08",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-22.05",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"gentype": "gentype",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
99
flake.nix
Normal file
99
flake.nix
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
description = "Squiggle packages";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "nixpkgs/nixos-22.05";
|
||||||
|
gentype = {
|
||||||
|
url = "github:rescript-association/genType";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, gentype, flake-utils }:
|
||||||
|
let
|
||||||
|
version = builtins.substring 0 8 self.lastModifiedDate;
|
||||||
|
overlays = [
|
||||||
|
(final: prev: {
|
||||||
|
# set the node version here
|
||||||
|
nodejs = prev.nodejs-18_x;
|
||||||
|
# The override is the only way to get it into mkYarnModules
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
commonFn = pkgs: {
|
||||||
|
buildInputs = with pkgs; [ nodejs yarn ];
|
||||||
|
prettier = with pkgs.nodePackages; [ prettier ];
|
||||||
|
which = [ pkgs.which ];
|
||||||
|
};
|
||||||
|
gentypeOutputFn = pkgs: gentype.outputs.packages.${pkgs.system}.default;
|
||||||
|
langFn = { pkgs, ... }:
|
||||||
|
# Probably doesn't work on i686-linux
|
||||||
|
import ./nix/squiggle-lang.nix {
|
||||||
|
inherit pkgs commonFn gentypeOutputFn;
|
||||||
|
};
|
||||||
|
componentsFn = { pkgs, ... }:
|
||||||
|
import ./nix/squiggle-components.nix { inherit pkgs commonFn langFn; };
|
||||||
|
websiteFn = { pkgs, ... }:
|
||||||
|
import ./nix/squiggle-website.nix {
|
||||||
|
inherit pkgs commonFn langFn componentsFn;
|
||||||
|
};
|
||||||
|
vscodeextFn = { pkgs, ... }:
|
||||||
|
import ./nix/squiggle-vscode.nix {
|
||||||
|
inherit pkgs commonFn langFn componentsFn;
|
||||||
|
};
|
||||||
|
cliFn = { pkgs, ... }:
|
||||||
|
import ./nix/squiggle-cli.nix {
|
||||||
|
inherit pkgs commonFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
# local machines
|
||||||
|
localFlakeOutputs = { pkgs, ... }:
|
||||||
|
let
|
||||||
|
lang = langFn pkgs;
|
||||||
|
components = componentsFn pkgs;
|
||||||
|
website = websiteFn pkgs;
|
||||||
|
vscodeext = vscodeextFn pkgs;
|
||||||
|
cli = cliFn pkgs;
|
||||||
|
in {
|
||||||
|
# validating
|
||||||
|
checks = flake-utils.lib.flattenTree {
|
||||||
|
lang-lint = lang.lint;
|
||||||
|
lang-test = lang.test;
|
||||||
|
components-lint = components.lint;
|
||||||
|
docusaurus-lint = website.lint;
|
||||||
|
cli-lint = cli.lint;
|
||||||
|
};
|
||||||
|
# building
|
||||||
|
packages = flake-utils.lib.flattenTree {
|
||||||
|
default = components.build;
|
||||||
|
lang = lang.build;
|
||||||
|
lang-bundle = lang.bundle;
|
||||||
|
lang-test = lang.test;
|
||||||
|
components = components.build;
|
||||||
|
components-bundle = components.bundle;
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
lang-lint = lang.lint;
|
||||||
|
components-lint = components.lint;
|
||||||
|
docusaurus-lint = website.lint;
|
||||||
|
vscode-lint = vscodeext.lint;
|
||||||
|
cli-lint = cli.lint;
|
||||||
|
};
|
||||||
|
|
||||||
|
# developing
|
||||||
|
devShells = let shellNix = import ./nix/shell.nix { inherit pkgs; };
|
||||||
|
in flake-utils.lib.flattenTree {
|
||||||
|
default = shellNix.all;
|
||||||
|
js = shellNix.just-js;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = overlays;
|
||||||
|
};
|
||||||
|
|
||||||
|
in localFlakeOutputs pkgs);
|
||||||
|
}
|
1
nix/README.md
Normal file
1
nix/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Visit `quantified-uncertainty.cachix.org` for information about how to add our binary cache to your local dev environment.
|
25
nix/shell.nix
Normal file
25
nix/shell.nix
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{ pkgs }:
|
||||||
|
with pkgs;
|
||||||
|
let
|
||||||
|
js = [ yarn nodejs nodePackages.ts-node ];
|
||||||
|
rust = [
|
||||||
|
wasm-pack
|
||||||
|
cargo
|
||||||
|
rustup
|
||||||
|
pkg-config
|
||||||
|
libressl
|
||||||
|
rustfmt
|
||||||
|
wasmtime
|
||||||
|
binaryen
|
||||||
|
wasm-bindgen-cli
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
all = mkShell {
|
||||||
|
name = "squiggle_yarn-wasm-devshell";
|
||||||
|
buildInputs = builtins.concatLists [ js rust [ nixfmt ] ];
|
||||||
|
};
|
||||||
|
just-js = mkShell {
|
||||||
|
name = "squiggle_yarn-devshell";
|
||||||
|
buildInputs = js ++ [ nixfmt ];
|
||||||
|
};
|
||||||
|
}
|
13
nix/squiggle-cli.nix
Normal file
13
nix/squiggle-cli.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ pkgs, commonFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-cli-lint";
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
src = ../packages/cli;
|
||||||
|
buildPhase = "prettier --check .";
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
}
|
75
nix/squiggle-components.nix
Normal file
75
nix/squiggle-components.nix
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
{ pkgs, commonFn, langFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
lang = langFn pkgs;
|
||||||
|
componentsPackageJson = let
|
||||||
|
raw = pkgs.lib.importJSON ../packages/components/package.json;
|
||||||
|
modified =
|
||||||
|
pkgs.lib.recursiveUpdate raw { dependencies.react-dom = "^18.2.0"; };
|
||||||
|
packageJsonString = builtins.toJSON modified;
|
||||||
|
in pkgs.writeText "packages/components/patched-package.json"
|
||||||
|
packageJsonString;
|
||||||
|
yarn-source = pkgs.mkYarnPackage {
|
||||||
|
name = "squiggle-components_yarnsource";
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
src = ../packages/components;
|
||||||
|
packageJSON = componentsPackageJson;
|
||||||
|
yarnLock = ../yarn.lock;
|
||||||
|
packageResolutions."@quri/squiggle-lang" = lang.build;
|
||||||
|
};
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-components-lint";
|
||||||
|
src = ../packages/components;
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
buildPhase = "yarn lint";
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
build = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-components-build";
|
||||||
|
src = yarn-source + "/libexec/@quri/squiggle-components";
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
cp -r node_modules/@quri/squiggle-lang deps/@quri
|
||||||
|
pushd deps/@quri/squiggle-components
|
||||||
|
|
||||||
|
yarn --offline build:cjs
|
||||||
|
yarn --offline build:css
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
|
||||||
|
# annoying hack because permissions on transitive dependencies later on
|
||||||
|
mv deps/@quri/squiggle-components/node_modules deps/@quri/squiggle-components/NODE_MODULES
|
||||||
|
mv node_modules deps/@quri/squiggle-components
|
||||||
|
|
||||||
|
# patching .gitignore so flake keeps build artefacts
|
||||||
|
sed -i /dist/d deps/@quri/squiggle-components/.gitignore
|
||||||
|
cp -r deps/@quri/squiggle-components/. $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
bundle = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-components-bundle";
|
||||||
|
src = yarn-source + "/libexec/@quri/squiggle-components";
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
cp -r node_modules/@quri/squiggle-lang deps/@quri
|
||||||
|
pushd deps/@quri/squiggle-components
|
||||||
|
|
||||||
|
yarn --offline bundle
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
|
||||||
|
# annoying hack because permissions on transitive dependencies later on
|
||||||
|
mv deps/@quri/squiggle-components/node_modules deps/@quri/squiggle-components/NODE_MODULES
|
||||||
|
mv node_modules deps/@quri/squiggle-components
|
||||||
|
|
||||||
|
# patching .gitignore so flake keeps build artefacts
|
||||||
|
sed -i /dist/d deps/@quri/squiggle-components/.gitignore
|
||||||
|
cp -r deps/@quri/squiggle-components/. $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
116
nix/squiggle-lang.nix
Normal file
116
nix/squiggle-lang.nix
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
{ pkgs, commonFn, gentypeOutputFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
langPackageJson = let
|
||||||
|
raw = pkgs.lib.importJSON ../packages/squiggle-lang/package.json;
|
||||||
|
modified = pkgs.lib.recursiveUpdate raw {
|
||||||
|
devDependencies."@types/lodash" = "^4.14.167";
|
||||||
|
};
|
||||||
|
packageJsonString = builtins.toJSON modified;
|
||||||
|
in pkgs.writeText "packages/squiggle-lang/patched-package.json"
|
||||||
|
packageJsonString;
|
||||||
|
yarn-source = pkgs.mkYarnPackage {
|
||||||
|
name = "squiggle-lang_yarnsource";
|
||||||
|
src = ../packages/squiggle-lang;
|
||||||
|
packageJSON = langPackageJson;
|
||||||
|
yarnLock = ../yarn.lock;
|
||||||
|
pkgConfig = {
|
||||||
|
rescript = {
|
||||||
|
buildInputs = common.which
|
||||||
|
++ (if pkgs.system != "i686-linux" then [ pkgs.gcc_multi ] else [ ]);
|
||||||
|
postInstall = ''
|
||||||
|
echo "PATCHELF'ING RESCRIPT EXECUTABLES (INCL NINJA)"
|
||||||
|
# Patching interpreter for linux/*.exe's
|
||||||
|
THE_LD=$(patchelf --print-interpreter $(which mkdir))
|
||||||
|
patchelf --set-interpreter $THE_LD linux/*.exe && echo "- patched interpreter for linux/*.exe's"
|
||||||
|
|
||||||
|
# Replacing needed shared library for linux/ninja.exe
|
||||||
|
THE_SO=$(find /nix/store/*/lib64 -name libstdc++.so.6 | head -n 1)
|
||||||
|
patchelf --replace-needed libstdc++.so.6 $THE_SO linux/ninja.exe && echo "- replaced needed for linux/ninja.exe"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
gentype = {
|
||||||
|
postInstall = ''
|
||||||
|
mv gentype.exe ELFLESS-gentype.exe
|
||||||
|
cp ${gentypeOutputFn pkgs}/src/GenType.exe gentype.exe
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-lang-lint";
|
||||||
|
src = yarn-source + "/libexec/@quri/squiggle-lang/deps/@quri/squiggle-lang";
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
buildPhase = ''
|
||||||
|
yarn lint:prettier
|
||||||
|
yarn lint:rescript
|
||||||
|
'';
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
build = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-lang-build";
|
||||||
|
# `peggy` is in the `node_modules` that's adjacent to `deps`.
|
||||||
|
src = yarn-source + "/libexec/@quri/squiggle-lang";
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
# so that the path to ppx doesn't need to be patched.
|
||||||
|
mv node_modules deps
|
||||||
|
|
||||||
|
pushd deps/@quri/squiggle-lang
|
||||||
|
yarn --offline build:peggy
|
||||||
|
yarn --offline build:rescript
|
||||||
|
yarn --offline build:typescript
|
||||||
|
|
||||||
|
# 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
|
||||||
|
sed -i /\*.gen.js/d GITIGNORE
|
||||||
|
sed -i /helpers.js/d GITIGNORE
|
||||||
|
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
# mkdir -p $out/node_modules
|
||||||
|
mv deps/@quri/squiggle-lang/GITIGNORE deps/@quri/squiggle-lang/.gitignore
|
||||||
|
|
||||||
|
# annoying hack because permissions on transitive dependencies later on
|
||||||
|
mv deps/@quri/squiggle-lang/node_modules deps/@quri/squiggle-lang/NODE_MODULES
|
||||||
|
mv deps/node_modules deps/@quri/squiggle-lang
|
||||||
|
|
||||||
|
# the proper install phase
|
||||||
|
cp -r deps/@quri/squiggle-lang/. $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
test = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-lang-test";
|
||||||
|
src = build;
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
yarn --offline test
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r . $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
bundle = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-lang-bundle";
|
||||||
|
src = test;
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
yarn --offline bundle
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r dist $out
|
||||||
|
cp *.json $out/dist
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
24
nix/squiggle-vscode.nix
Normal file
24
nix/squiggle-vscode.nix
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{ pkgs, commonFn, langFn, componentsFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
lang = langFn pkgs;
|
||||||
|
components = componentsFn pkgs;
|
||||||
|
|
||||||
|
yarn-source = pkgs.mkYarnPackage {
|
||||||
|
name = "squiggle-vscodeext_yarnsource";
|
||||||
|
src = ../packages/vscode-ext;
|
||||||
|
packageJson = ../packages/vscode-ext/package.json;
|
||||||
|
yarnLock = ../yarn.lock;
|
||||||
|
packageResolutions."@quri/squiggle-lang" = lang.build;
|
||||||
|
packageResolutions."@quri/squiggle-components" = components.build;
|
||||||
|
};
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-vscode-lint";
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
src =
|
||||||
|
../packages/vscode-ext; # yarn-source + "/libexec/vscode-squiggle/deps/vscode-squiggle";
|
||||||
|
buildPhase = "prettier --check .";
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
}
|
30
nix/squiggle-website.nix
Normal file
30
nix/squiggle-website.nix
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{ pkgs, commonFn, langFn, componentsFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
lang = langFn pkgs;
|
||||||
|
components = componentsFn pkgs;
|
||||||
|
websitePackageJson = let
|
||||||
|
raw = pkgs.lib.importJSON ../packages/website/package.json;
|
||||||
|
modified = pkgs.lib.recursiveUpdate raw {
|
||||||
|
dependencies.postcss-import = "^14.1.0";
|
||||||
|
dependencies.tailwindcss = "^3.1.8";
|
||||||
|
};
|
||||||
|
packageJsonString = builtins.toJSON modified;
|
||||||
|
in pkgs.writeText "packages/website/patched-package.json" packageJsonString;
|
||||||
|
yarn-source = pkgs.mkYarnPackage {
|
||||||
|
name = "squiggle-website_yarnsource";
|
||||||
|
src = ../packages/website;
|
||||||
|
packageJSON = websitePackageJson;
|
||||||
|
yarnLock = ../yarn.lock;
|
||||||
|
packageResolutions."@quri/squiggle-lang" = lang.build;
|
||||||
|
packageResolutions."@quri/squiggle-components" = components.build;
|
||||||
|
};
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-website-lint";
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
src = ../packages/website;
|
||||||
|
buildPhase = "yarn lint";
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
}
|
8
nixos.sh
8
nixos.sh
|
@ -5,14 +5,14 @@
|
||||||
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
|
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
fhsShellName="squiggle-development"
|
fhsShellName="squiggle-fhs-development"
|
||||||
fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn]; runScript = \"yarn\"; }).env"
|
fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn pkgs.glibc]; runScript = \"yarn\"; }).env"
|
||||||
nix-shell - <<<"$fhsShellDotNix"
|
nix-shell - <<<"$fhsShellDotNix"
|
||||||
|
|
||||||
theLd=$(patchelf --print-interpreter $(which mkdir))
|
theLd=$(patchelf --print-interpreter $(which mkdir))
|
||||||
patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe
|
patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe
|
||||||
patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.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/ppx
|
||||||
patchelf --set-interpreter $theLd ./node_moduels/bisect_ppx/bisect-ppx-report
|
patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/bisect-ppx-report
|
||||||
theSo=$(find /nix/store/*$fhsShellName*/lib64 -name libstdc++.so.6 | grep $fhsShellName | head -n 1)
|
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
|
patchelf --replace-needed libstdc++.so.6 $theSo ./node_modules/rescript/linux/ninja.exe
|
||||||
|
|
|
@ -2,12 +2,11 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "squiggle",
|
"name": "squiggle",
|
||||||
"scripts": {
|
"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": {
|
"devDependencies": {
|
||||||
"prettier": "^2.7.1"
|
"prettier": "^2.7.1",
|
||||||
|
"turbo": "^1.5.5"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -20,3 +20,30 @@ Runs compilation in the current directory and all of its subdirectories.
|
||||||
### `npx squiggle-cli-experimental watch`
|
### `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.
|
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,13 +7,15 @@
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ."
|
"start": "node .",
|
||||||
|
"lint": "prettier --check .",
|
||||||
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.0.1",
|
"chalk": "^5.1.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"commander": "^9.4.0",
|
"commander": "^9.4.1",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"glob": "^8.0.3",
|
"glob": "^8.0.3",
|
||||||
"indent-string": "^5.0.0"
|
"indent-string": "^5.0.0"
|
||||||
|
|
6
packages/components/jest.config.js
Normal file
6
packages/components/jest.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: "ts-jest",
|
||||||
|
setupFilesAfterEnv: ["<rootDir>/test/setup.js"],
|
||||||
|
testEnvironment: "jsdom",
|
||||||
|
};
|
|
@ -1,8 +0,0 @@
|
||||||
[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,64 +1,73 @@
|
||||||
{
|
{
|
||||||
"name": "@quri/squiggle-components",
|
"name": "@quri/squiggle-components",
|
||||||
"version": "0.3.1",
|
"version": "0.5.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react-dom": "^1.0.0",
|
"@floating-ui/react-dom": "^1.0.0",
|
||||||
"@floating-ui/react-dom-interactions": "^0.9.2",
|
"@floating-ui/react-dom-interactions": "^0.10.1",
|
||||||
"@headlessui/react": "^1.6.6",
|
"@headlessui/react": "^1.7.3",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@hookform/resolvers": "^2.9.7",
|
"@hookform/resolvers": "^2.9.8",
|
||||||
"@quri/squiggle-lang": "^0.3.0",
|
"@quri/squiggle-lang": "^0.5.0",
|
||||||
"@react-hook/size": "^2.1.2",
|
"@react-hook/size": "^2.1.2",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"framer-motion": "^7.2.0",
|
"framer-motion": "^7.5.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-ace": "^10.1.0",
|
"react-ace": "^10.1.0",
|
||||||
"react-hook-form": "^7.34.2",
|
"react-hook-form": "^7.37.0",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"react-vega": "^7.6.0",
|
"react-vega": "^7.6.0",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"vega": "^5.22.1",
|
"vega": "^5.22.1",
|
||||||
"vega-embed": "^6.21.0",
|
"vega-embed": "^6.21.0",
|
||||||
"vega-lite": "^5.5.0",
|
"vega-lite": "^5.5.0",
|
||||||
"vscode-uri": "^3.0.3",
|
"vscode-uri": "^3.0.6",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
|
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
|
||||||
"@storybook/addon-actions": "^6.5.9",
|
"@storybook/addon-actions": "^6.5.12",
|
||||||
"@storybook/addon-essentials": "^6.5.10",
|
"@storybook/addon-essentials": "^6.5.12",
|
||||||
"@storybook/addon-links": "^6.5.10",
|
"@storybook/addon-links": "^6.5.12",
|
||||||
"@storybook/builder-webpack5": "^6.5.10",
|
"@storybook/builder-webpack5": "^6.5.12",
|
||||||
"@storybook/manager-webpack5": "^6.5.10",
|
"@storybook/manager-webpack5": "^6.5.12",
|
||||||
"@storybook/node-logger": "^6.5.9",
|
"@storybook/node-logger": "^6.5.9",
|
||||||
"@storybook/preset-create-react-app": "^4.1.2",
|
"@storybook/preset-create-react-app": "^4.1.2",
|
||||||
"@storybook/react": "^6.5.10",
|
"@storybook/react": "^6.5.12",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@types/jest": "^27.5.0",
|
"@types/jest": "^27.5.0",
|
||||||
"@types/lodash": "^4.14.184",
|
"@types/lodash": "^4.14.186",
|
||||||
"@types/node": "^18.7.9",
|
"@types/node": "^18.8.3",
|
||||||
"@types/react": "^18.0.9",
|
"@types/react": "^18.0.21",
|
||||||
"@types/styled-components": "^5.1.26",
|
"@types/styled-components": "^5.1.26",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
|
"canvas": "^2.10.1",
|
||||||
"cross-env": "^7.0.3",
|
"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",
|
"mini-css-extract-plugin": "^2.6.1",
|
||||||
"postcss-cli": "^10.0.0",
|
"postcss-cli": "^10.0.0",
|
||||||
"postcss-import": "^14.1.0",
|
"postcss-import": "^15.0.0",
|
||||||
"postcss-loader": "^7.0.1",
|
"postcss-loader": "^7.0.1",
|
||||||
|
"postcss-nesting": "^10.2.0",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.1.8",
|
||||||
"ts-loader": "^9.3.0",
|
"ts-jest": "^29.0.3",
|
||||||
|
"ts-loader": "^9.4.1",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^4.8.4",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^3.0.3",
|
||||||
"webpack": "^5.74.0",
|
"webpack": "^5.74.0",
|
||||||
"webpack-cli": "^4.10.0",
|
"webpack-cli": "^4.10.0",
|
||||||
"webpack-dev-server": "^4.10.0"
|
"webpack-dev-server": "^4.11.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17 || ^18",
|
"react": "^16.8.0 || ^17 || ^18",
|
||||||
|
@ -66,7 +75,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
||||||
"build:cjs": "tsc -b",
|
"build:cjs": "rm -rf dist/src && rm -f dist/tsconfig.tsbuildinfo && tsc -b",
|
||||||
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
|
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
|
||||||
"build:storybook": "build-storybook -s public",
|
"build:storybook": "build-storybook -s public",
|
||||||
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
|
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
|
||||||
|
@ -74,7 +83,10 @@
|
||||||
"all": "yarn bundle && yarn build",
|
"all": "yarn bundle && yarn build",
|
||||||
"lint": "prettier --check .",
|
"lint": "prettier --check .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"prepack": "yarn bundle && tsc -b"
|
"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"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const Alert: React.FC<{
|
||||||
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
|
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<div className="ml-3">
|
<div className="ml-3 grow">
|
||||||
<header className={clsx("text-sm font-medium", headingColor)}>
|
<header className={clsx("text-sm font-medium", headingColor)}>
|
||||||
{heading}
|
{heading}
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -5,6 +5,8 @@ import AceEditor from "react-ace";
|
||||||
import "ace-builds/src-noconflict/mode-golang";
|
import "ace-builds/src-noconflict/mode-golang";
|
||||||
import "ace-builds/src-noconflict/theme-github";
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
|
|
||||||
|
import { SqLocation } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
interface CodeEditorProps {
|
interface CodeEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
|
@ -13,15 +15,17 @@ interface CodeEditorProps {
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
showGutter?: boolean;
|
showGutter?: boolean;
|
||||||
|
errorLocations?: SqLocation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeEditor: FC<CodeEditorProps> = ({
|
export const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
height,
|
||||||
oneLine = false,
|
oneLine = false,
|
||||||
showGutter = false,
|
showGutter = false,
|
||||||
height,
|
errorLocations = [],
|
||||||
}) => {
|
}) => {
|
||||||
const lineCount = value.split("\n").length;
|
const lineCount = value.split("\n").length;
|
||||||
const id = useMemo(() => _.uniqueId(), []);
|
const id = useMemo(() => _.uniqueId(), []);
|
||||||
|
@ -30,8 +34,11 @@ export const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
const onSubmitRef = useRef<typeof onSubmit | null>(null);
|
const onSubmitRef = useRef<typeof onSubmit | null>(null);
|
||||||
onSubmitRef.current = onSubmit;
|
onSubmitRef.current = onSubmit;
|
||||||
|
|
||||||
|
const editorEl = useRef<AceEditor | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AceEditor
|
<AceEditor
|
||||||
|
ref={editorEl}
|
||||||
value={value}
|
value={value}
|
||||||
mode="golang"
|
mode="golang"
|
||||||
theme="github"
|
theme="github"
|
||||||
|
@ -59,6 +66,14 @@ export const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
exec: () => onSubmitRef.current?.(),
|
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",
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
Distribution,
|
SqDistribution,
|
||||||
result,
|
result,
|
||||||
distributionError,
|
SqDistributionError,
|
||||||
distributionErrorToString,
|
|
||||||
squiggleExpression,
|
|
||||||
resultMap,
|
resultMap,
|
||||||
|
SqRecord,
|
||||||
|
environment,
|
||||||
|
SqDistributionTag,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { Vega } from "react-vega";
|
import { Vega } from "react-vega";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
@ -28,17 +29,17 @@ export type DistributionPlottingSettings = {
|
||||||
|
|
||||||
export type DistributionChartProps = {
|
export type DistributionChartProps = {
|
||||||
plot: Plot;
|
plot: Plot;
|
||||||
|
environment: environment;
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
xAxisType?: "number" | "dateTime";
|
||||||
} & DistributionPlottingSettings;
|
} & DistributionPlottingSettings;
|
||||||
|
|
||||||
export function defaultPlot(distribution: Distribution): Plot {
|
export function defaultPlot(distribution: SqDistribution): Plot {
|
||||||
return { distributions: [{ name: "default", distribution }] };
|
return { distributions: [{ name: "default", distribution }] };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makePlot(record: {
|
export function makePlot(record: SqRecord): Plot | void {
|
||||||
[key: string]: squiggleExpression;
|
|
||||||
}): Plot | void {
|
|
||||||
const plotResult = parsePlot(record);
|
const plotResult = parsePlot(record);
|
||||||
if (plotResult.tag === "Ok") {
|
if (plotResult.tag === "Ok") {
|
||||||
return plotResult.value;
|
return plotResult.value;
|
||||||
|
@ -46,38 +47,68 @@ export function makePlot(record: {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
const { plot, height, showSummary, width, logX, actions = false } = props;
|
const {
|
||||||
|
plot,
|
||||||
|
environment,
|
||||||
|
height,
|
||||||
|
showSummary,
|
||||||
|
width,
|
||||||
|
logX,
|
||||||
|
actions = false,
|
||||||
|
} = props;
|
||||||
const [sized] = useSize((size) => {
|
const [sized] = useSize((size) => {
|
||||||
let shapes = flattenResult(
|
const shapes = flattenResult(
|
||||||
plot.distributions.map((x) =>
|
plot.distributions.map((x) =>
|
||||||
resultMap(x.distribution.pointSet(), (shape) => ({
|
resultMap(x.distribution.pointSet(environment), (pointSet) => ({
|
||||||
name: x.name,
|
name: x.name,
|
||||||
// color: x.color, // not supported yet
|
// color: x.color, // not supported yet
|
||||||
continuous: shape.continuous,
|
...pointSet.asShape(),
|
||||||
discrete: shape.discrete,
|
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (shapes.tag === "Error") {
|
if (shapes.tag === "Error") {
|
||||||
return (
|
return (
|
||||||
<ErrorAlert heading="Distribution Error">
|
<ErrorAlert heading="Distribution Error">
|
||||||
{distributionErrorToString(shapes.value)}
|
{shapes.value.toString()}
|
||||||
</ErrorAlert>
|
</ErrorAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const spec = buildVegaSpec(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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let widthProp = width ? width : size.width;
|
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;
|
||||||
if (widthProp < 20) {
|
if (widthProp < 20) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Width of Distribution is set to ${widthProp}, which is too small`
|
`Width of Distribution is set to ${widthProp}, which is too small`
|
||||||
);
|
);
|
||||||
widthProp = 20;
|
widthProp = 20;
|
||||||
}
|
}
|
||||||
const domain = shapes.value.flatMap((shape) =>
|
|
||||||
shape.discrete.concat(shape.continuous)
|
const vegaData = { data: shapes.value, samples };
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: widthProp }}>
|
<div style={{ width: widthProp }}>
|
||||||
|
@ -88,7 +119,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
) : (
|
) : (
|
||||||
<Vega
|
<Vega
|
||||||
spec={spec}
|
spec={spec}
|
||||||
data={{ data: shapes.value, domain }}
|
data={vegaData}
|
||||||
width={widthProp - 10}
|
width={widthProp - 10}
|
||||||
height={height}
|
height={height}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
|
@ -96,7 +127,10 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
{showSummary && plot.distributions.length === 1 && (
|
{showSummary && plot.distributions.length === 1 && (
|
||||||
<SummaryTable distribution={plot.distributions[0].distribution} />
|
<SummaryTable
|
||||||
|
distribution={plot.distributions[0].distribution}
|
||||||
|
environment={environment}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,32 +154,36 @@ const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
);
|
);
|
||||||
|
|
||||||
type SummaryTableProps = {
|
type SummaryTableProps = {
|
||||||
distribution: Distribution;
|
distribution: SqDistribution;
|
||||||
|
environment: environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => {
|
const SummaryTable: React.FC<SummaryTableProps> = ({
|
||||||
const mean = distribution.mean();
|
distribution,
|
||||||
const stdev = distribution.stdev();
|
environment,
|
||||||
const p5 = distribution.inv(0.05);
|
}) => {
|
||||||
const p10 = distribution.inv(0.1);
|
const mean = distribution.mean(environment);
|
||||||
const p25 = distribution.inv(0.25);
|
const stdev = distribution.stdev(environment);
|
||||||
const p50 = distribution.inv(0.5);
|
const p5 = distribution.inv(environment, 0.05);
|
||||||
const p75 = distribution.inv(0.75);
|
const p10 = distribution.inv(environment, 0.1);
|
||||||
const p90 = distribution.inv(0.9);
|
const p25 = distribution.inv(environment, 0.25);
|
||||||
const p95 = distribution.inv(0.95);
|
const p50 = distribution.inv(environment, 0.5);
|
||||||
|
const p75 = distribution.inv(environment, 0.75);
|
||||||
|
const p90 = distribution.inv(environment, 0.9);
|
||||||
|
const p95 = distribution.inv(environment, 0.95);
|
||||||
|
|
||||||
const hasResult = (x: result<number, distributionError>): boolean =>
|
const hasResult = (x: result<number, SqDistributionError>): boolean =>
|
||||||
x.tag === "Ok";
|
x.tag === "Ok";
|
||||||
|
|
||||||
const unwrapResult = (
|
const unwrapResult = (
|
||||||
x: result<number, distributionError>
|
x: result<number, SqDistributionError>
|
||||||
): React.ReactNode => {
|
): React.ReactNode => {
|
||||||
if (x.tag === "Ok") {
|
if (x.tag === "Ok") {
|
||||||
return <NumberShower number={x.value} />;
|
return <NumberShower number={x.value} />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<ErrorAlert heading="Distribution Error">
|
<ErrorAlert heading="Distribution Error">
|
||||||
{distributionErrorToString(x.value)}
|
{x.value.toString()}
|
||||||
</ErrorAlert>
|
</ErrorAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
lambdaValue,
|
SqLambda,
|
||||||
environment,
|
environment,
|
||||||
runForeign,
|
SqValueTag,
|
||||||
errorValueToString,
|
SqError,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
||||||
import { FunctionChart1Number } from "./FunctionChart1Number";
|
import { FunctionChart1Number } from "./FunctionChart1Number";
|
||||||
import { DistributionPlottingSettings } from "./DistributionChart";
|
import { DistributionPlottingSettings } from "./DistributionChart";
|
||||||
import { ErrorAlert, MessageAlert } from "./Alert";
|
import { MessageAlert } from "./Alert";
|
||||||
|
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
||||||
|
|
||||||
export type FunctionChartSettings = {
|
export type FunctionChartSettings = {
|
||||||
start: number;
|
start: number;
|
||||||
|
@ -17,13 +18,32 @@ export type FunctionChartSettings = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FunctionChartProps {
|
interface FunctionChartProps {
|
||||||
fn: lambdaValue;
|
fn: SqLambda;
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
environment: environment;
|
environment: environment;
|
||||||
height: number;
|
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> = ({
|
export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
fn,
|
fn,
|
||||||
chartSettings,
|
chartSettings,
|
||||||
|
@ -31,15 +51,16 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
distributionPlotSettings,
|
distributionPlotSettings,
|
||||||
height,
|
height,
|
||||||
}) => {
|
}) => {
|
||||||
if (fn.parameters.length > 1) {
|
console.log(fn.parameters().length);
|
||||||
|
if (fn.parameters().length !== 1) {
|
||||||
return (
|
return (
|
||||||
<MessageAlert heading="Function Display Not Supported">
|
<MessageAlert heading="Function Display Not Supported">
|
||||||
Only functions with one parameter are displayed.
|
Only functions with one parameter are displayed.
|
||||||
</MessageAlert>
|
</MessageAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const result1 = runForeign(fn, [chartSettings.start], environment);
|
const result1 = fn.call([chartSettings.start]);
|
||||||
const result2 = runForeign(fn, [chartSettings.stop], environment);
|
const result2 = fn.call([chartSettings.stop]);
|
||||||
const getValidResult = () => {
|
const getValidResult = () => {
|
||||||
if (result1.tag === "Ok") {
|
if (result1.tag === "Ok") {
|
||||||
return result1;
|
return result1;
|
||||||
|
@ -52,15 +73,11 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
const validResult = getValidResult();
|
const validResult = getValidResult();
|
||||||
|
|
||||||
if (validResult.tag === "Error") {
|
if (validResult.tag === "Error") {
|
||||||
return (
|
return <FunctionCallErrorAlert error={validResult.value} />;
|
||||||
<ErrorAlert heading="Error">
|
|
||||||
{errorValueToString(validResult.value)}
|
|
||||||
</ErrorAlert>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (validResult.value.tag) {
|
switch (validResult.value.tag) {
|
||||||
case "distribution":
|
case SqValueTag.Distribution:
|
||||||
return (
|
return (
|
||||||
<FunctionChart1Dist
|
<FunctionChart1Dist
|
||||||
fn={fn}
|
fn={fn}
|
||||||
|
@ -70,7 +87,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case "number":
|
case SqValueTag.Number:
|
||||||
return (
|
return (
|
||||||
<FunctionChart1Number
|
<FunctionChart1Number
|
||||||
fn={fn}
|
fn={fn}
|
||||||
|
|
|
@ -2,14 +2,13 @@ import * as React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type { Spec } from "vega";
|
import type { Spec } from "vega";
|
||||||
import {
|
import {
|
||||||
Distribution,
|
SqDistribution,
|
||||||
result,
|
result,
|
||||||
lambdaValue,
|
SqLambda,
|
||||||
environment,
|
environment,
|
||||||
runForeign,
|
SqError,
|
||||||
squiggleExpression,
|
SqValue,
|
||||||
errorValue,
|
SqValueTag,
|
||||||
errorValueToString,
|
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { createClassFromSpec } from "react-vega";
|
import { createClassFromSpec } from "react-vega";
|
||||||
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
||||||
|
@ -46,7 +45,7 @@ export type FunctionChartSettings = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FunctionChart1DistProps {
|
interface FunctionChart1DistProps {
|
||||||
fn: lambdaValue;
|
fn: SqLambda;
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
environment: environment;
|
environment: environment;
|
||||||
|
@ -77,9 +76,17 @@ type errors = _.Dictionary<
|
||||||
}[]
|
}[]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type point = { x: number; value: result<Distribution, string> };
|
type point = { x: number; value: result<SqDistribution, string> };
|
||||||
|
|
||||||
let getPercentiles = ({ chartSettings, fn, environment }) => {
|
let getPercentiles = ({
|
||||||
|
chartSettings,
|
||||||
|
fn,
|
||||||
|
environment,
|
||||||
|
}: {
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
fn: SqLambda;
|
||||||
|
environment: environment;
|
||||||
|
}) => {
|
||||||
let chartPointsToRender = _rangeByCount(
|
let chartPointsToRender = _rangeByCount(
|
||||||
chartSettings.start,
|
chartSettings.start,
|
||||||
chartSettings.stop,
|
chartSettings.stop,
|
||||||
|
@ -87,9 +94,9 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||||
let result = runForeign(fn, [x], environment);
|
let result = fn.call([x]);
|
||||||
if (result.tag === "Ok") {
|
if (result.tag === "Ok") {
|
||||||
if (result.value.tag === "distribution") {
|
if (result.value.tag === SqValueTag.Distribution) {
|
||||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
return { x, value: { tag: "Ok", value: result.value.value } };
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
@ -104,13 +111,13 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
value: { tag: "Error", value: errorValueToString(result.value) },
|
value: { tag: "Error", value: result.value.toString() },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let initialPartition: [
|
let initialPartition: [
|
||||||
{ x: number; value: Distribution }[],
|
{ x: number; value: SqDistribution }[],
|
||||||
{ x: number; value: string }[]
|
{ x: number; value: string }[]
|
||||||
] = [[], []];
|
] = [[], []];
|
||||||
|
|
||||||
|
@ -126,26 +133,23 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
|
||||||
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
|
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
|
||||||
|
|
||||||
let percentiles: percentiles = functionImage.map(({ x, value }) => {
|
let percentiles: percentiles = functionImage.map(({ x, value }) => {
|
||||||
// We convert it to to a pointSet distribution first, so that in case its a sample set
|
const res = {
|
||||||
// distribution, it doesn't internally convert it to a pointSet distribution for every
|
|
||||||
// single inv() call.
|
|
||||||
let toPointSet: Distribution = unwrap(value.toPointSet());
|
|
||||||
return {
|
|
||||||
x: x,
|
x: x,
|
||||||
p1: unwrap(toPointSet.inv(0.01)),
|
p1: unwrap(value.inv(environment, 0.01)),
|
||||||
p5: unwrap(toPointSet.inv(0.05)),
|
p5: unwrap(value.inv(environment, 0.05)),
|
||||||
p10: unwrap(toPointSet.inv(0.1)),
|
p10: unwrap(value.inv(environment, 0.1)),
|
||||||
p20: unwrap(toPointSet.inv(0.2)),
|
p20: unwrap(value.inv(environment, 0.2)),
|
||||||
p30: unwrap(toPointSet.inv(0.3)),
|
p30: unwrap(value.inv(environment, 0.3)),
|
||||||
p40: unwrap(toPointSet.inv(0.4)),
|
p40: unwrap(value.inv(environment, 0.4)),
|
||||||
p50: unwrap(toPointSet.inv(0.5)),
|
p50: unwrap(value.inv(environment, 0.5)),
|
||||||
p60: unwrap(toPointSet.inv(0.6)),
|
p60: unwrap(value.inv(environment, 0.6)),
|
||||||
p70: unwrap(toPointSet.inv(0.7)),
|
p70: unwrap(value.inv(environment, 0.7)),
|
||||||
p80: unwrap(toPointSet.inv(0.8)),
|
p80: unwrap(value.inv(environment, 0.8)),
|
||||||
p90: unwrap(toPointSet.inv(0.9)),
|
p90: unwrap(value.inv(environment, 0.9)),
|
||||||
p95: unwrap(toPointSet.inv(0.95)),
|
p95: unwrap(value.inv(environment, 0.95)),
|
||||||
p99: unwrap(toPointSet.inv(0.99)),
|
p99: unwrap(value.inv(environment, 0.99)),
|
||||||
};
|
};
|
||||||
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { percentiles, errors: groupedErrors };
|
return { percentiles, errors: groupedErrors };
|
||||||
|
@ -168,19 +172,20 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
||||||
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
||||||
|
|
||||||
//TODO: This custom error handling is a bit hacky and should be improved.
|
//TODO: This custom error handling is a bit hacky and should be improved.
|
||||||
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
|
let mouseItem: result<SqValue, SqError> = !!mouseOverlay
|
||||||
? runForeign(fn, [mouseOverlay], environment)
|
? fn.call([mouseOverlay])
|
||||||
: {
|
: {
|
||||||
tag: "Error",
|
tag: "Error",
|
||||||
value: {
|
value: SqError.createOtherError(
|
||||||
tag: "RETodo",
|
"Hover x-coordinate returned NaN. Expected a number."
|
||||||
value: "Hover x-coordinate returned NaN. Expected a number.",
|
),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let showChart =
|
let showChart =
|
||||||
mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? (
|
mouseItem.tag === "Ok" &&
|
||||||
|
mouseItem.value.tag === SqValueTag.Distribution ? (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
plot={defaultPlot(mouseItem.value.value)}
|
plot={defaultPlot(mouseItem.value.value)}
|
||||||
|
environment={environment}
|
||||||
width={400}
|
width={400}
|
||||||
height={50}
|
height={50}
|
||||||
{...distributionPlotSettings}
|
{...distributionPlotSettings}
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type { Spec } from "vega";
|
import type { Spec } from "vega";
|
||||||
import {
|
import { result, SqLambda, environment, SqValueTag } from "@quri/squiggle-lang";
|
||||||
result,
|
|
||||||
lambdaValue,
|
|
||||||
environment,
|
|
||||||
runForeign,
|
|
||||||
errorValueToString,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { createClassFromSpec } from "react-vega";
|
import { createClassFromSpec } from "react-vega";
|
||||||
import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
|
import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
import { squiggleValueTag } from "@quri/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag";
|
||||||
|
|
||||||
let SquiggleLineChart = createClassFromSpec({
|
let SquiggleLineChart = createClassFromSpec({
|
||||||
spec: lineChartSpec as Spec,
|
spec: lineChartSpec as Spec,
|
||||||
|
@ -30,7 +25,7 @@ export type FunctionChartSettings = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FunctionChart1NumberProps {
|
interface FunctionChart1NumberProps {
|
||||||
fn: lambdaValue;
|
fn: SqLambda;
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
environment: environment;
|
environment: environment;
|
||||||
height: number;
|
height: number;
|
||||||
|
@ -38,7 +33,15 @@ interface FunctionChart1NumberProps {
|
||||||
|
|
||||||
type point = { x: number; value: result<number, string> };
|
type point = { x: number; value: result<number, string> };
|
||||||
|
|
||||||
let getFunctionImage = ({ chartSettings, fn, environment }) => {
|
let getFunctionImage = ({
|
||||||
|
chartSettings,
|
||||||
|
fn,
|
||||||
|
environment,
|
||||||
|
}: {
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
fn: SqLambda;
|
||||||
|
environment: environment;
|
||||||
|
}) => {
|
||||||
let chartPointsToRender = _rangeByCount(
|
let chartPointsToRender = _rangeByCount(
|
||||||
chartSettings.start,
|
chartSettings.start,
|
||||||
chartSettings.stop,
|
chartSettings.stop,
|
||||||
|
@ -46,9 +49,9 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||||
let result = runForeign(fn, [x], environment);
|
let result = fn.call([x]);
|
||||||
if (result.tag === "Ok") {
|
if (result.tag === "Ok") {
|
||||||
if (result.value.tag == "number") {
|
if (result.value.tag === SqValueTag.Number) {
|
||||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
return { x, value: { tag: "Ok", value: result.value.value } };
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
@ -62,7 +65,7 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => {
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
value: { tag: "Error", value: errorValueToString(result.value) },
|
value: { tag: "Error", value: result.value.toString() },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,25 +1,17 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import { SqValue, environment, SqProject } from "@quri/squiggle-lang";
|
||||||
squiggleExpression,
|
|
||||||
bindings,
|
|
||||||
environment,
|
|
||||||
jsImports,
|
|
||||||
defaultImports,
|
|
||||||
defaultBindings,
|
|
||||||
defaultEnvironment,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { useSquiggle } from "../lib/hooks";
|
import { useSquiggle } from "../lib/hooks";
|
||||||
import { SquiggleViewer } from "./SquiggleViewer";
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
|
import { JsImports } from "../lib/jsImports";
|
||||||
|
import { getValueToRender } from "../lib/utility";
|
||||||
|
|
||||||
export interface SquiggleChartProps {
|
export type SquiggleChartProps = {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
code?: string;
|
code: string;
|
||||||
/** Allows to re-run the code if code hasn't changed */
|
/** Allows to re-run the code if code hasn't changed */
|
||||||
executionId?: number;
|
executionId?: number;
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
sampleCount?: number;
|
sampleCount?: number;
|
||||||
/** The amount of points returned to draw the distribution */
|
|
||||||
environment?: environment;
|
|
||||||
/** If the result is a function, where the function domain starts */
|
/** If the result is a function, where the function domain starts */
|
||||||
diagramStart?: number;
|
diagramStart?: number;
|
||||||
/** If the result is a function, where the function domain ends */
|
/** If the result is a function, where the function domain ends */
|
||||||
|
@ -27,14 +19,12 @@ export interface SquiggleChartProps {
|
||||||
/** If the result is a function, the amount of stops sampled */
|
/** If the result is a function, the amount of stops sampled */
|
||||||
diagramCount?: number;
|
diagramCount?: number;
|
||||||
/** When the squiggle code gets reevaluated */
|
/** When the squiggle code gets reevaluated */
|
||||||
onChange?(expr: squiggleExpression | undefined): void;
|
onChange?(expr: SqValue | undefined, sourceName: string): void;
|
||||||
/** CSS width of the element */
|
/** CSS width of the element */
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
/** Bindings of previous variables declared */
|
|
||||||
bindings?: bindings;
|
|
||||||
/** JS imported parameters */
|
/** JS imported parameters */
|
||||||
jsImports?: jsImports;
|
jsImports?: JsImports;
|
||||||
/** Whether to show a summary of the distribution */
|
/** Whether to show a summary of the distribution */
|
||||||
showSummary?: boolean;
|
showSummary?: boolean;
|
||||||
/** Set the x scale to be logarithmic by deault */
|
/** Set the x scale to be logarithmic by deault */
|
||||||
|
@ -51,24 +41,36 @@ export interface SquiggleChartProps {
|
||||||
minX?: number;
|
minX?: number;
|
||||||
/** Specify the upper bound of the x scale */
|
/** Specify the upper bound of the x scale */
|
||||||
maxX?: number;
|
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 */
|
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
||||||
distributionChartActions?: boolean;
|
distributionChartActions?: boolean;
|
||||||
enableLocalSettings?: 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 defaultOnChange = () => {};
|
||||||
|
const defaultImports: JsImports = {};
|
||||||
|
const defaultContinues: string[] = [];
|
||||||
|
|
||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
|
||||||
({
|
const {
|
||||||
code = "",
|
|
||||||
executionId = 0,
|
|
||||||
environment,
|
|
||||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
|
||||||
height = 200,
|
|
||||||
bindings = defaultBindings,
|
|
||||||
jsImports = defaultImports,
|
|
||||||
showSummary = false,
|
showSummary = false,
|
||||||
width,
|
|
||||||
logX = false,
|
logX = false,
|
||||||
expY = false,
|
expY = false,
|
||||||
diagramStart = 0,
|
diagramStart = 0,
|
||||||
|
@ -79,44 +81,79 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
maxX,
|
maxX,
|
||||||
color,
|
color,
|
||||||
title,
|
title,
|
||||||
|
xAxisType = "number",
|
||||||
distributionChartActions,
|
distributionChartActions,
|
||||||
enableLocalSettings = false,
|
} = props;
|
||||||
}) => {
|
|
||||||
const result = useSquiggle({
|
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 {
|
||||||
|
code,
|
||||||
|
jsImports = defaultImports,
|
||||||
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
|
executionId = 0,
|
||||||
|
width,
|
||||||
|
height = 200,
|
||||||
|
enableLocalSettings = false,
|
||||||
|
continues = defaultContinues,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const p = React.useMemo(() => {
|
||||||
|
if (props.project) {
|
||||||
|
return props.project;
|
||||||
|
} else {
|
||||||
|
const p = SqProject.create();
|
||||||
|
if (props.environment) {
|
||||||
|
p.setEnvironment(props.environment);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}, [props.project, props.environment]);
|
||||||
|
|
||||||
|
const resultAndBindings = useSquiggle({
|
||||||
|
continues,
|
||||||
|
project: p,
|
||||||
code,
|
code,
|
||||||
bindings,
|
|
||||||
environment,
|
|
||||||
jsImports,
|
jsImports,
|
||||||
onChange,
|
onChange,
|
||||||
executionId,
|
executionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const distributionPlotSettings = {
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
showSummary,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
format: tickFormat,
|
|
||||||
minX,
|
|
||||||
maxX,
|
|
||||||
color,
|
|
||||||
title,
|
|
||||||
actions: distributionChartActions,
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartSettings = {
|
|
||||||
start: diagramStart,
|
|
||||||
stop: diagramStop,
|
|
||||||
count: diagramCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SquiggleViewer
|
<SquiggleViewer
|
||||||
result={result}
|
result={valueToRender}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
chartSettings={chartSettings}
|
chartSettings={chartSettings}
|
||||||
environment={environment ?? defaultEnvironment}
|
environment={p.getEnvironment()}
|
||||||
enableLocalSettings={enableLocalSettings}
|
enableLocalSettings={enableLocalSettings}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { CodeEditor } from "./CodeEditor";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import { environment, bindings, jsImports } from "@quri/squiggle-lang";
|
|
||||||
import { defaultImports, defaultBindings } from "@quri/squiggle-lang";
|
|
||||||
import { SquiggleContainer } from "./SquiggleContainer";
|
import { SquiggleContainer } from "./SquiggleContainer";
|
||||||
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart";
|
import {
|
||||||
import { useSquigglePartial, useMaybeControlledValue } from "../lib/hooks";
|
splitSquiggleChartSettings,
|
||||||
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
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";
|
||||||
|
|
||||||
const WrappedCodeEditor: React.FC<{
|
const WrappedCodeEditor: React.FC<{
|
||||||
code: string;
|
code: string;
|
||||||
setCode: (code: string) => void;
|
setCode: (code: string) => void;
|
||||||
}> = ({ code, setCode }) => (
|
errorLocations?: SqLocation[];
|
||||||
<div className="border border-grey-200 p-2 m-4">
|
}> = ({ code, setCode, errorLocations }) => (
|
||||||
|
<div className="border border-grey-200 p-2 m-4" data-testid="squiggle-editor">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={code}
|
value={code}
|
||||||
onChange={setCode}
|
onChange={setCode}
|
||||||
oneLine={true}
|
oneLine={true}
|
||||||
showGutter={false}
|
showGutter={false}
|
||||||
height={20}
|
height={20}
|
||||||
|
errorLocations={errorLocations}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -27,6 +33,9 @@ export type SquiggleEditorProps = SquiggleChartProps & {
|
||||||
onCodeChange?: (code: string) => void;
|
onCodeChange?: (code: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultOnChange = () => {};
|
||||||
|
const defaultImports: JsImports = {};
|
||||||
|
|
||||||
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
||||||
const [code, setCode] = useMaybeControlledValue({
|
const [code, setCode] = useMaybeControlledValue({
|
||||||
value: props.code,
|
value: props.code,
|
||||||
|
@ -34,59 +43,54 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
||||||
onChange: props.onCodeChange,
|
onChange: props.onCodeChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
let chartProps = { ...props, code };
|
const { distributionPlotSettings, chartSettings } =
|
||||||
return (
|
splitSquiggleChartSettings(props);
|
||||||
<SquiggleContainer>
|
|
||||||
<WrappedCodeEditor code={code} setCode={setCode} />
|
|
||||||
<SquiggleChart {...chartProps} />
|
|
||||||
</SquiggleContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface SquigglePartialProps {
|
const {
|
||||||
/** The text inside the input (controlled) */
|
|
||||||
code?: string;
|
|
||||||
/** The default text inside the input (unControlled) */
|
|
||||||
defaultCode?: string;
|
|
||||||
/** when the environment changes. Used again for notebook magic*/
|
|
||||||
onChange?(expr: bindings | undefined): void;
|
|
||||||
/** When the code changes */
|
|
||||||
onCodeChange?(code: string): void;
|
|
||||||
/** Previously declared variables */
|
|
||||||
bindings?: bindings;
|
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
|
||||||
environment?: environment;
|
|
||||||
/** Variables imported from js */
|
|
||||||
jsImports?: jsImports;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SquigglePartial: React.FC<SquigglePartialProps> = ({
|
|
||||||
code: controlledCode,
|
|
||||||
defaultCode = "",
|
|
||||||
onChange,
|
|
||||||
onCodeChange,
|
|
||||||
bindings = defaultBindings,
|
|
||||||
environment,
|
|
||||||
jsImports = defaultImports,
|
|
||||||
}: SquigglePartialProps) => {
|
|
||||||
const [code, setCode] = useMaybeControlledValue<string>({
|
|
||||||
value: controlledCode,
|
|
||||||
defaultValue: defaultCode,
|
|
||||||
onChange: onCodeChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = useSquigglePartial({
|
|
||||||
code,
|
|
||||||
bindings,
|
|
||||||
environment,
|
environment,
|
||||||
|
jsImports = defaultImports,
|
||||||
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
|
executionId = 0,
|
||||||
|
width,
|
||||||
|
height = 200,
|
||||||
|
enableLocalSettings = false,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const project = React.useMemo(() => {
|
||||||
|
const p = SqProject.create();
|
||||||
|
if (environment) {
|
||||||
|
p.setEnvironment(environment);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}, [environment]);
|
||||||
|
|
||||||
|
const resultAndBindings = useSquiggle({
|
||||||
|
code,
|
||||||
|
project,
|
||||||
jsImports,
|
jsImports,
|
||||||
onChange,
|
onChange,
|
||||||
|
executionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
|
const errorLocations = getErrorLocations(resultAndBindings.result);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SquiggleContainer>
|
<SquiggleContainer>
|
||||||
<WrappedCodeEditor code={code} setCode={setCode} />
|
<WrappedCodeEditor
|
||||||
{result.tag !== "Ok" ? <SquiggleErrorAlert error={result.value} /> : null}
|
code={code}
|
||||||
|
setCode={setCode}
|
||||||
|
errorLocations={errorLocations}
|
||||||
|
/>
|
||||||
|
<SquiggleViewer
|
||||||
|
result={valueToRender}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
|
chartSettings={chartSettings}
|
||||||
|
environment={environment ?? defaultEnvironment}
|
||||||
|
enableLocalSettings={enableLocalSettings}
|
||||||
|
/>
|
||||||
</SquiggleContainer>
|
</SquiggleContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { SquiggleEditor } from "./SquiggleEditor";
|
|
||||||
import type { SquiggleEditorProps } from "./SquiggleEditor";
|
|
||||||
import { runPartial, defaultBindings } from "@quri/squiggle-lang";
|
|
||||||
import type {
|
|
||||||
result,
|
|
||||||
errorValue,
|
|
||||||
bindings as bindingsType,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
|
|
||||||
function resultDefault(x: result<bindingsType, errorValue>): bindingsType {
|
|
||||||
switch (x.tag) {
|
|
||||||
case "Ok":
|
|
||||||
return x.value;
|
|
||||||
case "Error":
|
|
||||||
return defaultBindings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SquiggleEditorWithImportedBindingsProps = SquiggleEditorProps & {
|
|
||||||
bindingsImportUrl: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SquiggleEditorWithImportedBindings: React.FC<
|
|
||||||
SquiggleEditorWithImportedBindingsProps
|
|
||||||
> = (props) => {
|
|
||||||
const { bindingsImportUrl, ...editorProps } = props;
|
|
||||||
const [bindingsResult, setBindingsResult] = React.useState({
|
|
||||||
tag: "Ok",
|
|
||||||
value: defaultBindings,
|
|
||||||
} as result<bindingsType, errorValue>);
|
|
||||||
React.useEffect(() => {
|
|
||||||
async function retrieveBindings(fileName: string) {
|
|
||||||
let contents = await fetch(fileName).then((response) => {
|
|
||||||
return response.text();
|
|
||||||
});
|
|
||||||
setBindingsResult(
|
|
||||||
runPartial(
|
|
||||||
contents,
|
|
||||||
editorProps.bindings,
|
|
||||||
editorProps.environment,
|
|
||||||
editorProps.jsImports
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
retrieveBindings(bindingsImportUrl);
|
|
||||||
}, [bindingsImportUrl]);
|
|
||||||
const deliveredBindings = resultDefault(bindingsResult);
|
|
||||||
return (
|
|
||||||
<SquiggleEditor {...{ ...editorProps, bindings: deliveredBindings }} />
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,11 +1,44 @@
|
||||||
import { errorValue, errorValueToString } from "@quri/squiggle-lang";
|
import { SqError, SqFrame } from "@quri/squiggle-lang";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
error: errorValue;
|
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 }) => {
|
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
|
||||||
return <ErrorAlert heading="Error">{errorValueToString(error)}</ErrorAlert>;
|
return (
|
||||||
|
<ErrorAlert heading="Error">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>{error.toString()}</div>
|
||||||
|
<StackTrace error={error} />
|
||||||
|
</div>
|
||||||
|
</ErrorAlert>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,11 @@ import React, {
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { useMaybeControlledValue, useRunnerState } from "../lib/hooks";
|
import {
|
||||||
|
useMaybeControlledValue,
|
||||||
|
useRunnerState,
|
||||||
|
useSquiggle,
|
||||||
|
} from "../lib/hooks";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import {
|
import {
|
||||||
ChartSquareBarIcon,
|
ChartSquareBarIcon,
|
||||||
|
@ -24,9 +28,9 @@ import {
|
||||||
} from "@heroicons/react/solid";
|
} from "@heroicons/react/solid";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import { defaultBindings, environment } from "@quri/squiggle-lang";
|
import { environment, SqProject } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart";
|
import { SquiggleChartProps } from "./SquiggleChart";
|
||||||
import { CodeEditor } from "./CodeEditor";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import { JsonEditor } from "./JsonEditor";
|
import { JsonEditor } from "./JsonEditor";
|
||||||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||||
|
@ -39,6 +43,9 @@ import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
|
||||||
import { HeadedSection } from "./ui/HeadedSection";
|
import { HeadedSection } from "./ui/HeadedSection";
|
||||||
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||||
import { Button } from "./ui/Button";
|
import { Button } from "./ui/Button";
|
||||||
|
import { JsImports } from "../lib/jsImports";
|
||||||
|
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
||||||
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
|
|
||||||
type PlaygroundProps = SquiggleChartProps & {
|
type PlaygroundProps = SquiggleChartProps & {
|
||||||
/** The initial squiggle string to put in the playground */
|
/** The initial squiggle string to put in the playground */
|
||||||
|
@ -112,8 +119,8 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const InputVariablesSettings: React.FC<{
|
const InputVariablesSettings: React.FC<{
|
||||||
initialImports: any; // TODO - any json type
|
initialImports: JsImports;
|
||||||
setImports: (imports: any) => void;
|
setImports: (imports: JsImports) => void;
|
||||||
}> = ({ initialImports, setImports }) => {
|
}> = ({ initialImports, setImports }) => {
|
||||||
const [importString, setImportString] = useState(() =>
|
const [importString, setImportString] = useState(() =>
|
||||||
JSON.stringify(initialImports)
|
JSON.stringify(initialImports)
|
||||||
|
@ -122,7 +129,7 @@ const InputVariablesSettings: React.FC<{
|
||||||
|
|
||||||
const onChange = (value: string) => {
|
const onChange = (value: string) => {
|
||||||
setImportString(value);
|
setImportString(value);
|
||||||
let imports = {} as any;
|
let imports = {};
|
||||||
try {
|
try {
|
||||||
imports = JSON.parse(value);
|
imports = JSON.parse(value);
|
||||||
setImportsAreValid(true);
|
setImportsAreValid(true);
|
||||||
|
@ -175,7 +182,7 @@ const RunControls: React.FC<{
|
||||||
const CurrentPlayIcon = isRunning ? RefreshIcon : PlayIcon;
|
const CurrentPlayIcon = isRunning ? RefreshIcon : PlayIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex space-x-1 items-center">
|
<div className="flex space-x-1 items-center" data-testid="autorun-controls">
|
||||||
{autorunMode ? null : (
|
{autorunMode ? null : (
|
||||||
<button onClick={run}>
|
<button onClick={run}>
|
||||||
<CurrentPlayIcon
|
<CurrentPlayIcon
|
||||||
|
@ -231,7 +238,7 @@ export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
||||||
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
defaultCode = "",
|
defaultCode = "",
|
||||||
height = 500,
|
height = 500,
|
||||||
showSummary = false,
|
showSummary = true,
|
||||||
logX = false,
|
logX = false,
|
||||||
expY = false,
|
expY = false,
|
||||||
title,
|
title,
|
||||||
|
@ -251,7 +258,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
onChange: onCodeChange,
|
onChange: onCodeChange,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [imports, setImports] = useState({});
|
const [imports, setImports] = useState<JsImports>({});
|
||||||
|
|
||||||
const { register, control } = useForm({
|
const { register, control } = useForm({
|
||||||
resolver: yupResolver(schema),
|
resolver: yupResolver(schema),
|
||||||
|
@ -281,7 +288,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
onSettingsChange?.(vars);
|
onSettingsChange?.(vars);
|
||||||
}, [vars, onSettingsChange]);
|
}, [vars, onSettingsChange]);
|
||||||
|
|
||||||
const env: environment = useMemo(
|
const environment: environment = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
sampleCount: Number(vars.sampleCount),
|
sampleCount: Number(vars.sampleCount),
|
||||||
xyPointLength: Number(vars.xyPointLength),
|
xyPointLength: Number(vars.xyPointLength),
|
||||||
|
@ -298,27 +305,59 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
executionId,
|
executionId,
|
||||||
} = useRunnerState(code);
|
} = useRunnerState(code);
|
||||||
|
|
||||||
|
const project = React.useMemo(() => {
|
||||||
|
const p = SqProject.create();
|
||||||
|
if (environment) {
|
||||||
|
p.setEnvironment(environment);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}, [environment]);
|
||||||
|
|
||||||
|
const resultAndBindings = useSquiggle({
|
||||||
|
code: renderedCode,
|
||||||
|
project,
|
||||||
|
jsImports: imports,
|
||||||
|
executionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
|
|
||||||
const squiggleChart =
|
const squiggleChart =
|
||||||
renderedCode === "" ? null : (
|
renderedCode === "" ? null : (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{isRunning ? (
|
{isRunning ? (
|
||||||
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
||||||
) : null}
|
) : null}
|
||||||
<SquiggleChart
|
<SquiggleViewer
|
||||||
code={renderedCode}
|
result={valueToRender}
|
||||||
executionId={executionId}
|
environment={environment}
|
||||||
environment={env}
|
height={vars.chartHeight || 150}
|
||||||
{...vars}
|
distributionPlotSettings={{
|
||||||
bindings={defaultBindings}
|
showSummary: vars.showSummary ?? false,
|
||||||
jsImports={imports}
|
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,
|
||||||
|
}}
|
||||||
enableLocalSettings={true}
|
enableLocalSettings={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const errorLocations = getErrorLocations(resultAndBindings.result);
|
||||||
|
|
||||||
const firstTab = vars.showEditor ? (
|
const firstTab = vars.showEditor ? (
|
||||||
<div className="border border-slate-200">
|
<div className="border border-slate-200" data-testid="squiggle-editor">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
errorLocations={errorLocations}
|
||||||
value={code}
|
value={code}
|
||||||
onChange={setCode}
|
onChange={setCode}
|
||||||
onSubmit={run}
|
onSubmit={run}
|
||||||
|
@ -368,7 +407,9 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
>
|
>
|
||||||
{tabs}
|
{tabs}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
|
<div className="w-1/2 p-2 pl-4" data-testid="playground-result">
|
||||||
|
{squiggleChart}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import React from "react";
|
import React, { useContext } from "react";
|
||||||
import { squiggleExpression, declaration } from "@quri/squiggle-lang";
|
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
|
||||||
import { NumberShower } from "../NumberShower";
|
import { NumberShower } from "../NumberShower";
|
||||||
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
|
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
|
||||||
import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
|
import { FunctionChart } from "../FunctionChart";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { VariableBox } from "./VariableBox";
|
import { VariableBox } from "./VariableBox";
|
||||||
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
||||||
import { hasMassBelowZero } from "../../lib/distributionUtils";
|
import { hasMassBelowZero } from "../../lib/distributionUtils";
|
||||||
import { MergedItemSettings } from "./utils";
|
import { MergedItemSettings } from "./utils";
|
||||||
|
import { ViewerContext } from "./ViewerContext";
|
||||||
|
|
||||||
|
/*
|
||||||
|
// DISABLED FOR 0.4 branch, for now
|
||||||
function getRange<a>(x: declaration<a>) {
|
function getRange<a>(x: declaration<a>) {
|
||||||
const first = x.args[0];
|
const first = x.args[0];
|
||||||
switch (first.tag) {
|
switch (first.tag) {
|
||||||
|
@ -31,15 +34,21 @@ function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
||||||
count: 20,
|
count: 20,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
const VariableList: React.FC<{
|
const VariableList: React.FC<{
|
||||||
path: string[];
|
value: SqValue;
|
||||||
heading: string;
|
heading: string;
|
||||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||||
}> = ({ path, heading, children }) => (
|
}> = ({ value, heading, children }) => (
|
||||||
<VariableBox path={path} heading={heading}>
|
<VariableBox value={value} heading={heading}>
|
||||||
{(settings) => (
|
{(settings) => (
|
||||||
<div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}>
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"space-y-3",
|
||||||
|
value.location.path.items.length ? "pt-1 mt-1" : null
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children(settings)}
|
{children(settings)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -48,51 +57,44 @@ const VariableList: React.FC<{
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
/** The output of squiggle's run */
|
/** The output of squiggle's run */
|
||||||
expression: squiggleExpression;
|
value: SqValue;
|
||||||
/** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */
|
|
||||||
path: string[];
|
|
||||||
width?: number;
|
width?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExpressionViewer: React.FC<Props> = ({
|
export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
path,
|
const { getMergedSettings } = useContext(ViewerContext);
|
||||||
expression,
|
|
||||||
width,
|
switch (value.tag) {
|
||||||
}) => {
|
case SqValueTag.Number:
|
||||||
if (typeof expression !== "object") {
|
|
||||||
return (
|
|
||||||
<VariableList path={path} heading="Error">
|
|
||||||
{() => `Unknown expression: ${expression}`}
|
|
||||||
</VariableList>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
switch (expression.tag) {
|
|
||||||
case "number":
|
|
||||||
return (
|
return (
|
||||||
<VariableBox path={path} heading="Number">
|
<VariableBox value={value} heading="Number">
|
||||||
{() => (
|
{() => (
|
||||||
<div className="font-semibold text-slate-600">
|
<div className="font-semibold text-slate-600">
|
||||||
<NumberShower precision={3} number={expression.value} />
|
<NumberShower precision={3} number={value.value} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
case "distribution": {
|
case SqValueTag.Distribution: {
|
||||||
const distType = expression.value.type();
|
const distType = value.value.tag;
|
||||||
return (
|
return (
|
||||||
<VariableBox
|
<VariableBox
|
||||||
path={path}
|
value={value}
|
||||||
heading={`Distribution (${distType})\n${
|
heading={`Distribution (${distType})\n${
|
||||||
distType === "Symbolic" ? expression.value.toString() : ""
|
distType === SqDistributionTag.Symbolic
|
||||||
|
? value.value.toString()
|
||||||
|
: ""
|
||||||
}`}
|
}`}
|
||||||
renderSettingsMenu={({ onChange }) => {
|
renderSettingsMenu={({ onChange }) => {
|
||||||
const shape = expression.value.pointSet();
|
const shape = value.value.pointSet(
|
||||||
|
getMergedSettings(value.location).environment
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<ItemSettingsMenu
|
<ItemSettingsMenu
|
||||||
path={path}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disableLogX={
|
disableLogX={
|
||||||
shape.tag === "Ok" && hasMassBelowZero(shape.value)
|
shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape())
|
||||||
}
|
}
|
||||||
withFunctionSettings={false}
|
withFunctionSettings={false}
|
||||||
/>
|
/>
|
||||||
|
@ -102,7 +104,8 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
{(settings) => {
|
{(settings) => {
|
||||||
return (
|
return (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
plot={defaultPlot(expression.value)}
|
plot={defaultPlot(value.value)}
|
||||||
|
environment={settings.environment}
|
||||||
{...settings.distributionPlotSettings}
|
{...settings.distributionPlotSettings}
|
||||||
height={settings.height}
|
height={settings.height}
|
||||||
width={width}
|
width={width}
|
||||||
|
@ -112,77 +115,54 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "string":
|
case SqValueTag.String:
|
||||||
return (
|
return (
|
||||||
<VariableBox path={path} heading="String">
|
<VariableBox value={value} heading="String">
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<span className="text-slate-400">"</span>
|
<span className="text-slate-400">"</span>
|
||||||
<span className="text-slate-600 font-semibold font-mono">
|
<span className="text-slate-600 font-semibold font-mono">
|
||||||
{expression.value}
|
{value.value}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-400">"</span>
|
<span className="text-slate-400">"</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
case "boolean":
|
case SqValueTag.Bool:
|
||||||
return (
|
return (
|
||||||
<VariableBox path={path} heading="Boolean">
|
<VariableBox value={value} heading="Boolean">
|
||||||
{() => expression.value.toString()}
|
{() => value.value.toString()}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
case "symbol":
|
case SqValueTag.Date:
|
||||||
return (
|
return (
|
||||||
<VariableBox path={path} heading="Symbol">
|
<VariableBox value={value} heading="Date">
|
||||||
{() => (
|
{() => value.value.toDateString()}
|
||||||
<>
|
|
||||||
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
|
|
||||||
<span className="text-slate-600">{expression.value}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
case "call":
|
case SqValueTag.Void:
|
||||||
return (
|
return (
|
||||||
<VariableBox path={path} heading="Call">
|
<VariableBox value={value} heading="Void">
|
||||||
{() => expression.value}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "arraystring":
|
|
||||||
return (
|
|
||||||
<VariableBox path={path} heading="Array String">
|
|
||||||
{() => expression.value.map((r) => `"${r}"`).join(", ")}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "date":
|
|
||||||
return (
|
|
||||||
<VariableBox path={path} heading="Date">
|
|
||||||
{() => expression.value.toDateString()}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "void":
|
|
||||||
return (
|
|
||||||
<VariableBox path={path} heading="Void">
|
|
||||||
{() => "Void"}
|
{() => "Void"}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
case "timeDuration": {
|
case SqValueTag.TimeDuration: {
|
||||||
return (
|
return (
|
||||||
<VariableBox path={path} heading="Time Duration">
|
<VariableBox value={value} heading="Time Duration">
|
||||||
{() => <NumberShower precision={3} number={expression.value} />}
|
{() => <NumberShower precision={3} number={value.value} />}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "lambda":
|
case SqValueTag.Lambda:
|
||||||
return (
|
return (
|
||||||
<VariableBox
|
<VariableBox
|
||||||
path={path}
|
value={value}
|
||||||
heading="Function"
|
heading="Function"
|
||||||
renderSettingsMenu={({ onChange }) => {
|
renderSettingsMenu={({ onChange }) => {
|
||||||
return (
|
return (
|
||||||
<ItemSettingsMenu
|
<ItemSettingsMenu
|
||||||
path={path}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
withFunctionSettings={true}
|
withFunctionSettings={true}
|
||||||
/>
|
/>
|
||||||
|
@ -191,11 +171,11 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
>
|
>
|
||||||
{(settings) => (
|
{(settings) => (
|
||||||
<>
|
<>
|
||||||
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join(
|
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${value.value
|
||||||
","
|
.parameters()
|
||||||
)})`}</div>
|
.join(",")})`}</div>
|
||||||
<FunctionChart
|
<FunctionChart
|
||||||
fn={expression.value}
|
fn={value.value}
|
||||||
chartSettings={settings.chartSettings}
|
chartSettings={settings.chartSettings}
|
||||||
distributionPlotSettings={settings.distributionPlotSettings}
|
distributionPlotSettings={settings.distributionPlotSettings}
|
||||||
height={settings.height}
|
height={settings.height}
|
||||||
|
@ -208,71 +188,57 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
)}
|
)}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
case "lambdaDeclaration": {
|
case SqValueTag.Declaration: {
|
||||||
return (
|
return (
|
||||||
<VariableBox
|
<VariableBox
|
||||||
path={path}
|
value={value}
|
||||||
heading="Function Declaration"
|
heading="Function Declaration"
|
||||||
renderSettingsMenu={({ onChange }) => {
|
renderSettingsMenu={({ onChange }) => {
|
||||||
return (
|
return (
|
||||||
<ItemSettingsMenu
|
<ItemSettingsMenu
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
path={path}
|
value={value}
|
||||||
withFunctionSettings={true}
|
withFunctionSettings={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(settings) => (
|
{(settings) => (
|
||||||
<FunctionChart
|
<div>NOT IMPLEMENTED IN 0.4 YET</div>
|
||||||
fn={expression.value.fn}
|
// <FunctionChart
|
||||||
chartSettings={getChartSettings(expression.value)}
|
// fn={expression.value.fn}
|
||||||
distributionPlotSettings={settings.distributionPlotSettings}
|
// chartSettings={getChartSettings(expression.value)}
|
||||||
height={settings.height}
|
// distributionPlotSettings={settings.distributionPlotSettings}
|
||||||
environment={{
|
// height={settings.height}
|
||||||
sampleCount: settings.environment.sampleCount / 10,
|
// environment={{
|
||||||
xyPointLength: settings.environment.xyPointLength / 10,
|
// sampleCount: settings.environment.sampleCount / 10,
|
||||||
}}
|
// xyPointLength: settings.environment.xyPointLength / 10,
|
||||||
/>
|
// }}
|
||||||
|
// />
|
||||||
)}
|
)}
|
||||||
</VariableBox>
|
</VariableBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "module": {
|
case SqValueTag.Record:
|
||||||
return (
|
const plot = makePlot(value.value);
|
||||||
<VariableList path={path} heading="Module">
|
|
||||||
{(_) =>
|
|
||||||
Object.entries(expression.value)
|
|
||||||
.filter(([key, _]) => !key.match(/^(Math|System)\./))
|
|
||||||
.map(([key, r]) => (
|
|
||||||
<ExpressionViewer
|
|
||||||
key={key}
|
|
||||||
path={[...path, key]}
|
|
||||||
expression={r}
|
|
||||||
width={width !== undefined ? width - 20 : width}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</VariableList>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "record":
|
|
||||||
const plot = makePlot(expression.value);
|
|
||||||
if (plot) {
|
if (plot) {
|
||||||
return (
|
return (
|
||||||
<VariableBox
|
<VariableBox
|
||||||
path={path}
|
value={value}
|
||||||
heading={"Plot"}
|
heading="Plot"
|
||||||
renderSettingsMenu={({ onChange }) => {
|
renderSettingsMenu={({ onChange }) => {
|
||||||
let disableLogX = plot.distributions.some((x) => {
|
let disableLogX = plot.distributions.some((x) => {
|
||||||
let pointSet = x.distribution.pointSet();
|
let pointSet = x.distribution.pointSet(
|
||||||
|
getMergedSettings(value.location).environment
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
pointSet.tag === "Ok" && hasMassBelowZero(pointSet.value)
|
pointSet.tag === "Ok" &&
|
||||||
|
hasMassBelowZero(pointSet.value.asShape())
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<ItemSettingsMenu
|
<ItemSettingsMenu
|
||||||
path={path}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disableLogX={disableLogX}
|
disableLogX={disableLogX}
|
||||||
withFunctionSettings={false}
|
withFunctionSettings={false}
|
||||||
|
@ -284,6 +250,7 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
return (
|
return (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
plot={plot}
|
plot={plot}
|
||||||
|
environment={settings.environment}
|
||||||
{...settings.distributionPlotSettings}
|
{...settings.distributionPlotSettings}
|
||||||
height={settings.height}
|
height={settings.height}
|
||||||
width={width}
|
width={width}
|
||||||
|
@ -294,43 +261,45 @@ export const ExpressionViewer: React.FC<Props> = ({
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<VariableList path={path} heading="Record">
|
<VariableList value={value} heading="Record">
|
||||||
{(_) =>
|
{(_) =>
|
||||||
Object.entries(expression.value).map(([key, r]) => (
|
value.value
|
||||||
<ExpressionViewer
|
.entries()
|
||||||
key={key}
|
.map(([key, r]) => (
|
||||||
path={[...path, key]}
|
<ExpressionViewer
|
||||||
expression={r}
|
key={key}
|
||||||
width={width !== undefined ? width - 20 : width}
|
value={r}
|
||||||
/>
|
width={width !== undefined ? width - 20 : width}
|
||||||
))
|
/>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
</VariableList>
|
</VariableList>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "array":
|
case SqValueTag.Array:
|
||||||
return (
|
return (
|
||||||
<VariableList path={path} heading="Array">
|
<VariableList value={value} heading="Array">
|
||||||
{(_) =>
|
{(_) =>
|
||||||
expression.value.map((r, i) => (
|
value.value
|
||||||
<ExpressionViewer
|
.getValues()
|
||||||
key={i}
|
.map((r, i) => (
|
||||||
path={[...path, String(i)]}
|
<ExpressionViewer
|
||||||
expression={r}
|
key={i}
|
||||||
width={width !== undefined ? width - 20 : width}
|
value={r}
|
||||||
/>
|
width={width !== undefined ? width - 20 : width}
|
||||||
))
|
/>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
</VariableList>
|
</VariableList>
|
||||||
);
|
);
|
||||||
default: {
|
default: {
|
||||||
return (
|
return (
|
||||||
<VariableList path={path} heading="Error">
|
<VariableList value={value} heading="Error">
|
||||||
{() => (
|
{() => (
|
||||||
<div>
|
<div>
|
||||||
<span>No display for type: </span>{" "}
|
<span>No display for type: </span>{" "}
|
||||||
<span className="font-semibold text-slate-600">
|
<span className="font-semibold text-slate-600">
|
||||||
{expression.tag}
|
{(value as { tag: string }).tag}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -4,13 +4,14 @@ import { useForm } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { Modal } from "../ui/Modal";
|
import { Modal } from "../ui/Modal";
|
||||||
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
||||||
import { Path, pathAsString } from "./utils";
|
|
||||||
import { ViewerContext } from "./ViewerContext";
|
import { ViewerContext } from "./ViewerContext";
|
||||||
import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
|
import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
|
||||||
import { PlaygroundContext } from "../SquigglePlayground";
|
import { PlaygroundContext } from "../SquigglePlayground";
|
||||||
|
import { SqValue } from "@quri/squiggle-lang";
|
||||||
|
import { locationAsString } from "./utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
path: Path;
|
value: SqValue;
|
||||||
onChange: () => void;
|
onChange: () => void;
|
||||||
disableLogX?: boolean;
|
disableLogX?: boolean;
|
||||||
withFunctionSettings: boolean;
|
withFunctionSettings: boolean;
|
||||||
|
@ -19,7 +20,7 @@ type Props = {
|
||||||
const ItemSettingsModal: React.FC<
|
const ItemSettingsModal: React.FC<
|
||||||
Props & { close: () => void; resetScroll: () => void }
|
Props & { close: () => void; resetScroll: () => void }
|
||||||
> = ({
|
> = ({
|
||||||
path,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
disableLogX,
|
disableLogX,
|
||||||
withFunctionSettings,
|
withFunctionSettings,
|
||||||
|
@ -29,7 +30,7 @@ const ItemSettingsModal: React.FC<
|
||||||
const { setSettings, getSettings, getMergedSettings } =
|
const { setSettings, getSettings, getMergedSettings } =
|
||||||
useContext(ViewerContext);
|
useContext(ViewerContext);
|
||||||
|
|
||||||
const mergedSettings = getMergedSettings(path);
|
const mergedSettings = getMergedSettings(value.location);
|
||||||
|
|
||||||
const { register, watch } = useForm({
|
const { register, watch } = useForm({
|
||||||
resolver: yupResolver(viewSettingsSchema),
|
resolver: yupResolver(viewSettingsSchema),
|
||||||
|
@ -53,8 +54,8 @@ const ItemSettingsModal: React.FC<
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = watch((vars) => {
|
const subscription = watch((vars) => {
|
||||||
const settings = getSettings(path); // get the latest version
|
const settings = getSettings(value.location); // get the latest version
|
||||||
setSettings(path, {
|
setSettings(value.location, {
|
||||||
...settings,
|
...settings,
|
||||||
distributionPlotSettings: {
|
distributionPlotSettings: {
|
||||||
showSummary: vars.showSummary,
|
showSummary: vars.showSummary,
|
||||||
|
@ -75,7 +76,7 @@ const ItemSettingsModal: React.FC<
|
||||||
onChange();
|
onChange();
|
||||||
});
|
});
|
||||||
return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
}, [getSettings, setSettings, onChange, path, watch]);
|
}, [getSettings, setSettings, onChange, value.location, watch]);
|
||||||
|
|
||||||
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ const ItemSettingsModal: React.FC<
|
||||||
<Modal container={getLeftPanelElement()} close={close}>
|
<Modal container={getLeftPanelElement()} close={close}>
|
||||||
<Modal.Header>
|
<Modal.Header>
|
||||||
Chart settings
|
Chart settings
|
||||||
{path.length ? (
|
{value.location.path.items.length ? (
|
||||||
<>
|
<>
|
||||||
{" for "}
|
{" for "}
|
||||||
<span
|
<span
|
||||||
|
@ -91,7 +92,7 @@ const ItemSettingsModal: React.FC<
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={resetScroll}
|
onClick={resetScroll}
|
||||||
>
|
>
|
||||||
{pathAsString(path)}
|
{locationAsString(value.location)}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
@ -120,7 +121,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
||||||
if (!enableLocalSettings) {
|
if (!enableLocalSettings) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const settings = getSettings(props.path);
|
const settings = getSettings(props.value.location);
|
||||||
|
|
||||||
const resetScroll = () => {
|
const resetScroll = () => {
|
||||||
if (!ref.current) return;
|
if (!ref.current) return;
|
||||||
|
@ -139,7 +140,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
||||||
{settings.distributionPlotSettings || settings.chartSettings ? (
|
{settings.distributionPlotSettings || settings.chartSettings ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSettings(props.path, {
|
setSettings(props.value.location, {
|
||||||
...settings,
|
...settings,
|
||||||
distributionPlotSettings: undefined,
|
distributionPlotSettings: undefined,
|
||||||
chartSettings: undefined,
|
chartSettings: undefined,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SqValue } from "@quri/squiggle-lang";
|
||||||
import React, { useContext, useReducer } from "react";
|
import React, { useContext, useReducer } from "react";
|
||||||
import { Tooltip } from "../ui/Tooltip";
|
import { Tooltip } from "../ui/Tooltip";
|
||||||
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||||
|
@ -8,14 +9,14 @@ type SettingsMenuParams = {
|
||||||
};
|
};
|
||||||
|
|
||||||
type VariableBoxProps = {
|
type VariableBoxProps = {
|
||||||
path: string[];
|
value: SqValue;
|
||||||
heading: string;
|
heading: string;
|
||||||
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
|
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
|
||||||
children: (settings: MergedItemSettings) => React.ReactNode;
|
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VariableBox: React.FC<VariableBoxProps> = ({
|
export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||||
path,
|
value: { location },
|
||||||
heading = "Error",
|
heading = "Error",
|
||||||
renderSettingsMenu,
|
renderSettingsMenu,
|
||||||
children,
|
children,
|
||||||
|
@ -27,10 +28,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||||
// So we use `forceUpdate` to force rerendering.
|
// So we use `forceUpdate` to force rerendering.
|
||||||
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
const settings = getSettings(path);
|
const settings = getSettings(location);
|
||||||
|
|
||||||
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
|
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
|
||||||
setSettings(path, newSettings);
|
setSettings(location, newSettings);
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,8 +39,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||||
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
|
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
|
||||||
};
|
};
|
||||||
|
|
||||||
const isTopLevel = path.length === 0;
|
const isTopLevel = location.path.items.length === 0;
|
||||||
const name = isTopLevel ? "Result" : path[path.length - 1];
|
const name = isTopLevel
|
||||||
|
? { result: "Result", bindings: "Bindings" }[location.path.root]
|
||||||
|
: location.path.items[location.path.items.length - 1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -65,13 +68,13 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||||
</header>
|
</header>
|
||||||
{settings.collapsed ? null : (
|
{settings.collapsed ? null : (
|
||||||
<div className="flex w-full">
|
<div className="flex w-full">
|
||||||
{path.length ? (
|
{location.path.items.length ? (
|
||||||
<div
|
<div
|
||||||
className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
|
className="shrink-0 border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
|
||||||
onClick={toggleCollapsed}
|
onClick={toggleCollapsed}
|
||||||
></div>
|
></div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="grow">{children(getMergedSettings(path))}</div>
|
<div className="grow">{children(getMergedSettings(location))}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { defaultEnvironment } from "@quri/squiggle-lang";
|
import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { LocalItemSettings, MergedItemSettings, Path } from "./utils";
|
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||||
|
|
||||||
type ViewerContextShape = {
|
type ViewerContextShape = {
|
||||||
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
|
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
|
||||||
// Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
|
// Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
|
||||||
// See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
|
// See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
|
||||||
getSettings(path: Path): LocalItemSettings;
|
getSettings(location: SqValueLocation): LocalItemSettings;
|
||||||
getMergedSettings(path: Path): MergedItemSettings;
|
getMergedSettings(location: SqValueLocation): MergedItemSettings;
|
||||||
setSettings(path: Path, value: LocalItemSettings): void;
|
setSettings(location: SqValueLocation, value: LocalItemSettings): void;
|
||||||
enableLocalSettings: boolean; // show local settings icon in the UI
|
enableLocalSettings: boolean; // show local settings icon in the UI
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
import React, { useCallback, useRef } from "react";
|
import React, { useCallback, useRef } from "react";
|
||||||
import { environment } from "@quri/squiggle-lang";
|
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||||
import { FunctionChartSettings } from "../FunctionChart";
|
import { FunctionChartSettings } from "../FunctionChart";
|
||||||
import { ExpressionViewer } from "./ExpressionViewer";
|
import { ExpressionViewer } from "./ExpressionViewer";
|
||||||
import { ViewerContext } from "./ViewerContext";
|
import { ViewerContext } from "./ViewerContext";
|
||||||
import {
|
import {
|
||||||
LocalItemSettings,
|
LocalItemSettings,
|
||||||
|
locationAsString,
|
||||||
MergedItemSettings,
|
MergedItemSettings,
|
||||||
Path,
|
|
||||||
pathAsString,
|
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import { useSquiggle } from "../../lib/hooks";
|
import { useSquiggle } from "../../lib/hooks";
|
||||||
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
/** The output of squiggle's run */
|
/** The output of squiggle's run */
|
||||||
result: ReturnType<typeof useSquiggle>;
|
result: ReturnType<typeof useSquiggle>["result"];
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
|
@ -45,22 +44,22 @@ export const SquiggleViewer: React.FC<Props> = ({
|
||||||
const settingsRef = useRef<Settings>({});
|
const settingsRef = useRef<Settings>({});
|
||||||
|
|
||||||
const getSettings = useCallback(
|
const getSettings = useCallback(
|
||||||
(path: Path) => {
|
(location: SqValueLocation) => {
|
||||||
return settingsRef.current[pathAsString(path)] || defaultSettings;
|
return settingsRef.current[locationAsString(location)] || defaultSettings;
|
||||||
},
|
},
|
||||||
[settingsRef]
|
[settingsRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setSettings = useCallback(
|
const setSettings = useCallback(
|
||||||
(path: Path, value: LocalItemSettings) => {
|
(location: SqValueLocation, value: LocalItemSettings) => {
|
||||||
settingsRef.current[pathAsString(path)] = value;
|
settingsRef.current[locationAsString(location)] = value;
|
||||||
},
|
},
|
||||||
[settingsRef]
|
[settingsRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getMergedSettings = useCallback(
|
const getMergedSettings = useCallback(
|
||||||
(path: Path) => {
|
(location: SqValueLocation) => {
|
||||||
const localSettings = getSettings(path);
|
const localSettings = getSettings(location);
|
||||||
const result: MergedItemSettings = {
|
const result: MergedItemSettings = {
|
||||||
distributionPlotSettings: {
|
distributionPlotSettings: {
|
||||||
...distributionPlotSettings,
|
...distributionPlotSettings,
|
||||||
|
@ -91,7 +90,7 @@ export const SquiggleViewer: React.FC<Props> = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{result.tag === "Ok" ? (
|
{result.tag === "Ok" ? (
|
||||||
<ExpressionViewer path={[]} expression={result.value} width={width} />
|
<ExpressionViewer value={result.value} width={width} />
|
||||||
) : (
|
) : (
|
||||||
<SquiggleErrorAlert error={result.value} />
|
<SquiggleErrorAlert error={result.value} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DistributionPlottingSettings } from "../DistributionChart";
|
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||||
import { FunctionChartSettings } from "../FunctionChart";
|
import { FunctionChartSettings } from "../FunctionChart";
|
||||||
import { environment } from "@quri/squiggle-lang";
|
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
export type LocalItemSettings = {
|
export type LocalItemSettings = {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
|
@ -17,6 +17,5 @@ export type MergedItemSettings = {
|
||||||
environment: environment;
|
environment: environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Path = string[];
|
export const locationAsString = (location: SqValueLocation) =>
|
||||||
|
location.path.items.join(".");
|
||||||
export const pathAsString = (path: Path) => path.join(".");
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Path, UseFormRegister } from "react-hook-form";
|
import { Path, UseFormRegister, FieldValues } from "react-hook-form";
|
||||||
|
|
||||||
export function Checkbox<T>({
|
export function Checkbox<T extends FieldValues>({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
register,
|
register,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Path, UseFormRegister } from "react-hook-form";
|
import { Path, UseFormRegister, FieldValues } from "react-hook-form";
|
||||||
|
|
||||||
export function InputItem<T>({
|
export function InputItem<T extends FieldValues>({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
type,
|
type,
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
|
export { SqProject } from "@quri/squiggle-lang/";
|
||||||
export { SquiggleChart } from "./components/SquiggleChart";
|
export { SquiggleChart } from "./components/SquiggleChart";
|
||||||
export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor";
|
export { SquiggleEditor } from "./components/SquiggleEditor";
|
||||||
export { SquigglePlayground } from "./components/SquigglePlayground";
|
export { SquigglePlayground } from "./components/SquigglePlayground";
|
||||||
export { SquiggleContainer } from "./components/SquiggleContainer";
|
export { SquiggleContainer } from "./components/SquiggleContainer";
|
||||||
export { SquiggleEditorWithImportedBindings } from "./components/SquiggleEditorWithImportedBindings";
|
|
||||||
|
|
||||||
export { mergeBindings } from "@quri/squiggle-lang";
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { VisualizationSpec } from "react-vega";
|
import { VisualizationSpec } from "react-vega";
|
||||||
import type { LogScale, LinearScale, PowScale } from "vega";
|
import type { LogScale, LinearScale, PowScale, TimeScale } from "vega";
|
||||||
|
|
||||||
export type DistributionChartSpecOptions = {
|
export type DistributionChartSpecOptions = {
|
||||||
/** Set the x scale to be logarithmic by deault */
|
/** Set the x scale to be logarithmic by deault */
|
||||||
|
@ -14,26 +14,21 @@ export type DistributionChartSpecOptions = {
|
||||||
title?: string;
|
title?: string;
|
||||||
/** The formatting of the ticks */
|
/** The formatting of the ticks */
|
||||||
format?: string;
|
format?: string;
|
||||||
|
/** Whether the x-axis should be dates or numbers */
|
||||||
|
xAxisType?: "number" | "dateTime";
|
||||||
};
|
};
|
||||||
|
|
||||||
export let linearXScale: LinearScale = {
|
/** X Scales */
|
||||||
|
export const linearXScale: LinearScale = {
|
||||||
name: "xscale",
|
name: "xscale",
|
||||||
clamp: true,
|
clamp: true,
|
||||||
type: "linear",
|
type: "linear",
|
||||||
range: "width",
|
range: "width",
|
||||||
zero: false,
|
zero: false,
|
||||||
nice: 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 let logXScale: LogScale = {
|
export const logXScale: LogScale = {
|
||||||
name: "xscale",
|
name: "xscale",
|
||||||
type: "log",
|
type: "log",
|
||||||
range: "width",
|
range: "width",
|
||||||
|
@ -41,60 +36,104 @@ export let logXScale: LogScale = {
|
||||||
base: 10,
|
base: 10,
|
||||||
nice: false,
|
nice: false,
|
||||||
clamp: true,
|
clamp: true,
|
||||||
domain: { data: "domain", field: "x" },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export let expYScale: PowScale = {
|
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 = {
|
||||||
name: "yscale",
|
name: "yscale",
|
||||||
type: "pow",
|
type: "pow",
|
||||||
exponent: 0.1,
|
exponent: 0.1,
|
||||||
range: "height",
|
range: "height",
|
||||||
zero: true,
|
zero: true,
|
||||||
nice: false,
|
nice: false,
|
||||||
domain: { data: "domain", field: "y" },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultTickFormat = ".9~s";
|
export const defaultTickFormat = ".9~s";
|
||||||
|
export const timeTickFormat = "%b %d, %Y %H:%M";
|
||||||
|
const width = 500;
|
||||||
|
|
||||||
export function buildVegaSpec(
|
export function buildVegaSpec(
|
||||||
specOptions: DistributionChartSpecOptions
|
specOptions: DistributionChartSpecOptions & { maxY: number }
|
||||||
): VisualizationSpec {
|
): VisualizationSpec {
|
||||||
const {
|
const {
|
||||||
format = defaultTickFormat,
|
|
||||||
title,
|
title,
|
||||||
minX,
|
minX,
|
||||||
maxX,
|
maxX,
|
||||||
logX,
|
logX,
|
||||||
expY,
|
expY,
|
||||||
|
xAxisType = "number",
|
||||||
|
maxY,
|
||||||
} = specOptions;
|
} = specOptions;
|
||||||
|
|
||||||
let xScale = logX ? logXScale : linearXScale;
|
const dateTime = xAxisType === "dateTime";
|
||||||
if (minX !== undefined && Number.isFinite(minX)) {
|
|
||||||
xScale = { ...xScale, domainMin: minX };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxX !== undefined && Number.isFinite(maxX)) {
|
// some fallbacks
|
||||||
xScale = { ...xScale, domainMax: maxX };
|
const format = specOptions?.format
|
||||||
}
|
? specOptions.format
|
||||||
|
: dateTime
|
||||||
|
? timeTickFormat
|
||||||
|
: defaultTickFormat;
|
||||||
|
|
||||||
let spec: VisualizationSpec = {
|
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 = {
|
||||||
$schema: "https://vega.github.io/schema/vega/v5.json",
|
$schema: "https://vega.github.io/schema/vega/v5.json",
|
||||||
description: "Squiggle plot chart",
|
description: "Squiggle plot chart",
|
||||||
width: 500,
|
width: width,
|
||||||
height: 100,
|
height: 100,
|
||||||
padding: 5,
|
padding: 5,
|
||||||
data: [
|
data: [{ name: "data" }, { name: "domain" }, { name: "samples" }],
|
||||||
|
signals: [
|
||||||
{
|
{
|
||||||
name: "data",
|
name: "hover",
|
||||||
|
value: null,
|
||||||
|
on: [
|
||||||
|
{ events: "mouseover", update: "datum" },
|
||||||
|
{ events: "mouseout", update: "null" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "domain",
|
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]) : ''",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
signals: [],
|
|
||||||
scales: [
|
scales: [
|
||||||
xScale,
|
xScale,
|
||||||
expY ? expYScale : linearYScale,
|
yScale,
|
||||||
{
|
{
|
||||||
name: "color",
|
name: "color",
|
||||||
type: "ordinal",
|
type: "ordinal",
|
||||||
|
@ -115,7 +154,7 @@ export function buildVegaSpec(
|
||||||
domainColor: "#fff",
|
domainColor: "#fff",
|
||||||
domainOpacity: 0.0,
|
domainOpacity: 0.0,
|
||||||
format: format,
|
format: format,
|
||||||
tickCount: 10,
|
tickCount: dateTime ? 3 : 10,
|
||||||
labelOverlap: "greedy",
|
labelOverlap: "greedy",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -232,13 +271,16 @@ export function buildVegaSpec(
|
||||||
},
|
},
|
||||||
size: [{ value: 100 }],
|
size: [{ value: 100 }],
|
||||||
tooltip: {
|
tooltip: {
|
||||||
signal: "{ probability: datum.y, value: datum.x }",
|
signal: dateTime
|
||||||
|
? "{ probability: datum.y, value: datetime(datum.x) }"
|
||||||
|
: "{ probability: datum.y, value: datum.x }",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
x: {
|
x: {
|
||||||
scale: "xscale",
|
scale: "xscale",
|
||||||
field: "x",
|
field: "x",
|
||||||
|
offset: 0.5, // if this is not included, the circles are slightly left of center.
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
scale: "yscale",
|
scale: "yscale",
|
||||||
|
@ -255,6 +297,69 @@ 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: [
|
legends: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { shape } from "@quri/squiggle-lang";
|
import { SqShape } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
export const hasMassBelowZero = (shape: shape) =>
|
export const hasMassBelowZero = (shape: SqShape) =>
|
||||||
shape.continuous.some((x) => x.x <= 0) ||
|
shape.continuous.some((x) => x.x <= 0) ||
|
||||||
shape.discrete.some((x) => x.x <= 0);
|
shape.discrete.some((x) => x.x <= 0);
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export { useMaybeControlledValue } from "./useMaybeControlledValue";
|
export { useMaybeControlledValue } from "./useMaybeControlledValue";
|
||||||
export { useSquiggle, useSquigglePartial } from "./useSquiggle";
|
export { useSquiggle } from "./useSquiggle";
|
||||||
export { useRunnerState } from "./useRunnerState";
|
export { useRunnerState } from "./useRunnerState";
|
||||||
|
|
|
@ -1,53 +1,85 @@
|
||||||
import {
|
import {
|
||||||
bindings,
|
result,
|
||||||
environment,
|
SqError,
|
||||||
jsImports,
|
SqProject,
|
||||||
run,
|
SqRecord,
|
||||||
runPartial,
|
SqValue,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = {
|
type SquiggleArgs = {
|
||||||
code: string;
|
code: string;
|
||||||
executionId?: number;
|
executionId?: number;
|
||||||
bindings?: bindings;
|
jsImports?: JsImports;
|
||||||
jsImports?: jsImports;
|
project: SqProject;
|
||||||
environment?: environment;
|
continues?: string[];
|
||||||
onChange?: (expr: Extract<T, { tag: "Ok" }>["value"] | undefined) => void;
|
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>(
|
export type ResultAndBindings = {
|
||||||
args: SquiggleArgs<T>,
|
result: result<SqValue, SqError>;
|
||||||
f: (...args: Parameters<typeof run>) => T
|
bindings: SqRecord;
|
||||||
) => {
|
};
|
||||||
const result: T = useMemo<T>(
|
|
||||||
() => f(args.code, args.bindings, args.environment, args.jsImports),
|
const importSourceName = (sourceName: string) => "imports-" + sourceName;
|
||||||
|
const defaultContinues = [];
|
||||||
|
|
||||||
|
export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
||||||
|
const sourceName = useMemo(() => uuid.v4(), []);
|
||||||
|
|
||||||
|
const env = args.project.getEnvironment();
|
||||||
|
const continues = args.continues || defaultContinues;
|
||||||
|
|
||||||
|
const result = useMemo(
|
||||||
|
() => {
|
||||||
|
const project = args.project;
|
||||||
|
|
||||||
|
project.setSource(sourceName, args.code);
|
||||||
|
let fullContinues = continues;
|
||||||
|
if (args.jsImports && Object.keys(args.jsImports).length) {
|
||||||
|
const importsSource = jsImportsToSquiggleCode(args.jsImports);
|
||||||
|
project.setSource(importSourceName(sourceName), importsSource);
|
||||||
|
fullContinues = continues.concat(importSourceName(sourceName));
|
||||||
|
}
|
||||||
|
project.setContinues(sourceName, fullContinues);
|
||||||
|
project.run(sourceName);
|
||||||
|
const result = project.getResult(sourceName);
|
||||||
|
const bindings = project.getBindings(sourceName);
|
||||||
|
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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[
|
[
|
||||||
f,
|
|
||||||
args.code,
|
args.code,
|
||||||
args.bindings,
|
|
||||||
args.environment,
|
|
||||||
args.jsImports,
|
args.jsImports,
|
||||||
args.executionId,
|
args.executionId,
|
||||||
|
sourceName,
|
||||||
|
continues,
|
||||||
|
args.project,
|
||||||
|
env,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { onChange } = args;
|
const { onChange } = args;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChange?.(result.tag === "Ok" ? result.value : undefined);
|
onChange?.(
|
||||||
}, [result, onChange]);
|
result.result.tag === "Ok" ? result.result.value : undefined,
|
||||||
|
sourceName
|
||||||
|
);
|
||||||
|
}, [result, onChange, sourceName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
args.project.removeSource(sourceName);
|
||||||
|
if (args.project.getSource(importSourceName(sourceName)))
|
||||||
|
args.project.removeSource(importSourceName(sourceName));
|
||||||
|
};
|
||||||
|
}, [args.project, sourceName]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSquigglePartial = (
|
|
||||||
args: SquiggleArgs<ReturnType<typeof runPartial>>
|
|
||||||
) => {
|
|
||||||
return useSquiggleAny(args, runPartial);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
|
|
||||||
return useSquiggleAny(args, run);
|
|
||||||
};
|
|
||||||
|
|
51
packages/components/src/lib/jsImports.ts
Normal file
51
packages/components/src/lib/jsImports.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
type JsImportsValue =
|
||||||
|
| number
|
||||||
|
| string
|
||||||
|
| JsImportsValue[]
|
||||||
|
| {
|
||||||
|
[k: string]: JsImportsValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JsImports = {
|
||||||
|
[k: string]: JsImportsValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const quote = (arg: string) => `"${arg.replace(new RegExp('"', "g"), '\\"')}"`;
|
||||||
|
|
||||||
|
const jsImportsValueToSquiggleCode = (v: JsImportsValue): string => {
|
||||||
|
if (typeof v === "number") {
|
||||||
|
return String(v);
|
||||||
|
} else if (typeof v === "string") {
|
||||||
|
return quote(v);
|
||||||
|
} else if (v instanceof Array) {
|
||||||
|
return "[" + v.map((x) => jsImportsValueToSquiggleCode(x)) + "]";
|
||||||
|
} else {
|
||||||
|
if (Object.keys(v).length) {
|
||||||
|
return (
|
||||||
|
"{" +
|
||||||
|
Object.entries(v)
|
||||||
|
.map(([k, v]) => `${quote(k)}:${jsImportsValueToSquiggleCode(v)},`)
|
||||||
|
.join("") +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return "0"; // squiggle doesn't support empty `{}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const jsImportsToSquiggleCode = (v: JsImports) => {
|
||||||
|
const validId = new RegExp("[a-zA-Z][[a-zA-Z0-9]*");
|
||||||
|
let result = Object.entries(v)
|
||||||
|
.map(([k, v]) => {
|
||||||
|
if (!k.match(validId)) {
|
||||||
|
return ""; // skipping without warnings; can be improved
|
||||||
|
}
|
||||||
|
return `$${k} = ${jsImportsValueToSquiggleCode(v)}\n`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
if (!result) {
|
||||||
|
result = "$__no_valid_imports__ = 1"; // without this generated squiggle code can be invalid
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
|
@ -1,9 +1,15 @@
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { Distribution, result, squiggleExpression } from "@quri/squiggle-lang";
|
import {
|
||||||
|
SqValue,
|
||||||
|
SqValueTag,
|
||||||
|
SqDistribution,
|
||||||
|
result,
|
||||||
|
SqRecord,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
|
|
||||||
export type LabeledDistribution = {
|
export type LabeledDistribution = {
|
||||||
name: string;
|
name: string;
|
||||||
distribution: Distribution;
|
distribution: SqDistribution;
|
||||||
color?: string;
|
color?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,50 +27,55 @@ function ok<a, b>(x: a): result<a, b> {
|
||||||
|
|
||||||
const schema = yup
|
const schema = yup
|
||||||
.object()
|
.object()
|
||||||
.strict()
|
|
||||||
.noUnknown()
|
.noUnknown()
|
||||||
|
.strict()
|
||||||
.shape({
|
.shape({
|
||||||
distributions: yup.object().shape({
|
distributions: yup
|
||||||
tag: yup.mixed().oneOf(["array"]),
|
.array()
|
||||||
value: yup
|
.required()
|
||||||
.array()
|
.of(
|
||||||
.of(
|
yup.object().required().shape({
|
||||||
yup.object().shape({
|
name: yup.string().required(),
|
||||||
tag: yup.mixed().oneOf(["record"]),
|
distribution: yup.mixed().required(),
|
||||||
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(),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function parsePlot(record: {
|
type JsonObject =
|
||||||
[key: string]: squiggleExpression;
|
| string
|
||||||
}): result<Plot, 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 {
|
try {
|
||||||
const plotRecord = schema.validateSync(record);
|
const plotRecord = schema.validateSync(toJsonRecord(record));
|
||||||
return ok({
|
if (plotRecord.distributions) {
|
||||||
distributions: plotRecord.distributions.value.map((x) => ({
|
return ok({ distributions: plotRecord.distributions.map((x) => x) });
|
||||||
name: x.value.name.value,
|
} else {
|
||||||
// color: x.value.color?.value, // not supported yet
|
// I have no idea why yup's typings thinks this is possible
|
||||||
distribution: x.value.distribution.value,
|
return error("no distributions field. Should never get here");
|
||||||
})),
|
}
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = e instanceof Error ? e.message : "Unknown error";
|
const message = e instanceof Error ? e.message : "Unknown error";
|
||||||
return error(message);
|
return error(message);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { result } from "@quri/squiggle-lang";
|
import { result, resultMap, SqValueTag } from "@quri/squiggle-lang";
|
||||||
|
import { ResultAndBindings } from "./hooks/useSquiggle";
|
||||||
|
|
||||||
export function flattenResult<a, b>(x: result<a, b>[]): result<a[], b> {
|
export function flattenResult<a, b>(x: result<a, b>[]): result<a[], b> {
|
||||||
if (x.length === 0) {
|
if (x.length === 0) {
|
||||||
|
@ -35,3 +36,18 @@ export function all(arr: boolean[]): boolean {
|
||||||
export function some(arr: boolean[]): boolean {
|
export function some(arr: boolean[]): boolean {
|
||||||
return arr.reduce((x, y) => x || y, false);
|
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,6 +79,22 @@ could be continuous, discrete or mixed.
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
### Date Distribution
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Date Distribution"
|
||||||
|
args={{
|
||||||
|
code: "mx(1661819770311, 1661829770311, 1661839770311)",
|
||||||
|
width,
|
||||||
|
xAxisType: "dateTime",
|
||||||
|
width,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
## Mixed distributions
|
## Mixed distributions
|
||||||
|
|
||||||
<Canvas>
|
<Canvas>
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
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,3 +22,8 @@ but this line is still necessary for proper initialization of `--tw-*` variables
|
||||||
.ace_cursor {
|
.ace_cursor {
|
||||||
border-left: 2px solid !important;
|
border-left: 2px solid !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ace-error-marker {
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1px solid red;
|
||||||
|
}
|
||||||
|
|
55
packages/components/test/autorun.test.tsx
Normal file
55
packages/components/test/autorun.test.tsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
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")
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
});
|
13
packages/components/test/basic.test.tsx
Normal file
13
packages/components/test/basic.test.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { SquiggleChart } from "../src/index";
|
||||||
|
|
||||||
|
test("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();
|
||||||
|
});
|
39
packages/components/test/cleanup.test.tsx
Normal file
39
packages/components/test/cleanup.test.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
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([]);
|
||||||
|
});
|
8
packages/components/test/setup.js
Normal file
8
packages/components/test/setup.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
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),
|
||||||
|
};
|
4
packages/components/vercel.json
Normal file
4
packages/components/vercel.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"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,3 +23,4 @@ coverage
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
||||||
src/rescript/Reducer/Reducer_Peggy/helpers.js
|
src/rescript/Reducer/Reducer_Peggy/helpers.js
|
||||||
|
src/rescript/ReducerProject/ReducerProject_IncludeParser.js
|
||||||
|
|
|
@ -6,3 +6,5 @@ lib
|
||||||
_coverage/
|
_coverage/
|
||||||
.cache/
|
.cache/
|
||||||
Reducer_Peggy_GeneratedParser.js
|
Reducer_Peggy_GeneratedParser.js
|
||||||
|
ReducerProject_IncludeParser.js
|
||||||
|
src/rescript/Reducer/Reducer_Peggy/helpers.js
|
||||||
|
|
|
@ -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.
|
Validate that the addition of means equals the mean of the addition, similar for subtraction and multiplication.
|
||||||
|
|
||||||
Details in https://develop--squiggle-documentation.netlify.app/docs/internal/invariants/
|
Details in https://squiggle-language.com/docs/internal/invariants/
|
||||||
|
|
||||||
Note: epsilon of 1e3 means the invariants are, in general, not being satisfied.
|
Note: epsilon of 1e3 means the invariants are, in general, not being satisfied.
|
||||||
*/
|
*/
|
||||||
|
|
21
packages/squiggle-lang/__tests__/E/A_test.res
Normal file
21
packages/squiggle-lang/__tests__/E/A_test.res
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
open Jest
|
||||||
|
open TestHelpers
|
||||||
|
|
||||||
|
describe("E.A.getByFmap", () => {
|
||||||
|
makeTest("Empty list returns None", E.A.getByFmap([], x => x + 1, x => mod(x, 2) == 0), None)
|
||||||
|
makeTest(
|
||||||
|
"Never predicate returns None",
|
||||||
|
E.A.getByFmap([1, 2, 3, 4, 5, 6], x => x + 1, _ => false),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
makeTest(
|
||||||
|
"function evaluates",
|
||||||
|
E.A.getByFmap([1, 1, 1, 1, 1, 1, 1, 2, 1, 1], x => 3 * x, x => x > 4),
|
||||||
|
Some(6),
|
||||||
|
)
|
||||||
|
makeTest(
|
||||||
|
"always predicate returns fn(fst(a))",
|
||||||
|
E.A.getByFmap([0, 1, 2, 3, 4, 5, 6], x => 10 + x, _ => true),
|
||||||
|
Some(10),
|
||||||
|
)
|
||||||
|
})
|
|
@ -9,22 +9,28 @@ let prepareInputs = (ar, minWeight) =>
|
||||||
describe("Continuous and discrete splits", () => {
|
describe("Continuous and discrete splits", () => {
|
||||||
makeTest(
|
makeTest(
|
||||||
"is empty, with no common elements",
|
"is empty, with no common elements",
|
||||||
prepareInputs([1.432, 1.33455, 2.0], 2),
|
prepareInputs([1.33455, 1.432, 2.0], 2),
|
||||||
([1.33455, 1.432, 2.0], []),
|
([1.33455, 1.432, 2.0], []),
|
||||||
)
|
)
|
||||||
|
|
||||||
makeTest(
|
makeTest(
|
||||||
"only stores 3.5 as discrete when minWeight is 3",
|
"only stores 3.5 as discrete when minWeight is 3",
|
||||||
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
|
prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
|
||||||
([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]),
|
([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]),
|
||||||
)
|
)
|
||||||
|
|
||||||
makeTest(
|
makeTest(
|
||||||
"doesn't store 3.5 as discrete when minWeight is 5",
|
"doesn't store 3.5 as discrete when minWeight is 5",
|
||||||
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 5),
|
prepareInputs([1.33455, 1.432, 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], []),
|
([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 makeDuplicatedArray = count => {
|
||||||
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
|
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
|
||||||
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
|
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
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)]),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
@@warning("-44")
|
||||||
|
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)
|
||||||
|
test("get", () => {
|
||||||
|
expect(bindings->Bindings.get("value")) == Some(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("get nonexisting value", () => {
|
||||||
|
expect(bindings->Bindings.get("nosuchvalue")) == None
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,146 +0,0 @@
|
||||||
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)")
|
|
||||||
})
|
|
|
@ -1,37 +0,0 @@
|
||||||
module ExpressionValue = ReducerInterface.ExternalExpressionValue
|
|
||||||
|
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
let expectEvalToBe = (expr: string, answer: string) =>
|
|
||||||
Reducer.evaluate(expr)->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
|
|
||||||
})
|
|
|
@ -1,22 +0,0 @@
|
||||||
// Reducer_Helpers
|
|
||||||
module ErrorValue = Reducer_ErrorValue
|
|
||||||
module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue
|
|
||||||
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 removeDefaultsExternal = (ev: ExternalExpressionValue.t): ExternalExpressionValue.t =>
|
|
||||||
ev->InternalExpressionValue.toInternal->removeDefaultsInternal->InternalExpressionValue.toExternal
|
|
||||||
|
|
||||||
let rRemoveDefaultsInternal = r => Belt.Result.map(r, removeDefaultsInternal)
|
|
||||||
let rRemoveDefaultsExternal = r => Belt.Result.map(r, removeDefaultsExternal)
|
|
|
@ -1,31 +0,0 @@
|
||||||
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")),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
@@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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -14,47 +14,46 @@ describe("Peggy parse", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("literals operators parenthesis", () => {
|
describe("literals operators parenthesis", () => {
|
||||||
// Note that there is always an outer block. Otherwise, external bindings are ignrored at the first statement
|
|
||||||
testParse("1", "{1}")
|
testParse("1", "{1}")
|
||||||
testParse("'hello'", "{'hello'}")
|
testParse("'hello'", "{'hello'}")
|
||||||
testParse("true", "{true}")
|
testParse("true", "{true}")
|
||||||
testParse("1+2", "{(::add 1 2)}")
|
testParse("1+2", "{(:add 1 2)}")
|
||||||
testParse("add(1,2)", "{(::add 1 2)}")
|
testParse("add(1,2)", "{(:add 1 2)}")
|
||||||
testParse("(1)", "{1}")
|
testParse("(1)", "{1}")
|
||||||
testParse("(1+2)", "{(::add 1 2)}")
|
testParse("(1+2)", "{(:add 1 2)}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("unary", () => {
|
describe("unary", () => {
|
||||||
testParse("-1", "{(::unaryMinus 1)}")
|
testParse("-1", "{(:unaryMinus 1)}")
|
||||||
testParse("!true", "{(::not true)}")
|
testParse("!true", "{(:not true)}")
|
||||||
testParse("1 + -1", "{(::add 1 (::unaryMinus 1))}")
|
testParse("1 + -1", "{(:add 1 (:unaryMinus 1))}")
|
||||||
testParse("-a[0]", "{(::unaryMinus (::$_atIndex_$ :a 0))}")
|
testParse("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}")
|
||||||
testParse("!a[0]", "{(::not (::$_atIndex_$ :a 0))}")
|
testParse("!a[0]", "{(:not (:$_atIndex_$ :a 0))}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("multiplicative", () => {
|
describe("multiplicative", () => {
|
||||||
testParse("1 * 2", "{(::multiply 1 2)}")
|
testParse("1 * 2", "{(:multiply 1 2)}")
|
||||||
testParse("1 / 2", "{(::divide 1 2)}")
|
testParse("1 / 2", "{(:divide 1 2)}")
|
||||||
testParse("1 * 2 * 3", "{(::multiply (::multiply 1 2) 3)}")
|
testParse("1 * 2 * 3", "{(:multiply (:multiply 1 2) 3)}")
|
||||||
testParse("1 * 2 / 3", "{(::divide (::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", "{(:multiply (:divide 1 2) 3)}")
|
||||||
testParse("1 / 2 / 3", "{(::divide (::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", "{(:add (:multiply 1 2) (:multiply 3 4))}")
|
||||||
testParse("1 * 2 - 3 * 4", "{(::subtract (::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", "{(:dotAdd (:multiply 1 2) (:multiply 3 4))}")
|
||||||
testParse("1 * 2 .- 3 * 4", "{(::dotSubtract (::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) (:dotMultiply 3 4))}")
|
||||||
testParse("1 * 2 + 3 / 4", "{(::add (::multiply 1 2) (::divide 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", "{(: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) (:dotMultiply 3 4))}")
|
||||||
testParse("1 * 2 - 3 / 4", "{(::subtract (::multiply 1 2) (::divide 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", "{(: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 - 3 * 4^5", "{(:subtract (:multiply 1 2) (:multiply 3 (:pow 4 5)))}")
|
||||||
testParse(
|
testParse(
|
||||||
"1 * 2 - 3 * 4^5^6",
|
"1 * 2 - 3 * 4^5^6",
|
||||||
"{(::subtract (::multiply 1 2) (::multiply 3 (::pow (::pow 4 5) 6)))}",
|
"{(:subtract (:multiply 1 2) (:multiply 3 (:pow (:pow 4 5) 6)))}",
|
||||||
)
|
)
|
||||||
testParse("1 * -a[-2]", "{(::multiply 1 (::unaryMinus (::$_atIndex_$ :a (::unaryMinus 2))))}")
|
testParse("1 * -a[-2]", "{(:multiply 1 (:unaryMinus (:$_atIndex_$ :a (:unaryMinus 2))))}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("multi-line", () => {
|
describe("multi-line", () => {
|
||||||
|
@ -70,27 +69,27 @@ describe("Peggy parse", () => {
|
||||||
|
|
||||||
describe("functions", () => {
|
describe("functions", () => {
|
||||||
testParse("identity(x) = x", "{:identity = {|:x| {:x}}}") // Function definitions become lambda assignments
|
testParse("identity(x) = x", "{:identity = {|:x| {:x}}}") // Function definitions become lambda assignments
|
||||||
testParse("identity(x)", "{(::identity :x)}")
|
testParse("identity(x)", "{(:identity :x)}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("arrays", () => {
|
describe("arrays", () => {
|
||||||
testParse("[]", "{(::$_constructArray_$ ())}")
|
testParse("[]", "{[]}")
|
||||||
testParse("[0, 1, 2]", "{(::$_constructArray_$ (0 1 2))}")
|
testParse("[0, 1, 2]", "{[0; 1; 2]}")
|
||||||
testParse("['hello', 'world']", "{(::$_constructArray_$ ('hello' 'world'))}")
|
testParse("['hello', 'world']", "{['hello'; 'world']}")
|
||||||
testParse("([0,1,2])[1]", "{(::$_atIndex_$ (::$_constructArray_$ (0 1 2)) 1)}")
|
testParse("([0,1,2])[1]", "{(:$_atIndex_$ [0; 1; 2] 1)}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("records", () => {
|
describe("records", () => {
|
||||||
testParse("{a: 1, b: 2}", "{(::$_constructRecord_$ ('a': 1 'b': 2))}")
|
testParse("{a: 1, b: 2}", "{{'a': 1, 'b': 2}}")
|
||||||
testParse("{1+0: 1, 2+0: 2}", "{(::$_constructRecord_$ ((::add 1 0): 1 (::add 2 0): 2))}") // key can be any expression
|
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("record.property", "{(:$_atIndex_$ :record 'property')}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("post operators", () => {
|
describe("post operators", () => {
|
||||||
//function call, array and record access are post operators with higher priority than unary 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 (:b 1)))}")
|
||||||
testParse("a==!b[1]", "{(::equal :a (::not (::$_atIndex_$ :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.one", "{(:equal :a (:not (:$_atIndex_$ :b 'one')))}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("comments", () => {
|
describe("comments", () => {
|
||||||
|
@ -100,10 +99,10 @@ describe("Peggy parse", () => {
|
||||||
testParse("/* This is a multi line comment */ 1", "{1}")
|
testParse("/* This is a multi line comment */ 1", "{1}")
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
/* This is
|
/* This is
|
||||||
a multi line
|
a multi line
|
||||||
comment */
|
comment */
|
||||||
1`,
|
1`,
|
||||||
"{1}",
|
"{1}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -126,70 +125,67 @@ describe("Peggy parse", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("logical", () => {
|
describe("logical", () => {
|
||||||
testParse("true || false", "{(::or true false)}")
|
testParse("true || false", "{(:or true false)}")
|
||||||
testParse("true && false", "{(::and true false)}")
|
testParse("true && false", "{(:and true false)}")
|
||||||
testParse("a * b + c", "{(::add (::multiply :a :b) :c)}") // for comparison
|
testParse("a * b + c", "{(:add (:multiply :a :b) :c)}") // for comparison
|
||||||
testParse("a && b || c", "{(::or (::and :a :b) :c)}")
|
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 && d", "{(:or (:and :a :b) (:and :c :d))}")
|
||||||
testParse("a && !b || c", "{(::or (::and :a (::not :b)) :c)}")
|
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 (:equal :b :c)) :d)}")
|
||||||
testParse("a && b!=c || d", "{(::or (::and :a (::unequal :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 (: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 (: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 (: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 (:smallerEq :b :c)) :d)}")
|
||||||
testParse("a && b>c || d", "{(::or (::and :a (::larger :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 || 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 (::$_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<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 || d", "{(:or (:and :a (:smaller :b (:add 1 2))) :d)}")
|
||||||
testParse(
|
testParse("a && b<1+2*3 || d", "{(:or (:and :a (:smaller :b (:add 1 (:multiply 2 3)))) :d)}")
|
||||||
"a && b<1+2*3 || d",
|
|
||||||
"{(::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d)}",
|
|
||||||
)
|
|
||||||
testParse(
|
testParse(
|
||||||
"a && b<1+2*-3+4 || d",
|
"a && b<1+2*-3+4 || d",
|
||||||
"{(::or (::and :a (::smaller :b (::add (::add 1 (::multiply 2 (::unaryMinus 3))) 4))) :d)}",
|
"{(:or (:and :a (:smaller :b (:add (:add 1 (:multiply 2 (:unaryMinus 3))) 4))) :d)}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
"a && b<1+2*3 || d ? true : false",
|
"a && b<1+2*3 || d ? true : false",
|
||||||
"{(::$$_ternary_$$ (::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d) true false)}",
|
"{(::$$_ternary_$$ (:or (:and :a (:smaller :b (:add 1 (:multiply 2 3)))) :d) true false)}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("pipe", () => {
|
describe("pipe", () => {
|
||||||
testParse("1 -> add(2)", "{(::add 1 2)}")
|
testParse("1 -> add(2)", "{(:add 1 2)}")
|
||||||
testParse("-1 -> add(2)", "{(::add (::unaryMinus 1) 2)}")
|
testParse("-1 -> add(2)", "{(:add (:unaryMinus 1) 2)}")
|
||||||
testParse("-a[1] -> add(2)", "{(::add (::unaryMinus (::$_atIndex_$ :a 1)) 2)}")
|
testParse("-a[1] -> add(2)", "{(:add (:unaryMinus (:$_atIndex_$ :a 1)) 2)}")
|
||||||
testParse("-f(1) -> add(2)", "{(::add (::unaryMinus (::f 1)) 2)}")
|
testParse("-f(1) -> add(2)", "{(:add (:unaryMinus (:f 1)) 2)}")
|
||||||
testParse("1 + 2 -> add(3)", "{(::add 1 (::add 2 3))}")
|
testParse("1 + 2 -> add(3)", "{(:add 1 (:add 2 3))}")
|
||||||
testParse("1 -> add(2) * 3", "{(::multiply (::add 1 2) 3)}")
|
testParse("1 -> add(2) * 3", "{(:multiply (:add 1 2) 3)}")
|
||||||
testParse("1 -> subtract(2)", "{(::subtract 1 2)}")
|
testParse("1 -> subtract(2)", "{(:subtract 1 2)}")
|
||||||
testParse("-1 -> subtract(2)", "{(::subtract (::unaryMinus 1) 2)}")
|
testParse("-1 -> subtract(2)", "{(:subtract (:unaryMinus 1) 2)}")
|
||||||
testParse("1 -> subtract(2) * 3", "{(::multiply (::subtract 1 2) 3)}")
|
testParse("1 -> subtract(2) * 3", "{(:multiply (:subtract 1 2) 3)}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("elixir pipe", () => {
|
describe("elixir pipe", () => {
|
||||||
//handled together with -> so there is no need for seperate tests
|
//handled together with -> so there is no need for seperate tests
|
||||||
testParse("1 |> add(2)", "{(::add 1 2)}")
|
testParse("1 |> add(2)", "{(:add 1 2)}")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("to", () => {
|
describe("to", () => {
|
||||||
testParse("1 to 2", "{(::credibleIntervalToDistribution 1 2)}")
|
testParse("1 to 2", "{(:credibleIntervalToDistribution 1 2)}")
|
||||||
testParse("-1 to -2", "{(::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2))}") // lower than unary
|
testParse("-1 to -2", "{(:credibleIntervalToDistribution (:unaryMinus 1) (:unaryMinus 2))}") // lower than unary
|
||||||
testParse(
|
testParse(
|
||||||
"a[1] to a[2]",
|
"a[1] to a[2]",
|
||||||
"{(::credibleIntervalToDistribution (::$_atIndex_$ :a 1) (::$_atIndex_$ :a 2))}",
|
"{(:credibleIntervalToDistribution (:$_atIndex_$ :a 1) (:$_atIndex_$ :a 2))}",
|
||||||
) // lower than post
|
) // lower than post
|
||||||
testParse(
|
testParse(
|
||||||
"a.p1 to a.p2",
|
"a.p1 to a.p2",
|
||||||
"{(::credibleIntervalToDistribution (::$_atIndex_$ :a 'p1') (::$_atIndex_$ :a 'p2'))}",
|
"{(:credibleIntervalToDistribution (:$_atIndex_$ :a 'p1') (:$_atIndex_$ :a 'p2'))}",
|
||||||
) // lower than post
|
) // lower than post
|
||||||
testParse("1 to 2 + 3", "{(::add (::credibleIntervalToDistribution 1 2) 3)}") // higher than binary operators
|
testParse("1 to 2 + 3", "{(:credibleIntervalToDistribution 1 (:add 2 3))}")
|
||||||
testParse(
|
testParse(
|
||||||
"1->add(2) to 3->add(4) -> add(4)",
|
"1->add(2) to 3->add(4) -> add(4)",
|
||||||
"{(::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4))}",
|
"{(:credibleIntervalToDistribution (:add 1 2) (:add (:add 3 4) 4))}",
|
||||||
) // lower than chain
|
) // lower than chain
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -200,8 +196,8 @@ describe("Peggy parse", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("lambda", () => {
|
describe("lambda", () => {
|
||||||
testParse("{|x| x}", "{{|:x| {:x}}}")
|
testParse("{|x| x}", "{{|:x| :x}}")
|
||||||
testParse("f={|x| x}", "{:f = {{|:x| {:x}}}}")
|
testParse("f={|x| x}", "{:f = {|:x| :x}}")
|
||||||
testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments
|
testParse("f(x)=x", "{: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("f(x)=x ? 1 : 0", "{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}}") // Function definitions are lambda assignments
|
||||||
})
|
})
|
||||||
|
@ -209,31 +205,25 @@ describe("Peggy parse", () => {
|
||||||
describe("Using lambda as value", () => {
|
describe("Using lambda as value", () => {
|
||||||
testParse(
|
testParse(
|
||||||
"myadd(x,y)=x+y; z=myadd; z",
|
"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}; :z}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
"myadd(x,y)=x+y; z=[myadd]; z",
|
"myadd(x,y)=x+y; z=[myadd]; z",
|
||||||
"{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructArray_$ (:myadd))}; :z}",
|
"{:myadd = {|:x,:y| {(:add :x :y)}}; :z = {[:myadd]}; :z}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
"myaddd(x,y)=x+y; z={x: myaddd}; z",
|
"myaddd(x,y)=x+y; z={x: myaddd}; z",
|
||||||
"{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructRecord_$ ('x': :myaddd))}; :z}",
|
"{:myaddd = {|:x,:y| {(:add :x :y)}}; :z = {{'x': :myaddd}}; :z}",
|
||||||
)
|
|
||||||
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 (::$_constructArray_$ (1 2 3)) {|:x| {(::add :x 1)}})}",
|
|
||||||
)
|
|
||||||
testParse(
|
|
||||||
"[1,2,3]->map({|x| x+1})",
|
|
||||||
"{(::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", () => {
|
describe("unit", () => {
|
||||||
testParse("1m", "{(::fromUnit_m 1)}")
|
testParse("1m", "{(:fromUnit_m 1)}")
|
||||||
testParse("1M", "{(::fromUnit_M 1)}")
|
testParse("1M", "{(:fromUnit_M 1)}")
|
||||||
testParse("1m+2cm", "{(::add (::fromUnit_m 1) (::fromUnit_cm 2))}")
|
testParse("1m+2cm", "{(:add (:fromUnit_m 1) (:fromUnit_cm 2))}")
|
||||||
})
|
})
|
||||||
describe("Module", () => {
|
describe("Module", () => {
|
||||||
testParse("x", "{:x}")
|
testParse("x", "{:x}")
|
||||||
|
@ -244,118 +234,118 @@ describe("Peggy parse", () => {
|
||||||
describe("parsing new line", () => {
|
describe("parsing new line", () => {
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
a +
|
a +
|
||||||
b`,
|
b`,
|
||||||
"{(::add :a :b)}",
|
"{(:add :a :b)}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
x=
|
x=
|
||||||
1`,
|
1`,
|
||||||
"{:x = {1}}",
|
"{:x = {1}}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
x=1
|
x=1
|
||||||
y=2`,
|
y=2`,
|
||||||
"{:x = {1}; :y = {2}}",
|
"{:x = {1}; :y = {2}}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
x={
|
x={
|
||||||
y=2;
|
y=2;
|
||||||
y }
|
y }
|
||||||
x`,
|
x`,
|
||||||
"{:x = {:y = {2}; :y}; :x}",
|
"{:x = {:y = {2}; :y}; :x}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
x={
|
x={
|
||||||
y=2
|
|
||||||
y }
|
|
||||||
x`,
|
|
||||||
"{:x = {:y = {2}; :y}; :x}",
|
|
||||||
)
|
|
||||||
testParse(
|
|
||||||
`
|
|
||||||
x={
|
|
||||||
y=2
|
|
||||||
y
|
|
||||||
}
|
|
||||||
x`,
|
|
||||||
"{:x = {:y = {2}; :y}; :x}",
|
|
||||||
)
|
|
||||||
testParse(
|
|
||||||
`
|
|
||||||
x=1
|
|
||||||
y=2
|
y=2
|
||||||
z=3
|
y }
|
||||||
`,
|
x`,
|
||||||
|
"{:x = {:y = {2}; :y}; :x}",
|
||||||
|
)
|
||||||
|
testParse(
|
||||||
|
`
|
||||||
|
x={
|
||||||
|
y=2
|
||||||
|
y
|
||||||
|
}
|
||||||
|
x`,
|
||||||
|
"{:x = {:y = {2}; :y}; :x}",
|
||||||
|
)
|
||||||
|
testParse(
|
||||||
|
`
|
||||||
|
x=1
|
||||||
|
y=2
|
||||||
|
z=3
|
||||||
|
`,
|
||||||
"{:x = {1}; :y = {2}; :z = {3}}",
|
"{:x = {1}; :y = {2}; :z = {3}}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
f={
|
f={
|
||||||
x=1
|
x=1
|
||||||
y=2
|
y=2
|
||||||
z=3
|
z=3
|
||||||
x+y+z
|
x+y+z
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}}",
|
||||||
|
)
|
||||||
|
testParse(
|
||||||
|
`
|
||||||
|
f={
|
||||||
|
x=1
|
||||||
|
y=2
|
||||||
|
z=3
|
||||||
|
x+y+z
|
||||||
|
}
|
||||||
|
g=f+4
|
||||||
|
g
|
||||||
|
`,
|
||||||
|
"{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}; :g = {(:add :f 4)}; :g}",
|
||||||
|
)
|
||||||
|
testParse(
|
||||||
|
`
|
||||||
|
f =
|
||||||
|
{
|
||||||
|
x=1; //x
|
||||||
|
y=2 //y
|
||||||
|
z=
|
||||||
|
3
|
||||||
|
x+
|
||||||
|
y+
|
||||||
|
z
|
||||||
}
|
}
|
||||||
`,
|
g =
|
||||||
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}}",
|
f +
|
||||||
|
4
|
||||||
|
g ->
|
||||||
|
h ->
|
||||||
|
p ->
|
||||||
|
q
|
||||||
|
`,
|
||||||
|
"{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}; :g = {(:add :f 4)}; (:q (:p (:h :g)))}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
f={
|
a |>
|
||||||
x=1
|
b |>
|
||||||
y=2
|
c |>
|
||||||
z=3
|
d
|
||||||
x+y+z
|
`,
|
||||||
}
|
"{(:d (:c (:b :a)))}",
|
||||||
g=f+4
|
|
||||||
g
|
|
||||||
`,
|
|
||||||
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; :g}",
|
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
`
|
`
|
||||||
f =
|
a |>
|
||||||
{
|
b |>
|
||||||
x=1; //x
|
c |>
|
||||||
y=2 //y
|
d +
|
||||||
z=
|
e
|
||||||
3
|
`,
|
||||||
x+
|
"{(:add (:d (:c (:b :a))) :e)}",
|
||||||
y+
|
|
||||||
z
|
|
||||||
}
|
|
||||||
g =
|
|
||||||
f +
|
|
||||||
4
|
|
||||||
g ->
|
|
||||||
h ->
|
|
||||||
p ->
|
|
||||||
q
|
|
||||||
`,
|
|
||||||
"{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::q (::p (::h :g)))}",
|
|
||||||
)
|
|
||||||
testParse(
|
|
||||||
`
|
|
||||||
a |>
|
|
||||||
b |>
|
|
||||||
c |>
|
|
||||||
d
|
|
||||||
`,
|
|
||||||
"{(::d (::c (::b :a)))}",
|
|
||||||
)
|
|
||||||
testParse(
|
|
||||||
`
|
|
||||||
a |>
|
|
||||||
b |>
|
|
||||||
c |>
|
|
||||||
d +
|
|
||||||
e
|
|
||||||
`,
|
|
||||||
"{(::add (::d (::c (::b :a))) :e)}",
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,77 +3,83 @@ open Reducer_Peggy_TestHelpers
|
||||||
|
|
||||||
describe("Peggy parse type", () => {
|
describe("Peggy parse type", () => {
|
||||||
describe("type of", () => {
|
describe("type of", () => {
|
||||||
testParse("p: number", "{(::$_typeOf_$ :p #number)}")
|
testParse("p: number", "{(::$_typeOf_$ :p #number); (::$_endOfOuterBlock_$ () ())}")
|
||||||
})
|
})
|
||||||
describe("type alias", () => {
|
describe("type alias", () => {
|
||||||
testParse("type index=number", "{(::$_typeAlias_$ #index #number)}")
|
testParse(
|
||||||
|
"type index=number",
|
||||||
|
"{(::$_typeAlias_$ #index #number); (::$_endOfOuterBlock_$ () ())}",
|
||||||
|
)
|
||||||
})
|
})
|
||||||
describe("type or", () => {
|
describe("type or", () => {
|
||||||
testParse(
|
testParse(
|
||||||
"answer: number|string",
|
"answer: number|string",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string))))}",
|
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ #number #string))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("type function", () => {
|
describe("type function", () => {
|
||||||
testParse(
|
testParse(
|
||||||
"f: number=>number=>number",
|
"f: number=>number=>number",
|
||||||
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}",
|
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ #number #number #number))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("high priority contract", () => {
|
describe("high priority contract", () => {
|
||||||
testParse(
|
testParse(
|
||||||
"answer: number<-min<-max(100)|string",
|
"answer: number<-min<-max(100)|string",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}",
|
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
"answer: number<-memberOf([1,3,5])",
|
"answer: number<-memberOf([1,3,5])",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}",
|
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ 1 3 5))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("low priority contract", () => {
|
describe("low priority contract", () => {
|
||||||
testParse(
|
testParse(
|
||||||
"answer: number | string $ opaque",
|
"answer: number | string $ opaque",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
|
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ #number #string)))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("type array", () => {
|
describe("type array", () => {
|
||||||
testParse("answer: [number]", "{(::$_typeOf_$ :answer (::$_typeArray_$ #number))}")
|
testParse(
|
||||||
|
"answer: [number]",
|
||||||
|
"{(::$_typeOf_$ :answer (::$_typeArray_$ #number)); (::$_endOfOuterBlock_$ () ())}",
|
||||||
|
)
|
||||||
})
|
})
|
||||||
describe("type record", () => {
|
describe("type record", () => {
|
||||||
testParse(
|
testParse(
|
||||||
"answer: {a: number, b: string}",
|
"answer: {a: number, b: string}",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string))))}",
|
"{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string)))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("type constructor", () => {
|
describe("type constructor", () => {
|
||||||
testParse(
|
testParse(
|
||||||
"answer: Age(number)",
|
"answer: Age(number)",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number))))}",
|
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ #number))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
"answer: Complex(number, number)",
|
"answer: Complex(number, number)",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number))))}",
|
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ #number #number))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
"answer: Person({age: number, name: string})",
|
"answer: Person({age: number, name: string})",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ ((::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))))}",
|
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ (::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
testParse(
|
testParse(
|
||||||
"weekend: Saturday | Sunday",
|
"weekend: Saturday | Sunday",
|
||||||
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}",
|
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ (::$_typeConstructor_$ #Saturday (::$_constructArray_$)) (::$_typeConstructor_$ #Sunday (::$_constructArray_$))))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("type parenthesis", () => {
|
describe("type parenthesis", () => {
|
||||||
//$ is introduced to avoid parenthesis
|
//$ is introduced to avoid parenthesis
|
||||||
testParse(
|
testParse(
|
||||||
"answer: (number|string)<-opaque",
|
"answer: (number|string)<-opaque",
|
||||||
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}",
|
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ #number #string)))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("squiggle expressions in type contracts", () => {
|
describe("squiggle expressions in type contracts", () => {
|
||||||
testParse(
|
testParse(
|
||||||
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
|
"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)))}",
|
"{:odds1 = {(::$_constructArray_$ 1 3 5)}; :odds2 = {(::$_constructArray_$ 7 9)}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2))); (::$_endOfOuterBlock_$ () ())}",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
module Expression = Reducer_Expression
|
module Expression = Reducer_Expression
|
||||||
module ExpressionT = Reducer_Expression_T
|
module ExpressionT = Reducer_Expression_T
|
||||||
module ExpressionValue = ReducerInterface.InternalExpressionValue
|
|
||||||
module Parse = Reducer_Peggy_Parse
|
module Parse = Reducer_Peggy_Parse
|
||||||
module Result = Belt.Result
|
module Result = Belt.Result
|
||||||
module ToExpression = Reducer_Peggy_ToExpression
|
module ToExpression = Reducer_Peggy_ToExpression
|
||||||
|
@ -10,12 +9,12 @@ open Jest
|
||||||
open Expect
|
open Expect
|
||||||
|
|
||||||
let expectParseToBe = (expr, answer) =>
|
let expectParseToBe = (expr, answer) =>
|
||||||
Parse.parse(expr)->Parse.toStringResult->expect->toBe(answer)
|
Parse.parse(expr, "test")->Parse.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
||||||
|
|
||||||
let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
|
let expectExpressionToBe = (expr, answer, ~v="_", ()) => {
|
||||||
let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode)
|
let rExpr = Parse.parse(expr, "test")->Result.map(ToExpression.fromNode)
|
||||||
let a1 = rExpr->ExpressionT.toStringResultOkless
|
let a1 = rExpr->ExpressionT.toStringResultOkless
|
||||||
|
|
||||||
if v == "_" {
|
if v == "_" {
|
||||||
|
@ -23,30 +22,24 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
|
||||||
} else {
|
} else {
|
||||||
let a2 =
|
let a2 =
|
||||||
rExpr
|
rExpr
|
||||||
->Result.flatMap(expr =>
|
->E.R2.errMap(e => e->SqError.fromParseError)
|
||||||
Expression.reduceExpression(
|
->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr))
|
||||||
expr,
|
->Reducer_Value.toStringResultOkless
|
||||||
ReducerInterface_StdLib.internalStdLib,
|
|
||||||
ExpressionValue.defaultEnvironment,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
->Reducer_Helpers.rRemoveDefaultsInternal
|
|
||||||
->ExpressionValue.toStringResultOkless
|
|
||||||
(a1, a2)->expect->toEqual((answer, v))
|
(a1, a2)->expect->toEqual((answer, v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let testToExpression = (expr, answer, ~v="_", ()) =>
|
let testToExpression = (expr, answer, ~v="_", ()) =>
|
||||||
test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
test(expr, () => expectExpressionToBe(expr, answer, ~v, ()))
|
||||||
|
|
||||||
module MyOnly = {
|
module MyOnly = {
|
||||||
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
||||||
let testToExpression = (expr, answer, ~v="_", ()) =>
|
let testToExpression = (expr, answer, ~v="_", ()) =>
|
||||||
Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
Only.test(expr, () => expectExpressionToBe(expr, answer, ~v, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
module MySkip = {
|
module MySkip = {
|
||||||
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
||||||
let testToExpression = (expr, answer, ~v="_", ()) =>
|
let testToExpression = (expr, answer, ~v="_", ()) =>
|
||||||
Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ()))
|
Skip.test(expr, () => expectExpressionToBe(expr, answer, ~v, ()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
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", ())
|
||||||
|
})
|
|
@ -1,5 +1,4 @@
|
||||||
module Bindings = Reducer_Bindings
|
module Bindings = Reducer_Bindings
|
||||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
|
||||||
|
|
||||||
open Jest
|
open Jest
|
||||||
open Reducer_Peggy_TestHelpers
|
open Reducer_Peggy_TestHelpers
|
||||||
|
@ -7,123 +6,94 @@ open Reducer_Peggy_TestHelpers
|
||||||
describe("Peggy to Expression", () => {
|
describe("Peggy to Expression", () => {
|
||||||
describe("literals operators parenthesis", () => {
|
describe("literals operators parenthesis", () => {
|
||||||
// Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement
|
// Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement
|
||||||
testToExpression("1", "{1}", ~v="1", ())
|
testToExpression("1", "1", ~v="1", ())
|
||||||
testToExpression("'hello'", "{'hello'}", ~v="'hello'", ())
|
testToExpression("'hello'", "'hello'", ~v="'hello'", ())
|
||||||
testToExpression("true", "{true}", ~v="true", ())
|
testToExpression("true", "true", ~v="true", ())
|
||||||
testToExpression("1+2", "{(:add 1 2)}", ~v="3", ())
|
testToExpression("1+2", "(add)(1, 2)", ~v="3", ())
|
||||||
testToExpression("add(1,2)", "{(:add 1 2)}", ~v="3", ())
|
testToExpression("add(1,2)", "(add)(1, 2)", ~v="3", ())
|
||||||
testToExpression("(1)", "{1}", ())
|
testToExpression("(1)", "1", ())
|
||||||
testToExpression("(1+2)", "{(:add 1 2)}", ())
|
testToExpression("(1+2)", "(add)(1, 2)", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("unary", () => {
|
describe("unary", () => {
|
||||||
testToExpression("-1", "{(:unaryMinus 1)}", ~v="-1", ())
|
testToExpression("-1", "(unaryMinus)(1)", ~v="-1", ())
|
||||||
testToExpression("!true", "{(:not true)}", ~v="false", ())
|
testToExpression("!true", "(not)(true)", ~v="false", ())
|
||||||
testToExpression("1 + -1", "{(:add 1 (:unaryMinus 1))}", ~v="0", ())
|
testToExpression("1 + -1", "(add)(1, (unaryMinus)(1))", ~v="0", ())
|
||||||
testToExpression("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}", ())
|
testToExpression("-a[0]", "(unaryMinus)(($_atIndex_$)(a, 0))", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("multi-line", () => {
|
describe("multi-line", () => {
|
||||||
testToExpression("x=1; 2", "{(:$_let_$ :x {1}); 2}", ~v="2", ())
|
testToExpression("x=1; 2", "x = {1}; 2", ~v="2", ())
|
||||||
testToExpression("x=1; y=2", "{(:$_let_$ :x {1}); (:$_let_$ :y {2})}", ~v="@{x: 1,y: 2}", ())
|
testToExpression("x=1; y=2", "x = {1}; y = {2}", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("variables", () => {
|
describe("variables", () => {
|
||||||
testToExpression("x = 1", "{(:$_let_$ :x {1})}", ~v="@{x: 1}", ())
|
testToExpression("x = 1", "x = {1}", ())
|
||||||
testToExpression("x", "{:x}", ~v=":x", ()) //TODO: value should return error
|
testToExpression("x", "x", ~v="Error(x is not defined)", ()) //TODO: value should return error
|
||||||
testToExpression("x = 1; x", "{(:$_let_$ :x {1}); :x}", ~v="1", ())
|
testToExpression("x = 1; x", "x = {1}; x", ~v="1", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("functions", () => {
|
describe("functions", () => {
|
||||||
testToExpression(
|
testToExpression("identity(x) = x", "identity = {|x| {x}}", ()) // Function definitions become lambda assignments
|
||||||
"identity(x) = x",
|
testToExpression("identity(x)", "(identity)(x)", ()) // Note value returns error properly
|
||||||
"{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x}))}",
|
|
||||||
~v="@{identity: lambda(x=>internal code)}",
|
|
||||||
(),
|
|
||||||
) // Function definitions become lambda assignments
|
|
||||||
testToExpression("identity(x)", "{(:identity :x)}", ()) // Note value returns error properly
|
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"f(x) = x> 2 ? 0 : 1; f(3)",
|
"f(x) = x> 2 ? 0 : 1; f(3)",
|
||||||
"{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:f 3)}",
|
"f = {|x| {(larger)(x, 2) ? (0) : (1)}}; (f)(3)",
|
||||||
~v="0",
|
~v="0",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("arrays", () => {
|
describe("arrays", () => {
|
||||||
testToExpression("[]", "{(:$_constructArray_$ ())}", ~v="[]", ())
|
testToExpression("[]", "[]", ~v="[]", ())
|
||||||
testToExpression("[0, 1, 2]", "{(:$_constructArray_$ (0 1 2))}", ~v="[0,1,2]", ())
|
testToExpression("[0, 1, 2]", "[0, 1, 2]", ~v="[0,1,2]", ())
|
||||||
testToExpression(
|
testToExpression("['hello', 'world']", "['hello', 'world']", ~v="['hello','world']", ())
|
||||||
"['hello', 'world']",
|
testToExpression("([0,1,2])[1]", "($_atIndex_$)([0, 1, 2], 1)", ~v="1", ())
|
||||||
"{(:$_constructArray_$ ('hello' 'world'))}",
|
|
||||||
~v="['hello','world']",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
testToExpression("([0,1,2])[1]", "{(:$_atIndex_$ (:$_constructArray_$ (0 1 2)) 1)}", ~v="1", ())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("records", () => {
|
describe("records", () => {
|
||||||
testToExpression(
|
testToExpression("{a: 1, b: 2}", "{'a': 1, 'b': 2}", ~v="{a: 1,b: 2}", ())
|
||||||
"{a: 1, b: 2}",
|
testToExpression("{1+0: 1, 2+0: 2}", "{(add)(1, 0): 1, (add)(2, 0): 2}", ()) // key can be any expression
|
||||||
"{(:$_constructRecord_$ (('a' 1) ('b' 2)))}",
|
testToExpression("record.property", "($_atIndex_$)(record, 'property')", ())
|
||||||
~v="{a: 1,b: 2}",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
testToExpression(
|
|
||||||
"{1+0: 1, 2+0: 2}",
|
|
||||||
"{(:$_constructRecord_$ (((:add 1 0) 1) ((:add 2 0) 2)))}",
|
|
||||||
(),
|
|
||||||
) // key can be any expression
|
|
||||||
testToExpression("record.property", "{(:$_atIndex_$ :record 'property')}", ())
|
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"record={property: 1}; record.property",
|
"record={property: 1}; record.property",
|
||||||
"{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_atIndex_$ :record 'property')}",
|
"record = {{'property': 1}}; ($_atIndex_$)(record, 'property')",
|
||||||
~v="1",
|
~v="1",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("comments", () => {
|
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 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("1 /* This is a multi line comment */", "1", ~v="1", ())
|
||||||
testToExpression("/* This is a multi line comment */ 1", "{1}", ~v="1", ())
|
testToExpression("/* This is a multi line comment */ 1", "1", ~v="1", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("ternary operator", () => {
|
describe("ternary operator", () => {
|
||||||
testToExpression("true ? 1 : 0", "{(:$$_ternary_$$ true 1 0)}", ~v="1", ())
|
testToExpression("true ? 1 : 0", "true ? (1) : (0)", ~v="1", ())
|
||||||
testToExpression("false ? 1 : 0", "{(:$$_ternary_$$ false 1 0)}", ~v="0", ())
|
testToExpression("false ? 1 : 0", "false ? (1) : (0)", ~v="0", ())
|
||||||
testToExpression(
|
testToExpression("true ? 1 : false ? 2 : 0", "true ? (1) : (false ? (2) : (0))", ~v="1", ()) // nested ternary
|
||||||
"true ? 1 : false ? 2 : 0",
|
testToExpression("false ? 1 : false ? 2 : 0", "false ? (1) : (false ? (2) : (0))", ~v="0", ()) // nested ternary
|
||||||
"{(:$$_ternary_$$ true 1 (:$$_ternary_$$ false 2 0))}",
|
|
||||||
~v="1",
|
|
||||||
(),
|
|
||||||
) // nested ternary
|
|
||||||
testToExpression(
|
|
||||||
"false ? 1 : false ? 2 : 0",
|
|
||||||
"{(:$$_ternary_$$ false 1 (:$$_ternary_$$ false 2 0))}",
|
|
||||||
~v="0",
|
|
||||||
(),
|
|
||||||
) // nested ternary
|
|
||||||
describe("ternary bindings", () => {
|
describe("ternary bindings", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
// expression binding
|
// expression binding
|
||||||
"f(a) = a > 5 ? 1 : 0; f(6)",
|
"f(a) = a > 5 ? 1 : 0; f(6)",
|
||||||
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:f 6)}",
|
"f = {|a| {(larger)(a, 5) ? (1) : (0)}}; (f)(6)",
|
||||||
~v="1",
|
~v="1",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
// when true binding
|
// when true binding
|
||||||
"f(a) = a > 5 ? a : 0; f(6)",
|
"f(a) = a > 5 ? a : 0; f(6)",
|
||||||
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:f 6)}",
|
"f = {|a| {(larger)(a, 5) ? (a) : (0)}}; (f)(6)",
|
||||||
~v="6",
|
~v="6",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
// when false binding
|
// when false binding
|
||||||
"f(a) = a < 5 ? 1 : a; f(6)",
|
"f(a) = a < 5 ? 1 : a; f(6)",
|
||||||
"{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:f 6)}",
|
"f = {|a| {(smaller)(a, 5) ? (1) : (a)}}; (f)(6)",
|
||||||
~v="6",
|
~v="6",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|
@ -131,23 +101,23 @@ describe("Peggy to Expression", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("if then else", () => {
|
describe("if then else", () => {
|
||||||
testToExpression("if true then 2 else 3", "{(:$$_ternary_$$ true {2} {3})}", ())
|
testToExpression("if true then 2 else 3", "true ? ({2}) : ({3})", ())
|
||||||
testToExpression("if true then {2} else {3}", "{(:$$_ternary_$$ true {2} {3})}", ())
|
testToExpression("if true then {2} else {3}", "true ? ({2}) : ({3})", ())
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"if false then {2} else if false then {4} else {5}",
|
"if false then {2} else if false then {4} else {5}",
|
||||||
"{(:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5}))}",
|
"false ? ({2}) : (false ? ({4}) : ({5}))",
|
||||||
(),
|
(),
|
||||||
) //nested if
|
) //nested if
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("pipe", () => {
|
describe("pipe", () => {
|
||||||
testToExpression("1 -> add(2)", "{(:add 1 2)}", ~v="3", ())
|
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)", "(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) * 3", "(multiply)((add)(1, 2), 3)", ~v="9", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("elixir pipe", () => {
|
describe("elixir pipe", () => {
|
||||||
testToExpression("1 |> add(2)", "{(:add 1 2)}", ~v="3", ())
|
testToExpression("1 |> add(2)", "(add)(1, 2)", ~v="3", ())
|
||||||
})
|
})
|
||||||
|
|
||||||
// see testParse for priorities of to and credibleIntervalToDistribution
|
// see testParse for priorities of to and credibleIntervalToDistribution
|
||||||
|
@ -157,43 +127,28 @@ describe("Peggy to Expression", () => {
|
||||||
// Like lambdas they have a local scope.
|
// Like lambdas they have a local scope.
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"y=99; x={y=1; y}",
|
"y=99; x={y=1; y}",
|
||||||
"{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y})}",
|
"y = {99}; x = {y = {1}; y}",
|
||||||
~v="@{x: 1,y: 99}",
|
// "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y}); (:$_endOfOuterBlock_$ () ())}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("lambda", () => {
|
describe("lambda", () => {
|
||||||
testToExpression("{|x| x}", "{(:$$_lambda_$$ [x] {:x})}", ~v="lambda(x=>internal code)", ())
|
testToExpression("{|x| x}", "{|x| x}", ~v="lambda(x=>internal code)", ())
|
||||||
testToExpression(
|
testToExpression("f={|x| x}", "f = {|x| x}", ())
|
||||||
"f={|x| x}",
|
testToExpression("f(x)=x", "f = {|x| {x}}", ()) // Function definitions are lambda assignments
|
||||||
"{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})})}",
|
testToExpression("f(x)=x ? 1 : 0", "f = {|x| {x ? (1) : (0)}}", ())
|
||||||
~v="@{f: lambda(x=>internal code)}",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
testToExpression(
|
|
||||||
"f(x)=x",
|
|
||||||
"{(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))}",
|
|
||||||
~v="@{f: lambda(x=>internal code)}",
|
|
||||||
(),
|
|
||||||
) // Function definitions are lambda assignments
|
|
||||||
testToExpression(
|
|
||||||
"f(x)=x ? 1 : 0",
|
|
||||||
"{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ :x 1 0)}))}",
|
|
||||||
~v="@{f: lambda(x=>internal code)}",
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("module", () => {
|
describe("module", () => {
|
||||||
// testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ())
|
// testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ())
|
||||||
// Only.test("stdlibrary", () => {
|
// Only.test("stdlibrary", () => {
|
||||||
// ReducerInterface_StdLib.internalStdLib
|
// SquiggleLibrary_StdLib.stdLib
|
||||||
// ->IEvBindings
|
// ->IEvBindings
|
||||||
// ->InternalExpressionValue.toString
|
// ->Reducer_Value.toString
|
||||||
// ->expect
|
// ->expect
|
||||||
// ->toBe("")
|
// ->toBe("")
|
||||||
// })
|
// })
|
||||||
testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ())
|
testToExpression("Math.pi", "Math.pi", ~v="3.141592653589793", ())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,92 +5,92 @@ describe("Peggy Types to Expression", () => {
|
||||||
describe("type of", () => {
|
describe("type of", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"p: number",
|
"p: number",
|
||||||
"{(:$_typeOf_$ :p #number)}",
|
"{(:$_typeOf_$ :p #number); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {p: #number}}",
|
// ~v="@{_typeReferences_: {p: #number}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("type alias", () => {
|
describe("type alias", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"type index=number",
|
"type index=number",
|
||||||
"{(:$_typeAlias_$ #index #number)}",
|
"{(:$_typeAlias_$ #index #number); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeAliases_: {index: #number}}",
|
// ~v="@{_typeAliases_: {index: #number}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("type or", () => {
|
describe("type or", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"answer: number|string|distribution",
|
"answer: number|string|distribution",
|
||||||
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution))))}",
|
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ #number #string #distribution))); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}",
|
// ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("type function", () => {
|
describe("type function", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"f: number=>number=>number",
|
"f: number=>number=>number",
|
||||||
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number))))}",
|
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ #number #number #number))); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}",
|
// ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"f: number=>number",
|
"f: number=>number",
|
||||||
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number))))}",
|
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ #number #number))); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}",
|
// ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("high priority contract", () => {
|
describe("high priority contract", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"answer: number<-min(1)<-max(100)|string",
|
"answer: number<-min(1)<-max(100)|string",
|
||||||
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}",
|
"{(:$_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'}}}",
|
// ~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"answer: number<-memberOf([1,3,5])",
|
"answer: number<-memberOf([1,3,5])",
|
||||||
"{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ (1 3 5))))}",
|
"{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ 1 3 5))); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
// ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"answer: number<-min(1)",
|
"answer: number<-min(1)",
|
||||||
"{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1))}",
|
"{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1)); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
// ~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"answer: number<-max(10)",
|
"answer: number<-max(10)",
|
||||||
"{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10))}",
|
"{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10)); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
// ~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"answer: number<-min(1)<-max(10)",
|
"answer: number<-min(1)<-max(10)",
|
||||||
"{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10))}",
|
"{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10)); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
// ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"answer: number<-max(10)<-min(1)",
|
"answer: number<-max(10)<-min(1)",
|
||||||
"{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1))}",
|
"{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1)); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
// ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("low priority contract", () => {
|
describe("low priority contract", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"answer: number | string $ opaque",
|
"answer: number | string $ opaque",
|
||||||
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}",
|
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ #number #string)))); (:$_endOfOuterBlock_$ () ())}",
|
||||||
~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}",
|
// ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
describe("squiggle expressions in type contracts", () => {
|
describe("squiggle expressions in type contracts", () => {
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
|
"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)))}",
|
"{(:$_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]}",
|
// ~v="@{_typeAliases_: {odds: {memberOf: [1,3,5,7,9],typeIdentifier: #number,typeTag: 'typeIdentifier'}},odds1: [1,3,5],odds2: [7,9]}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,18 +3,18 @@ open Reducer_Peggy_TestHelpers
|
||||||
|
|
||||||
describe("Peggy void", () => {
|
describe("Peggy void", () => {
|
||||||
//literal
|
//literal
|
||||||
testToExpression("()", "{()}", ~v="()", ())
|
testToExpression("()", "()", ~v="()", ())
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"fn()=1",
|
"fn()=1",
|
||||||
"{(:$_let_$ :fn (:$$_lambda_$$ [_] {1}))}",
|
"fn = {|_| {1}}",
|
||||||
~v="@{fn: lambda(_=>internal code)}",
|
// ~v="@{fn: lambda(_=>internal code)}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
testToExpression("fn()=1; fn()", "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:fn ())}", ~v="1", ())
|
testToExpression("fn()=1; fn()", "fn = {|_| {1}}; (fn)(())", ~v="1", ())
|
||||||
testToExpression(
|
testToExpression(
|
||||||
"fn(a)=(); call fn(1)",
|
"fn(a)=(); call fn(1)",
|
||||||
"{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)})}",
|
"fn = {|a| {()}}; _ = {(fn)(1)}",
|
||||||
~v="@{_: (),fn: lambda(a=>internal code)}",
|
// ~v="@{_: (),fn: lambda(a=>internal code)}",
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
module Expression = Reducer_Expression
|
||||||
module ExpressionT = Reducer_Expression_T
|
module ExpressionT = Reducer_Expression_T
|
||||||
module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue
|
|
||||||
module ErrorValue = Reducer_ErrorValue
|
|
||||||
|
|
||||||
open Jest
|
open Jest
|
||||||
open Expect
|
open Expect
|
||||||
|
@ -8,30 +7,22 @@ open Expect
|
||||||
let unwrapRecord = rValue =>
|
let unwrapRecord = rValue =>
|
||||||
rValue->Belt.Result.flatMap(value =>
|
rValue->Belt.Result.flatMap(value =>
|
||||||
switch value {
|
switch value {
|
||||||
| ExternalExpressionValue.EvRecord(aRecord) => Ok(aRecord)
|
| Reducer_T.IEvRecord(aRecord) => Ok(aRecord)
|
||||||
| _ => ErrorValue.RETodo("TODO: External bindings must be returned")->Error
|
| _ => SqError.Message.RETodo("TODO: Internal bindings must be returned")->Error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
let expectParseToBe = (expr: string, answer: string) =>
|
let expectParseToBe = (code: string, answer: string) =>
|
||||||
Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer)
|
Expression.BackCompatible.parse(code)->ExpressionT.toStringResult->expect->toBe(answer)
|
||||||
|
|
||||||
let expectEvalToBe = (expr: string, answer: string) =>
|
let expectEvalToBe = (code: string, answer: string) =>
|
||||||
Reducer.evaluate(expr)
|
Expression.BackCompatible.evaluateString(code)->Reducer_Value.toStringResult->expect->toBe(answer)
|
||||||
->Reducer_Helpers.rRemoveDefaultsExternal
|
|
||||||
->ExternalExpressionValue.toStringResult
|
let expectEvalError = (code: string) =>
|
||||||
|
Expression.BackCompatible.evaluateString(code)
|
||||||
|
->Reducer_Value.toStringResult
|
||||||
->expect
|
->expect
|
||||||
->toBe(answer)
|
->toMatch("Error\(")
|
||||||
|
|
||||||
let expectEvalError = (expr: string) =>
|
|
||||||
Reducer.evaluate(expr)->ExternalExpressionValue.toStringResult->expect->toMatch("Error\(")
|
|
||||||
|
|
||||||
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
|
|
||||||
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
|
|
||||||
->Reducer_Helpers.rRemoveDefaultsExternal
|
|
||||||
->ExternalExpressionValue.toStringResult
|
|
||||||
->expect
|
|
||||||
->toBe(answer)
|
|
||||||
|
|
||||||
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
|
||||||
let testDescriptionParseToBe = (desc, expr, answer) =>
|
let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||||
|
@ -40,18 +31,12 @@ let testDescriptionParseToBe = (desc, expr, answer) =>
|
||||||
let testEvalError = expr => test(expr, () => expectEvalError(expr))
|
let testEvalError = expr => test(expr, () => expectEvalError(expr))
|
||||||
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
||||||
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
|
||||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
|
||||||
test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
|
||||||
|
|
||||||
module MySkip = {
|
module MySkip = {
|
||||||
let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
|
||||||
let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(expr, answer))
|
let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(expr, answer))
|
||||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
|
||||||
Skip.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
|
||||||
}
|
}
|
||||||
module MyOnly = {
|
module MyOnly = {
|
||||||
let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
|
||||||
let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(expr, answer))
|
let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(expr, answer))
|
||||||
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
|
|
||||||
Only.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
module BindingsReplacer = Reducer_Expression_BindingsReplacer
|
|
||||||
module Expression = Reducer_Expression
|
|
||||||
// module ExpressionValue = ReducerInterface.ExpressionValue
|
|
||||||
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
|
|
||||||
module ExpressionWithContext = Reducer_ExpressionWithContext
|
|
||||||
module Macro = Reducer_Expression_Macro
|
|
||||||
module T = Reducer_Expression_T
|
|
||||||
module Bindings = Reducer_Bindings
|
|
||||||
|
|
||||||
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,
|
|
||||||
InternalExpressionValue.defaultEnvironment,
|
|
||||||
Expression.reduceExpression,
|
|
||||||
)
|
|
||||||
->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,
|
|
||||||
InternalExpressionValue.defaultEnvironment,
|
|
||||||
Expression.reduceExpression,
|
|
||||||
)
|
|
||||||
->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)
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
module Expression = Reducer_Expression
|
|
||||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
|
||||||
module Bindings = Reducer_Bindings
|
|
||||||
module T = Reducer_Type_T
|
|
||||||
module TypeCompile = Reducer_Type_Compile
|
|
||||||
|
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
let myIevEval = (aTypeSourceCode: string) =>
|
|
||||||
TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression)
|
|
||||||
let myIevEvalToString = (aTypeSourceCode: string) =>
|
|
||||||
myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult
|
|
||||||
|
|
||||||
let myIevExpectEqual = (aTypeSourceCode, answer) =>
|
|
||||||
expect(myIevEvalToString(aTypeSourceCode))->toEqual(answer)
|
|
||||||
|
|
||||||
let myIevTest = (test, aTypeSourceCode, answer) =>
|
|
||||||
test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer))
|
|
||||||
|
|
||||||
let myTypeEval = (aTypeSourceCode: string) =>
|
|
||||||
TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression)
|
|
||||||
let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult
|
|
||||||
|
|
||||||
let myTypeExpectEqual = (aTypeSourceCode, answer) =>
|
|
||||||
expect(myTypeEvalToString(aTypeSourceCode))->toEqual(answer)
|
|
||||||
|
|
||||||
let myTypeTest = (test, aTypeSourceCode, answer) =>
|
|
||||||
test(aTypeSourceCode, () => myTypeExpectEqual(aTypeSourceCode, answer))
|
|
||||||
|
|
||||||
// | ItTypeIdentifier(string)
|
|
||||||
myTypeTest(test, "number", "number")
|
|
||||||
myTypeTest(test, "(number)", "number")
|
|
||||||
// | ItModifiedType({modifiedType: iType})
|
|
||||||
myIevTest(test, "number<-min(0)", "Ok({min: 0,typeIdentifier: #number,typeTag: 'typeIdentifier'})")
|
|
||||||
myTypeTest(test, "number<-min(0)", "number<-min(0)")
|
|
||||||
// | ItTypeOr({typeOr: array<iType>})
|
|
||||||
myTypeTest(test, "number | string", "(number | string)")
|
|
||||||
// | ItTypeFunction({inputs: array<iType>, output: iType})
|
|
||||||
myTypeTest(test, "number => number => number", "(number => number => number)")
|
|
||||||
// | ItTypeArray({element: iType})
|
|
||||||
myIevTest(test, "[number]", "Ok({element: #number,typeTag: 'typeArray'})")
|
|
||||||
myTypeTest(test, "[number]", "[number]")
|
|
||||||
// | ItTypeTuple({elements: array<iType>})
|
|
||||||
myTypeTest(test, "[number, string]", "[number, string]")
|
|
||||||
// | ItTypeRecord({properties: Belt.Map.String.t<iType>})
|
|
||||||
myIevTest(
|
|
||||||
test,
|
|
||||||
"{age: number, name: string}",
|
|
||||||
"Ok({properties: {age: #number,name: #string},typeTag: 'typeRecord'})",
|
|
||||||
)
|
|
||||||
myTypeTest(test, "{age: number, name: string}", "{age: number, name: string}")
|
|
|
@ -1,41 +0,0 @@
|
||||||
module Expression = Reducer_Expression
|
|
||||||
module ExpressionT = Reducer_Expression_T
|
|
||||||
module ErrorValue = Reducer_ErrorValue
|
|
||||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
|
||||||
module Bindings = Reducer_Bindings
|
|
||||||
module T = Reducer_Type_T
|
|
||||||
module TypeChecker = Reducer_Type_TypeChecker
|
|
||||||
|
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
|
|
||||||
'v,
|
|
||||||
ErrorValue.t,
|
|
||||||
> => {
|
|
||||||
let reducerFn = Expression.reduceExpression
|
|
||||||
let rResult =
|
|
||||||
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr =>
|
|
||||||
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment)
|
|
||||||
)
|
|
||||||
rResult->Belt.Result.flatMap(result =>
|
|
||||||
switch result {
|
|
||||||
| IEvArray(args) => TypeChecker.checkArguments(aTypeSourceCode, args, reducerFn)
|
|
||||||
| _ => Js.Exn.raiseError("Arguments has to be an array")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let myCheckArguments = (aTypeSourceCode: string, sourceCode: string): string =>
|
|
||||||
switch checkArgumentsSourceCode(aTypeSourceCode, sourceCode) {
|
|
||||||
| Ok(_) => "Ok"
|
|
||||||
| Error(error) => ErrorValue.errorToString(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
let myCheckArgumentsExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
|
|
||||||
expect(myCheckArguments(aTypeSourceCode, sourceCode))->toEqual(answer)
|
|
||||||
|
|
||||||
let myCheckArgumentsTest = (test, aTypeSourceCode, sourceCode, answer) =>
|
|
||||||
test(aTypeSourceCode, () => myCheckArgumentsExpectEqual(aTypeSourceCode, sourceCode, answer))
|
|
||||||
|
|
||||||
myCheckArgumentsTest(test, "number=>number=>number", "[1,2]", "Ok")
|
|
|
@ -1,72 +0,0 @@
|
||||||
module Expression = Reducer_Expression
|
|
||||||
module ExpressionT = Reducer_Expression_T
|
|
||||||
module ErrorValue = Reducer_ErrorValue
|
|
||||||
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
|
|
||||||
module Bindings = Reducer_Bindings
|
|
||||||
module T = Reducer_Type_T
|
|
||||||
module TypeChecker = Reducer_Type_TypeChecker
|
|
||||||
|
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
// In development, you are expected to use TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn).
|
|
||||||
// isTypeOfSourceCode is written to use strings instead of expression values.
|
|
||||||
|
|
||||||
let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
|
|
||||||
'v,
|
|
||||||
ErrorValue.t,
|
|
||||||
> => {
|
|
||||||
let reducerFn = Expression.reduceExpression
|
|
||||||
let rResult =
|
|
||||||
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr =>
|
|
||||||
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment)
|
|
||||||
)
|
|
||||||
rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn))
|
|
||||||
}
|
|
||||||
|
|
||||||
let myTypeCheck = (aTypeSourceCode: string, sourceCode: string): string =>
|
|
||||||
switch isTypeOfSourceCode(aTypeSourceCode, sourceCode) {
|
|
||||||
| Ok(_) => "Ok"
|
|
||||||
| Error(error) => ErrorValue.errorToString(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
let myTypeCheckExpectEqual = (aTypeSourceCode, sourceCode, answer) =>
|
|
||||||
expect(myTypeCheck(aTypeSourceCode, sourceCode))->toEqual(answer)
|
|
||||||
|
|
||||||
let myTypeCheckTest = (test, aTypeSourceCode, sourceCode, answer) =>
|
|
||||||
test(aTypeSourceCode, () => myTypeCheckExpectEqual(aTypeSourceCode, sourceCode, answer))
|
|
||||||
|
|
||||||
myTypeCheckTest(test, "number", "1", "Ok")
|
|
||||||
myTypeCheckTest(test, "number", "'2'", "Expected type: number but got: '2'")
|
|
||||||
myTypeCheckTest(test, "string", "3", "Expected type: string but got: 3")
|
|
||||||
myTypeCheckTest(test, "string", "'a'", "Ok")
|
|
||||||
myTypeCheckTest(test, "[number]", "[1,2,3]", "Ok")
|
|
||||||
myTypeCheckTest(test, "[number]", "['a','a','a']", "Expected type: number but got: 'a'")
|
|
||||||
myTypeCheckTest(test, "[number]", "[1,'a',3]", "Expected type: number but got: 'a'")
|
|
||||||
myTypeCheckTest(test, "[number, string]", "[1,'a']", "Ok")
|
|
||||||
myTypeCheckTest(test, "[number, string]", "[1, 2]", "Expected type: string but got: 2")
|
|
||||||
myTypeCheckTest(
|
|
||||||
test,
|
|
||||||
"[number, string, string]",
|
|
||||||
"[1,'a']",
|
|
||||||
"Expected type: [number, string, string] but got: [1,'a']",
|
|
||||||
)
|
|
||||||
myTypeCheckTest(
|
|
||||||
test,
|
|
||||||
"[number, string]",
|
|
||||||
"[1,'a', 3]",
|
|
||||||
"Expected type: [number, string] but got: [1,'a',3]",
|
|
||||||
)
|
|
||||||
myTypeCheckTest(test, "{age: number, name: string}", "{age: 1, name: 'a'}", "Ok")
|
|
||||||
myTypeCheckTest(
|
|
||||||
test,
|
|
||||||
"{age: number, name: string}",
|
|
||||||
"{age: 1, name: 'a', job: 'IT'}",
|
|
||||||
"Expected type: {age: number, name: string} but got: {age: 1,job: 'IT',name: 'a'}",
|
|
||||||
)
|
|
||||||
myTypeCheckTest(test, "number | string", "1", "Ok")
|
|
||||||
myTypeCheckTest(test, "date | string", "1", "Expected type: (date | string) but got: 1")
|
|
||||||
myTypeCheckTest(test, "number<-min(10)", "10", "Ok")
|
|
||||||
myTypeCheckTest(test, "number<-min(10)", "0", "Expected type: number<-min(10) but got: 0")
|
|
||||||
myTypeCheckTest(test, "any", "0", "Ok")
|
|
||||||
myTypeCheckTest(test, "any", "'a'", "Ok")
|
|
|
@ -1,123 +0,0 @@
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
module DispatchT = Reducer_Dispatch_T
|
|
||||||
module Expression = Reducer_Expression
|
|
||||||
module ExpressionT = Reducer_Expression_T
|
|
||||||
module TypeCompile = Reducer_Type_Compile
|
|
||||||
module TypeChecker = Reducer_Type_TypeChecker
|
|
||||||
open ReducerInterface_InternalExpressionValue
|
|
||||||
|
|
||||||
type errorValue = Reducer_ErrorValue.errorValue
|
|
||||||
|
|
||||||
// Let's build a function to replace switch statements
|
|
||||||
// In dispatchChainPiece, we execute an return the result of execution if there is a type match.
|
|
||||||
// Otherwise we return None so that the call chain can continue.
|
|
||||||
// So we want to build a function like
|
|
||||||
// dispatchChainPiece = (call: functionCall, environment): option<result<internalExpressionValue, errorValue>>
|
|
||||||
|
|
||||||
// Now lets make the dispatchChainPiece itself.
|
|
||||||
// Note that I am not passing the reducer to the dispatchChainPiece as an argument because it is in the context anyway.
|
|
||||||
// Keep in mind that reducerFn is necessary for map/reduce so dispatchChainPiece should have a reducerFn in context.
|
|
||||||
|
|
||||||
let makeMyDispatchChainPiece = (reducer: ExpressionT.reducerFn): DispatchT.dispatchChainPiece => {
|
|
||||||
// Let's have a pure implementations
|
|
||||||
module Implementation = {
|
|
||||||
let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b)
|
|
||||||
let arrayConcat = (
|
|
||||||
a: Js.Array2.t<internalExpressionValue>,
|
|
||||||
b: Js.Array2.t<internalExpressionValue>,
|
|
||||||
): Js.Array2.t<internalExpressionValue> => Js.Array2.concat(a, b)
|
|
||||||
let plot = _r => "yey, plotted"
|
|
||||||
}
|
|
||||||
|
|
||||||
let extractStringString = args =>
|
|
||||||
switch args {
|
|
||||||
| [IEvString(a), IEvString(b)] => (a, b)
|
|
||||||
| _ => raise(Reducer_Exception.ImpossibleException("extractStringString developer error"))
|
|
||||||
}
|
|
||||||
|
|
||||||
let extractArrayArray = args =>
|
|
||||||
switch args {
|
|
||||||
| [IEvArray(a), IEvArray(b)] => (a, b)
|
|
||||||
| _ => raise(Reducer_Exception.ImpossibleException("extractArrayArray developer error"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's bridge the pure implementation to expression values
|
|
||||||
module Bridge = {
|
|
||||||
let stringConcat: DispatchT.genericIEvFunction = (args, _environment) => {
|
|
||||||
let (a, b) = extractStringString(args)
|
|
||||||
Implementation.stringConcat(a, b)->IEvString->Ok
|
|
||||||
}
|
|
||||||
let arrayConcat: DispatchT.genericIEvFunction = (args, _environment) => {
|
|
||||||
let (a, b) = extractArrayArray(args)
|
|
||||||
Implementation.arrayConcat(a, b)->IEvArray->Ok
|
|
||||||
}
|
|
||||||
let plot: DispatchT.genericIEvFunction = (args, _environment) => {
|
|
||||||
switch args {
|
|
||||||
// Just assume that we are doing the business of extracting and converting the deep record
|
|
||||||
| [IEvRecord(_)] => Implementation.plot({"title": "This is a plot"})->IEvString->Ok
|
|
||||||
| _ => raise(Reducer_Exception.ImpossibleException("plot developer error"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// concat functions are to illustrate polymoprhism. And the plot function is to illustrate complex types
|
|
||||||
let jumpTable = [
|
|
||||||
(
|
|
||||||
"concat",
|
|
||||||
TypeCompile.fromTypeExpressionExn("string=>string=>string", reducer),
|
|
||||||
Bridge.stringConcat,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"concat",
|
|
||||||
TypeCompile.fromTypeExpressionExn("[any]=>[any]=>[any]", reducer),
|
|
||||||
Bridge.arrayConcat,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"plot",
|
|
||||||
TypeCompile.fromTypeExpressionExn(
|
|
||||||
// Nested complex types are available
|
|
||||||
// records {property: type}
|
|
||||||
// arrays [type]
|
|
||||||
// tuples [type, type]
|
|
||||||
// <- type contracts are available naturally and they become part of dispatching
|
|
||||||
// Here we are not enumerating the possibilities because type checking has a dedicated test
|
|
||||||
"{title: string, line: {width: number, color: string}}=>string",
|
|
||||||
reducer,
|
|
||||||
),
|
|
||||||
Bridge.plot,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
//Here we are creating a dispatchChainPiece function that will do the actual dispatch from the jumpTable
|
|
||||||
Reducer_Dispatch_ChainPiece.makeFromTypes(jumpTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
// And finally, let's write a library dispatch for our external library
|
|
||||||
// Exactly the same as the one used in real life
|
|
||||||
let _dispatch = (
|
|
||||||
call: functionCall,
|
|
||||||
environment,
|
|
||||||
reducer: Reducer_Expression_T.reducerFn,
|
|
||||||
chain,
|
|
||||||
): result<internalExpressionValue, 'e> => {
|
|
||||||
let dispatchChainPiece = makeMyDispatchChainPiece(reducer)
|
|
||||||
dispatchChainPiece(call, environment)->E.O2.defaultFn(() => chain(call, environment, reducer))
|
|
||||||
}
|
|
||||||
|
|
||||||
// What is important about this implementation?
|
|
||||||
// A) Exactly the same function jump table can be used to create type guarded lambda functions
|
|
||||||
// Guarded lambda functions will be the basis of the next version of Squiggle
|
|
||||||
// B) Complicated recursive record types are not a problem.
|
|
||||||
|
|
||||||
describe("Type Dispatch", () => {
|
|
||||||
let reducerFn = Expression.reduceExpression
|
|
||||||
let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn)
|
|
||||||
test("stringConcat", () => {
|
|
||||||
let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")])
|
|
||||||
|
|
||||||
let result = dispatchChainPiece(call, defaultEnvironment)
|
|
||||||
expect(result)->toEqual(Some(Ok(IEvString("helloworld"))))
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
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')",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
|
@ -0,0 +1,13 @@
|
||||||
|
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)]",
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
})
|
|
@ -1,14 +0,0 @@
|
||||||
open Jest
|
|
||||||
open Reducer_TestHelpers
|
|
||||||
|
|
||||||
describe("Eval with Bindings", () => {
|
|
||||||
testEvalBindingsToBe("x", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(1)")
|
|
||||||
testEvalBindingsToBe("x+1", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(2)")
|
|
||||||
testParseToBe("y = x+1; y", "Ok({(:$_let_$ :y {(:add :x 1)}); :y})")
|
|
||||||
testEvalBindingsToBe("y = x+1; y", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(2)")
|
|
||||||
testEvalBindingsToBe(
|
|
||||||
"y = x+1",
|
|
||||||
list{("x", ExternalExpressionValue.EvNumber(1.))},
|
|
||||||
"Ok(@{x: 1,y: 2})",
|
|
||||||
)
|
|
||||||
})
|
|
|
@ -2,11 +2,15 @@ open Jest
|
||||||
open Reducer_TestHelpers
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
describe("Parse function assignment", () => {
|
describe("Parse function assignment", () => {
|
||||||
testParseToBe("f(x)=x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))})")
|
testParseToBe("f(x)=x", "Ok(f = {|x| {x}})")
|
||||||
testParseToBe("f(x)=2*x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)}))})")
|
testParseToBe("f(x)=2*x", "Ok(f = {|x| {(multiply)(2, x)}})")
|
||||||
//MathJs does not allow blocks in function definitions
|
//MathJs does not allow blocks in function definitions
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Evaluate function assignment", () => {
|
describe("Evaluate function assignment", () => {
|
||||||
testEvalToBe("f(x)=x; f(1)", "Ok(1)")
|
testEvalToBe("f(x)=x; f(1)", "Ok(1)")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("Shadowing", () => {
|
||||||
|
testEvalToBe("x = 5; f(y) = x*y; x = 6; f(2)", "Ok(10)")
|
||||||
|
})
|
||||||
|
|
|
@ -34,38 +34,29 @@ describe("symbol not defined", () => {
|
||||||
testEvalToBe("f(x)=x(y); f(f)", "Error(y is 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; 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(z)", "Error(z is not defined)")
|
||||||
testEvalToBe("f(x)=x(y); f(2)", "Error(2 is not a function)")
|
testEvalToBe("f(x)=x(y); f(2)", "Error(y is not defined)")
|
||||||
testEvalToBe("f(x)=x(1); f(2)", "Error(2 is not a function)")
|
testEvalToBe("f(x)=x(1); f(2)", "Error(2 is not a function)")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("call and bindings", () => {
|
describe("call and bindings", () => {
|
||||||
testEvalToBe("f(x)=x+1", "Ok(@{f: lambda(x=>internal code)})")
|
testEvalToBe("f(x)=x+1; f(0)", "Ok(1)")
|
||||||
testEvalToBe("f(x)=x+1; f(1)", "Ok(2)")
|
testEvalToBe("f(x)=x+1; f(1)", "Ok(2)")
|
||||||
testEvalToBe("f=1;y=2", "Ok(@{f: 1,y: 2})")
|
testEvalToBe("f=1;y=2", "Ok(())")
|
||||||
testEvalToBe("f(x)=x+1; y=f(1)", "Ok(@{f: lambda(x=>internal code),y: 2})")
|
testEvalToBe("f(x)=x+1; y=f(1); y", "Ok(2)")
|
||||||
testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)")
|
testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)")
|
||||||
testEvalToBe("f(x)=x+1; y=f(1); z=f(1)", "Ok(@{f: lambda(x=>internal code),y: 2,z: 2})")
|
testEvalToBe("f(x)=x+1; y=f(1); z=f(1); z", "Ok(2)")
|
||||||
testEvalToBe(
|
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(0)", "Ok(2)")
|
||||||
"f(x)=x+1; g(x)=f(x)+1",
|
testParseToBe("f=99; g(x)=f; g(2)", "Ok(f = {99}; g = {|x| {f}}; (g)(2))")
|
||||||
"Ok(@{f: lambda(x=>internal code),g: lambda(x=>internal code)})",
|
|
||||||
)
|
|
||||||
testParseToBe(
|
|
||||||
"f=99; g(x)=f; g(2)",
|
|
||||||
"Ok({(:$_let_$ :f {99}); (:$_let_$ :g (:$$_lambda_$$ [x] {:f})); (:g 2)})",
|
|
||||||
)
|
|
||||||
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
|
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
|
||||||
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
|
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
|
||||||
testEvalToBe(
|
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; y=g(2); y", "Ok(4)")
|
||||||
"f(x)=x+1; g(x)=f(x)+1; y=g(2)",
|
|
||||||
"Ok(@{f: lambda(x=>internal code),g: lambda(x=>internal code),y: 4})",
|
|
||||||
)
|
|
||||||
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(2)", "Ok(4)")
|
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(2)", "Ok(4)")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("function tricks", () => {
|
describe("function tricks", () => {
|
||||||
testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed
|
testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed
|
||||||
testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)")
|
testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)")
|
||||||
testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok(@{g: lambda(x=>internal code),y: 2})")
|
testEvalToBe("y=2;g(x)=inspect(y)+1;y", "Ok(2)")
|
||||||
MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout?
|
MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout?
|
||||||
MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters
|
MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters
|
||||||
testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))")
|
testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))")
|
||||||
|
@ -73,10 +64,7 @@ describe("function tricks", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("lambda in structures", () => {
|
describe("lambda in structures", () => {
|
||||||
testEvalToBe(
|
testEvalToBe("myadd(x,y)=x+y; z=[myadd]", "Ok(())")
|
||||||
"myadd(x,y)=x+y; z=[myadd]",
|
|
||||||
"Ok(@{myadd: lambda(x,y=>internal code),z: [lambda(x,y=>internal code)]})",
|
|
||||||
)
|
|
||||||
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))")
|
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))")
|
||||||
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)")
|
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)")
|
||||||
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})")
|
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})")
|
||||||
|
|
|
@ -5,3 +5,7 @@ Skip.describe("map reduce (sam)", () => {
|
||||||
testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???")
|
testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???")
|
||||||
testEvalToBe("addone(x)=x+1; map(2, {x: 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,7 @@ open Jest
|
||||||
open Reducer_TestHelpers
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
describe("Parse ternary operator", () => {
|
describe("Parse ternary operator", () => {
|
||||||
testParseToBe("true ? 'YES' : 'NO'", "Ok({(:$$_ternary_$$ true 'YES' 'NO')})")
|
testParseToBe("true ? 'YES' : 'NO'", "Ok(true ? ('YES') : ('NO'))")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Evaluate ternary operator", () => {
|
describe("Evaluate ternary operator", () => {
|
||||||
|
|
|
@ -2,19 +2,29 @@ open Jest
|
||||||
open Reducer_TestHelpers
|
open Reducer_TestHelpers
|
||||||
|
|
||||||
describe("eval", () => {
|
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", () => {
|
describe("expressions", () => {
|
||||||
testEvalToBe("1", "Ok(1)")
|
testEvalToBe("1", "Ok(1)")
|
||||||
|
testEvalToBe("-1", "Ok(-1)")
|
||||||
|
testEvalToBe("1-1", "Ok(0)")
|
||||||
testEvalToBe("1+2", "Ok(3)")
|
testEvalToBe("1+2", "Ok(3)")
|
||||||
testEvalToBe("(1+2)*3", "Ok(9)")
|
testEvalToBe("(1+2)*3", "Ok(9)")
|
||||||
testEvalToBe("2>1", "Ok(true)")
|
testEvalToBe("2>1", "Ok(true)")
|
||||||
testEvalToBe("concat('a ', 'b')", "Ok('a b')")
|
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("log(10)", "Ok(2.302585092994046)")
|
||||||
testEvalToBe("cos(10)", "Ok(-0.8390715290764524)")
|
testEvalToBe("Math.cos(10)", "Ok(-0.8390715290764524)")
|
||||||
// TODO more built ins
|
// 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", () => {
|
describe("arrays", () => {
|
||||||
test("empty array", () => expectEvalToBe("[]", "Ok([])"))
|
test("empty array", () => expectEvalToBe("[]", "Ok([])"))
|
||||||
testEvalToBe("[1, 2, 3]", "Ok([1,2,3])")
|
testEvalToBe("[1, 2, 3]", "Ok([1,2,3])")
|
||||||
|
@ -48,7 +58,11 @@ describe("eval", () => {
|
||||||
testEvalToBe("x=1; y=x+1; y+1", "Ok(3)")
|
testEvalToBe("x=1; y=x+1; y+1", "Ok(3)")
|
||||||
testEvalError("1; x=1")
|
testEvalError("1; x=1")
|
||||||
testEvalError("1; 1")
|
testEvalError("1; 1")
|
||||||
testEvalToBe("x=1; x=1", "Ok(@{x: 1})")
|
testEvalToBe("x=1; x=1; x", "Ok(1)")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("blocks", () => {
|
||||||
|
testEvalToBe("x = { y = { z = 5; z * 2 }; y + 3 }; x", "Ok(13)")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -64,3 +78,33 @@ describe("test exceptions", () => {
|
||||||
// "Error(TODO: unhandled rescript exception)",
|
// "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
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
open ReducerInterface.ExternalExpressionValue
|
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
describe("ExpressionValue", () => {
|
|
||||||
test("argsToString", () => expect([EvNumber(1.), EvString("a")]->argsToString)->toBe("1,'a'"))
|
|
||||||
|
|
||||||
test("toStringFunctionCall", () =>
|
|
||||||
expect(("fn", [EvNumber(1.), EvString("a")])->toStringFunctionCall)->toBe("fn(1,'a')")
|
|
||||||
)
|
|
||||||
})
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
@@warning("-44")
|
||||||
|
module Topology = ReducerProject_Topology
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
open Expect.Operators
|
||||||
|
|
||||||
|
describe("Topology Diff", () => {
|
||||||
|
test("when equal 1x", () => {
|
||||||
|
Topology.runOrderDiff(["a"], ["a"])->expect == []
|
||||||
|
})
|
||||||
|
|
||||||
|
test("when equal 3x", () => {
|
||||||
|
Topology.runOrderDiff(["a", "b", "c"], ["a", "b", "c"])->expect == []
|
||||||
|
})
|
||||||
|
|
||||||
|
test("less dependents", () => {
|
||||||
|
Topology.runOrderDiff(["a", "b"], ["a", "b", "c", "d"])->expect == []
|
||||||
|
})
|
||||||
|
|
||||||
|
test("more dependents", () => {
|
||||||
|
Topology.runOrderDiff(["a", "b", "c", "d"], ["a", "b"])->expect == ["c", "d"]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("change midway", () => {
|
||||||
|
Topology.runOrderDiff(["a", "b", "bb", "c", "d"], ["a", "b", "c", "d"])->expect == [
|
||||||
|
"bb",
|
||||||
|
"c",
|
||||||
|
"d",
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("swap", () => {
|
||||||
|
Topology.runOrderDiff(["a", "b", "c", "d"], ["a", "c", "b", "d"])->expect == ["b", "c", "d"]
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,111 @@
|
||||||
|
@@warning("-44")
|
||||||
|
module Project = ForTS_ReducerProject
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
open Expect.Operators
|
||||||
|
|
||||||
|
describe("Parse includes", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(
|
||||||
|
project,
|
||||||
|
"main",
|
||||||
|
`
|
||||||
|
#include 'common'
|
||||||
|
x=1`,
|
||||||
|
)
|
||||||
|
Project.parseIncludes(project, "main")
|
||||||
|
test("dependencies", () => {
|
||||||
|
expect(Project.getDependencies(project, "main")) == ["common"]
|
||||||
|
})
|
||||||
|
test("dependents", () => {
|
||||||
|
expect(Project.getDependents(project, "main")) == []
|
||||||
|
})
|
||||||
|
test("getIncludes", () => {
|
||||||
|
let mainIncludes = Project.getIncludes(project, "main")
|
||||||
|
switch mainIncludes {
|
||||||
|
| Ok(includes) => expect(includes) == ["common"]
|
||||||
|
| Error(error) => fail(error->SqError.toString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test("past chain", () => {
|
||||||
|
expect(project->Project.getPastChain("main")) == ["common"]
|
||||||
|
})
|
||||||
|
test("import as variables", () => {
|
||||||
|
expect(project->Project.Private.getIncludesAsVariables("main")) == []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Parse includes", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(
|
||||||
|
project,
|
||||||
|
"main",
|
||||||
|
`
|
||||||
|
#include 'common'
|
||||||
|
#include 'myModule' as myVariable
|
||||||
|
x=1`,
|
||||||
|
)
|
||||||
|
Project.parseIncludes(project, "main")
|
||||||
|
|
||||||
|
test("dependencies", () => {
|
||||||
|
expect(Project.getDependencies(project, "main")) == ["common", "myModule"]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dependents", () => {
|
||||||
|
expect(Project.getDependents(project, "main")) == []
|
||||||
|
})
|
||||||
|
|
||||||
|
test("getIncludes", () => {
|
||||||
|
let mainIncludes = Project.getIncludes(project, "main")
|
||||||
|
switch mainIncludes {
|
||||||
|
| Ok(includes) => expect(includes) == ["common", "myModule"]
|
||||||
|
| Error(error) => fail(error->SqError.toString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("direct past chain", () => {
|
||||||
|
expect(project->Project.Private.getPastChain("main")) == ["common"]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("direct includes", () => {
|
||||||
|
expect(project->Project.Private.getDirectIncludes("main")) == ["common"]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("include as variables", () => {
|
||||||
|
expect(project->Project.Private.getIncludesAsVariables("main")) == [("myVariable", "myModule")]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Parse multiple direct includes", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(
|
||||||
|
project,
|
||||||
|
"main",
|
||||||
|
`
|
||||||
|
#include 'common'
|
||||||
|
#include 'common2'
|
||||||
|
#include 'myModule' as myVariable
|
||||||
|
x=1`,
|
||||||
|
)
|
||||||
|
Project.parseIncludes(project, "main")
|
||||||
|
test("dependencies", () => {
|
||||||
|
expect(Project.getDependencies(project, "main")) == ["common", "common2", "myModule"]
|
||||||
|
})
|
||||||
|
test("dependents", () => {
|
||||||
|
expect(Project.getDependents(project, "main")) == []
|
||||||
|
})
|
||||||
|
test("getIncludes", () => {
|
||||||
|
let mainIncludes = Project.getIncludes(project, "main")
|
||||||
|
switch mainIncludes {
|
||||||
|
| Ok(includes) => expect(includes) == ["common", "common2", "myModule"]
|
||||||
|
| Error(error) => fail(error->SqError.toString)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
test("direct past chain", () => {
|
||||||
|
expect(Project.getPastChain(project, "main")) == ["common", "common2"]
|
||||||
|
})
|
||||||
|
test("include as variables", () => {
|
||||||
|
expect(project->Project.Private.getIncludesAsVariables("main")) == [("myVariable", "myModule")]
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,201 @@
|
||||||
|
@@warning("-44")
|
||||||
|
module Project = ForTS_ReducerProject
|
||||||
|
module Bindings = Reducer_Bindings
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
open Expect.Operators
|
||||||
|
|
||||||
|
let runFetchResult = (project, sourceId) => {
|
||||||
|
Project.run(project, sourceId)
|
||||||
|
Project.getResult(project, sourceId)->Reducer_Value.toStringResult
|
||||||
|
}
|
||||||
|
|
||||||
|
let runFetchFlatBindings = (project, sourceId) => {
|
||||||
|
Project.run(project, sourceId)
|
||||||
|
Project.getBindings(project, sourceId)->Reducer_Value.toStringRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
test("test result true", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(project, "main", "true")
|
||||||
|
runFetchResult(project, "main")->expect->toBe("Ok(true)")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("test result false", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(project, "main", "false")
|
||||||
|
runFetchResult(project, "main")->expect->toBe("Ok(false)")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("test library", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(project, "main", "x=Math.pi; x")
|
||||||
|
runFetchResult(project, "main")->expect->toBe("Ok(3.141592653589793)")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("test bindings", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(project, "variables", "myVariable=666")
|
||||||
|
runFetchFlatBindings(project, "variables")->expect->toBe("{myVariable: 666}")
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("project1", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(project, "first", "x=1")
|
||||||
|
Project.setSource(project, "main", "x")
|
||||||
|
Project.setContinues(project, "main", ["first"])
|
||||||
|
|
||||||
|
test("runOrder", () => {
|
||||||
|
expect(Project.getRunOrder(project)) == ["first", "main"]
|
||||||
|
})
|
||||||
|
test("dependents first", () => {
|
||||||
|
expect(Project.getDependents(project, "first")) == ["main"]
|
||||||
|
})
|
||||||
|
test("dependencies first", () => {
|
||||||
|
expect(Project.getDependencies(project, "first")) == []
|
||||||
|
})
|
||||||
|
test("dependents main", () => {
|
||||||
|
expect(Project.getDependents(project, "main")) == []
|
||||||
|
})
|
||||||
|
test("dependencies main", () => {
|
||||||
|
expect(Project.getDependencies(project, "main")) == ["first"]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("past chain first", () => {
|
||||||
|
expect(ReducerProject.getPastChain(project, "first")) == []
|
||||||
|
})
|
||||||
|
test("past chain main", () => {
|
||||||
|
expect(ReducerProject.getPastChain(project, "main")) == ["first"]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("test result", () => {
|
||||||
|
runFetchResult(project, "main")->expect->toBe("Ok(1)")
|
||||||
|
})
|
||||||
|
test("test bindings", () => {
|
||||||
|
runFetchFlatBindings(project, "first")->expect->toBe("{x: 1}")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("project2", () => {
|
||||||
|
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", "z=3;y")
|
||||||
|
|
||||||
|
test("runOrder", () => {
|
||||||
|
expect(Project.getRunOrder(project)) == ["first", "second", "main"]
|
||||||
|
})
|
||||||
|
test("runOrderFor", () => {
|
||||||
|
expect(Project.getRunOrderFor(project, "first")) == ["first"]
|
||||||
|
})
|
||||||
|
test("dependencies first", () => {
|
||||||
|
expect(Project.getDependencies(project, "first")) == []
|
||||||
|
})
|
||||||
|
test("dependents first", () => {
|
||||||
|
expect(Project.getDependents(project, "first")) == ["second", "main"]
|
||||||
|
})
|
||||||
|
test("dependents main", () => {
|
||||||
|
expect(Project.getDependents(project, "main")) == []
|
||||||
|
})
|
||||||
|
test("dependencies main", () => {
|
||||||
|
expect(Project.getDependencies(project, "main")) == ["first", "second"]
|
||||||
|
})
|
||||||
|
test("test result", () => {
|
||||||
|
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")) == []
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("project with include", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setContinues(project, "main", ["second"])
|
||||||
|
Project.setContinues(project, "second", ["first"])
|
||||||
|
|
||||||
|
Project.setSource(
|
||||||
|
project,
|
||||||
|
"first",
|
||||||
|
`
|
||||||
|
#include 'common'
|
||||||
|
x=1`,
|
||||||
|
)
|
||||||
|
Project.parseIncludes(project, "first")
|
||||||
|
Project.parseIncludes(project, "first") //The only way of setting includes
|
||||||
|
//Don't forget to parse includes after changing the source
|
||||||
|
|
||||||
|
Project.setSource(project, "common", "common=0")
|
||||||
|
Project.setSource(
|
||||||
|
project,
|
||||||
|
"second",
|
||||||
|
`
|
||||||
|
#include 'common'
|
||||||
|
y=2`,
|
||||||
|
)
|
||||||
|
Project.parseIncludes(project, "second") //The only way of setting includes
|
||||||
|
|
||||||
|
Project.setSource(project, "main", "z=3; y")
|
||||||
|
|
||||||
|
test("runOrder", () => {
|
||||||
|
expect(Project.getRunOrder(project)) == ["common", "first", "second", "main"]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("runOrderFor", () => {
|
||||||
|
expect(Project.getRunOrderFor(project, "first")) == ["common", "first"]
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dependencies first", () => {
|
||||||
|
expect(Project.getDependencies(project, "first")) == ["common"]
|
||||||
|
})
|
||||||
|
test("dependents first", () => {
|
||||||
|
expect(Project.getDependents(project, "first")) == ["second", "main"]
|
||||||
|
})
|
||||||
|
test("dependents main", () => {
|
||||||
|
expect(Project.getDependents(project, "main")) == []
|
||||||
|
})
|
||||||
|
test("dependencies main", () => {
|
||||||
|
expect(Project.getDependencies(project, "main")) == ["common", "first", "second"]
|
||||||
|
})
|
||||||
|
test("test result", () => {
|
||||||
|
runFetchResult(project, "main")->expect->toBe("Ok(2)")
|
||||||
|
})
|
||||||
|
test("test bindings", () => {
|
||||||
|
// bindings from continues are not exposed!
|
||||||
|
runFetchFlatBindings(project, "main")->expect->toBe("{z: 3}")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("project with independent sources", () => {
|
||||||
|
let project = Project.createProject()
|
||||||
|
Project.setSource(project, "first", "1")
|
||||||
|
Project.setSource(project, "second", "2")
|
||||||
|
test("run order of first", () => {
|
||||||
|
expect(Project.getRunOrderFor(project, "first")) == ["first"]
|
||||||
|
})
|
||||||
|
test("run order of second", () => {
|
||||||
|
expect(Project.getRunOrderFor(project, "second")) == ["second"]
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,104 @@
|
||||||
|
@@warning("-44")
|
||||||
|
module Project = ForTS_ReducerProject
|
||||||
|
module Bindings = Reducer_Bindings
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
open Expect.Operators
|
||||||
|
|
||||||
|
describe("ReducerProject Tutorial", () => {
|
||||||
|
describe("Single source", () => {
|
||||||
|
/*
|
||||||
|
Case "Running a single source".
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
|
||||||
|
/* 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.
|
||||||
|
|
||||||
|
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's display the result and bindings */
|
||||||
|
(result->Reducer_Value.toStringResult, bindings->Reducer_Value.toStringRecord)->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 with an environment", () => {
|
||||||
|
/* Running the source code like above allows you to set a custom environment */
|
||||||
|
let project = Project.createProject()
|
||||||
|
|
||||||
|
/* Optional. Set your custom environment anytime before running */
|
||||||
|
project->Project.setEnvironment(Reducer_Context.defaultEnvironment)
|
||||||
|
|
||||||
|
project->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)"
|
||||||
|
})
|
||||||
|
|
||||||
|
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)", "{}")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//TODO multiple sources
|
||||||
|
//TODO multiple sources with includes. Introduction to includes
|
||||||
|
//TODO multiple sources with multi level includes. Cycle detection
|
||||||
|
//TODO
|
||||||
|
//TODO: Implement a runOrder consideration - clean results based on run order.
|
||||||
|
//TODO: runOrder vs setSource/touchSource
|
||||||
|
//TODO: Advanced details: (below)
|
||||||
|
//TODO runOrder. includes vs continues. Run order based reexecution
|
||||||
|
//TODO: dependents and reexecution
|
||||||
|
//TODO: dependencies and reexecution
|
||||||
|
//TODO: cleanAllResults clean
|
||||||
|
//TODO: cleanAll clean
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user