Compare commits
	
		
			2 Commits
		
	
	
		
			develop
			...
			Discrete-m
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b87e952785 | ||
|  | 93f4c1e0c2 | 
							
								
								
									
										20
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							|  | @ -9,22 +9,22 @@ | |||
| # This also holds true for GitHub teams. | ||||
| 
 | ||||
| # Rescript  | ||||
| *.res @berekuk @OAGr | ||||
| *.resi @berekuk @OAGr | ||||
| *.res @OAGr | ||||
| *.resi @OAGr | ||||
| 
 | ||||
| # Typescript  | ||||
| *.tsx @Hazelfire @berekuk @OAGr  | ||||
| *.ts @Hazelfire @berekuk @OAGr | ||||
| *.tsx @Hazelfire @OAGr | ||||
| *.ts @Hazelfire @OAGr | ||||
| 
 | ||||
| # Javascript | ||||
| *.js @Hazelfire @berekuk @OAGr | ||||
| *.js @Hazelfire @OAGr | ||||
| 
 | ||||
| # Any opsy files | ||||
| .github/** @quinn-dougherty @berekuk @OAGr | ||||
| *.json @quinn-dougherty @Hazelfire @berekuk @OAGr | ||||
| *.y*ml @quinn-dougherty @berekuk @OAGr | ||||
| *.config.js @Hazelfire @berekuk @OAGr | ||||
| vercel.json @OAGr @berekuk @Hazelfire | ||||
| .github/** @quinn-dougherty @OAGr | ||||
| *.json @quinn-dougherty @Hazelfire @OAGr | ||||
| *.y*ml @quinn-dougherty @OAGr | ||||
| *.config.js @Hazelfire @OAGr | ||||
| netlify.toml @quinn-dougherty @OAGr @Hazelfire  | ||||
| 
 | ||||
| # Documentation | ||||
| *.md @quinn-dougherty @OAGr @Hazelfire | ||||
|  |  | |||
							
								
								
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -12,13 +12,9 @@ updates: | |||
|     commit-message: | ||||
|       prefix: "⬆️" | ||||
|     open-pull-requests-limit: 100 | ||||
|     labels: | ||||
|       - "dependencies" | ||||
|   - package-ecosystem: "github-actions" | ||||
|     directory: "/" | ||||
|     schedule: | ||||
|       interval: "weekly" | ||||
|     commit-message: | ||||
|       prefix: "⬆️" | ||||
|     labels: | ||||
|       - "dependencies" | ||||
|  |  | |||
							
								
								
									
										228
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										228
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -1,4 +1,4 @@ | |||
| name: Squiggle packages checks | ||||
| name: Squiggle packages check | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|  | @ -9,40 +9,214 @@ on: | |||
|     branches: | ||||
|       - master | ||||
|       - develop | ||||
| 
 | ||||
| env: | ||||
|   TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} | ||||
|   TURBO_TEAM: quantified-uncertainty | ||||
|       - reducer-dev | ||||
|       - epic-reducer-project | ||||
| 
 | ||||
| jobs: | ||||
|   build-test-lint: | ||||
|     name: Build, test, lint | ||||
|   pre_check: | ||||
|     name: Precheck for skipping redundant jobs | ||||
|     runs-on: ubuntu-latest | ||||
|     outputs: | ||||
|       should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }} | ||||
|       should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }} | ||||
|       should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }} | ||||
|       should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }} | ||||
|       should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - name: Setup Node.js environment | ||||
|         uses: actions/setup-node@v3 | ||||
|       - id: skip_lang_check | ||||
|         name: Check if the changes are about squiggle-lang src files | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           node-version: 16 | ||||
|           cache: 'yarn' | ||||
|       - name: Install dependencies | ||||
|         run: yarn --frozen-lockfile | ||||
|       - name: Turbo run | ||||
|         run: npx turbo run build test lint bundle | ||||
|           paths: '["packages/squiggle-lang/**"]' | ||||
|       - id: skip_components_check | ||||
|         name: Check if the changes are about components src files | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/components/**"]' | ||||
|       - id: skip_website_check | ||||
|         name: Check if the changes are about website src files | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/website/**"]' | ||||
|       - id: skip_vscodeext_check | ||||
|         name: Check if the changes are about vscode extension src files | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/vscode-ext/**"]' | ||||
|       - id: skip_cli_check | ||||
|         name: Check if the changes are about cli src files | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/cli/**"]' | ||||
| 
 | ||||
|   coverage: | ||||
|     name: Coverage | ||||
|   #  lang-lint: | ||||
|   #    name: Language lint | ||||
|   #    runs-on: ubuntu-latest | ||||
|   #    needs: pre_check | ||||
|   #    if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }} | ||||
|   #    defaults: | ||||
|   #      run: | ||||
|   #        shell: bash | ||||
|   #        working-directory: packages/squiggle-lang | ||||
|   #    steps: | ||||
|   #      - uses: actions/checkout@v3 | ||||
|   #      - name: Install Dependencies | ||||
|   #        run: cd ../../ && yarn | ||||
|   #      - name: Check rescript lint | ||||
|   #        run: yarn lint:rescript | ||||
|   #      - name: Check javascript, typescript, and markdown lint | ||||
|   #        uses: creyD/prettier_action@v4.2 | ||||
|   #        with: | ||||
|   #          dry: true | ||||
|   #          prettier_options: --check packages/squiggle-lang | ||||
| 
 | ||||
|   lang-build-test-bundle: | ||||
|     name: Language build, test, and bundle | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: pre_check | ||||
|     if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }} | ||||
|     defaults: | ||||
|       run: | ||||
|         shell: bash | ||||
|         working-directory: packages/squiggle-lang | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|         with: | ||||
|           fetch-depth: 2 | ||||
|       - name: Setup Node.js environment | ||||
|         uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: 16 | ||||
|           cache: 'yarn' | ||||
|       - name: Install dependencies | ||||
|         run: yarn | ||||
|       - name: Coverage | ||||
|         run: npx turbo run coverage | ||||
|       - name: Install dependencies from monorepo level | ||||
|         run: cd ../../ && yarn | ||||
|       - name: Build rescript codebase | ||||
|         run: yarn build | ||||
|       - name: Run rescript tests | ||||
|         run: yarn test:rescript | ||||
|       - name: Run typescript tests | ||||
|         run: yarn test:ts | ||||
|       - name: Run webpack | ||||
|         run: yarn bundle | ||||
|       - name: Upload rescript coverage report | ||||
|         run: yarn coverage:rescript:ci | ||||
|       - name: Upload typescript coverage report | ||||
|         run: yarn coverage:ts:ci | ||||
| 
 | ||||
|   #  components-lint: | ||||
|   #    name: Components lint | ||||
|   #    runs-on: ubuntu-latest | ||||
|   #    needs: pre_check | ||||
|   #    if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }} | ||||
|   #    defaults: | ||||
|   #      run: | ||||
|   #        shell: bash | ||||
|   #        working-directory: packages/components | ||||
|   #    steps: | ||||
|   #      - uses: actions/checkout@v3 | ||||
|   #      - name: Check javascript, typescript, and markdown lint | ||||
|   #        uses: creyD/prettier_action@v4.2 | ||||
|   #        with: | ||||
|   #          dry: true | ||||
|   #          prettier_options: --check packages/components --ignore-path packages/components/.prettierignore | ||||
|   # | ||||
|   #  components-bundle-build: | ||||
|   #    name: Components bundle and build | ||||
|   #    runs-on: ubuntu-latest | ||||
|   #    needs: pre_check | ||||
|   #    if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }} | ||||
|   #    defaults: | ||||
|   #      run: | ||||
|   #        shell: bash | ||||
|   #        working-directory: packages/components | ||||
|   #    steps: | ||||
|   #      - uses: actions/checkout@v3 | ||||
|   #      - name: Install dependencies from monorepo level | ||||
|   #        run: cd ../../ && yarn | ||||
|   #      - name: Build rescript codebase in squiggle-lang | ||||
|   #        run: cd ../squiggle-lang && yarn build | ||||
|   #      - name: Run webpack | ||||
|   #        run: yarn bundle | ||||
|   #      - name: Build storybook | ||||
|   #        run: yarn build | ||||
| 
 | ||||
|   #  website-lint: | ||||
|   #    name: Website lint | ||||
|   #    runs-on: ubuntu-latest | ||||
|   #    needs: pre_check | ||||
|   #    if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }} | ||||
|   #    defaults: | ||||
|   #      run: | ||||
|   #        shell: bash | ||||
|   #        working-directory: packages/website | ||||
|   #    steps: | ||||
|   #      - uses: actions/checkout@v3 | ||||
|   #      - name: Check javascript, typescript, and markdown lint | ||||
|   #        uses: creyD/prettier_action@v4.2 | ||||
|   #        with: | ||||
|   #          dry: true | ||||
|   #          prettier_options: --check packages/website | ||||
|   # | ||||
|   #  website-build: | ||||
|   #    name: Website build | ||||
|   #    runs-on: ubuntu-latest | ||||
|   #    needs: pre_check | ||||
|   #    if: ${{ (needs.pre_check.outputs.should_skip_website != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true')  || (needs.pre_check.outputs.should_skip_components != 'true') }} | ||||
|   #    defaults: | ||||
|   #      run: | ||||
|   #        shell: bash | ||||
|   #        working-directory: packages/website | ||||
|   #    steps: | ||||
|   #      - uses: actions/checkout@v3 | ||||
|   #      - name: Install dependencies from monorepo level | ||||
|   #        run: cd ../../ && yarn | ||||
|   #      - name: Build rescript in squiggle-lang | ||||
|   #        run: cd ../squiggle-lang && yarn build | ||||
|   #      - name: Build components | ||||
|   #        run: cd ../components && yarn build | ||||
|   #      - name: Build website assets | ||||
|   #        run: yarn build | ||||
|   # | ||||
|   #  vscode-ext-lint: | ||||
|   #    name: VS Code extension lint | ||||
|   #    runs-on: ubuntu-latest | ||||
|   #    needs: pre_check | ||||
|   #    if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }} | ||||
|   #    defaults: | ||||
|   #      run: | ||||
|   #        shell: bash | ||||
|   #        working-directory: packages/vscode-ext | ||||
|   #    steps: | ||||
|   #      - uses: actions/checkout@v3 | ||||
|   #      - name: Check javascript, typescript, and markdown lint | ||||
|   #        uses: creyD/prettier_action@v4.2 | ||||
|   #        with: | ||||
|   #          dry: true | ||||
|   #          prettier_options: --check packages/vscode-ext | ||||
| 
 | ||||
|   vscode-ext-build: | ||||
|     name: VS Code extension build | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: pre_check | ||||
|     if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }} || (needs.pre_check.outputs.should_skip_vscodeext != 'true') }} | ||||
|     defaults: | ||||
|       run: | ||||
|         shell: bash | ||||
|         working-directory: packages/vscode-ext | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - name: Install dependencies from monorepo level | ||||
|         run: cd ../../ && yarn | ||||
|       - name: Build | ||||
|         run: yarn compile | ||||
| #  cli-lint: | ||||
| #    name: CLI lint | ||||
| #    runs-on: ubuntu-latest | ||||
| #    needs: pre_check | ||||
| #    if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }} | ||||
| #    defaults: | ||||
| #      run: | ||||
| #        shell: bash | ||||
| #        working-directory: packages/cli | ||||
| #    steps: | ||||
| #      - uses: actions/checkout@v3 | ||||
| #      - name: Check javascript, typescript, and markdown lint | ||||
| #        uses: creyD/prettier_action@v4.2 | ||||
| #        with: | ||||
| #          dry: true | ||||
| #          prettier_options: --check packages/cli | ||||
|  |  | |||
							
								
								
									
										10
									
								
								.github/workflows/release-please.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/release-please.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -18,27 +18,27 @@ jobs: | |||
|     steps: | ||||
|       - id: skip_lang_check | ||||
|         name: Check if the changes are about squiggle-lang src files | ||||
|         uses: fkirc/skip-duplicate-actions@v5.2.0 | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/squiggle-lang/**"]' | ||||
|       - id: skip_components_check | ||||
|         name: Check if the changes are about components src files | ||||
|         uses: fkirc/skip-duplicate-actions@v5.2.0 | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/components/**"]' | ||||
|       - id: skip_website_check | ||||
|         name: Check if the changes are about website src files | ||||
|         uses: fkirc/skip-duplicate-actions@v5.2.0 | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/website/**"]' | ||||
|       - id: skip_vscodeext_check | ||||
|         name: Check if the changes are about vscode extension src files | ||||
|         uses: fkirc/skip-duplicate-actions@v5.2.0 | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/vscode-ext/**"]' | ||||
|       - id: skip_cli_check | ||||
|         name: Check if the changes are about cli src files | ||||
|         uses: fkirc/skip-duplicate-actions@v5.2.0 | ||||
|         uses: fkirc/skip-duplicate-actions@v4.0.0 | ||||
|         with: | ||||
|           paths: '["packages/cli/**"]' | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -7,9 +7,4 @@ yarn-error.log | |||
| **/.sync.ffs_db | ||||
| .direnv | ||||
| .log | ||||
| 
 | ||||
| .vscode | ||||
| todo.txt | ||||
| result | ||||
| shell.nix | ||||
| .turbo | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| { | ||||
|   "packages/cli": "0.0.3", | ||||
|   "packages/components": "0.4.1", | ||||
|   "packages/squiggle-lang": "0.4.1", | ||||
|   "packages/vscode-ext": "0.4.1", | ||||
|   "packages/website": "0.0.0" | ||||
|   "packages/components": "0.3.1", | ||||
|   "packages/squiggle-lang": "0.3.0", | ||||
|   "packages/vscode-ext": "0.3.1", | ||||
|   "packages/website": "0.3.0" | ||||
| } | ||||
|  |  | |||
|  | @ -1 +0,0 @@ | |||
| See the [Changelog.mdx page](./packages/website/docs/Changelog.mdx) for the changelog. | ||||
|  | @ -16,7 +16,7 @@ Squiggle is currently pre-alpha. | |||
| 
 | ||||
| # Bug reports | ||||
| 
 | ||||
| Anyone (with a github account) can file an issue at any time. Please allow Slava, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates. | ||||
| Anyone (with a github account) can file an issue at any time. Please allow Quinn, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates. | ||||
| 
 | ||||
| # Project structure | ||||
| 
 | ||||
|  | @ -28,7 +28,7 @@ Squiggle is a **monorepo** with three **packages**. | |||
| 
 | ||||
| # Deployment ops | ||||
| 
 | ||||
| We use Vercel, and it should only concern Slava, Sam, and Ozzie. | ||||
| We use netlify, and it should only concern Quinn, Sam, and Ozzie. | ||||
| 
 | ||||
| # Development environment, building, testing, dev server | ||||
| 
 | ||||
|  | @ -56,9 +56,9 @@ If you absolutely must, please prefix your commit message with `hotfix: `. | |||
| 
 | ||||
| Please work against `develop` branch. **Do not** work against `master`. | ||||
| 
 | ||||
| - For rescript code: Slava and Ozzie are reviewers | ||||
| - For rescript code: Quinn and Ozzie are reviewers | ||||
| - For js or typescript code: Sam and Ozzie are reviewers | ||||
| - For ops code (i.e. yaml, package.json): Slava and Sam are reviewers | ||||
| - For ops code (i.e. yaml, package.json): Quinn and Sam are reviewers | ||||
| 
 | ||||
| Autopings are set up: if you are not autopinged, you are welcome to comment, but please do not use the formal review feature, send approvals, rejections, or merges. | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.md
									
									
									
									
									
								
							|  | @ -21,10 +21,10 @@ _An estimation language_. | |||
| 
 | ||||
| ## Our deployments | ||||
| 
 | ||||
| - **website/docs prod**: https://squiggle-language.com | ||||
| - **website/docs staging**: https://preview.squiggle-language.com | ||||
| - **components storybook prod**: https://components.squiggle-language.com | ||||
| - **components storybook staging**: https://preview-components.squiggle-language.com | ||||
| - **website/docs prod**: https://squiggle-language.com [](https://app.netlify.com/sites/squiggle-documentation/deploys) | ||||
| - **website/docs staging**: https://develop--squiggle-documentation.netlify.app/ | ||||
| - **components storybook prod**: https://squiggle-components.netlify.app/ [](https://app.netlify.com/sites/squiggle-components/deploys) | ||||
| - **components storybook staging**: https://develop--squiggle-components.netlify.app/ | ||||
| - **legacy (2020) playground**: https://playground.squiggle-language.com | ||||
| 
 | ||||
| ## Packages | ||||
|  | @ -51,25 +51,7 @@ For any project in the repo, begin by running `yarn` in the top level | |||
| yarn | ||||
| ``` | ||||
| 
 | ||||
| Then use `turbo` to build the specific packages or the entire monorepo: | ||||
| 
 | ||||
| ```sh | ||||
| turbo run build | ||||
| ``` | ||||
| 
 | ||||
| Or: | ||||
| 
 | ||||
| ```sh | ||||
| turbo run build --filter=@quri/squiggle-components | ||||
| ``` | ||||
| 
 | ||||
| You can also run specific npm scripts for the package you're working on. See `packages/*/README.md` for the details. | ||||
| 
 | ||||
| # NixOS users | ||||
| 
 | ||||
| This repository requires the use of bundled binaries from node_modules, which | ||||
| are not linked statically. The easiest way to get them working is to enable | ||||
| [nix-ld](https://github.com/Mic92/nix-ld). | ||||
| See `packages/*/README.md` to work with whatever project you're interested in. | ||||
| 
 | ||||
| # Contributing | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,6 +30,16 @@ rec { | |||
|           patchelf --replace-needed libstdc++.so.6 $THE_SO linux/ninja.exe && echo "- replaced needed for linux/ninja.exe" | ||||
|         ''; | ||||
|       }; | ||||
|       bisect_ppx = { | ||||
|         buildInputs = common.which; | ||||
|         postInstall = '' | ||||
|           echo "PATCHELF'ING BISECT_PPX EXECUTABLE" | ||||
|           THE_LD=$(patchelf --print-interpreter $(which mkdir)) | ||||
|           patchelf --set-interpreter $THE_LD bin/linux/ppx | ||||
|           patchelf --set-interpreter $THE_LD bin/linux/bisect-ppx-report | ||||
|           cp bin/linux/ppx ppx | ||||
|         ''; | ||||
|       }; | ||||
|       gentype = { | ||||
|         postInstall = '' | ||||
|           mv gentype.exe ELFLESS-gentype.exe | ||||
|  | @ -65,7 +75,6 @@ rec { | |||
|       # custom gitignore so that the flake keeps build artefacts | ||||
|       mv .gitignore GITIGNORE | ||||
|       sed -i /Reducer_Peggy_GeneratedParser.js/d GITIGNORE | ||||
|       sed -i /ReducerProject_IncludeParser.js/d GITIGNORE | ||||
|       sed -i /\*.bs.js/d GITIGNORE | ||||
|       sed -i /\*.gen.ts/d GITIGNORE | ||||
|       sed -i /\*.gen.tsx/d GITIGNORE | ||||
|  |  | |||
							
								
								
									
										18
									
								
								nixos.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								nixos.sh
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| #!/usr/bin/env bash | ||||
| # This script is only relevant if you're rolling nixos. | ||||
| 
 | ||||
| # Esy (a bisect_ppx dependency/build tool) is borked on nixos without using an FHS shell. https://github.com/esy/esy/issues/858 | ||||
| # We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375 | ||||
| set -x | ||||
| 
 | ||||
| fhsShellName="squiggle-fhs-development" | ||||
| fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn pkgs.glibc]; runScript = \"yarn\"; }).env" | ||||
| nix-shell - <<<"$fhsShellDotNix" | ||||
| 
 | ||||
| theLd=$(patchelf --print-interpreter $(which mkdir)) | ||||
| patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe | ||||
| patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe | ||||
| patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/ppx | ||||
| patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/bisect-ppx-report | ||||
| theSo=$(find /nix/store/*$fhsShellName*/lib64 -name libstdc++.so.6 | head -n 1) | ||||
| patchelf --replace-needed libstdc++.so.6 $theSo ./node_modules/rescript/linux/ninja.exe | ||||
|  | @ -2,11 +2,12 @@ | |||
|   "private": true, | ||||
|   "name": "squiggle", | ||||
|   "scripts": { | ||||
|     "nodeclean": "rm -r node_modules && rm -r packages/*/node_modules" | ||||
|     "nodeclean": "rm -r node_modules && rm -r packages/*/node_modules", | ||||
|     "format:all": "prettier --write . && cd packages/squiggle-lang && yarn format", | ||||
|     "lint:all": "prettier --check . && cd packages/squiggle-lang && yarn lint:rescript" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "prettier": "^2.7.1", | ||||
|     "turbo": "^1.5.5" | ||||
|     "prettier": "^2.7.1" | ||||
|   }, | ||||
|   "workspaces": [ | ||||
|     "packages/*" | ||||
|  |  | |||
|  | @ -20,30 +20,3 @@ Runs compilation in the current directory and all of its subdirectories. | |||
| ### `npx squiggle-cli-experimental watch` | ||||
| 
 | ||||
| Watches `.squiggleU` files in the current directory (and subdirectories) and rebuilds them when they are saved. Note that this will _not_ rebuild files when their dependencies are changed, just when they are changed directly. | ||||
| 
 | ||||
| ## Further instructions | ||||
| 
 | ||||
| The above requires having node, npm and npx. To install the first two, see [here](https://nodejs.org/en/), to install npx, run: | ||||
| 
 | ||||
| ``` | ||||
| npm install -g npx | ||||
| ``` | ||||
| 
 | ||||
| Alternatively, you can run the following without the need for npx: | ||||
| 
 | ||||
| ``` | ||||
| npm install squiggle-cli-experimental | ||||
| node node_modules/squiggle-cli-experimental/index.js compile | ||||
| ``` | ||||
| 
 | ||||
| or you can add a script to your `package.json`, like: | ||||
| 
 | ||||
| ``` | ||||
|  ... | ||||
|  scripts: { | ||||
|   "compile": "squiggle-cli-experimental compile" | ||||
|  } | ||||
|  ... | ||||
| ``` | ||||
| 
 | ||||
| This can be run with `npm run compile`. `npm` knows how to reach into the node_modules directly, so it's not necessary to specify that. | ||||
|  |  | |||
|  | @ -7,15 +7,13 @@ | |||
|   "bin": "index.js", | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "start": "node .", | ||||
|     "lint": "prettier --check .", | ||||
|     "format": "prettier --write ." | ||||
|     "start": "node ." | ||||
|   }, | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "chalk": "^5.1.0", | ||||
|     "chalk": "^5.0.1", | ||||
|     "chokidar": "^3.5.3", | ||||
|     "commander": "^9.4.1", | ||||
|     "commander": "^9.4.0", | ||||
|     "fs": "^0.0.1-security", | ||||
|     "glob": "^8.0.3", | ||||
|     "indent-string": "^5.0.0" | ||||
|  |  | |||
|  | @ -1,6 +0,0 @@ | |||
| /** @type {import('ts-jest').JestConfigWithTsJest} */ | ||||
| module.exports = { | ||||
|   preset: "ts-jest", | ||||
|   setupFilesAfterEnv: ["<rootDir>/test/setup.js"], | ||||
|   testEnvironment: "jsdom", | ||||
| }; | ||||
							
								
								
									
										8
									
								
								packages/components/netlify.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/components/netlify.toml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| [build] | ||||
|     base = "packages/components/" | ||||
|     command = "cd ../squiggle-lang && yarn build && cd ../components && yarn build" | ||||
|     publish = "storybook-static/" | ||||
|     ignore = "node -e 'process.exitCode = process.env.BRANCH.includes(\"dependabot\") ? 0 : 1' && git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../squiggle-lang" | ||||
| 
 | ||||
| [build.environment] | ||||
|     NETLIFY_USE_YARN = "true" | ||||
|  | @ -1,73 +1,64 @@ | |||
| { | ||||
|   "name": "@quri/squiggle-components", | ||||
|   "version": "0.5.0", | ||||
|   "version": "0.3.2", | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "@floating-ui/react-dom": "^1.0.0", | ||||
|     "@floating-ui/react-dom-interactions": "^0.10.1", | ||||
|     "@headlessui/react": "^1.7.3", | ||||
|     "@floating-ui/react-dom-interactions": "^0.9.3", | ||||
|     "@headlessui/react": "^1.6.6", | ||||
|     "@heroicons/react": "^1.0.6", | ||||
|     "@hookform/resolvers": "^2.9.8", | ||||
|     "@quri/squiggle-lang": "^0.5.0", | ||||
|     "@hookform/resolvers": "^2.9.7", | ||||
|     "@quri/squiggle-lang": "^0.3.0", | ||||
|     "@react-hook/size": "^2.1.2", | ||||
|     "@types/uuid": "^8.3.4", | ||||
|     "clsx": "^1.2.1", | ||||
|     "framer-motion": "^7.5.3", | ||||
|     "framer-motion": "^7.2.1", | ||||
|     "lodash": "^4.17.21", | ||||
|     "react": "^18.1.0", | ||||
|     "react-ace": "^10.1.0", | ||||
|     "react-hook-form": "^7.37.0", | ||||
|     "react-hook-form": "^7.34.2", | ||||
|     "react-use": "^17.4.0", | ||||
|     "react-vega": "^7.6.0", | ||||
|     "uuid": "^9.0.0", | ||||
|     "vega": "^5.22.1", | ||||
|     "vega-embed": "^6.21.0", | ||||
|     "vega-lite": "^5.5.0", | ||||
|     "vscode-uri": "^3.0.6", | ||||
|     "vscode-uri": "^3.0.3", | ||||
|     "yup": "^0.32.11" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@babel/plugin-proposal-private-property-in-object": "^7.18.6", | ||||
|     "@storybook/addon-actions": "^6.5.12", | ||||
|     "@storybook/addon-essentials": "^6.5.12", | ||||
|     "@storybook/addon-links": "^6.5.12", | ||||
|     "@storybook/builder-webpack5": "^6.5.12", | ||||
|     "@storybook/manager-webpack5": "^6.5.12", | ||||
|     "@storybook/addon-actions": "^6.5.9", | ||||
|     "@storybook/addon-essentials": "^6.5.10", | ||||
|     "@storybook/addon-links": "^6.5.10", | ||||
|     "@storybook/builder-webpack5": "^6.5.10", | ||||
|     "@storybook/manager-webpack5": "^6.5.10", | ||||
|     "@storybook/node-logger": "^6.5.9", | ||||
|     "@storybook/preset-create-react-app": "^4.1.2", | ||||
|     "@storybook/react": "^6.5.12", | ||||
|     "@storybook/react": "^6.5.10", | ||||
|     "@testing-library/jest-dom": "^5.16.5", | ||||
|     "@testing-library/react": "^13.4.0", | ||||
|     "@testing-library/react": "^13.3.0", | ||||
|     "@testing-library/user-event": "^14.4.3", | ||||
|     "@types/jest": "^27.5.0", | ||||
|     "@types/lodash": "^4.14.186", | ||||
|     "@types/node": "^18.8.3", | ||||
|     "@types/react": "^18.0.21", | ||||
|     "@types/lodash": "^4.14.184", | ||||
|     "@types/node": "^18.7.13", | ||||
|     "@types/react": "^18.0.9", | ||||
|     "@types/styled-components": "^5.1.26", | ||||
|     "@types/uuid": "^8.3.4", | ||||
|     "@types/webpack": "^5.28.0", | ||||
|     "canvas": "^2.10.1", | ||||
|     "cross-env": "^7.0.3", | ||||
|     "jest": "^29.0.3", | ||||
|     "jest-environment-jsdom": "^29.1.2", | ||||
|     "jsdom": "^20.0.1", | ||||
|     "mini-css-extract-plugin": "^2.6.1", | ||||
|     "postcss-cli": "^10.0.0", | ||||
|     "postcss-import": "^15.0.0", | ||||
|     "postcss-import": "^14.1.0", | ||||
|     "postcss-loader": "^7.0.1", | ||||
|     "postcss-nesting": "^10.2.0", | ||||
|     "react": "^18.1.0", | ||||
|     "react-scripts": "^5.0.1", | ||||
|     "style-loader": "^3.3.1", | ||||
|     "tailwindcss": "^3.1.8", | ||||
|     "ts-jest": "^29.0.3", | ||||
|     "ts-loader": "^9.4.1", | ||||
|     "ts-loader": "^9.3.0", | ||||
|     "tsconfig-paths-webpack-plugin": "^4.0.0", | ||||
|     "typescript": "^4.8.4", | ||||
|     "web-vitals": "^3.0.3", | ||||
|     "typescript": "^4.8.2", | ||||
|     "web-vitals": "^3.0.0", | ||||
|     "webpack": "^5.74.0", | ||||
|     "webpack-cli": "^4.10.0", | ||||
|     "webpack-dev-server": "^4.11.1" | ||||
|     "webpack-dev-server": "^4.10.0" | ||||
|   }, | ||||
|   "peerDependencies": { | ||||
|     "react": "^16.8.0 || ^17 || ^18", | ||||
|  | @ -75,7 +66,7 @@ | |||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public", | ||||
|     "build:cjs": "rm -rf dist/src && rm -f dist/tsconfig.tsbuildinfo && tsc -b", | ||||
|     "build:cjs": "tsc -b", | ||||
|     "build:css": "postcss ./src/styles/main.css -o ./dist/main.css", | ||||
|     "build:storybook": "build-storybook -s public", | ||||
|     "build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook", | ||||
|  | @ -83,10 +74,7 @@ | |||
|     "all": "yarn bundle && yarn build", | ||||
|     "lint": "prettier --check .", | ||||
|     "format": "prettier --write .", | ||||
|     "prepack": "yarn run build:cjs && yarn run bundle", | ||||
|     "test": "jest", | ||||
|     "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand", | ||||
|     "test:profile": "node --cpu-prof node_modules/.bin/jest --runInBand" | ||||
|     "prepack": "yarn bundle && tsc -b" | ||||
|   }, | ||||
|   "eslintConfig": { | ||||
|     "extends": [ | ||||
|  |  | |||
|  | @ -24,13 +24,13 @@ export const Alert: React.FC<{ | |||
|   children, | ||||
| }) => { | ||||
|   return ( | ||||
|     <div className={clsx("rounded-md p-4", backgroundColor)} role="status"> | ||||
|     <div className={clsx("rounded-md p-4", backgroundColor)}> | ||||
|       <div className="flex"> | ||||
|         <Icon | ||||
|           className={clsx("h-5 w-5 flex-shrink-0", iconColor)} | ||||
|           aria-hidden="true" | ||||
|         /> | ||||
|         <div className="ml-3 grow"> | ||||
|         <div className="ml-3"> | ||||
|           <header className={clsx("text-sm font-medium", headingColor)}> | ||||
|             {heading} | ||||
|           </header> | ||||
|  |  | |||
|  | @ -5,8 +5,6 @@ import AceEditor from "react-ace"; | |||
| import "ace-builds/src-noconflict/mode-golang"; | ||||
| import "ace-builds/src-noconflict/theme-github"; | ||||
| 
 | ||||
| import { SqLocation } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| interface CodeEditorProps { | ||||
|   value: string; | ||||
|   onChange: (value: string) => void; | ||||
|  | @ -15,17 +13,15 @@ interface CodeEditorProps { | |||
|   width?: number; | ||||
|   height: number; | ||||
|   showGutter?: boolean; | ||||
|   errorLocations?: SqLocation[]; | ||||
| } | ||||
| 
 | ||||
| export const CodeEditor: FC<CodeEditorProps> = ({ | ||||
|   value, | ||||
|   onChange, | ||||
|   onSubmit, | ||||
|   height, | ||||
|   oneLine = false, | ||||
|   showGutter = false, | ||||
|   errorLocations = [], | ||||
|   height, | ||||
| }) => { | ||||
|   const lineCount = value.split("\n").length; | ||||
|   const id = useMemo(() => _.uniqueId(), []); | ||||
|  | @ -34,11 +30,8 @@ export const CodeEditor: FC<CodeEditorProps> = ({ | |||
|   const onSubmitRef = useRef<typeof onSubmit | null>(null); | ||||
|   onSubmitRef.current = onSubmit; | ||||
| 
 | ||||
|   const editorEl = useRef<AceEditor | null>(null); | ||||
| 
 | ||||
|   return ( | ||||
|     <AceEditor | ||||
|       ref={editorEl} | ||||
|       value={value} | ||||
|       mode="golang" | ||||
|       theme="github" | ||||
|  | @ -55,7 +48,10 @@ export const CodeEditor: FC<CodeEditorProps> = ({ | |||
|       editorProps={{ | ||||
|         $blockScrolling: true, | ||||
|       }} | ||||
|       setOptions={{}} | ||||
|       setOptions={{ | ||||
|         enableBasicAutocompletion: false, | ||||
|         enableLiveAutocompletion: false, | ||||
|       }} | ||||
|       commands={[ | ||||
|         { | ||||
|           name: "submit", | ||||
|  | @ -63,14 +59,6 @@ export const CodeEditor: FC<CodeEditorProps> = ({ | |||
|           exec: () => onSubmitRef.current?.(), | ||||
|         }, | ||||
|       ]} | ||||
|       markers={errorLocations?.map((location) => ({ | ||||
|         startRow: location.start.line - 1, | ||||
|         startCol: location.start.column - 1, | ||||
|         endRow: location.end.line - 1, | ||||
|         endCol: location.end.column - 1, | ||||
|         className: "ace-error-marker", | ||||
|         type: "text", | ||||
|       }))} | ||||
|     /> | ||||
|   ); | ||||
| }; | ||||
|  |  | |||
|  | @ -1,12 +1,11 @@ | |||
| import * as React from "react"; | ||||
| import { | ||||
|   SqDistribution, | ||||
|   Distribution, | ||||
|   result, | ||||
|   SqDistributionError, | ||||
|   distributionError, | ||||
|   distributionErrorToString, | ||||
|   squiggleExpression, | ||||
|   resultMap, | ||||
|   SqRecord, | ||||
|   environment, | ||||
|   SqDistributionTag, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { Vega } from "react-vega"; | ||||
| import { ErrorAlert } from "./Alert"; | ||||
|  | @ -29,17 +28,17 @@ export type DistributionPlottingSettings = { | |||
| 
 | ||||
| export type DistributionChartProps = { | ||||
|   plot: Plot; | ||||
|   environment: environment; | ||||
|   width?: number; | ||||
|   height: number; | ||||
|   xAxisType?: "number" | "dateTime"; | ||||
| } & DistributionPlottingSettings; | ||||
| 
 | ||||
| export function defaultPlot(distribution: SqDistribution): Plot { | ||||
| export function defaultPlot(distribution: Distribution): Plot { | ||||
|   return { distributions: [{ name: "default", distribution }] }; | ||||
| } | ||||
| 
 | ||||
| export function makePlot(record: SqRecord): Plot | void { | ||||
| export function makePlot(record: { | ||||
|   [key: string]: squiggleExpression; | ||||
| }): Plot | void { | ||||
|   const plotResult = parsePlot(record); | ||||
|   if (plotResult.tag === "Ok") { | ||||
|     return plotResult.value; | ||||
|  | @ -47,68 +46,38 @@ export function makePlot(record: SqRecord): Plot | void { | |||
| } | ||||
| 
 | ||||
| export const DistributionChart: React.FC<DistributionChartProps> = (props) => { | ||||
|   const { | ||||
|     plot, | ||||
|     environment, | ||||
|     height, | ||||
|     showSummary, | ||||
|     width, | ||||
|     logX, | ||||
|     actions = false, | ||||
|   } = props; | ||||
|   const { plot, height, showSummary, width, logX, actions = false } = props; | ||||
|   const [sized] = useSize((size) => { | ||||
|     const shapes = flattenResult( | ||||
|     let shapes = flattenResult( | ||||
|       plot.distributions.map((x) => | ||||
|         resultMap(x.distribution.pointSet(environment), (pointSet) => ({ | ||||
|         resultMap(x.distribution.pointSet(), (shape) => ({ | ||||
|           name: x.name, | ||||
|           // color: x.color, // not supported yet
 | ||||
|           ...pointSet.asShape(), | ||||
|           continuous: shape.continuous, | ||||
|           discrete: shape.discrete, | ||||
|         })) | ||||
|       ) | ||||
|     ); | ||||
| 
 | ||||
|     if (shapes.tag === "Error") { | ||||
|       return ( | ||||
|         <ErrorAlert heading="Distribution Error"> | ||||
|           {shapes.value.toString()} | ||||
|           {distributionErrorToString(shapes.value)} | ||||
|         </ErrorAlert> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     // if this is a sample set, include the samples
 | ||||
|     const samples: number[] = []; | ||||
|     for (const { distribution } of plot?.distributions) { | ||||
|       if (distribution.tag === SqDistributionTag.SampleSet) { | ||||
|         samples.push(...distribution.value()); | ||||
|       } | ||||
|     } | ||||
|     const spec = buildVegaSpec(props); | ||||
| 
 | ||||
|     const domain = shapes.value.flatMap((shape) => | ||||
|       shape.discrete.concat(shape.continuous) | ||||
|     ); | ||||
| 
 | ||||
|     const spec = buildVegaSpec({ | ||||
|       ...props, | ||||
|       minX: props.minX ?? Math.min(...domain.map((x) => x.x)), | ||||
|       maxX: props.minX ?? Math.max(...domain.map((x) => x.x)), | ||||
|       maxY: Math.max(...domain.map((x) => x.y)), | ||||
|     }); | ||||
| 
 | ||||
|     // I think size.width is sometimes not finite due to the component not being in a visible context
 | ||||
|     // This occurs during testing
 | ||||
|     let widthProp = width | ||||
|       ? width | ||||
|       : Number.isFinite(size.width) | ||||
|       ? size.width | ||||
|       : 400; | ||||
|     let widthProp = width ? width : size.width; | ||||
|     if (widthProp < 20) { | ||||
|       console.warn( | ||||
|         `Width of Distribution is set to ${widthProp}, which is too small` | ||||
|       ); | ||||
|       widthProp = 20; | ||||
|     } | ||||
| 
 | ||||
|     const vegaData = { data: shapes.value, samples }; | ||||
|     const domain = shapes.value.flatMap((shape) => | ||||
|       shape.discrete.concat(shape.continuous) | ||||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|       <div style={{ width: widthProp }}> | ||||
|  | @ -119,7 +88,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => { | |||
|         ) : ( | ||||
|           <Vega | ||||
|             spec={spec} | ||||
|             data={vegaData} | ||||
|             data={{ data: shapes.value, domain }} | ||||
|             width={widthProp - 10} | ||||
|             height={height} | ||||
|             actions={actions} | ||||
|  | @ -127,10 +96,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => { | |||
|         )} | ||||
|         <div className="flex justify-center"> | ||||
|           {showSummary && plot.distributions.length === 1 && ( | ||||
|             <SummaryTable | ||||
|               distribution={plot.distributions[0].distribution} | ||||
|               environment={environment} | ||||
|             /> | ||||
|             <SummaryTable distribution={plot.distributions[0].distribution} /> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|  | @ -154,36 +120,32 @@ const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => ( | |||
| ); | ||||
| 
 | ||||
| type SummaryTableProps = { | ||||
|   distribution: SqDistribution; | ||||
|   environment: environment; | ||||
|   distribution: Distribution; | ||||
| }; | ||||
| 
 | ||||
| const SummaryTable: React.FC<SummaryTableProps> = ({ | ||||
|   distribution, | ||||
|   environment, | ||||
| }) => { | ||||
|   const mean = distribution.mean(environment); | ||||
|   const stdev = distribution.stdev(environment); | ||||
|   const p5 = distribution.inv(environment, 0.05); | ||||
|   const p10 = distribution.inv(environment, 0.1); | ||||
|   const p25 = distribution.inv(environment, 0.25); | ||||
|   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 SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => { | ||||
|   const mean = distribution.mean(); | ||||
|   const stdev = distribution.stdev(); | ||||
|   const p5 = distribution.inv(0.05); | ||||
|   const p10 = distribution.inv(0.1); | ||||
|   const p25 = distribution.inv(0.25); | ||||
|   const p50 = distribution.inv(0.5); | ||||
|   const p75 = distribution.inv(0.75); | ||||
|   const p90 = distribution.inv(0.9); | ||||
|   const p95 = distribution.inv(0.95); | ||||
| 
 | ||||
|   const hasResult = (x: result<number, SqDistributionError>): boolean => | ||||
|   const hasResult = (x: result<number, distributionError>): boolean => | ||||
|     x.tag === "Ok"; | ||||
| 
 | ||||
|   const unwrapResult = ( | ||||
|     x: result<number, SqDistributionError> | ||||
|     x: result<number, distributionError> | ||||
|   ): React.ReactNode => { | ||||
|     if (x.tag === "Ok") { | ||||
|       return <NumberShower number={x.value} />; | ||||
|     } else { | ||||
|       return ( | ||||
|         <ErrorAlert heading="Distribution Error"> | ||||
|           {x.value.toString()} | ||||
|           {distributionErrorToString(x.value)} | ||||
|         </ErrorAlert> | ||||
|       ); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,15 +1,14 @@ | |||
| import * as React from "react"; | ||||
| import { | ||||
|   SqLambda, | ||||
|   lambdaValue, | ||||
|   environment, | ||||
|   SqValueTag, | ||||
|   SqError, | ||||
|   runForeign, | ||||
|   errorValueToString, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { FunctionChart1Dist } from "./FunctionChart1Dist"; | ||||
| import { FunctionChart1Number } from "./FunctionChart1Number"; | ||||
| import { DistributionPlottingSettings } from "./DistributionChart"; | ||||
| import { MessageAlert } from "./Alert"; | ||||
| import { SquiggleErrorAlert } from "./SquiggleErrorAlert"; | ||||
| import { ErrorAlert, MessageAlert } from "./Alert"; | ||||
| 
 | ||||
| export type FunctionChartSettings = { | ||||
|   start: number; | ||||
|  | @ -18,32 +17,13 @@ export type FunctionChartSettings = { | |||
| }; | ||||
| 
 | ||||
| interface FunctionChartProps { | ||||
|   fn: SqLambda; | ||||
|   fn: lambdaValue; | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   distributionPlotSettings: DistributionPlottingSettings; | ||||
|   environment: environment; | ||||
|   height: number; | ||||
| } | ||||
| 
 | ||||
| const FunctionCallErrorAlert = ({ error }: { error: SqError }) => { | ||||
|   const [expanded, setExpanded] = React.useState(false); | ||||
|   if (expanded) { | ||||
|   } | ||||
|   return ( | ||||
|     <MessageAlert heading="Function Display Failed"> | ||||
|       <div className="space-y-2"> | ||||
|         <span | ||||
|           className="underline decoration-dashed cursor-pointer" | ||||
|           onClick={() => setExpanded(!expanded)} | ||||
|         > | ||||
|           {expanded ? "Hide" : "Show"} error details | ||||
|         </span> | ||||
|         {expanded ? <SquiggleErrorAlert error={error} /> : null} | ||||
|       </div> | ||||
|     </MessageAlert> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export const FunctionChart: React.FC<FunctionChartProps> = ({ | ||||
|   fn, | ||||
|   chartSettings, | ||||
|  | @ -51,16 +31,15 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | |||
|   distributionPlotSettings, | ||||
|   height, | ||||
| }) => { | ||||
|   console.log(fn.parameters().length); | ||||
|   if (fn.parameters().length !== 1) { | ||||
|   if (fn.parameters.length > 1) { | ||||
|     return ( | ||||
|       <MessageAlert heading="Function Display Not Supported"> | ||||
|         Only functions with one parameter are displayed. | ||||
|       </MessageAlert> | ||||
|     ); | ||||
|   } | ||||
|   const result1 = fn.call([chartSettings.start]); | ||||
|   const result2 = fn.call([chartSettings.stop]); | ||||
|   const result1 = runForeign(fn, [chartSettings.start], environment); | ||||
|   const result2 = runForeign(fn, [chartSettings.stop], environment); | ||||
|   const getValidResult = () => { | ||||
|     if (result1.tag === "Ok") { | ||||
|       return result1; | ||||
|  | @ -73,11 +52,15 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | |||
|   const validResult = getValidResult(); | ||||
| 
 | ||||
|   if (validResult.tag === "Error") { | ||||
|     return <FunctionCallErrorAlert error={validResult.value} />; | ||||
|     return ( | ||||
|       <ErrorAlert heading="Error"> | ||||
|         {errorValueToString(validResult.value)} | ||||
|       </ErrorAlert> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   switch (validResult.value.tag) { | ||||
|     case SqValueTag.Distribution: | ||||
|     case "distribution": | ||||
|       return ( | ||||
|         <FunctionChart1Dist | ||||
|           fn={fn} | ||||
|  | @ -87,7 +70,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({ | |||
|           distributionPlotSettings={distributionPlotSettings} | ||||
|         /> | ||||
|       ); | ||||
|     case SqValueTag.Number: | ||||
|     case "number": | ||||
|       return ( | ||||
|         <FunctionChart1Number | ||||
|           fn={fn} | ||||
|  |  | |||
|  | @ -2,13 +2,14 @@ import * as React from "react"; | |||
| import _ from "lodash"; | ||||
| import type { Spec } from "vega"; | ||||
| import { | ||||
|   SqDistribution, | ||||
|   Distribution, | ||||
|   result, | ||||
|   SqLambda, | ||||
|   lambdaValue, | ||||
|   environment, | ||||
|   SqError, | ||||
|   SqValue, | ||||
|   SqValueTag, | ||||
|   runForeign, | ||||
|   squiggleExpression, | ||||
|   errorValue, | ||||
|   errorValueToString, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { createClassFromSpec } from "react-vega"; | ||||
| import * as percentilesSpec from "../vega-specs/spec-percentiles.json"; | ||||
|  | @ -45,7 +46,7 @@ export type FunctionChartSettings = { | |||
| }; | ||||
| 
 | ||||
| interface FunctionChart1DistProps { | ||||
|   fn: SqLambda; | ||||
|   fn: lambdaValue; | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   distributionPlotSettings: DistributionPlottingSettings; | ||||
|   environment: environment; | ||||
|  | @ -76,17 +77,9 @@ type errors = _.Dictionary< | |||
|   }[] | ||||
| >; | ||||
| 
 | ||||
| type point = { x: number; value: result<SqDistribution, string> }; | ||||
| type point = { x: number; value: result<Distribution, string> }; | ||||
| 
 | ||||
| let getPercentiles = ({ | ||||
|   chartSettings, | ||||
|   fn, | ||||
|   environment, | ||||
| }: { | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   fn: SqLambda; | ||||
|   environment: environment; | ||||
| }) => { | ||||
| let getPercentiles = ({ chartSettings, fn, environment }) => { | ||||
|   let chartPointsToRender = _rangeByCount( | ||||
|     chartSettings.start, | ||||
|     chartSettings.stop, | ||||
|  | @ -94,9 +87,9 @@ let getPercentiles = ({ | |||
|   ); | ||||
| 
 | ||||
|   let chartPointsData: point[] = chartPointsToRender.map((x) => { | ||||
|     let result = fn.call([x]); | ||||
|     let result = runForeign(fn, [x], environment); | ||||
|     if (result.tag === "Ok") { | ||||
|       if (result.value.tag === SqValueTag.Distribution) { | ||||
|       if (result.value.tag === "distribution") { | ||||
|         return { x, value: { tag: "Ok", value: result.value.value } }; | ||||
|       } else { | ||||
|         return { | ||||
|  | @ -111,13 +104,13 @@ let getPercentiles = ({ | |||
|     } else { | ||||
|       return { | ||||
|         x, | ||||
|         value: { tag: "Error", value: result.value.toString() }, | ||||
|         value: { tag: "Error", value: errorValueToString(result.value) }, | ||||
|       }; | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   let initialPartition: [ | ||||
|     { x: number; value: SqDistribution }[], | ||||
|     { x: number; value: Distribution }[], | ||||
|     { x: number; value: string }[] | ||||
|   ] = [[], []]; | ||||
| 
 | ||||
|  | @ -133,23 +126,26 @@ let getPercentiles = ({ | |||
|   let groupedErrors: errors = _.groupBy(errors, (x) => x.value); | ||||
| 
 | ||||
|   let percentiles: percentiles = functionImage.map(({ x, value }) => { | ||||
|     const res = { | ||||
|     // We convert it to to a pointSet distribution first, so that in case its a sample set
 | ||||
|     // 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, | ||||
|       p1: unwrap(value.inv(environment, 0.01)), | ||||
|       p5: unwrap(value.inv(environment, 0.05)), | ||||
|       p10: unwrap(value.inv(environment, 0.1)), | ||||
|       p20: unwrap(value.inv(environment, 0.2)), | ||||
|       p30: unwrap(value.inv(environment, 0.3)), | ||||
|       p40: unwrap(value.inv(environment, 0.4)), | ||||
|       p50: unwrap(value.inv(environment, 0.5)), | ||||
|       p60: unwrap(value.inv(environment, 0.6)), | ||||
|       p70: unwrap(value.inv(environment, 0.7)), | ||||
|       p80: unwrap(value.inv(environment, 0.8)), | ||||
|       p90: unwrap(value.inv(environment, 0.9)), | ||||
|       p95: unwrap(value.inv(environment, 0.95)), | ||||
|       p99: unwrap(value.inv(environment, 0.99)), | ||||
|       p1: unwrap(toPointSet.inv(0.01)), | ||||
|       p5: unwrap(toPointSet.inv(0.05)), | ||||
|       p10: unwrap(toPointSet.inv(0.1)), | ||||
|       p20: unwrap(toPointSet.inv(0.2)), | ||||
|       p30: unwrap(toPointSet.inv(0.3)), | ||||
|       p40: unwrap(toPointSet.inv(0.4)), | ||||
|       p50: unwrap(toPointSet.inv(0.5)), | ||||
|       p60: unwrap(toPointSet.inv(0.6)), | ||||
|       p70: unwrap(toPointSet.inv(0.7)), | ||||
|       p80: unwrap(toPointSet.inv(0.8)), | ||||
|       p90: unwrap(toPointSet.inv(0.9)), | ||||
|       p95: unwrap(toPointSet.inv(0.95)), | ||||
|       p99: unwrap(toPointSet.inv(0.99)), | ||||
|     }; | ||||
|     return res; | ||||
|   }); | ||||
| 
 | ||||
|   return { percentiles, errors: groupedErrors }; | ||||
|  | @ -172,20 +168,19 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({ | |||
|   const signalListeners = { mousemove: handleHover, mouseout: handleOut }; | ||||
| 
 | ||||
|   //TODO: This custom error handling is a bit hacky and should be improved.
 | ||||
|   let mouseItem: result<SqValue, SqError> = !!mouseOverlay | ||||
|     ? fn.call([mouseOverlay]) | ||||
|   let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay | ||||
|     ? runForeign(fn, [mouseOverlay], environment) | ||||
|     : { | ||||
|         tag: "Error", | ||||
|         value: SqError.createOtherError( | ||||
|           "Hover x-coordinate returned NaN. Expected a number." | ||||
|         ), | ||||
|         value: { | ||||
|           tag: "RETodo", | ||||
|           value: "Hover x-coordinate returned NaN. Expected a number.", | ||||
|         }, | ||||
|       }; | ||||
|   let showChart = | ||||
|     mouseItem.tag === "Ok" && | ||||
|     mouseItem.value.tag === SqValueTag.Distribution ? ( | ||||
|     mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? ( | ||||
|       <DistributionChart | ||||
|         plot={defaultPlot(mouseItem.value.value)} | ||||
|         environment={environment} | ||||
|         width={400} | ||||
|         height={50} | ||||
|         {...distributionPlotSettings} | ||||
|  |  | |||
|  | @ -1,11 +1,16 @@ | |||
| import * as React from "react"; | ||||
| import _ from "lodash"; | ||||
| import type { Spec } from "vega"; | ||||
| import { result, SqLambda, environment, SqValueTag } from "@quri/squiggle-lang"; | ||||
| import { | ||||
|   result, | ||||
|   lambdaValue, | ||||
|   environment, | ||||
|   runForeign, | ||||
|   errorValueToString, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { createClassFromSpec } from "react-vega"; | ||||
| import * as lineChartSpec from "../vega-specs/spec-line-chart.json"; | ||||
| import { ErrorAlert } from "./Alert"; | ||||
| import { squiggleValueTag } from "@quri/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag"; | ||||
| 
 | ||||
| let SquiggleLineChart = createClassFromSpec({ | ||||
|   spec: lineChartSpec as Spec, | ||||
|  | @ -25,7 +30,7 @@ export type FunctionChartSettings = { | |||
| }; | ||||
| 
 | ||||
| interface FunctionChart1NumberProps { | ||||
|   fn: SqLambda; | ||||
|   fn: lambdaValue; | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   environment: environment; | ||||
|   height: number; | ||||
|  | @ -33,15 +38,7 @@ interface FunctionChart1NumberProps { | |||
| 
 | ||||
| type point = { x: number; value: result<number, string> }; | ||||
| 
 | ||||
| let getFunctionImage = ({ | ||||
|   chartSettings, | ||||
|   fn, | ||||
|   environment, | ||||
| }: { | ||||
|   chartSettings: FunctionChartSettings; | ||||
|   fn: SqLambda; | ||||
|   environment: environment; | ||||
| }) => { | ||||
| let getFunctionImage = ({ chartSettings, fn, environment }) => { | ||||
|   let chartPointsToRender = _rangeByCount( | ||||
|     chartSettings.start, | ||||
|     chartSettings.stop, | ||||
|  | @ -49,9 +46,9 @@ let getFunctionImage = ({ | |||
|   ); | ||||
| 
 | ||||
|   let chartPointsData: point[] = chartPointsToRender.map((x) => { | ||||
|     let result = fn.call([x]); | ||||
|     let result = runForeign(fn, [x], environment); | ||||
|     if (result.tag === "Ok") { | ||||
|       if (result.value.tag === SqValueTag.Number) { | ||||
|       if (result.value.tag == "number") { | ||||
|         return { x, value: { tag: "Ok", value: result.value.value } }; | ||||
|       } else { | ||||
|         return { | ||||
|  | @ -65,7 +62,7 @@ let getFunctionImage = ({ | |||
|     } else { | ||||
|       return { | ||||
|         x, | ||||
|         value: { tag: "Error", value: result.value.toString() }, | ||||
|         value: { tag: "Error", value: errorValueToString(result.value) }, | ||||
|       }; | ||||
|     } | ||||
|   }); | ||||
|  |  | |||
|  | @ -1,22 +1,25 @@ | |||
| import * as React from "react"; | ||||
| import { | ||||
|   SqValue, | ||||
|   squiggleExpression, | ||||
|   bindings, | ||||
|   environment, | ||||
|   SqProject, | ||||
|   jsImports, | ||||
|   defaultImports, | ||||
|   defaultBindings, | ||||
|   defaultEnvironment, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { useSquiggle } from "../lib/hooks"; | ||||
| import { SquiggleViewer } from "./SquiggleViewer"; | ||||
| import { JsImports } from "../lib/jsImports"; | ||||
| import { getValueToRender } from "../lib/utility"; | ||||
| 
 | ||||
| export type SquiggleChartProps = { | ||||
| export interface SquiggleChartProps { | ||||
|   /** The input string for squiggle */ | ||||
|   code: string; | ||||
|   code?: string; | ||||
|   /** Allows to re-run the code if code hasn't changed */ | ||||
|   executionId?: number; | ||||
|   /** If the output requires monte carlo sampling, the amount of samples */ | ||||
|   sampleCount?: number; | ||||
|   /** The amount of points returned to draw the distribution */ | ||||
|   environment?: environment; | ||||
|   /** If the result is a function, where the function domain starts */ | ||||
|   diagramStart?: number; | ||||
|   /** If the result is a function, where the function domain ends */ | ||||
|  | @ -24,12 +27,14 @@ export type SquiggleChartProps = { | |||
|   /** If the result is a function, the amount of stops sampled */ | ||||
|   diagramCount?: number; | ||||
|   /** When the squiggle code gets reevaluated */ | ||||
|   onChange?(expr: SqValue | undefined, sourceName: string): void; | ||||
|   onChange?(expr: squiggleExpression | undefined): void; | ||||
|   /** CSS width of the element */ | ||||
|   width?: number; | ||||
|   height?: number; | ||||
|   /** Bindings of previous variables declared */ | ||||
|   bindings?: bindings; | ||||
|   /** JS imported parameters */ | ||||
|   jsImports?: JsImports; | ||||
|   jsImports?: jsImports; | ||||
|   /** Whether to show a summary of the distribution */ | ||||
|   showSummary?: boolean; | ||||
|   /** Set the x scale to be logarithmic by deault */ | ||||
|  | @ -46,35 +51,24 @@ export type SquiggleChartProps = { | |||
|   minX?: number; | ||||
|   /** Specify the upper bound of the x scale */ | ||||
|   maxX?: number; | ||||
|   /** Whether the x-axis should be dates or numbers */ | ||||
|   xAxisType?: "number" | "dateTime"; | ||||
|   /** Whether to show vega actions to the user, so they can copy the chart spec */ | ||||
|   distributionChartActions?: boolean; | ||||
|   enableLocalSettings?: boolean; | ||||
| } & (StandaloneExecutionProps | ProjectExecutionProps); | ||||
| } | ||||
| 
 | ||||
| // Props needed for a standalone execution
 | ||||
| type StandaloneExecutionProps = { | ||||
|   project?: undefined; | ||||
|   continues?: undefined; | ||||
|   /** The amount of points returned to draw the distribution, not needed if using a project */ | ||||
|   environment?: environment; | ||||
| }; | ||||
| 
 | ||||
| // Props needed when executing inside a project.
 | ||||
| type ProjectExecutionProps = { | ||||
|   environment?: undefined; | ||||
|   /** The project that this execution is part of */ | ||||
|   project: SqProject; | ||||
|   /** What other squiggle sources from the project to continue. Default [] */ | ||||
|   continues?: string[]; | ||||
| }; | ||||
| const defaultOnChange = () => {}; | ||||
| const defaultImports: JsImports = {}; | ||||
| 
 | ||||
| export const splitSquiggleChartSettings = (props: SquiggleChartProps) => { | ||||
|   const { | ||||
| export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | ||||
|   ({ | ||||
|     code = "", | ||||
|     executionId = 0, | ||||
|     environment, | ||||
|     onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
 | ||||
|     height = 200, | ||||
|     bindings = defaultBindings, | ||||
|     jsImports = defaultImports, | ||||
|     showSummary = false, | ||||
|     width, | ||||
|     logX = false, | ||||
|     expY = false, | ||||
|     diagramStart = 0, | ||||
|  | @ -85,9 +79,17 @@ export const splitSquiggleChartSettings = (props: SquiggleChartProps) => { | |||
|     maxX, | ||||
|     color, | ||||
|     title, | ||||
|     xAxisType = "number", | ||||
|     distributionChartActions, | ||||
|   } = props; | ||||
|     enableLocalSettings = false, | ||||
|   }) => { | ||||
|     const result = useSquiggle({ | ||||
|       code, | ||||
|       bindings, | ||||
|       environment, | ||||
|       jsImports, | ||||
|       onChange, | ||||
|       executionId, | ||||
|     }); | ||||
| 
 | ||||
|     const distributionPlotSettings = { | ||||
|       showSummary, | ||||
|  | @ -98,7 +100,6 @@ export const splitSquiggleChartSettings = (props: SquiggleChartProps) => { | |||
|       maxX, | ||||
|       color, | ||||
|       title, | ||||
|     xAxisType, | ||||
|       actions: distributionChartActions, | ||||
|     }; | ||||
| 
 | ||||
|  | @ -108,49 +109,14 @@ export const splitSquiggleChartSettings = (props: SquiggleChartProps) => { | |||
|       count: diagramCount, | ||||
|     }; | ||||
| 
 | ||||
|   return { distributionPlotSettings, chartSettings }; | ||||
| }; | ||||
| 
 | ||||
| export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( | ||||
|   (props) => { | ||||
|     const { distributionPlotSettings, chartSettings } = | ||||
|       splitSquiggleChartSettings(props); | ||||
| 
 | ||||
|     const { | ||||
|       code, | ||||
|       jsImports = defaultImports, | ||||
|       onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
 | ||||
|       executionId = 0, | ||||
|       width, | ||||
|       height = 200, | ||||
|       enableLocalSettings = false, | ||||
|       continues, | ||||
|       project, | ||||
|       environment, | ||||
|     } = props; | ||||
| 
 | ||||
|     const resultAndBindings = useSquiggle({ | ||||
|       environment, | ||||
|       continues, | ||||
|       project, | ||||
|       code, | ||||
|       jsImports, | ||||
|       onChange, | ||||
|       executionId, | ||||
|     }); | ||||
| 
 | ||||
|     const valueToRender = getValueToRender(resultAndBindings); | ||||
| 
 | ||||
|     return ( | ||||
|       <SquiggleViewer | ||||
|         result={valueToRender} | ||||
|         result={result} | ||||
|         width={width} | ||||
|         height={height} | ||||
|         distributionPlotSettings={distributionPlotSettings} | ||||
|         chartSettings={chartSettings} | ||||
|         environment={ | ||||
|           project ? project.getEnvironment() : environment ?? defaultEnvironment | ||||
|         } | ||||
|         environment={environment ?? defaultEnvironment} | ||||
|         enableLocalSettings={enableLocalSettings} | ||||
|       /> | ||||
|     ); | ||||
|  |  | |||
|  | @ -1,29 +1,23 @@ | |||
| import React from "react"; | ||||
| import { CodeEditor } from "./CodeEditor"; | ||||
| import { environment, bindings, jsImports } from "@quri/squiggle-lang"; | ||||
| import { defaultImports, defaultBindings } from "@quri/squiggle-lang"; | ||||
| import { SquiggleContainer } from "./SquiggleContainer"; | ||||
| import { | ||||
|   splitSquiggleChartSettings, | ||||
|   SquiggleChartProps, | ||||
| } from "./SquiggleChart"; | ||||
| import { useMaybeControlledValue, useSquiggle } from "../lib/hooks"; | ||||
| import { JsImports } from "../lib/jsImports"; | ||||
| import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang"; | ||||
| import { SquiggleViewer } from "./SquiggleViewer"; | ||||
| import { getErrorLocations, getValueToRender } from "../lib/utility"; | ||||
| import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; | ||||
| import { useSquigglePartial, useMaybeControlledValue } from "../lib/hooks"; | ||||
| import { SquiggleErrorAlert } from "./SquiggleErrorAlert"; | ||||
| 
 | ||||
| const WrappedCodeEditor: React.FC<{ | ||||
|   code: string; | ||||
|   setCode: (code: string) => void; | ||||
|   errorLocations?: SqLocation[]; | ||||
| }> = ({ code, setCode, errorLocations }) => ( | ||||
|   <div className="border border-grey-200 p-2 m-4" data-testid="squiggle-editor"> | ||||
| }> = ({ code, setCode }) => ( | ||||
|   <div className="border border-grey-200 p-2 m-4"> | ||||
|     <CodeEditor | ||||
|       value={code} | ||||
|       onChange={setCode} | ||||
|       oneLine={true} | ||||
|       showGutter={false} | ||||
|       height={20} | ||||
|       errorLocations={errorLocations} | ||||
|     /> | ||||
|   </div> | ||||
| ); | ||||
|  | @ -33,9 +27,6 @@ export type SquiggleEditorProps = SquiggleChartProps & { | |||
|   onCodeChange?: (code: string) => void; | ||||
| }; | ||||
| 
 | ||||
| const defaultOnChange = () => {}; | ||||
| const defaultImports: JsImports = {}; | ||||
| 
 | ||||
| export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => { | ||||
|   const [code, setCode] = useMaybeControlledValue({ | ||||
|     value: props.code, | ||||
|  | @ -43,50 +34,59 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => { | |||
|     onChange: props.onCodeChange, | ||||
|   }); | ||||
| 
 | ||||
|   const { distributionPlotSettings, chartSettings } = | ||||
|     splitSquiggleChartSettings(props); | ||||
| 
 | ||||
|   const { | ||||
|     environment, | ||||
|     jsImports = defaultImports, | ||||
|     onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
 | ||||
|     executionId = 0, | ||||
|     width, | ||||
|     height = 200, | ||||
|     enableLocalSettings = false, | ||||
|     continues, | ||||
|     project, | ||||
|   } = props; | ||||
| 
 | ||||
|   const resultAndBindings = useSquiggle({ | ||||
|     environment, | ||||
|     continues, | ||||
|     code, | ||||
|     project, | ||||
|     jsImports, | ||||
|     onChange, | ||||
|     executionId, | ||||
|   }); | ||||
| 
 | ||||
|   const valueToRender = getValueToRender(resultAndBindings); | ||||
|   const errorLocations = getErrorLocations(resultAndBindings.result); | ||||
| 
 | ||||
|   let chartProps = { ...props, code }; | ||||
|   return ( | ||||
|     <SquiggleContainer> | ||||
|       <WrappedCodeEditor | ||||
|         code={code} | ||||
|         setCode={setCode} | ||||
|         errorLocations={errorLocations} | ||||
|       /> | ||||
|       <SquiggleViewer | ||||
|         result={valueToRender} | ||||
|         width={width} | ||||
|         height={height} | ||||
|         distributionPlotSettings={distributionPlotSettings} | ||||
|         chartSettings={chartSettings} | ||||
|         environment={environment ?? defaultEnvironment} | ||||
|         enableLocalSettings={enableLocalSettings} | ||||
|       /> | ||||
|       <WrappedCodeEditor code={code} setCode={setCode} /> | ||||
|       <SquiggleChart {...chartProps} /> | ||||
|     </SquiggleContainer> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export interface SquigglePartialProps { | ||||
|   /** 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, | ||||
|     jsImports, | ||||
|     onChange, | ||||
|   }); | ||||
| 
 | ||||
|   return ( | ||||
|     <SquiggleContainer> | ||||
|       <WrappedCodeEditor code={code} setCode={setCode} /> | ||||
|       {result.tag !== "Ok" ? <SquiggleErrorAlert error={result.value} /> : null} | ||||
|     </SquiggleContainer> | ||||
|   ); | ||||
| }; | ||||
|  |  | |||
|  | @ -0,0 +1,52 @@ | |||
| import React from "react"; | ||||
| import { SquiggleEditor } from "./SquiggleEditor"; | ||||
| import type { SquiggleEditorProps } from "./SquiggleEditor"; | ||||
| import { runPartial, defaultBindings } from "@quri/squiggle-lang"; | ||||
| import type { | ||||
|   result, | ||||
|   errorValue, | ||||
|   bindings as bindingsType, | ||||
| } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| function resultDefault(x: result<bindingsType, errorValue>): bindingsType { | ||||
|   switch (x.tag) { | ||||
|     case "Ok": | ||||
|       return x.value; | ||||
|     case "Error": | ||||
|       return defaultBindings; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export type SquiggleEditorWithImportedBindingsProps = SquiggleEditorProps & { | ||||
|   bindingsImportUrl: string; | ||||
| }; | ||||
| 
 | ||||
| export const SquiggleEditorWithImportedBindings: React.FC< | ||||
|   SquiggleEditorWithImportedBindingsProps | ||||
| > = (props) => { | ||||
|   const { bindingsImportUrl, ...editorProps } = props; | ||||
|   const [bindingsResult, setBindingsResult] = React.useState({ | ||||
|     tag: "Ok", | ||||
|     value: defaultBindings, | ||||
|   } as result<bindingsType, errorValue>); | ||||
|   React.useEffect(() => { | ||||
|     async function retrieveBindings(fileName: string) { | ||||
|       let contents = await fetch(fileName).then((response) => { | ||||
|         return response.text(); | ||||
|       }); | ||||
|       setBindingsResult( | ||||
|         runPartial( | ||||
|           contents, | ||||
|           editorProps.bindings, | ||||
|           editorProps.environment, | ||||
|           editorProps.jsImports | ||||
|         ) | ||||
|       ); | ||||
|     } | ||||
|     retrieveBindings(bindingsImportUrl); | ||||
|   }, [bindingsImportUrl]); | ||||
|   const deliveredBindings = resultDefault(bindingsResult); | ||||
|   return ( | ||||
|     <SquiggleEditor {...{ ...editorProps, bindings: deliveredBindings }} /> | ||||
|   ); | ||||
| }; | ||||
|  | @ -1,44 +1,11 @@ | |||
| import { SqError, SqFrame } from "@quri/squiggle-lang"; | ||||
| import { errorValue, errorValueToString } from "@quri/squiggle-lang"; | ||||
| import React from "react"; | ||||
| import { ErrorAlert } from "./Alert"; | ||||
| 
 | ||||
| type Props = { | ||||
|   error: SqError; | ||||
| }; | ||||
| 
 | ||||
| const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => { | ||||
|   const location = frame.location(); | ||||
|   return ( | ||||
|     <div> | ||||
|       {frame.name()} | ||||
|       {location | ||||
|         ? ` at line ${location.start.line}, column ${location.start.column}` | ||||
|         : ""} | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| const StackTrace: React.FC<Props> = ({ error }) => { | ||||
|   const frames = error.getFrameArray(); | ||||
|   return frames.length ? ( | ||||
|     <div> | ||||
|       <div className="font-medium">Stack trace:</div> | ||||
|       <div className="ml-4"> | ||||
|         {frames.map((frame, i) => ( | ||||
|           <StackTraceFrame frame={frame} key={i} /> | ||||
|         ))} | ||||
|       </div> | ||||
|     </div> | ||||
|   ) : null; | ||||
|   error: errorValue; | ||||
| }; | ||||
| 
 | ||||
| export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => { | ||||
|   return ( | ||||
|     <ErrorAlert heading="Error"> | ||||
|       <div className="space-y-4"> | ||||
|         <div>{error.toString()}</div> | ||||
|         <StackTrace error={error} /> | ||||
|       </div> | ||||
|     </ErrorAlert> | ||||
|   ); | ||||
|   return <ErrorAlert heading="Error">{errorValueToString(error)}</ErrorAlert>; | ||||
| }; | ||||
|  |  | |||
|  | @ -8,11 +8,7 @@ import React, { | |||
| } from "react"; | ||||
| import { useForm, UseFormRegister, useWatch } from "react-hook-form"; | ||||
| import * as yup from "yup"; | ||||
| import { | ||||
|   useMaybeControlledValue, | ||||
|   useRunnerState, | ||||
|   useSquiggle, | ||||
| } from "../lib/hooks"; | ||||
| import { useMaybeControlledValue, useRunnerState } from "../lib/hooks"; | ||||
| import { yupResolver } from "@hookform/resolvers/yup"; | ||||
| import { | ||||
|   ChartSquareBarIcon, | ||||
|  | @ -28,9 +24,9 @@ import { | |||
| } from "@heroicons/react/solid"; | ||||
| import clsx from "clsx"; | ||||
| 
 | ||||
| import { environment, SqProject } from "@quri/squiggle-lang"; | ||||
| import { defaultBindings, environment } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| import { SquiggleChartProps } from "./SquiggleChart"; | ||||
| import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; | ||||
| import { CodeEditor } from "./CodeEditor"; | ||||
| import { JsonEditor } from "./JsonEditor"; | ||||
| import { ErrorAlert, SuccessAlert } from "./Alert"; | ||||
|  | @ -43,9 +39,6 @@ import { ViewSettings, viewSettingsSchema } from "./ViewSettings"; | |||
| import { HeadedSection } from "./ui/HeadedSection"; | ||||
| import { defaultTickFormat } from "../lib/distributionSpecBuilder"; | ||||
| import { Button } from "./ui/Button"; | ||||
| import { JsImports } from "../lib/jsImports"; | ||||
| import { getErrorLocations, getValueToRender } from "../lib/utility"; | ||||
| import { SquiggleViewer } from "./SquiggleViewer"; | ||||
| 
 | ||||
| type PlaygroundProps = SquiggleChartProps & { | ||||
|   /** The initial squiggle string to put in the playground */ | ||||
|  | @ -119,8 +112,8 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({ | |||
| ); | ||||
| 
 | ||||
| const InputVariablesSettings: React.FC<{ | ||||
|   initialImports: JsImports; | ||||
|   setImports: (imports: JsImports) => void; | ||||
|   initialImports: any; // TODO - any json type
 | ||||
|   setImports: (imports: any) => void; | ||||
| }> = ({ initialImports, setImports }) => { | ||||
|   const [importString, setImportString] = useState(() => | ||||
|     JSON.stringify(initialImports) | ||||
|  | @ -129,7 +122,7 @@ const InputVariablesSettings: React.FC<{ | |||
| 
 | ||||
|   const onChange = (value: string) => { | ||||
|     setImportString(value); | ||||
|     let imports = {}; | ||||
|     let imports = {} as any; | ||||
|     try { | ||||
|       imports = JSON.parse(value); | ||||
|       setImportsAreValid(true); | ||||
|  | @ -182,7 +175,7 @@ const RunControls: React.FC<{ | |||
|   const CurrentPlayIcon = isRunning ? RefreshIcon : PlayIcon; | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex space-x-1 items-center" data-testid="autorun-controls"> | ||||
|     <div className="flex space-x-1 items-center"> | ||||
|       {autorunMode ? null : ( | ||||
|         <button onClick={run}> | ||||
|           <CurrentPlayIcon | ||||
|  | @ -251,8 +244,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | |||
|   onSettingsChange, | ||||
|   showEditor = true, | ||||
|   showShareButton = false, | ||||
|   continues, | ||||
|   project, | ||||
| }) => { | ||||
|   const [code, setCode] = useMaybeControlledValue({ | ||||
|     value: controlledCode, | ||||
|  | @ -260,7 +251,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | |||
|     onChange: onCodeChange, | ||||
|   }); | ||||
| 
 | ||||
|   const [imports, setImports] = useState<JsImports>({}); | ||||
|   const [imports, setImports] = useState({}); | ||||
| 
 | ||||
|   const { register, control } = useForm({ | ||||
|     resolver: yupResolver(schema), | ||||
|  | @ -290,7 +281,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | |||
|     onSettingsChange?.(vars); | ||||
|   }, [vars, onSettingsChange]); | ||||
| 
 | ||||
|   const environment: environment = useMemo( | ||||
|   const env: environment = useMemo( | ||||
|     () => ({ | ||||
|       sampleCount: Number(vars.sampleCount), | ||||
|       xyPointLength: Number(vars.xyPointLength), | ||||
|  | @ -307,53 +298,27 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | |||
|     executionId, | ||||
|   } = useRunnerState(code); | ||||
| 
 | ||||
|   const resultAndBindings = useSquiggle({ | ||||
|     environment, | ||||
|     continues, | ||||
|     code: renderedCode, | ||||
|     project, | ||||
|     jsImports: imports, | ||||
|     executionId, | ||||
|   }); | ||||
| 
 | ||||
|   const valueToRender = getValueToRender(resultAndBindings); | ||||
| 
 | ||||
|   const squiggleChart = | ||||
|     renderedCode === "" ? null : ( | ||||
|       <div className="relative"> | ||||
|         {isRunning ? ( | ||||
|           <div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" /> | ||||
|         ) : null} | ||||
|         <SquiggleViewer | ||||
|           result={valueToRender} | ||||
|           environment={environment} | ||||
|           height={vars.chartHeight || 150} | ||||
|           distributionPlotSettings={{ | ||||
|             showSummary: vars.showSummary ?? false, | ||||
|             logX: vars.logX ?? false, | ||||
|             expY: vars.expY ?? false, | ||||
|             format: vars.tickFormat, | ||||
|             minX: vars.minX, | ||||
|             maxX: vars.maxX, | ||||
|             title: vars.title, | ||||
|             actions: vars.distributionChartActions, | ||||
|           }} | ||||
|           chartSettings={{ | ||||
|             start: vars.diagramStart ?? 0, | ||||
|             stop: vars.diagramStop ?? 10, | ||||
|             count: vars.diagramCount ?? 20, | ||||
|           }} | ||||
|         <SquiggleChart | ||||
|           code={renderedCode} | ||||
|           executionId={executionId} | ||||
|           environment={env} | ||||
|           {...vars} | ||||
|           bindings={defaultBindings} | ||||
|           jsImports={imports} | ||||
|           enableLocalSettings={true} | ||||
|         /> | ||||
|       </div> | ||||
|     ); | ||||
| 
 | ||||
|   const errorLocations = getErrorLocations(resultAndBindings.result); | ||||
| 
 | ||||
|   const firstTab = vars.showEditor ? ( | ||||
|     <div className="border border-slate-200" data-testid="squiggle-editor"> | ||||
|     <div className="border border-slate-200"> | ||||
|       <CodeEditor | ||||
|         errorLocations={errorLocations} | ||||
|         value={code} | ||||
|         onChange={setCode} | ||||
|         onSubmit={run} | ||||
|  | @ -403,9 +368,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({ | |||
|       > | ||||
|         {tabs} | ||||
|       </div> | ||||
|       <div className="w-1/2 p-2 pl-4" data-testid="playground-result"> | ||||
|         {squiggleChart} | ||||
|       </div> | ||||
|       <div className="w-1/2 p-2 pl-4">{squiggleChart}</div> | ||||
|     </div> | ||||
|   ); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,17 +1,14 @@ | |||
| import React, { useContext } from "react"; | ||||
| import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang"; | ||||
| import React from "react"; | ||||
| import { squiggleExpression, declaration } from "@quri/squiggle-lang"; | ||||
| import { NumberShower } from "../NumberShower"; | ||||
| import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart"; | ||||
| import { FunctionChart } from "../FunctionChart"; | ||||
| import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; | ||||
| import clsx from "clsx"; | ||||
| import { VariableBox } from "./VariableBox"; | ||||
| import { ItemSettingsMenu } from "./ItemSettingsMenu"; | ||||
| import { hasMassBelowZero } from "../../lib/distributionUtils"; | ||||
| import { MergedItemSettings } from "./utils"; | ||||
| import { ViewerContext } from "./ViewerContext"; | ||||
| 
 | ||||
| /* | ||||
| // DISABLED FOR 0.4 branch, for now
 | ||||
| function getRange<a>(x: declaration<a>) { | ||||
|   const first = x.args[0]; | ||||
|   switch (first.tag) { | ||||
|  | @ -34,21 +31,15 @@ function getChartSettings<a>(x: declaration<a>): FunctionChartSettings { | |||
|     count: 20, | ||||
|   }; | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| const VariableList: React.FC<{ | ||||
|   value: SqValue; | ||||
|   path: string[]; | ||||
|   heading: string; | ||||
|   children: (settings: MergedItemSettings) => React.ReactNode; | ||||
| }> = ({ value, heading, children }) => ( | ||||
|   <VariableBox value={value} heading={heading}> | ||||
| }> = ({ path, heading, children }) => ( | ||||
|   <VariableBox path={path} heading={heading}> | ||||
|     {(settings) => ( | ||||
|       <div | ||||
|         className={clsx( | ||||
|           "space-y-3", | ||||
|           value.location.path.items.length ? "pt-1 mt-1" : null | ||||
|         )} | ||||
|       > | ||||
|       <div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}> | ||||
|         {children(settings)} | ||||
|       </div> | ||||
|     )} | ||||
|  | @ -57,44 +48,51 @@ const VariableList: React.FC<{ | |||
| 
 | ||||
| export interface Props { | ||||
|   /** The output of squiggle's run */ | ||||
|   value: SqValue; | ||||
|   expression: squiggleExpression; | ||||
|   /** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */ | ||||
|   path: string[]; | ||||
|   width?: number; | ||||
| } | ||||
| 
 | ||||
| export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | ||||
|   const { getMergedSettings } = useContext(ViewerContext); | ||||
| 
 | ||||
|   switch (value.tag) { | ||||
|     case SqValueTag.Number: | ||||
| export const ExpressionViewer: React.FC<Props> = ({ | ||||
|   path, | ||||
|   expression, | ||||
|   width, | ||||
| }) => { | ||||
|   if (typeof expression !== "object") { | ||||
|     return ( | ||||
|         <VariableBox value={value} heading="Number"> | ||||
|       <VariableList path={path} heading="Error"> | ||||
|         {() => `Unknown expression: ${expression}`} | ||||
|       </VariableList> | ||||
|     ); | ||||
|   } | ||||
|   switch (expression.tag) { | ||||
|     case "number": | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Number"> | ||||
|           {() => ( | ||||
|             <div className="font-semibold text-slate-600"> | ||||
|               <NumberShower precision={3} number={value.value} /> | ||||
|               <NumberShower precision={3} number={expression.value} /> | ||||
|             </div> | ||||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case SqValueTag.Distribution: { | ||||
|       const distType = value.value.tag; | ||||
|     case "distribution": { | ||||
|       const distType = expression.value.type(); | ||||
|       return ( | ||||
|         <VariableBox | ||||
|           value={value} | ||||
|           path={path} | ||||
|           heading={`Distribution (${distType})\n${ | ||||
|             distType === SqDistributionTag.Symbolic | ||||
|               ? value.value.toString() | ||||
|               : "" | ||||
|             distType === "Symbolic" ? expression.value.toString() : "" | ||||
|           }`}
 | ||||
|           renderSettingsMenu={({ onChange }) => { | ||||
|             const shape = value.value.pointSet( | ||||
|               getMergedSettings(value.location).environment | ||||
|             ); | ||||
|             const shape = expression.value.pointSet(); | ||||
|             return ( | ||||
|               <ItemSettingsMenu | ||||
|                 value={value} | ||||
|                 path={path} | ||||
|                 onChange={onChange} | ||||
|                 disableLogX={ | ||||
|                   shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape()) | ||||
|                   shape.tag === "Ok" && hasMassBelowZero(shape.value) | ||||
|                 } | ||||
|                 withFunctionSettings={false} | ||||
|               /> | ||||
|  | @ -104,8 +102,7 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | |||
|           {(settings) => { | ||||
|             return ( | ||||
|               <DistributionChart | ||||
|                 plot={defaultPlot(value.value)} | ||||
|                 environment={settings.environment} | ||||
|                 plot={defaultPlot(expression.value)} | ||||
|                 {...settings.distributionPlotSettings} | ||||
|                 height={settings.height} | ||||
|                 width={width} | ||||
|  | @ -115,54 +112,77 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | |||
|         </VariableBox> | ||||
|       ); | ||||
|     } | ||||
|     case SqValueTag.String: | ||||
|     case "string": | ||||
|       return ( | ||||
|         <VariableBox value={value} heading="String"> | ||||
|         <VariableBox path={path} heading="String"> | ||||
|           {() => ( | ||||
|             <> | ||||
|               <span className="text-slate-400">"</span> | ||||
|               <span className="text-slate-600 font-semibold font-mono"> | ||||
|                 {value.value} | ||||
|                 {expression.value} | ||||
|               </span> | ||||
|               <span className="text-slate-400">"</span> | ||||
|             </> | ||||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case SqValueTag.Bool: | ||||
|     case "boolean": | ||||
|       return ( | ||||
|         <VariableBox value={value} heading="Boolean"> | ||||
|           {() => value.value.toString()} | ||||
|         <VariableBox path={path} heading="Boolean"> | ||||
|           {() => expression.value.toString()} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case SqValueTag.Date: | ||||
|     case "symbol": | ||||
|       return ( | ||||
|         <VariableBox value={value} heading="Date"> | ||||
|           {() => value.value.toDateString()} | ||||
|         <VariableBox path={path} heading="Symbol"> | ||||
|           {() => ( | ||||
|             <> | ||||
|               <span className="text-slate-500 mr-2">Undefined Symbol:</span> | ||||
|               <span className="text-slate-600">{expression.value}</span> | ||||
|             </> | ||||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case SqValueTag.Void: | ||||
|     case "call": | ||||
|       return ( | ||||
|         <VariableBox value={value} heading="Void"> | ||||
|         <VariableBox path={path} heading="Call"> | ||||
|           {() => expression.value} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "arraystring": | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Array String"> | ||||
|           {() => expression.value.map((r) => `"${r}"`).join(", ")} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "date": | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Date"> | ||||
|           {() => expression.value.toDateString()} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case "void": | ||||
|       return ( | ||||
|         <VariableBox path={path} heading="Void"> | ||||
|           {() => "Void"} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case SqValueTag.TimeDuration: { | ||||
|     case "timeDuration": { | ||||
|       return ( | ||||
|         <VariableBox value={value} heading="Time Duration"> | ||||
|           {() => <NumberShower precision={3} number={value.value} />} | ||||
|         <VariableBox path={path} heading="Time Duration"> | ||||
|           {() => <NumberShower precision={3} number={expression.value} />} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     } | ||||
|     case SqValueTag.Lambda: | ||||
|     case "lambda": | ||||
|       return ( | ||||
|         <VariableBox | ||||
|           value={value} | ||||
|           path={path} | ||||
|           heading="Function" | ||||
|           renderSettingsMenu={({ onChange }) => { | ||||
|             return ( | ||||
|               <ItemSettingsMenu | ||||
|                 value={value} | ||||
|                 path={path} | ||||
|                 onChange={onChange} | ||||
|                 withFunctionSettings={true} | ||||
|               /> | ||||
|  | @ -171,11 +191,11 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | |||
|         > | ||||
|           {(settings) => ( | ||||
|             <> | ||||
|               <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() | ||||
|                 .join(",")})`}</div>
 | ||||
|               <div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join( | ||||
|                 "," | ||||
|               )})`}</div>
 | ||||
|               <FunctionChart | ||||
|                 fn={value.value} | ||||
|                 fn={expression.value} | ||||
|                 chartSettings={settings.chartSettings} | ||||
|                 distributionPlotSettings={settings.distributionPlotSettings} | ||||
|                 height={settings.height} | ||||
|  | @ -188,57 +208,71 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | |||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     case SqValueTag.Declaration: { | ||||
|     case "lambdaDeclaration": { | ||||
|       return ( | ||||
|         <VariableBox | ||||
|           value={value} | ||||
|           path={path} | ||||
|           heading="Function Declaration" | ||||
|           renderSettingsMenu={({ onChange }) => { | ||||
|             return ( | ||||
|               <ItemSettingsMenu | ||||
|                 onChange={onChange} | ||||
|                 value={value} | ||||
|                 path={path} | ||||
|                 withFunctionSettings={true} | ||||
|               /> | ||||
|             ); | ||||
|           }} | ||||
|         > | ||||
|           {(settings) => ( | ||||
|             <div>NOT IMPLEMENTED IN 0.4 YET</div> | ||||
|             // <FunctionChart
 | ||||
|             //   fn={expression.value.fn}
 | ||||
|             //   chartSettings={getChartSettings(expression.value)}
 | ||||
|             //   distributionPlotSettings={settings.distributionPlotSettings}
 | ||||
|             //   height={settings.height}
 | ||||
|             //   environment={{
 | ||||
|             //     sampleCount: settings.environment.sampleCount / 10,
 | ||||
|             //     xyPointLength: settings.environment.xyPointLength / 10,
 | ||||
|             //   }}
 | ||||
|             // />
 | ||||
|             <FunctionChart | ||||
|               fn={expression.value.fn} | ||||
|               chartSettings={getChartSettings(expression.value)} | ||||
|               distributionPlotSettings={settings.distributionPlotSettings} | ||||
|               height={settings.height} | ||||
|               environment={{ | ||||
|                 sampleCount: settings.environment.sampleCount / 10, | ||||
|                 xyPointLength: settings.environment.xyPointLength / 10, | ||||
|               }} | ||||
|             /> | ||||
|           )} | ||||
|         </VariableBox> | ||||
|       ); | ||||
|     } | ||||
|     case SqValueTag.Record: | ||||
|       const plot = makePlot(value.value); | ||||
|     case "module": { | ||||
|       return ( | ||||
|         <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) { | ||||
|         return ( | ||||
|           <VariableBox | ||||
|             value={value} | ||||
|             heading="Plot" | ||||
|             path={path} | ||||
|             heading={"Plot"} | ||||
|             renderSettingsMenu={({ onChange }) => { | ||||
|               let disableLogX = plot.distributions.some((x) => { | ||||
|                 let pointSet = x.distribution.pointSet( | ||||
|                   getMergedSettings(value.location).environment | ||||
|                 ); | ||||
|                 let pointSet = x.distribution.pointSet(); | ||||
|                 return ( | ||||
|                   pointSet.tag === "Ok" && | ||||
|                   hasMassBelowZero(pointSet.value.asShape()) | ||||
|                   pointSet.tag === "Ok" && hasMassBelowZero(pointSet.value) | ||||
|                 ); | ||||
|               }); | ||||
|               return ( | ||||
|                 <ItemSettingsMenu | ||||
|                   value={value} | ||||
|                   path={path} | ||||
|                   onChange={onChange} | ||||
|                   disableLogX={disableLogX} | ||||
|                   withFunctionSettings={false} | ||||
|  | @ -250,7 +284,6 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | |||
|               return ( | ||||
|                 <DistributionChart | ||||
|                   plot={plot} | ||||
|                   environment={settings.environment} | ||||
|                   {...settings.distributionPlotSettings} | ||||
|                   height={settings.height} | ||||
|                   width={width} | ||||
|  | @ -261,14 +294,13 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | |||
|         ); | ||||
|       } else { | ||||
|         return ( | ||||
|           <VariableList value={value} heading="Record"> | ||||
|           <VariableList path={path} heading="Record"> | ||||
|             {(_) => | ||||
|               value.value | ||||
|                 .entries() | ||||
|                 .map(([key, r]) => ( | ||||
|               Object.entries(expression.value).map(([key, r]) => ( | ||||
|                 <ExpressionViewer | ||||
|                   key={key} | ||||
|                     value={r} | ||||
|                   path={[...path, key]} | ||||
|                   expression={r} | ||||
|                   width={width !== undefined ? width - 20 : width} | ||||
|                 /> | ||||
|               )) | ||||
|  | @ -276,16 +308,15 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | |||
|           </VariableList> | ||||
|         ); | ||||
|       } | ||||
|     case SqValueTag.Array: | ||||
|     case "array": | ||||
|       return ( | ||||
|         <VariableList value={value} heading="Array"> | ||||
|         <VariableList path={path} heading="Array"> | ||||
|           {(_) => | ||||
|             value.value | ||||
|               .getValues() | ||||
|               .map((r, i) => ( | ||||
|             expression.value.map((r, i) => ( | ||||
|               <ExpressionViewer | ||||
|                 key={i} | ||||
|                   value={r} | ||||
|                 path={[...path, String(i)]} | ||||
|                 expression={r} | ||||
|                 width={width !== undefined ? width - 20 : width} | ||||
|               /> | ||||
|             )) | ||||
|  | @ -294,12 +325,12 @@ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => { | |||
|       ); | ||||
|     default: { | ||||
|       return ( | ||||
|         <VariableList value={value} heading="Error"> | ||||
|         <VariableList path={path} heading="Error"> | ||||
|           {() => ( | ||||
|             <div> | ||||
|               <span>No display for type: </span>{" "} | ||||
|               <span className="font-semibold text-slate-600"> | ||||
|                 {(value as { tag: string }).tag} | ||||
|                 {expression.tag} | ||||
|               </span> | ||||
|             </div> | ||||
|           )} | ||||
|  |  | |||
|  | @ -4,14 +4,13 @@ import { useForm } from "react-hook-form"; | |||
| import { yupResolver } from "@hookform/resolvers/yup"; | ||||
| import { Modal } from "../ui/Modal"; | ||||
| import { ViewSettings, viewSettingsSchema } from "../ViewSettings"; | ||||
| import { Path, pathAsString } from "./utils"; | ||||
| import { ViewerContext } from "./ViewerContext"; | ||||
| import { defaultTickFormat } from "../../lib/distributionSpecBuilder"; | ||||
| import { PlaygroundContext } from "../SquigglePlayground"; | ||||
| import { SqValue } from "@quri/squiggle-lang"; | ||||
| import { locationAsString } from "./utils"; | ||||
| 
 | ||||
| type Props = { | ||||
|   value: SqValue; | ||||
|   path: Path; | ||||
|   onChange: () => void; | ||||
|   disableLogX?: boolean; | ||||
|   withFunctionSettings: boolean; | ||||
|  | @ -20,7 +19,7 @@ type Props = { | |||
| const ItemSettingsModal: React.FC< | ||||
|   Props & { close: () => void; resetScroll: () => void } | ||||
| > = ({ | ||||
|   value, | ||||
|   path, | ||||
|   onChange, | ||||
|   disableLogX, | ||||
|   withFunctionSettings, | ||||
|  | @ -30,7 +29,7 @@ const ItemSettingsModal: React.FC< | |||
|   const { setSettings, getSettings, getMergedSettings } = | ||||
|     useContext(ViewerContext); | ||||
| 
 | ||||
|   const mergedSettings = getMergedSettings(value.location); | ||||
|   const mergedSettings = getMergedSettings(path); | ||||
| 
 | ||||
|   const { register, watch } = useForm({ | ||||
|     resolver: yupResolver(viewSettingsSchema), | ||||
|  | @ -54,8 +53,8 @@ const ItemSettingsModal: React.FC< | |||
|   }); | ||||
|   useEffect(() => { | ||||
|     const subscription = watch((vars) => { | ||||
|       const settings = getSettings(value.location); // get the latest version
 | ||||
|       setSettings(value.location, { | ||||
|       const settings = getSettings(path); // get the latest version
 | ||||
|       setSettings(path, { | ||||
|         ...settings, | ||||
|         distributionPlotSettings: { | ||||
|           showSummary: vars.showSummary, | ||||
|  | @ -76,7 +75,7 @@ const ItemSettingsModal: React.FC< | |||
|       onChange(); | ||||
|     }); | ||||
|     return () => subscription.unsubscribe(); | ||||
|   }, [getSettings, setSettings, onChange, value.location, watch]); | ||||
|   }, [getSettings, setSettings, onChange, path, watch]); | ||||
| 
 | ||||
|   const { getLeftPanelElement } = useContext(PlaygroundContext); | ||||
| 
 | ||||
|  | @ -84,7 +83,7 @@ const ItemSettingsModal: React.FC< | |||
|     <Modal container={getLeftPanelElement()} close={close}> | ||||
|       <Modal.Header> | ||||
|         Chart settings | ||||
|         {value.location.path.items.length ? ( | ||||
|         {path.length ? ( | ||||
|           <> | ||||
|             {" for "} | ||||
|             <span | ||||
|  | @ -92,7 +91,7 @@ const ItemSettingsModal: React.FC< | |||
|               className="cursor-pointer" | ||||
|               onClick={resetScroll} | ||||
|             > | ||||
|               {locationAsString(value.location)} | ||||
|               {pathAsString(path)} | ||||
|             </span>{" "} | ||||
|           </> | ||||
|         ) : ( | ||||
|  | @ -121,7 +120,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => { | |||
|   if (!enableLocalSettings) { | ||||
|     return null; | ||||
|   } | ||||
|   const settings = getSettings(props.value.location); | ||||
|   const settings = getSettings(props.path); | ||||
| 
 | ||||
|   const resetScroll = () => { | ||||
|     if (!ref.current) return; | ||||
|  | @ -140,7 +139,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => { | |||
|       {settings.distributionPlotSettings || settings.chartSettings ? ( | ||||
|         <button | ||||
|           onClick={() => { | ||||
|             setSettings(props.value.location, { | ||||
|             setSettings(props.path, { | ||||
|               ...settings, | ||||
|               distributionPlotSettings: undefined, | ||||
|               chartSettings: undefined, | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| import { SqValue } from "@quri/squiggle-lang"; | ||||
| import React, { useContext, useReducer } from "react"; | ||||
| import { Tooltip } from "../ui/Tooltip"; | ||||
| import { LocalItemSettings, MergedItemSettings } from "./utils"; | ||||
|  | @ -9,14 +8,14 @@ type SettingsMenuParams = { | |||
| }; | ||||
| 
 | ||||
| type VariableBoxProps = { | ||||
|   value: SqValue; | ||||
|   path: string[]; | ||||
|   heading: string; | ||||
|   renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode; | ||||
|   children: (settings: MergedItemSettings) => React.ReactNode; | ||||
| }; | ||||
| 
 | ||||
| export const VariableBox: React.FC<VariableBoxProps> = ({ | ||||
|   value: { location }, | ||||
|   path, | ||||
|   heading = "Error", | ||||
|   renderSettingsMenu, | ||||
|   children, | ||||
|  | @ -28,10 +27,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | |||
|   // So we use `forceUpdate` to force rerendering.
 | ||||
|   const [_, forceUpdate] = useReducer((x) => x + 1, 0); | ||||
| 
 | ||||
|   const settings = getSettings(location); | ||||
|   const settings = getSettings(path); | ||||
| 
 | ||||
|   const setSettingsAndUpdate = (newSettings: LocalItemSettings) => { | ||||
|     setSettings(location, newSettings); | ||||
|     setSettings(path, newSettings); | ||||
|     forceUpdate(); | ||||
|   }; | ||||
| 
 | ||||
|  | @ -39,13 +38,11 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | |||
|     setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed }); | ||||
|   }; | ||||
| 
 | ||||
|   const isTopLevel = location.path.items.length === 0; | ||||
|   const name = isTopLevel | ||||
|     ? { result: "Result", bindings: "Bindings" }[location.path.root] | ||||
|     : location.path.items[location.path.items.length - 1]; | ||||
|   const isTopLevel = path.length === 0; | ||||
|   const name = isTopLevel ? "Result" : path[path.length - 1]; | ||||
| 
 | ||||
|   return ( | ||||
|     <div role={isTopLevel ? "status" : undefined}> | ||||
|     <div> | ||||
|       <header className="inline-flex space-x-1"> | ||||
|         <Tooltip text={heading}> | ||||
|           <span | ||||
|  | @ -68,13 +65,13 @@ export const VariableBox: React.FC<VariableBoxProps> = ({ | |||
|       </header> | ||||
|       {settings.collapsed ? null : ( | ||||
|         <div className="flex w-full"> | ||||
|           {location.path.items.length ? ( | ||||
|           {path.length ? ( | ||||
|             <div | ||||
|               className="shrink-0 border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer" | ||||
|               className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer" | ||||
|               onClick={toggleCollapsed} | ||||
|             ></div> | ||||
|           ) : null} | ||||
|           <div className="grow">{children(getMergedSettings(location))}</div> | ||||
|           <div className="grow">{children(getMergedSettings(path))}</div> | ||||
|         </div> | ||||
|       )} | ||||
|     </div> | ||||
|  |  | |||
|  | @ -1,14 +1,14 @@ | |||
| import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang"; | ||||
| import { defaultEnvironment } from "@quri/squiggle-lang"; | ||||
| import React from "react"; | ||||
| import { LocalItemSettings, MergedItemSettings } from "./utils"; | ||||
| import { LocalItemSettings, MergedItemSettings, Path } from "./utils"; | ||||
| 
 | ||||
| type ViewerContextShape = { | ||||
|   // Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
 | ||||
|   // Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
 | ||||
|   // See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
 | ||||
|   getSettings(location: SqValueLocation): LocalItemSettings; | ||||
|   getMergedSettings(location: SqValueLocation): MergedItemSettings; | ||||
|   setSettings(location: SqValueLocation, value: LocalItemSettings): void; | ||||
|   getSettings(path: Path): LocalItemSettings; | ||||
|   getMergedSettings(path: Path): MergedItemSettings; | ||||
|   setSettings(path: Path, value: LocalItemSettings): void; | ||||
|   enableLocalSettings: boolean; // show local settings icon in the UI
 | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,20 +1,21 @@ | |||
| import React, { useCallback, useRef } from "react"; | ||||
| import { environment, SqValueLocation } from "@quri/squiggle-lang"; | ||||
| import { environment } from "@quri/squiggle-lang"; | ||||
| import { DistributionPlottingSettings } from "../DistributionChart"; | ||||
| import { FunctionChartSettings } from "../FunctionChart"; | ||||
| import { ExpressionViewer } from "./ExpressionViewer"; | ||||
| import { ViewerContext } from "./ViewerContext"; | ||||
| import { | ||||
|   LocalItemSettings, | ||||
|   locationAsString, | ||||
|   MergedItemSettings, | ||||
|   Path, | ||||
|   pathAsString, | ||||
| } from "./utils"; | ||||
| import { useSquiggle } from "../../lib/hooks"; | ||||
| import { SquiggleErrorAlert } from "../SquiggleErrorAlert"; | ||||
| 
 | ||||
| type Props = { | ||||
|   /** The output of squiggle's run */ | ||||
|   result: ReturnType<typeof useSquiggle>["result"]; | ||||
|   result: ReturnType<typeof useSquiggle>; | ||||
|   width?: number; | ||||
|   height: number; | ||||
|   distributionPlotSettings: DistributionPlottingSettings; | ||||
|  | @ -44,22 +45,22 @@ export const SquiggleViewer: React.FC<Props> = ({ | |||
|   const settingsRef = useRef<Settings>({}); | ||||
| 
 | ||||
|   const getSettings = useCallback( | ||||
|     (location: SqValueLocation) => { | ||||
|       return settingsRef.current[locationAsString(location)] || defaultSettings; | ||||
|     (path: Path) => { | ||||
|       return settingsRef.current[pathAsString(path)] || defaultSettings; | ||||
|     }, | ||||
|     [settingsRef] | ||||
|   ); | ||||
| 
 | ||||
|   const setSettings = useCallback( | ||||
|     (location: SqValueLocation, value: LocalItemSettings) => { | ||||
|       settingsRef.current[locationAsString(location)] = value; | ||||
|     (path: Path, value: LocalItemSettings) => { | ||||
|       settingsRef.current[pathAsString(path)] = value; | ||||
|     }, | ||||
|     [settingsRef] | ||||
|   ); | ||||
| 
 | ||||
|   const getMergedSettings = useCallback( | ||||
|     (location: SqValueLocation) => { | ||||
|       const localSettings = getSettings(location); | ||||
|     (path: Path) => { | ||||
|       const localSettings = getSettings(path); | ||||
|       const result: MergedItemSettings = { | ||||
|         distributionPlotSettings: { | ||||
|           ...distributionPlotSettings, | ||||
|  | @ -90,7 +91,7 @@ export const SquiggleViewer: React.FC<Props> = ({ | |||
|       }} | ||||
|     > | ||||
|       {result.tag === "Ok" ? ( | ||||
|         <ExpressionViewer value={result.value} width={width} /> | ||||
|         <ExpressionViewer path={[]} expression={result.value} width={width} /> | ||||
|       ) : ( | ||||
|         <SquiggleErrorAlert error={result.value} /> | ||||
|       )} | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { DistributionPlottingSettings } from "../DistributionChart"; | ||||
| import { FunctionChartSettings } from "../FunctionChart"; | ||||
| import { environment, SqValueLocation } from "@quri/squiggle-lang"; | ||||
| import { environment } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| export type LocalItemSettings = { | ||||
|   collapsed: boolean; | ||||
|  | @ -17,5 +17,6 @@ export type MergedItemSettings = { | |||
|   environment: environment; | ||||
| }; | ||||
| 
 | ||||
| export const locationAsString = (location: SqValueLocation) => | ||||
|   location.path.items.join("."); | ||||
| export type Path = string[]; | ||||
| 
 | ||||
| export const pathAsString = (path: Path) => path.join("."); | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| export { SqProject } from "@quri/squiggle-lang/"; | ||||
| export { SquiggleChart } from "./components/SquiggleChart"; | ||||
| export { SquiggleEditor } from "./components/SquiggleEditor"; | ||||
| export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor"; | ||||
| export { SquigglePlayground } from "./components/SquigglePlayground"; | ||||
| export { SquiggleContainer } from "./components/SquiggleContainer"; | ||||
| export { SquiggleEditorWithImportedBindings } from "./components/SquiggleEditorWithImportedBindings"; | ||||
| 
 | ||||
| export { mergeBindings } from "@quri/squiggle-lang"; | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { VisualizationSpec } from "react-vega"; | ||||
| import type { LogScale, LinearScale, PowScale, TimeScale } from "vega"; | ||||
| import type { LogScale, LinearScale, PowScale } from "vega"; | ||||
| 
 | ||||
| export type DistributionChartSpecOptions = { | ||||
|   /** Set the x scale to be logarithmic by deault */ | ||||
|  | @ -14,21 +14,26 @@ export type DistributionChartSpecOptions = { | |||
|   title?: string; | ||||
|   /** The formatting of the ticks */ | ||||
|   format?: string; | ||||
|   /** Whether the x-axis should be dates or numbers */ | ||||
|   xAxisType?: "number" | "dateTime"; | ||||
| }; | ||||
| 
 | ||||
| /** X Scales */ | ||||
| export const linearXScale: LinearScale = { | ||||
| export let linearXScale: LinearScale = { | ||||
|   name: "xscale", | ||||
|   clamp: true, | ||||
|   type: "linear", | ||||
|   range: "width", | ||||
|   zero: false, | ||||
|   nice: false, | ||||
|   domain: { data: "domain", field: "x" }, | ||||
| }; | ||||
| export let linearYScale: LinearScale = { | ||||
|   name: "yscale", | ||||
|   type: "linear", | ||||
|   range: "height", | ||||
|   zero: true, | ||||
|   domain: { data: "domain", field: "y" }, | ||||
| }; | ||||
| 
 | ||||
| export const logXScale: LogScale = { | ||||
| export let logXScale: LogScale = { | ||||
|   name: "xscale", | ||||
|   type: "log", | ||||
|   range: "width", | ||||
|  | @ -36,104 +41,60 @@ export const logXScale: LogScale = { | |||
|   base: 10, | ||||
|   nice: false, | ||||
|   clamp: true, | ||||
|   domain: { data: "domain", field: "x" }, | ||||
| }; | ||||
| 
 | ||||
| export const timeXScale: TimeScale = { | ||||
|   name: "xscale", | ||||
|   clamp: true, | ||||
|   type: "time", | ||||
|   range: "width", | ||||
|   nice: false, | ||||
| }; | ||||
| 
 | ||||
| /** Y Scales */ | ||||
| export const linearYScale: LinearScale = { | ||||
|   name: "yscale", | ||||
|   type: "linear", | ||||
|   range: "height", | ||||
|   zero: true, | ||||
| }; | ||||
| 
 | ||||
| export const expYScale: PowScale = { | ||||
| export let expYScale: PowScale = { | ||||
|   name: "yscale", | ||||
|   type: "pow", | ||||
|   exponent: 0.1, | ||||
|   range: "height", | ||||
|   zero: true, | ||||
|   nice: false, | ||||
|   domain: { data: "domain", field: "y" }, | ||||
| }; | ||||
| 
 | ||||
| export const defaultTickFormat = ".9~s"; | ||||
| export const timeTickFormat = "%b %d, %Y %H:%M"; | ||||
| const width = 500; | ||||
| 
 | ||||
| export function buildVegaSpec( | ||||
|   specOptions: DistributionChartSpecOptions & { maxY: number } | ||||
|   specOptions: DistributionChartSpecOptions | ||||
| ): VisualizationSpec { | ||||
|   const { | ||||
|     format = defaultTickFormat, | ||||
|     title, | ||||
|     minX, | ||||
|     maxX, | ||||
|     logX, | ||||
|     expY, | ||||
|     xAxisType = "number", | ||||
|     maxY, | ||||
|   } = specOptions; | ||||
| 
 | ||||
|   const dateTime = xAxisType === "dateTime"; | ||||
|   let xScale = logX ? logXScale : linearXScale; | ||||
|   if (minX !== undefined && Number.isFinite(minX)) { | ||||
|     xScale = { ...xScale, domainMin: minX }; | ||||
|   } | ||||
| 
 | ||||
|   // some fallbacks
 | ||||
|   const format = specOptions?.format | ||||
|     ? specOptions.format | ||||
|     : dateTime | ||||
|     ? timeTickFormat | ||||
|     : defaultTickFormat; | ||||
|   if (maxX !== undefined && Number.isFinite(maxX)) { | ||||
|     xScale = { ...xScale, domainMax: maxX }; | ||||
|   } | ||||
| 
 | ||||
|   let xScale = dateTime ? timeXScale : logX ? logXScale : linearXScale; | ||||
| 
 | ||||
|   xScale = { | ||||
|     ...xScale, | ||||
|     domain: [minX ?? 0, maxX ?? 1], | ||||
|     domainMin: minX, | ||||
|     domainMax: maxX, | ||||
|   }; | ||||
| 
 | ||||
|   let yScale = expY ? expYScale : linearYScale; | ||||
|   yScale = { ...yScale, domain: [0, maxY ?? 1], domainMin: 0, domainMax: maxY }; | ||||
| 
 | ||||
|   const spec: VisualizationSpec = { | ||||
|   let spec: VisualizationSpec = { | ||||
|     $schema: "https://vega.github.io/schema/vega/v5.json", | ||||
|     description: "Squiggle plot chart", | ||||
|     width: width, | ||||
|     width: 500, | ||||
|     height: 100, | ||||
|     padding: 5, | ||||
|     data: [{ name: "data" }, { name: "domain" }, { name: "samples" }], | ||||
|     signals: [ | ||||
|     data: [ | ||||
|       { | ||||
|         name: "hover", | ||||
|         value: null, | ||||
|         on: [ | ||||
|           { events: "mouseover", update: "datum" }, | ||||
|           { events: "mouseout", update: "null" }, | ||||
|         ], | ||||
|         name: "data", | ||||
|       }, | ||||
|       { | ||||
|         name: "position", | ||||
|         value: "[0, 0]", | ||||
|         on: [ | ||||
|           { events: "mousemove", update: "xy() " }, | ||||
|           { events: "mouseout", update: "null" }, | ||||
|         ], | ||||
|       }, | ||||
|       { | ||||
|         name: "position_scaled", | ||||
|         value: null, | ||||
|         update: "isArray(position) ? invert('xscale', position[0]) : ''", | ||||
|         name: "domain", | ||||
|       }, | ||||
|     ], | ||||
|     signals: [], | ||||
|     scales: [ | ||||
|       xScale, | ||||
|       yScale, | ||||
|       expY ? expYScale : linearYScale, | ||||
|       { | ||||
|         name: "color", | ||||
|         type: "ordinal", | ||||
|  | @ -154,7 +115,7 @@ export function buildVegaSpec( | |||
|         domainColor: "#fff", | ||||
|         domainOpacity: 0.0, | ||||
|         format: format, | ||||
|         tickCount: dateTime ? 3 : 10, | ||||
|         tickCount: 10, | ||||
|         labelOverlap: "greedy", | ||||
|       }, | ||||
|     ], | ||||
|  | @ -271,16 +232,13 @@ export function buildVegaSpec( | |||
|                     }, | ||||
|                     size: [{ value: 100 }], | ||||
|                     tooltip: { | ||||
|                       signal: dateTime | ||||
|                         ? "{ probability: datum.y, value: datetime(datum.x) }" | ||||
|                         : "{ probability: datum.y, value: datum.x }", | ||||
|                       signal: "{ probability: datum.y, value: datum.x }", | ||||
|                     }, | ||||
|                   }, | ||||
|                   update: { | ||||
|                     x: { | ||||
|                       scale: "xscale", | ||||
|                       field: "x", | ||||
|                       offset: 0.5, // if this is not included, the circles are slightly left of center.
 | ||||
|                     }, | ||||
|                     y: { | ||||
|                       scale: "yscale", | ||||
|  | @ -297,69 +255,6 @@ export function buildVegaSpec( | |||
|           }, | ||||
|         ], | ||||
|       }, | ||||
| 
 | ||||
|       { | ||||
|         name: "sampleset", | ||||
|         type: "rect", | ||||
|         from: { data: "samples" }, | ||||
|         encode: { | ||||
|           enter: { | ||||
|             x: { scale: "xscale", field: "data" }, | ||||
|             width: { value: 0.1 }, | ||||
| 
 | ||||
|             y: { value: 25, offset: { signal: "height" } }, | ||||
|             height: { value: 5 }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         type: "text", | ||||
|         name: "announcer", | ||||
|         interactive: false, | ||||
|         encode: { | ||||
|           enter: { | ||||
|             x: { signal: String(width), offset: 1 }, // vega would prefer its internal ` "width" ` variable, but that breaks the squiggle playground. Just setting it to the same var as used elsewhere in the spec achieves the same result.
 | ||||
|             fill: { value: "black" }, | ||||
|             fontSize: { value: 20 }, | ||||
|             align: { value: "right" }, | ||||
|           }, | ||||
|           update: { | ||||
|             text: { | ||||
|               signal: dateTime | ||||
|                 ? "position_scaled ? utcyear(position_scaled) + '-' + utcmonth(position_scaled) + '-' + utcdate(position_scaled) + 'T' + utchours(position_scaled)+':' +utcminutes(position_scaled) : ''" | ||||
|                 : "position_scaled ? format(position_scaled, ',.4r')  : ''", | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         type: "rule", | ||||
|         interactive: false, | ||||
|         encode: { | ||||
|           enter: { | ||||
|             x: { value: 0 }, | ||||
|             y: { scale: "yscale", value: 0 }, | ||||
| 
 | ||||
|             y2: { | ||||
|               signal: "height", | ||||
|               offset: 2, | ||||
|             }, | ||||
|             strokeDash: { value: [5, 5] }, | ||||
|           }, | ||||
| 
 | ||||
|           update: { | ||||
|             x: { | ||||
|               signal: | ||||
|                 "position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null", | ||||
|             }, | ||||
| 
 | ||||
|             opacity: { | ||||
|               signal: | ||||
|                 "position ? position[0] < 0 ? 0 : position[0] > width ? 0 : 1 : 0", | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|     ], | ||||
|     legends: [ | ||||
|       { | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { SqShape } from "@quri/squiggle-lang"; | ||||
| import { shape } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| export const hasMassBelowZero = (shape: SqShape) => | ||||
| export const hasMassBelowZero = (shape: shape) => | ||||
|   shape.continuous.some((x) => x.x <= 0) || | ||||
|   shape.discrete.some((x) => x.x <= 0); | ||||
|  |  | |||
|  | @ -1,3 +1,3 @@ | |||
| export { useMaybeControlledValue } from "./useMaybeControlledValue"; | ||||
| export { useSquiggle } from "./useSquiggle"; | ||||
| export { useSquiggle, useSquigglePartial } from "./useSquiggle"; | ||||
| export { useRunnerState } from "./useRunnerState"; | ||||
|  |  | |||
|  | @ -1,97 +1,53 @@ | |||
| import { | ||||
|   result, | ||||
|   SqError, | ||||
|   SqProject, | ||||
|   SqRecord, | ||||
|   SqValue, | ||||
|   bindings, | ||||
|   environment, | ||||
|   jsImports, | ||||
|   run, | ||||
|   runPartial, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { useEffect, useMemo } from "react"; | ||||
| import { JsImports, jsImportsToSquiggleCode } from "../jsImports"; | ||||
| import * as uuid from "uuid"; | ||||
| 
 | ||||
| type SquiggleArgs = { | ||||
|   environment?: environment; | ||||
| type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = { | ||||
|   code: string; | ||||
|   executionId?: number; | ||||
|   jsImports?: JsImports; | ||||
|   project?: SqProject; | ||||
|   continues?: string[]; | ||||
|   onChange?: (expr: SqValue | undefined, sourceName: string) => void; | ||||
|   bindings?: bindings; | ||||
|   jsImports?: jsImports; | ||||
|   environment?: environment; | ||||
|   onChange?: (expr: Extract<T, { tag: "Ok" }>["value"] | undefined) => void; | ||||
| }; | ||||
| 
 | ||||
| export type ResultAndBindings = { | ||||
|   result: result<SqValue, SqError>; | ||||
|   bindings: SqRecord; | ||||
| }; | ||||
| 
 | ||||
| const importSourceName = (sourceName: string) => "imports-" + sourceName; | ||||
| const defaultContinues = []; | ||||
| 
 | ||||
| export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => { | ||||
|   const project = useMemo(() => { | ||||
|     if (args.project) { | ||||
|       return args.project; | ||||
|     } else { | ||||
|       const p = SqProject.create(); | ||||
|       if (args.environment) { | ||||
|         p.setEnvironment(args.environment); | ||||
|       } | ||||
|       return p; | ||||
|     } | ||||
|   }, [args.project, args.environment]); | ||||
| 
 | ||||
|   const sourceName = useMemo(() => uuid.v4(), []); | ||||
| 
 | ||||
|   const env = project.getEnvironment(); | ||||
|   const continues = args.continues || defaultContinues; | ||||
| 
 | ||||
|   const result = useMemo( | ||||
|     () => { | ||||
|       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
 | ||||
| const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>( | ||||
|   args: SquiggleArgs<T>, | ||||
|   f: (...args: Parameters<typeof run>) => T | ||||
| ) => { | ||||
|   const result: T = useMemo<T>( | ||||
|     () => f(args.code, args.bindings, args.environment, args.jsImports), | ||||
|     // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||
|     [ | ||||
|       f, | ||||
|       args.code, | ||||
|       args.bindings, | ||||
|       args.environment, | ||||
|       args.jsImports, | ||||
|       args.executionId, | ||||
|       sourceName, | ||||
|       continues, | ||||
|       project, | ||||
|       env, | ||||
|     ] | ||||
|   ); | ||||
| 
 | ||||
|   const { onChange } = args; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     onChange?.( | ||||
|       result.result.tag === "Ok" ? result.result.value : undefined, | ||||
|       sourceName | ||||
|     ); | ||||
|   }, [result, onChange, sourceName]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     return () => { | ||||
|       project.removeSource(sourceName); | ||||
|       if (project.getSource(importSourceName(sourceName))) | ||||
|         project.removeSource(importSourceName(sourceName)); | ||||
|     }; | ||||
|   }, [project, sourceName]); | ||||
|     onChange?.(result.tag === "Ok" ? result.value : undefined); | ||||
|   }, [result, onChange]); | ||||
| 
 | ||||
|   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); | ||||
| }; | ||||
|  |  | |||
|  | @ -1,51 +0,0 @@ | |||
| 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,15 +1,9 @@ | |||
| import * as yup from "yup"; | ||||
| import { | ||||
|   SqValue, | ||||
|   SqValueTag, | ||||
|   SqDistribution, | ||||
|   result, | ||||
|   SqRecord, | ||||
| } from "@quri/squiggle-lang"; | ||||
| import { Distribution, result, squiggleExpression } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| export type LabeledDistribution = { | ||||
|   name: string; | ||||
|   distribution: SqDistribution; | ||||
|   distribution: Distribution; | ||||
|   color?: string; | ||||
| }; | ||||
| 
 | ||||
|  | @ -27,55 +21,50 @@ function ok<a, b>(x: a): result<a, b> { | |||
| 
 | ||||
| const schema = yup | ||||
|   .object() | ||||
|   .noUnknown() | ||||
|   .strict() | ||||
|   .noUnknown() | ||||
|   .shape({ | ||||
|     distributions: yup | ||||
|     distributions: yup.object().shape({ | ||||
|       tag: yup.mixed().oneOf(["array"]), | ||||
|       value: yup | ||||
|         .array() | ||||
|       .required() | ||||
|         .of( | ||||
|         yup.object().required().shape({ | ||||
|           name: yup.string().required(), | ||||
|           distribution: yup.mixed().required(), | ||||
|           yup.object().shape({ | ||||
|             tag: yup.mixed().oneOf(["record"]), | ||||
|             value: yup.object({ | ||||
|               name: yup.object().shape({ | ||||
|                 tag: yup.mixed().oneOf(["string"]), | ||||
|                 value: yup.string().required(), | ||||
|               }), | ||||
|               // color: yup
 | ||||
|               //   .object({
 | ||||
|               //     tag: yup.mixed().oneOf(["string"]),
 | ||||
|               //     value: yup.string().required(),
 | ||||
|               //   })
 | ||||
|               //   .default(undefined),
 | ||||
|               distribution: yup.object({ | ||||
|                 tag: yup.mixed().oneOf(["distribution"]), | ||||
|                 value: yup.mixed(), | ||||
|               }), | ||||
|             }), | ||||
|           }) | ||||
|       ), | ||||
|         ) | ||||
|         .required(), | ||||
|     }), | ||||
|   }); | ||||
| 
 | ||||
| type JsonObject = | ||||
|   | string | ||||
|   | { [key: string]: JsonObject } | ||||
|   | JsonObject[] | ||||
|   | SqDistribution; | ||||
| 
 | ||||
| function toJson(val: SqValue): JsonObject { | ||||
|   if (val.tag === SqValueTag.String) { | ||||
|     return val.value; | ||||
|   } else if (val.tag === SqValueTag.Record) { | ||||
|     return toJsonRecord(val.value); | ||||
|   } else if (val.tag === SqValueTag.Array) { | ||||
|     return val.value.getValues().map(toJson); | ||||
|   } else if (val.tag === SqValueTag.Distribution) { | ||||
|     return val.value; | ||||
|   } else { | ||||
|     throw new Error("Could not parse object of type " + val.tag); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function toJsonRecord(val: SqRecord): JsonObject { | ||||
|   let recordObject: JsonObject = {}; | ||||
|   val.entries().forEach(([key, value]) => (recordObject[key] = toJson(value))); | ||||
|   return recordObject; | ||||
| } | ||||
| 
 | ||||
| export function parsePlot(record: SqRecord): result<Plot, string> { | ||||
| export function parsePlot(record: { | ||||
|   [key: string]: squiggleExpression; | ||||
| }): result<Plot, string> { | ||||
|   try { | ||||
|     const plotRecord = schema.validateSync(toJsonRecord(record)); | ||||
|     if (plotRecord.distributions) { | ||||
|       return ok({ distributions: plotRecord.distributions.map((x) => x) }); | ||||
|     } else { | ||||
|       // I have no idea why yup's typings thinks this is possible
 | ||||
|       return error("no distributions field. Should never get here"); | ||||
|     } | ||||
|     const plotRecord = schema.validateSync(record); | ||||
|     return ok({ | ||||
|       distributions: plotRecord.distributions.value.map((x) => ({ | ||||
|         name: x.value.name.value, | ||||
|         // color: x.value.color?.value, // not supported yet
 | ||||
|         distribution: x.value.distribution.value, | ||||
|       })), | ||||
|     }); | ||||
|   } catch (e) { | ||||
|     const message = e instanceof Error ? e.message : "Unknown error"; | ||||
|     return error(message); | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import { result, resultMap, SqValueTag } from "@quri/squiggle-lang"; | ||||
| import { ResultAndBindings } from "./hooks/useSquiggle"; | ||||
| import { result } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| export function flattenResult<a, b>(x: result<a, b>[]): result<a[], b> { | ||||
|   if (x.length === 0) { | ||||
|  | @ -36,18 +35,3 @@ export function all(arr: boolean[]): boolean { | |||
| export function some(arr: boolean[]): boolean { | ||||
|   return arr.reduce((x, y) => x || y, false); | ||||
| } | ||||
| 
 | ||||
| export function getValueToRender({ result, bindings }: ResultAndBindings) { | ||||
|   return resultMap(result, (value) => | ||||
|     value.tag === SqValueTag.Void ? bindings.asValue() : value | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export function getErrorLocations(result: ResultAndBindings["result"]) { | ||||
|   if (result.tag === "Error") { | ||||
|     const location = result.value.location(); | ||||
|     return location ? [location] : []; | ||||
|   } else { | ||||
|     return []; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -79,22 +79,6 @@ could be continuous, discrete or mixed. | |||
|   </Story> | ||||
| </Canvas> | ||||
| 
 | ||||
| ### Date Distribution | ||||
| 
 | ||||
| <Canvas> | ||||
|   <Story | ||||
|     name="Date Distribution" | ||||
|     args={{ | ||||
|       code: "mx(1661819770311, 1661829770311, 1661839770311)", | ||||
|       width, | ||||
|       xAxisType: "dateTime", | ||||
|       width, | ||||
|     }} | ||||
|   > | ||||
|     {Template.bind({})} | ||||
|   </Story> | ||||
| </Canvas> | ||||
| 
 | ||||
| ## Mixed distributions | ||||
| 
 | ||||
| <Canvas> | ||||
|  |  | |||
							
								
								
									
										51
									
								
								packages/components/src/stories/SquigglePartial.stories.mdx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								packages/components/src/stories/SquigglePartial.stories.mdx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| import { SquigglePartial, SquiggleEditor } from "../components/SquiggleEditor"; | ||||
| import { useState } from "react"; | ||||
| import { Canvas, Meta, Story, Props } from "@storybook/addon-docs"; | ||||
| 
 | ||||
| <Meta title="Squiggle/SquigglePartial" component={SquigglePartial} /> | ||||
| 
 | ||||
| export const Template = (props) => <SquigglePartial {...props} />; | ||||
| 
 | ||||
| # Squiggle Partial | ||||
| 
 | ||||
| A Squiggle Partial is an editor that does not return a graph to the user, but | ||||
| instead returns bindings that can be used by further Squiggle Editors. | ||||
| 
 | ||||
| <Canvas> | ||||
|   <Story | ||||
|     name="Standalone" | ||||
|     args={{ | ||||
|       defaultCode: "x = normal(5,2)", | ||||
|     }} | ||||
|   > | ||||
|     {Template.bind({})} | ||||
|   </Story> | ||||
| </Canvas> | ||||
| 
 | ||||
| <Canvas> | ||||
|   <Story | ||||
|     name="With Editor" | ||||
|     args={{ | ||||
|       initialPartialString: "x = normal(5,2)", | ||||
|       initialEditorString: "x", | ||||
|     }} | ||||
|   > | ||||
|     {(props) => { | ||||
|       let [bindings, setBindings] = useState({}); | ||||
|       return ( | ||||
|         <> | ||||
|           <SquigglePartial | ||||
|             {...props} | ||||
|             defaultCode={props.initialPartialString} | ||||
|             onChange={setBindings} | ||||
|           /> | ||||
|           <SquiggleEditor | ||||
|             {...props} | ||||
|             defaultCode={props.initialEditorString} | ||||
|             bindings={bindings} | ||||
|           /> | ||||
|         </> | ||||
|       ); | ||||
|     }} | ||||
|   </Story> | ||||
| </Canvas> | ||||
|  | @ -22,8 +22,3 @@ but this line is still necessary for proper initialization of `--tw-*` variables | |||
| .ace_cursor { | ||||
|   border-left: 2px solid !important; | ||||
| } | ||||
| 
 | ||||
| .ace-error-marker { | ||||
|   position: absolute; | ||||
|   border-bottom: 1px solid red; | ||||
| } | ||||
|  |  | |||
|  | @ -1,55 +0,0 @@ | |||
| import { render, screen, waitFor, within } from "@testing-library/react"; | ||||
| import userEvent from "@testing-library/user-event"; | ||||
| import * as React from "react"; | ||||
| import "@testing-library/jest-dom"; | ||||
| import { SquigglePlayground } from "../src/index"; | ||||
| 
 | ||||
| test("Autorun is default", async () => { | ||||
|   render(<SquigglePlayground code="70*30" />); | ||||
|   await waitFor(() => | ||||
|     expect(screen.getByTestId("playground-result")).toHaveTextContent("2100") | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| test("Autorun can be switched off", async () => { | ||||
|   const user = userEvent.setup(); | ||||
|   render(<SquigglePlayground code="70*30" />); | ||||
| 
 | ||||
|   expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Autorun"); | ||||
| 
 | ||||
|   await waitFor(() => | ||||
|     expect(screen.getByTestId("playground-result")).toHaveTextContent("2100") | ||||
|   ); | ||||
| 
 | ||||
|   await user.click(screen.getByText("Autorun")); // disable
 | ||||
|   expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Paused"); | ||||
|   expect(screen.getByTestId("autorun-controls")).not.toHaveTextContent( | ||||
|     "Autorun" | ||||
|   ); | ||||
| 
 | ||||
|   await user.click(screen.getByText("Paused")); // enable autorun again
 | ||||
|   expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Autorun"); | ||||
| 
 | ||||
|   // we should replace the code here, but it's hard to update react-ace state via user events: https://github.com/securingsincity/react-ace/issues/923
 | ||||
|   // ...or replace react-ace with something else
 | ||||
| 
 | ||||
|   // TODO:
 | ||||
| 
 | ||||
|   /* | ||||
|   const editor = screen | ||||
|     .getByTestId("squiggle-editor") | ||||
|     .querySelector(".ace_editor") as HTMLElement; | ||||
|   editor.focus(); | ||||
|   //   await user.clear(editor);
 | ||||
|   await userEvent.paste("40*40"); // https://github.com/securingsincity/react-ace/issues/923#issuecomment-755502696
 | ||||
|   screen.debug(editor); | ||||
| 
 | ||||
|   // this makes the tests slower, but it's hard to test otherwise that the code _didn't_ execute
 | ||||
|   await new Promise((r) => setTimeout(r, 300)); | ||||
|   expect(screen.getByTestId("playground-result")).toHaveTextContent("2100"); // still the old value
 | ||||
| 
 | ||||
|   await waitFor(() => | ||||
|     expect(screen.getByTestId("playground-result")).toHaveTextContent("1600") | ||||
|   ); | ||||
| */ | ||||
| }); | ||||
|  | @ -1,53 +0,0 @@ | |||
| import { render, screen } from "@testing-library/react"; | ||||
| import React from "react"; | ||||
| import "@testing-library/jest-dom"; | ||||
| import { | ||||
|   SquiggleChart, | ||||
|   SquiggleEditor, | ||||
|   SquigglePlayground, | ||||
| } from "../src/index"; | ||||
| import { SqProject } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| test("Chart logs nothing on render", async () => { | ||||
|   const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />); | ||||
|   unmount(); | ||||
| 
 | ||||
|   expect(console.log).not.toBeCalled(); | ||||
|   expect(console.warn).not.toBeCalled(); | ||||
|   expect(console.error).not.toBeCalled(); | ||||
| }); | ||||
| 
 | ||||
| test("Editor logs nothing on render", async () => { | ||||
|   const { unmount } = render(<SquiggleEditor code={"normal(0, 1)"} />); | ||||
|   unmount(); | ||||
| 
 | ||||
|   expect(console.log).not.toBeCalled(); | ||||
|   expect(console.warn).not.toBeCalled(); | ||||
|   expect(console.error).not.toBeCalled(); | ||||
| }); | ||||
| 
 | ||||
| test("Project dependencies work in editors", async () => { | ||||
|   const project = SqProject.create(); | ||||
| 
 | ||||
|   render(<SquiggleEditor code={"x = 1"} project={project} />); | ||||
|   const source = project.getSourceIds()[0]; | ||||
|   const { container } = render( | ||||
|     <SquiggleEditor code={"x + 1"} project={project} continues={[source]} /> | ||||
|   ); | ||||
|   expect(container).toHaveTextContent("2"); | ||||
| }); | ||||
| 
 | ||||
| test("Project dependencies work in playgrounds", async () => { | ||||
|   const project = SqProject.create(); | ||||
|   project.setSource("depend", "x = 1"); | ||||
| 
 | ||||
|   render( | ||||
|     <SquigglePlayground | ||||
|       code={"x + 1"} | ||||
|       project={project} | ||||
|       continues={["depend"]} | ||||
|     /> | ||||
|   ); | ||||
|   // We must await here because SquigglePlayground loads results asynchronously
 | ||||
|   expect(await screen.findByRole("status")).toHaveTextContent("2"); | ||||
| }); | ||||
|  | @ -1,39 +0,0 @@ | |||
| import { render } from "@testing-library/react"; | ||||
| import React from "react"; | ||||
| import "@testing-library/jest-dom"; | ||||
| import { SquiggleChart } from "../src/index"; | ||||
| import { SqProject } from "@quri/squiggle-lang"; | ||||
| 
 | ||||
| test("Creates and cleans up source", async () => { | ||||
|   const project = SqProject.create(); | ||||
| 
 | ||||
|   const { unmount } = render( | ||||
|     <SquiggleChart code={"normal(0, 1)"} project={project} /> | ||||
|   ); | ||||
| 
 | ||||
|   expect(project.getSourceIds().length).toBe(1); | ||||
| 
 | ||||
|   const sourceId = project.getSourceIds()[0]; | ||||
|   expect(project.getSource(sourceId)).toBe("normal(0, 1)"); | ||||
| 
 | ||||
|   unmount(); | ||||
|   expect(project.getSourceIds().length).toBe(0); | ||||
|   expect(project.getSource(sourceId)).toBe(undefined); | ||||
| }); | ||||
| 
 | ||||
| test("Creates and cleans up source and imports", async () => { | ||||
|   const project = SqProject.create(); | ||||
| 
 | ||||
|   const { unmount } = render( | ||||
|     <SquiggleChart | ||||
|       code={"normal($x, 1)"} | ||||
|       project={project} | ||||
|       jsImports={{ x: 3 }} | ||||
|     /> | ||||
|   ); | ||||
| 
 | ||||
|   expect(project.getSourceIds().length).toBe(2); | ||||
| 
 | ||||
|   unmount(); | ||||
|   expect(project.getSourceIds()).toStrictEqual([]); | ||||
| }); | ||||
|  | @ -1,8 +0,0 @@ | |||
| global.console = { | ||||
|   ...console, | ||||
|   log: jest.fn(console.log), | ||||
|   debug: jest.fn(console.debug), | ||||
|   info: jest.fn(console.info), | ||||
|   warn: jest.fn(console.warn), | ||||
|   error: jest.fn(console.error), | ||||
| }; | ||||
|  | @ -1,4 +0,0 @@ | |||
| { | ||||
|   "buildCommand": "cd ../.. && npx turbo run build --filter=@quri/squiggle-components", | ||||
|   "outputDirectory": "storybook-static" | ||||
| } | ||||
							
								
								
									
										1
									
								
								packages/squiggle-lang/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								packages/squiggle-lang/.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -23,4 +23,3 @@ coverage | |||
| .nyc_output/ | ||||
| src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js | ||||
| src/rescript/Reducer/Reducer_Peggy/helpers.js | ||||
| src/rescript/ReducerProject/ReducerProject_IncludeParser.js | ||||
|  |  | |||
|  | @ -3,8 +3,6 @@ lib | |||
| *.bs.js | ||||
| *.gen.tsx | ||||
| .nyc_output/ | ||||
| coverage/ | ||||
| _coverage/ | ||||
| .cache/ | ||||
| Reducer_Peggy_GeneratedParser.js | ||||
| ReducerProject_IncludeParser.js | ||||
| src/rescript/Reducer/Reducer_Peggy/helpers.js | ||||
|  |  | |||
|  | @ -32,10 +32,7 @@ describe("dotSubtract", () => { | |||
|  */ | ||||
|   Skip.test("mean of normal minus exponential (property)", () => { | ||||
|     assert_( | ||||
|       property2( | ||||
|         float_(), | ||||
|         floatRange(1e-5, 1e5), | ||||
|         (mean, rate) => { | ||||
|       property2(float_(), floatRange(1e-5, 1e5), (mean, rate) => { | ||||
|         // We limit ourselves to stdev=1 so that the integral is trivial | ||||
|         let dotDifference = DistributionOperation.Constructors.pointwiseSubtract( | ||||
|           ~env, | ||||
|  | @ -53,8 +50,7 @@ describe("dotSubtract", () => { | |||
|         | Ok(meanValue) => abs_float(meanValue -. meanAnalytical) /. abs_float(meanValue) < 1e-2 // 1% relative error | ||||
|         | Error(err) => err === DistributionTypes.OperationError(DivisionByZeroError) | ||||
|         } | ||||
|         }, | ||||
|       ), | ||||
|       }), | ||||
|     ) | ||||
|     pass | ||||
|   }) | ||||
|  |  | |||
|  | @ -40,9 +40,7 @@ let algebraicPower = algebraicPower(~env) | |||
| 
 | ||||
| describe("(Algebraic) addition of distributions", () => { | ||||
|   describe("mean", () => { | ||||
|     test( | ||||
|       "normal(mean=5) + normal(mean=20)", | ||||
|       () => { | ||||
|     test("normal(mean=5) + normal(mean=20)", () => { | ||||
|       normalDist5 | ||||
|       ->algebraicAdd(normalDist20) | ||||
|       ->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean) | ||||
|  | @ -51,12 +49,9 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       ->E.R.toExn("Expected float", _) | ||||
|       ->expect | ||||
|       ->toBe(Some(2.5e1)) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
| 
 | ||||
|     test( | ||||
|       "uniform(low=9, high=10) + beta(alpha=2, beta=5)", | ||||
|       () => { | ||||
|     test("uniform(low=9, high=10) + beta(alpha=2, beta=5)", () => { | ||||
|       // let uniformMean = (9.0 +. 10.0) /. 2.0 | ||||
|       // let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0) | ||||
|       let received = | ||||
|  | @ -72,11 +67,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       // sometimes it works with ~digits=2. | ||||
|       | Some(x) => x->expect->toBeSoCloseTo(9.786831807237022, ~digits=1) // (uniformMean +. betaMean) | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "beta(alpha=2, beta=5) + uniform(low=9, high=10)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("beta(alpha=2, beta=5) + uniform(low=9, high=10)", () => { | ||||
|       // let uniformMean = (9.0 +. 10.0) /. 2.0 | ||||
|       // let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0) | ||||
|       let received = | ||||
|  | @ -92,8 +84,7 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       // sometimes it works with ~digits=2. | ||||
|       | Some(x) => x->expect->toBeSoCloseTo(9.784290207736126, ~digits=1) // (uniformMean +. betaMean) | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
|   }) | ||||
|   describe("pdf", () => { | ||||
|     // TEST IS WRONG. SEE STDEV ADDITION EXPRESSION. | ||||
|  | @ -131,9 +122,7 @@ describe("(Algebraic) addition of distributions", () => { | |||
|         } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(normal(mean=10) + normal(mean=10)).pdf(1.9e1)", | ||||
|       () => { | ||||
|     test("(normal(mean=10) + normal(mean=10)).pdf(1.9e1)", () => { | ||||
|       let received = | ||||
|         normalDist20 | ||||
|         ->Ok | ||||
|  | @ -161,11 +150,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|         | Some(y) => x->expect->toBeSoCloseTo(y, ~digits=1) | ||||
|         } | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(uniform(low=9, high=10) + beta(alpha=2, beta=5)).pdf(10)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).pdf(10)", () => { | ||||
|       let received = | ||||
|         uniformDist | ||||
|         ->algebraicAdd(betaDist) | ||||
|  | @ -180,11 +166,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       // This value was calculated by a python script | ||||
|       | Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0) | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(beta(alpha=2, beta=5) + uniform(low=9, high=10)).pdf(10)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).pdf(10)", () => { | ||||
|       let received = | ||||
|         betaDist | ||||
|         ->algebraicAdd(uniformDist) | ||||
|  | @ -197,14 +180,10 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       // This is nondeterministic. | ||||
|       | Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0) | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
|   }) | ||||
|   describe("cdf", () => { | ||||
|     testAll( | ||||
|       "(normal(mean=5) + normal(mean=5)).cdf (imprecise)", | ||||
|       list{6e0, 8e0, 1e1, 1.2e1}, | ||||
|       x => { | ||||
|     testAll("(normal(mean=5) + normal(mean=5)).cdf (imprecise)", list{6e0, 8e0, 1e1, 1.2e1}, x => { | ||||
|       let received = | ||||
|         normalDist10 | ||||
|         ->Ok | ||||
|  | @ -233,11 +212,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|         | Some(y) => x->expect->toBeSoCloseTo(y, ~digits=0) | ||||
|         } | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(normal(mean=10) + normal(mean=10)).cdf(1.25e1)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("(normal(mean=10) + normal(mean=10)).cdf(1.25e1)", () => { | ||||
|       let received = | ||||
|         normalDist20 | ||||
|         ->Ok | ||||
|  | @ -265,11 +241,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|         | Some(y) => x->expect->toBeSoCloseTo(y, ~digits=2) | ||||
|         } | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(uniform(low=9, high=10) + beta(alpha=2, beta=5)).cdf(10)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).cdf(10)", () => { | ||||
|       let received = | ||||
|         uniformDist | ||||
|         ->algebraicAdd(betaDist) | ||||
|  | @ -283,11 +256,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       // The value was calculated externally using a python script | ||||
|       | Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1) | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(beta(alpha=2, beta=5) + uniform(low=9, high=10)).cdf(10)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).cdf(10)", () => { | ||||
|       let received = | ||||
|         betaDist | ||||
|         ->algebraicAdd(uniformDist) | ||||
|  | @ -301,15 +271,11 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       // The value was calculated externally using a python script | ||||
|       | Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1) | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
|   }) | ||||
| 
 | ||||
|   describe("inv", () => { | ||||
|     testAll( | ||||
|       "(normal(mean=5) + normal(mean=5)).inv (imprecise)", | ||||
|       list{5e-2, 4.2e-3, 9e-3}, | ||||
|       x => { | ||||
|     testAll("(normal(mean=5) + normal(mean=5)).inv (imprecise)", list{5e-2, 4.2e-3, 9e-3}, x => { | ||||
|       let received = | ||||
|         normalDist10 | ||||
|         ->Ok | ||||
|  | @ -338,11 +304,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|         | Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1) | ||||
|         } | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(normal(mean=10) + normal(mean=10)).inv(1e-1)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("(normal(mean=10) + normal(mean=10)).inv(1e-1)", () => { | ||||
|       let received = | ||||
|         normalDist20 | ||||
|         ->Ok | ||||
|  | @ -370,11 +333,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|         | Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1) | ||||
|         } | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(uniform(low=9, high=10) + beta(alpha=2, beta=5)).inv(2e-2)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).inv(2e-2)", () => { | ||||
|       let received = | ||||
|         uniformDist | ||||
|         ->algebraicAdd(betaDist) | ||||
|  | @ -388,11 +348,8 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       // sometimes it works with ~digits=2. | ||||
|       | Some(x) => x->expect->toBeSoCloseTo(9.179319623146968, ~digits=0) | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "(beta(alpha=2, beta=5) + uniform(low=9, high=10)).inv(2e-2)", | ||||
|       () => { | ||||
|     }) | ||||
|     test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).inv(2e-2)", () => { | ||||
|       let received = | ||||
|         betaDist | ||||
|         ->algebraicAdd(uniformDist) | ||||
|  | @ -406,7 +363,6 @@ describe("(Algebraic) addition of distributions", () => { | |||
|       // sometimes it works with ~digits=2. | ||||
|       | Some(x) => x->expect->toBeSoCloseTo(9.190872365862756, ~digits=0) | ||||
|       } | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ This is the most basic file in our invariants family of tests. | |||
| 
 | ||||
| Validate that the addition of means equals the mean of the addition, similar for subtraction and multiplication.  | ||||
| 
 | ||||
| Details in https://squiggle-language.com/docs/internal/invariants/ | ||||
| Details in https://develop--squiggle-documentation.netlify.app/docs/internal/invariants/ | ||||
| 
 | ||||
| Note: epsilon of 1e3 means the invariants are, in general, not being satisfied.  | ||||
| */ | ||||
|  | @ -87,22 +87,14 @@ describe("Means are invariant", () => { | |||
|     let testAddInvariant = (t1, t2) => | ||||
|       E.R.liftM2(testAdditionMean, t1, t2)->E.R.toExn("Means were not invariant", _) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two of the same distribution", | ||||
|       distributions, | ||||
|       dist => { | ||||
|     testAll("with two of the same distribution", distributions, dist => { | ||||
|       testAddInvariant(dist, dist) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two different distributions", | ||||
|       pairsOfDifferentDistributions, | ||||
|       dists => { | ||||
|     testAll("with two different distributions", pairsOfDifferentDistributions, dists => { | ||||
|       let (dist1, dist2) = dists | ||||
|       testAddInvariant(dist1, dist2) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two different distributions in swapped order", | ||||
|  | @ -124,22 +116,14 @@ describe("Means are invariant", () => { | |||
|     let testSubtractInvariant = (t1, t2) => | ||||
|       E.R.liftM2(testSubtractionMean, t1, t2)->E.R.toExn("Means were not invariant", _) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two of the same distribution", | ||||
|       distributions, | ||||
|       dist => { | ||||
|     testAll("with two of the same distribution", distributions, dist => { | ||||
|       testSubtractInvariant(dist, dist) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two different distributions", | ||||
|       pairsOfDifferentDistributions, | ||||
|       dists => { | ||||
|     testAll("with two different distributions", pairsOfDifferentDistributions, dists => { | ||||
|       let (dist1, dist2) = dists | ||||
|       testSubtractInvariant(dist1, dist2) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two different distributions in swapped order", | ||||
|  | @ -161,22 +145,14 @@ describe("Means are invariant", () => { | |||
|     let testMultiplicationInvariant = (t1, t2) => | ||||
|       E.R.liftM2(testMultiplicationMean, t1, t2)->E.R.toExn("Means were not invariant", _) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two of the same distribution", | ||||
|       distributions, | ||||
|       dist => { | ||||
|     testAll("with two of the same distribution", distributions, dist => { | ||||
|       testMultiplicationInvariant(dist, dist) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two different distributions", | ||||
|       pairsOfDifferentDistributions, | ||||
|       dists => { | ||||
|     testAll("with two different distributions", pairsOfDifferentDistributions, dists => { | ||||
|       let (dist1, dist2) = dists | ||||
|       testMultiplicationInvariant(dist1, dist2) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
| 
 | ||||
|     testAll( | ||||
|       "with two different distributions in swapped order", | ||||
|  |  | |||
|  | @ -17,9 +17,10 @@ describe("klDivergence: continuous -> continuous -> float", () => { | |||
|       let answer = | ||||
|         uniformMakeR(lowAnswer, highAnswer)->E.R2.errMap(s => DistributionTypes.ArgumentError(s)) | ||||
|       let prediction = | ||||
|         uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap( | ||||
|           s => DistributionTypes.ArgumentError(s), | ||||
|         ) | ||||
|         uniformMakeR( | ||||
|           lowPrediction, | ||||
|           highPrediction, | ||||
|         )->E.R2.errMap(s => DistributionTypes.ArgumentError(s)) | ||||
|       // integral along the support of the answer of answer.pdf(x) times log of prediction.pdf(x) divided by answer.pdf(x) dx | ||||
|       let analyticalKl = Js.Math.log((highPrediction -. lowPrediction) /. (highAnswer -. lowAnswer)) | ||||
|       let kl = E.R.liftJoin2(klDivergence, prediction, answer) | ||||
|  | @ -182,9 +183,9 @@ describe("combineAlongSupportOfSecondArgument0", () => { | |||
|     let answer = | ||||
|       uniformMakeR(lowAnswer, highAnswer)->E.R2.errMap(s => DistributionTypes.ArgumentError(s)) | ||||
|     let prediction = | ||||
|       uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap( | ||||
|         s => DistributionTypes.ArgumentError(s), | ||||
|       ) | ||||
|       uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap(s => DistributionTypes.ArgumentError( | ||||
|         s, | ||||
|       )) | ||||
|     let answerWrapped = E.R.fmap(a => run(FromDist(#ToDist(ToPointSet), a)), answer) | ||||
|     let predictionWrapped = E.R.fmap(a => run(FromDist(#ToDist(ToPointSet), a)), prediction) | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ open Expect | |||
| open TestHelpers | ||||
| 
 | ||||
| // TODO: use Normal.make (but preferably after teh new validation dispatch is in) | ||||
| let mkNormal = (mean, stdev) => DistributionTypes.Symbolic(#Normal({mean, stdev})) | ||||
| let mkNormal = (mean, stdev) => DistributionTypes.Symbolic(#Normal({mean: mean, stdev: stdev})) | ||||
| 
 | ||||
| describe("(Symbolic) normalize", () => { | ||||
|   testAll("has no impact on normal distributions", list{-1e8, -1e-2, 0.0, 1e-4, 1e16}, mean => { | ||||
|  | @ -47,7 +47,10 @@ describe("(Symbolic) mean", () => { | |||
|     tup => { | ||||
|       let (low, medium, high) = tup | ||||
|       let meanValue = run( | ||||
|         FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Triangular({low, medium, high}))), | ||||
|         FromDist( | ||||
|           #ToFloat(#Mean), | ||||
|           DistributionTypes.Symbolic(#Triangular({low: low, medium: medium, high: high})), | ||||
|         ), | ||||
|       ) | ||||
|       meanValue->unpackFloat->expect->toBeCloseTo((low +. medium +. high) /. 3.0) // https://www.statology.org/triangular-distribution/ | ||||
|     }, | ||||
|  | @ -60,7 +63,7 @@ describe("(Symbolic) mean", () => { | |||
|     tup => { | ||||
|       let (alpha, beta) = tup | ||||
|       let meanValue = run( | ||||
|         FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha, beta}))), | ||||
|         FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha: alpha, beta: beta}))), | ||||
|       ) | ||||
|       meanValue->unpackFloat->expect->toBeCloseTo(1.0 /. (1.0 +. beta /. alpha)) // https://en.wikipedia.org/wiki/Beta_distribution#Mean | ||||
|     }, | ||||
|  | @ -81,8 +84,8 @@ describe("(Symbolic) mean", () => { | |||
|       let (mean, stdev) = tup | ||||
|       let betaDistribution = SymbolicDist.Beta.fromMeanAndStdev(mean, stdev) | ||||
|       let meanValue = | ||||
|         betaDistribution->E.R2.fmap( | ||||
|           d => run(FromDist(#ToFloat(#Mean), d->DistributionTypes.Symbolic)), | ||||
|         betaDistribution->E.R2.fmap(d => | ||||
|           run(FromDist(#ToFloat(#Mean), d->DistributionTypes.Symbolic)) | ||||
|         ) | ||||
|       switch meanValue { | ||||
|       | Ok(value) => value->unpackFloat->expect->toBeCloseTo(mean) | ||||
|  | @ -97,7 +100,7 @@ describe("(Symbolic) mean", () => { | |||
|     tup => { | ||||
|       let (mu, sigma) = tup | ||||
|       let meanValue = run( | ||||
|         FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Lognormal({mu, sigma}))), | ||||
|         FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Lognormal({mu: mu, sigma: sigma}))), | ||||
|       ) | ||||
|       meanValue->unpackFloat->expect->toBeCloseTo(Js.Math.exp(mu +. sigma ** 2.0 /. 2.0)) // https://brilliant.org/wiki/log-normal-distribution/ | ||||
|     }, | ||||
|  | @ -109,7 +112,7 @@ describe("(Symbolic) mean", () => { | |||
|     tup => { | ||||
|       let (low, high) = tup | ||||
|       let meanValue = run( | ||||
|         FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Uniform({low, high}))), | ||||
|         FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Uniform({low: low, high: high}))), | ||||
|       ) | ||||
|       meanValue->unpackFloat->expect->toBeCloseTo((low +. high) /. 2.0) // https://en.wikipedia.org/wiki/Continuous_uniform_distribution#Moments | ||||
|     }, | ||||
|  |  | |||
|  | @ -9,28 +9,22 @@ let prepareInputs = (ar, minWeight) => | |||
| describe("Continuous and discrete splits", () => { | ||||
|   makeTest( | ||||
|     "is empty, with no common elements", | ||||
|     prepareInputs([1.33455, 1.432, 2.0], 2), | ||||
|     prepareInputs([1.432, 1.33455, 2.0], 2), | ||||
|     ([1.33455, 1.432, 2.0], []), | ||||
|   ) | ||||
| 
 | ||||
|   makeTest( | ||||
|     "only stores 3.5 as discrete when minWeight is 3", | ||||
|     prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 3), | ||||
|     prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 3), | ||||
|     ([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]), | ||||
|   ) | ||||
| 
 | ||||
|   makeTest( | ||||
|     "doesn't store 3.5 as discrete when minWeight is 5", | ||||
|     prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 5), | ||||
|     prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 5), | ||||
|     ([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], []), | ||||
|   ) | ||||
| 
 | ||||
|   makeTest( | ||||
|     "more general test", | ||||
|     prepareInputs([10., 10., 11., 11., 11., 12., 13., 13., 13., 13., 13., 14.], 3), | ||||
|     ([10., 10., 12., 14.], [(11., 3.), (13., 5.)]), | ||||
|   ) | ||||
| 
 | ||||
|   let makeDuplicatedArray = count => { | ||||
|     let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int) | ||||
|     let sorted = arr |> Belt.SortArray.stableSortBy(_, compare) | ||||
|  |  | |||
							
								
								
									
										20
									
								
								packages/squiggle-lang/__tests__/Lodash_test.res
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/squiggle-lang/__tests__/Lodash_test.res
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | |||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| let makeTest = (~only=false, str, item1, item2) => | ||||
|   only | ||||
|     ? Only.test(str, () => expect(item1)->toEqual(item2)) | ||||
|     : test(str, () => expect(item1)->toEqual(item2)) | ||||
| 
 | ||||
| describe("Lodash", () => | ||||
|   describe("Lodash", () => { | ||||
|     makeTest("min", Lodash.min([1, 3, 4]), 1) | ||||
|     makeTest("max", Lodash.max([1, 3, 4]), 4) | ||||
|     makeTest("uniq", Lodash.uniq([1, 3, 4, 4]), [1, 3, 4]) | ||||
|     makeTest( | ||||
|       "countBy", | ||||
|       Lodash.countBy([1, 3, 4, 4], r => r), | ||||
|       Js.Dict.fromArray([("1", 1), ("3", 1), ("4", 2)]), | ||||
|     ) | ||||
|   }) | ||||
| ) | ||||
|  | @ -1,50 +0,0 @@ | |||
| @@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) | ||||
|       }, | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  | @ -0,0 +1,146 @@ | |||
| open Jest | ||||
| // open Expect | ||||
| 
 | ||||
| open Reducer_Expression_ExpressionBuilder | ||||
| open Reducer_TestMacroHelpers | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| 
 | ||||
| let exampleExpression = eNumber(1.) | ||||
| let exampleExpressionY = eSymbol("y") | ||||
| let exampleStatementY = eLetStatement("y", eNumber(1.)) | ||||
| let exampleStatementX = eLetStatement("y", eSymbol("x")) | ||||
| let exampleStatementZ = eLetStatement("z", eSymbol("y")) | ||||
| 
 | ||||
| // If it is not a macro then it is not expanded | ||||
| testMacro([], exampleExpression, "Ok(1)") | ||||
| 
 | ||||
| describe("bindStatement", () => { | ||||
|   // A statement is bound by the bindings created by the previous statement | ||||
|   testMacro( | ||||
|     [], | ||||
|     eBindStatement(eBindings([]), exampleStatementY), | ||||
|     "Ok((:$_setBindings_$ @{} :y 1) context: @{})", | ||||
|   ) | ||||
|   // Then it answers the bindings for the next statement when reduced | ||||
|   testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok(@{y: 1})") | ||||
|   // Now let's feed a binding to see what happens | ||||
|   testMacro( | ||||
|     [], | ||||
|     eBindStatement(eBindings([("x", IEvNumber(2.))]), exampleStatementX), | ||||
|     "Ok((:$_setBindings_$ @{x: 2} :y 2) context: @{x: 2})", | ||||
|   ) | ||||
|   // An expression does not return a binding, thus error | ||||
|   testMacro([], eBindStatement(eBindings([]), exampleExpression), "Assignment expected") | ||||
|   // When bindings from previous statement are missing the context is injected. This must be the first statement of a block | ||||
|   testMacro( | ||||
|     [("z", IEvNumber(99.))], | ||||
|     eBindStatementDefault(exampleStatementY), | ||||
|     "Ok((:$_setBindings_$ @{z: 99} :y 1) context: @{z: 99})", | ||||
|   ) | ||||
| }) | ||||
| 
 | ||||
| describe("bindExpression", () => { | ||||
|   // x is simply bound in the expression | ||||
|   testMacro( | ||||
|     [], | ||||
|     eBindExpression(eBindings([("x", IEvNumber(2.))]), eSymbol("x")), | ||||
|     "Ok(2 context: @{x: 2})", | ||||
|   ) | ||||
|   // When an let statement is the end expression then bindings are returned | ||||
|   testMacro( | ||||
|     [], | ||||
|     eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY), | ||||
|     "Ok((:$_exportBindings_$ (:$_setBindings_$ @{x: 2} :y 1)) context: @{x: 2})", | ||||
|   ) | ||||
|   // Now let's reduce that expression | ||||
|   testMacroEval( | ||||
|     [], | ||||
|     eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY), | ||||
|     "Ok(@{x: 2,y: 1})", | ||||
|   ) | ||||
|   // When bindings are missing the context is injected. This must be the first and last statement of a block | ||||
|   testMacroEval( | ||||
|     [("z", IEvNumber(99.))], | ||||
|     eBindExpressionDefault(exampleStatementY), | ||||
|     "Ok(@{y: 1,z: 99})", | ||||
|   ) | ||||
| }) | ||||
| 
 | ||||
| describe("block", () => { | ||||
|   // Block with a single expression | ||||
|   testMacro([], eBlock(list{exampleExpression}), "Ok((:$$_bindExpression_$$ 1))") | ||||
|   testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)") | ||||
|   // Block with a single statement | ||||
|   testMacro([], eBlock(list{exampleStatementY}), "Ok((:$$_bindExpression_$$ (:$_let_$ :y 1)))") | ||||
|   testMacroEval([], eBlock(list{exampleStatementY}), "Ok(@{y: 1})") | ||||
|   // Block with a statement and an expression | ||||
|   testMacro( | ||||
|     [], | ||||
|     eBlock(list{exampleStatementY, exampleExpressionY}), | ||||
|     "Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) :y))", | ||||
|   ) | ||||
|   testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)") | ||||
|   // Block with a statement and another statement | ||||
|   testMacro( | ||||
|     [], | ||||
|     eBlock(list{exampleStatementY, exampleStatementZ}), | ||||
|     "Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) (:$_let_$ :z :y)))", | ||||
|   ) | ||||
|   testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok(@{y: 1,z: 1})") | ||||
|   // Block inside a block | ||||
|   testMacro([], eBlock(list{eBlock(list{exampleExpression})}), "Ok((:$$_bindExpression_$$ {1}))") | ||||
|   testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)") | ||||
|   // Block assigned to a variable | ||||
|   testMacro( | ||||
|     [], | ||||
|     eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}), | ||||
|     "Ok((:$$_bindExpression_$$ (:$_let_$ :z {{:y}})))", | ||||
|   ) | ||||
|   testMacroEval( | ||||
|     [], | ||||
|     eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}), | ||||
|     "Ok(@{z: :y})", | ||||
|   ) | ||||
|   // Empty block | ||||
|   testMacro([], eBlock(list{}), "Ok(:undefined block)") //TODO: should be an error | ||||
|   //   :$$_block_$$ (:$$_block_$$ (:$_let_$ :y (:add :x 1)) :y)" | ||||
|   testMacro( | ||||
|     [], | ||||
|     eBlock(list{ | ||||
|       eBlock(list{ | ||||
|         eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})), | ||||
|         eSymbol("y"), | ||||
|       }), | ||||
|     }), | ||||
|     "Ok((:$$_bindExpression_$$ {(:$_let_$ :y (:add :x 1)); :y}))", | ||||
|   ) | ||||
|   testMacroEval( | ||||
|     [("x", IEvNumber(1.))], | ||||
|     eBlock(list{ | ||||
|       eBlock(list{ | ||||
|         eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})), | ||||
|         eSymbol("y"), | ||||
|       }), | ||||
|     }), | ||||
|     "Ok(2)", | ||||
|   ) | ||||
| }) | ||||
| 
 | ||||
| describe("lambda", () => { | ||||
|   // assign a lambda to a variable | ||||
|   let lambdaExpression = eFunction("$$_lambda_$$", list{eArrayString(["y"]), exampleExpressionY}) | ||||
|   testMacro([], lambdaExpression, "Ok(lambda(y=>internal code))") | ||||
|   // call a lambda | ||||
|   let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList | ||||
|   testMacro([], callLambdaExpression, "Ok(((:$$_lambda_$$ [y] :y) 1))") | ||||
|   testMacroEval([], callLambdaExpression, "Ok(1)") | ||||
|   // Parameters shadow the outer scope | ||||
|   testMacroEval([("y", IEvNumber(666.))], callLambdaExpression, "Ok(1)") | ||||
|   // When not shadowed by the parameters, the outer scope variables are available | ||||
|   let lambdaExpression = eFunction( | ||||
|     "$$_lambda_$$", | ||||
|     list{eArrayString(["z"]), eFunction("add", list{eSymbol("y"), eSymbol("z")})}, | ||||
|   ) | ||||
|   let callLambdaExpression = eList(list{lambdaExpression, eNumber(1.)}) | ||||
|   testMacroEval([("y", IEvNumber(666.))], callLambdaExpression, "Ok(667)") | ||||
| }) | ||||
|  | @ -0,0 +1,37 @@ | |||
| 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 | ||||
| }) | ||||
							
								
								
									
										22
									
								
								packages/squiggle-lang/__tests__/Reducer/Reducer_Helpers.res
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								packages/squiggle-lang/__tests__/Reducer/Reducer_Helpers.res
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| // 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) | ||||
|  | @ -0,0 +1,31 @@ | |||
| module MathJs = Reducer_MathJs | ||||
| module ErrorValue = Reducer.ErrorValue | ||||
| 
 | ||||
| open Jest | ||||
| open ExpectJs | ||||
| 
 | ||||
| describe("eval", () => { | ||||
|   test("Number", () => expect(MathJs.Eval.eval("1"))->toEqual(Ok(IEvNumber(1.)))) | ||||
|   test("Number expr", () => expect(MathJs.Eval.eval("1-1"))->toEqual(Ok(IEvNumber(0.)))) | ||||
|   test("String", () => expect(MathJs.Eval.eval("'hello'"))->toEqual(Ok(IEvString("hello")))) | ||||
|   test("String expr", () => | ||||
|     expect(MathJs.Eval.eval("concat('hello ','world')"))->toEqual(Ok(IEvString("hello world"))) | ||||
|   ) | ||||
|   test("Boolean", () => expect(MathJs.Eval.eval("true"))->toEqual(Ok(IEvBool(true)))) | ||||
|   test("Boolean expr", () => expect(MathJs.Eval.eval("2>1"))->toEqual(Ok(IEvBool(true)))) | ||||
| }) | ||||
| 
 | ||||
| describe("errors", () => { | ||||
|   // All those errors propagete up and are returned by the resolver | ||||
|   test("unknown function", () => | ||||
|     expect(MathJs.Eval.eval("testZadanga()"))->toEqual( | ||||
|       Error(ErrorValue.REJavaScriptExn(Some("Undefined function testZadanga"), Some("Error"))), | ||||
|     ) | ||||
|   ) | ||||
| 
 | ||||
|   test("unknown answer type", () => | ||||
|     expect(MathJs.Eval.eval("1+1i"))->toEqual( | ||||
|       Error(ErrorValue.RETodo("Unhandled MathJs literal type: object")), | ||||
|     ) | ||||
|   ) | ||||
| }) | ||||
|  | @ -1,62 +0,0 @@ | |||
| @@warning("-44") | ||||
| module Namespace = Reducer_Namespace | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| let makeValue = (v: float) => v->Reducer_T.IEvNumber | ||||
| 
 | ||||
| describe("Namespace", () => { | ||||
|   let value = makeValue(5.) | ||||
|   let v2 = makeValue(2.) | ||||
|   let ns = Namespace.make()->Namespace.set("value", value) | ||||
| 
 | ||||
|   test("get", () => { | ||||
|     expect(ns->Namespace.get("value")) == Some(value) | ||||
|   }) | ||||
| 
 | ||||
|   test("get nonexisting value", () => { | ||||
|     expect(ns->Namespace.get("nosuchvalue")) == None | ||||
|   }) | ||||
| 
 | ||||
|   test("set", () => { | ||||
|     let ns2 = ns->Namespace.set("v2", v2) | ||||
|     expect(ns2->Namespace.get("v2")) == Some(v2) | ||||
|   }) | ||||
| 
 | ||||
|   test("immutable", () => { | ||||
|     let _ = ns->Namespace.set("v2", Reducer_T.IEvNumber(2.)) | ||||
|     expect(ns->Namespace.get("v2")) == None | ||||
|   }) | ||||
| 
 | ||||
|   describe("merge many", () => { | ||||
|     let x1 = makeValue(10.) | ||||
|     let x2 = makeValue(20.) | ||||
|     let x3 = makeValue(30.) | ||||
|     let x4 = makeValue(40.) | ||||
|     let ns1 = Namespace.make()->Namespace.set("x1", x1)->Namespace.set("x2", x2) | ||||
|     let ns2 = Namespace.make()->Namespace.set("x3", x3)->Namespace.set("x4", x4) | ||||
| 
 | ||||
|     let nsMerged = Namespace.mergeMany([ns, ns1, ns2]) | ||||
| 
 | ||||
|     test( | ||||
|       "merge many 1", | ||||
|       () => { | ||||
|         expect(nsMerged->Namespace.get("x1")) == Some(x1) | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "merge many 2", | ||||
|       () => { | ||||
|         expect(nsMerged->Namespace.get("x4")) == Some(x4) | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "merge many 3", | ||||
|       () => { | ||||
|         expect(nsMerged->Namespace.get("value")) == Some(value) | ||||
|       }, | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  | @ -14,46 +14,47 @@ describe("Peggy parse", () => { | |||
|   }) | ||||
| 
 | ||||
|   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("'hello'", "{'hello'}") | ||||
|     testParse("true", "{true}") | ||||
|     testParse("1+2", "{(:add 1 2)}") | ||||
|     testParse("add(1,2)", "{(:add 1 2)}") | ||||
|     testParse("1+2", "{(::add 1 2)}") | ||||
|     testParse("add(1,2)", "{(::add 1 2)}") | ||||
|     testParse("(1)", "{1}") | ||||
|     testParse("(1+2)", "{(:add 1 2)}") | ||||
|     testParse("(1+2)", "{(::add 1 2)}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("unary", () => { | ||||
|     testParse("-1", "{(:unaryMinus 1)}") | ||||
|     testParse("!true", "{(:not true)}") | ||||
|     testParse("1 + -1", "{(:add 1 (:unaryMinus 1))}") | ||||
|     testParse("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}") | ||||
|     testParse("!a[0]", "{(:not (:$_atIndex_$ :a 0))}") | ||||
|     testParse("-1", "{(::unaryMinus 1)}") | ||||
|     testParse("!true", "{(::not true)}") | ||||
|     testParse("1 + -1", "{(::add 1 (::unaryMinus 1))}") | ||||
|     testParse("-a[0]", "{(::unaryMinus (::$_atIndex_$ :a 0))}") | ||||
|     testParse("!a[0]", "{(::not (::$_atIndex_$ :a 0))}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("multiplicative", () => { | ||||
|     testParse("1 * 2", "{(:multiply 1 2)}") | ||||
|     testParse("1 / 2", "{(:divide 1 2)}") | ||||
|     testParse("1 * 2 * 3", "{(:multiply (:multiply 1 2) 3)}") | ||||
|     testParse("1 * 2 / 3", "{(:divide (:multiply 1 2) 3)}") | ||||
|     testParse("1 / 2 * 3", "{(:multiply (:divide 1 2) 3)}") | ||||
|     testParse("1 / 2 / 3", "{(:divide (:divide 1 2) 3)}") | ||||
|     testParse("1 * 2 + 3 * 4", "{(:add (:multiply 1 2) (:multiply 3 4))}") | ||||
|     testParse("1 * 2 - 3 * 4", "{(:subtract (:multiply 1 2) (:multiply 3 4))}") | ||||
|     testParse("1 * 2 .+ 3 * 4", "{(:dotAdd (:multiply 1 2) (:multiply 3 4))}") | ||||
|     testParse("1 * 2 .- 3 * 4", "{(:dotSubtract (:multiply 1 2) (:multiply 3 4))}") | ||||
|     testParse("1 * 2 + 3 .* 4", "{(:add (:multiply 1 2) (:dotMultiply 3 4))}") | ||||
|     testParse("1 * 2 + 3 / 4", "{(:add (:multiply 1 2) (:divide 3 4))}") | ||||
|     testParse("1 * 2 + 3 ./ 4", "{(:add (:multiply 1 2) (:dotDivide 3 4))}") | ||||
|     testParse("1 * 2 - 3 .* 4", "{(:subtract (:multiply 1 2) (:dotMultiply 3 4))}") | ||||
|     testParse("1 * 2 - 3 / 4", "{(:subtract (:multiply 1 2) (:divide 3 4))}") | ||||
|     testParse("1 * 2 - 3 ./ 4", "{(:subtract (:multiply 1 2) (:dotDivide 3 4))}") | ||||
|     testParse("1 * 2 - 3 * 4^5", "{(:subtract (:multiply 1 2) (:multiply 3 (:pow 4 5)))}") | ||||
|     testParse("1 * 2", "{(::multiply 1 2)}") | ||||
|     testParse("1 / 2", "{(::divide 1 2)}") | ||||
|     testParse("1 * 2 * 3", "{(::multiply (::multiply 1 2) 3)}") | ||||
|     testParse("1 * 2 / 3", "{(::divide (::multiply 1 2) 3)}") | ||||
|     testParse("1 / 2 * 3", "{(::multiply (::divide 1 2) 3)}") | ||||
|     testParse("1 / 2 / 3", "{(::divide (::divide 1 2) 3)}") | ||||
|     testParse("1 * 2 + 3 * 4", "{(::add (::multiply 1 2) (::multiply 3 4))}") | ||||
|     testParse("1 * 2 - 3 * 4", "{(::subtract (::multiply 1 2) (::multiply 3 4))}") | ||||
|     testParse("1 * 2 .+ 3 * 4", "{(::dotAdd (::multiply 1 2) (::multiply 3 4))}") | ||||
|     testParse("1 * 2 .- 3 * 4", "{(::dotSubtract (::multiply 1 2) (::multiply 3 4))}") | ||||
|     testParse("1 * 2 + 3 .* 4", "{(::add (::multiply 1 2) (::dotMultiply 3 4))}") | ||||
|     testParse("1 * 2 + 3 / 4", "{(::add (::multiply 1 2) (::divide 3 4))}") | ||||
|     testParse("1 * 2 + 3 ./ 4", "{(::add (::multiply 1 2) (::dotDivide 3 4))}") | ||||
|     testParse("1 * 2 - 3 .* 4", "{(::subtract (::multiply 1 2) (::dotMultiply 3 4))}") | ||||
|     testParse("1 * 2 - 3 / 4", "{(::subtract (::multiply 1 2) (::divide 3 4))}") | ||||
|     testParse("1 * 2 - 3 ./ 4", "{(::subtract (::multiply 1 2) (::dotDivide 3 4))}") | ||||
|     testParse("1 * 2 - 3 * 4^5", "{(::subtract (::multiply 1 2) (::multiply 3 (::pow 4 5)))}") | ||||
|     testParse( | ||||
|       "1 * 2 - 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", () => { | ||||
|  | @ -69,27 +70,27 @@ describe("Peggy parse", () => { | |||
| 
 | ||||
|   describe("functions", () => { | ||||
|     testParse("identity(x) = x", "{:identity = {|:x| {:x}}}") // Function definitions become lambda assignments | ||||
|     testParse("identity(x)", "{(:identity :x)}") | ||||
|     testParse("identity(x)", "{(::identity :x)}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("arrays", () => { | ||||
|     testParse("[]", "{[]}") | ||||
|     testParse("[0, 1, 2]", "{[0; 1; 2]}") | ||||
|     testParse("['hello', 'world']", "{['hello'; 'world']}") | ||||
|     testParse("([0,1,2])[1]", "{(:$_atIndex_$ [0; 1; 2] 1)}") | ||||
|     testParse("[]", "{(::$_constructArray_$ ())}") | ||||
|     testParse("[0, 1, 2]", "{(::$_constructArray_$ (0 1 2))}") | ||||
|     testParse("['hello', 'world']", "{(::$_constructArray_$ ('hello' 'world'))}") | ||||
|     testParse("([0,1,2])[1]", "{(::$_atIndex_$ (::$_constructArray_$ (0 1 2)) 1)}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("records", () => { | ||||
|     testParse("{a: 1, b: 2}", "{{'a': 1, 'b': 2}}") | ||||
|     testParse("{1+0: 1, 2+0: 2}", "{{(:add 1 0): 1, (:add 2 0): 2}}") // key can be any expression | ||||
|     testParse("record.property", "{(:$_atIndex_$ :record 'property')}") | ||||
|     testParse("{a: 1, b: 2}", "{(::$_constructRecord_$ ('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("record.property", "{(::$_atIndex_$ :record 'property')}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("post operators", () => { | ||||
|     //function call, array and record access are post operators with higher priority than unary operators | ||||
|     testParse("a==!b(1)", "{(:equal :a (:not (:b 1)))}") | ||||
|     testParse("a==!b[1]", "{(:equal :a (:not (:$_atIndex_$ :b 1)))}") | ||||
|     testParse("a==!b.one", "{(:equal :a (:not (:$_atIndex_$ :b 'one')))}") | ||||
|     testParse("a==!b(1)", "{(::equal :a (::not (::b 1)))}") | ||||
|     testParse("a==!b[1]", "{(::equal :a (::not (::$_atIndex_$ :b 1)))}") | ||||
|     testParse("a==!b.one", "{(::equal :a (::not (::$_atIndex_$ :b 'one')))}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("comments", () => { | ||||
|  | @ -125,67 +126,70 @@ describe("Peggy parse", () => { | |||
|   }) | ||||
| 
 | ||||
|   describe("logical", () => { | ||||
|     testParse("true || false", "{(:or true false)}") | ||||
|     testParse("true && false", "{(:and true false)}") | ||||
|     testParse("a * b + c", "{(:add (:multiply :a :b) :c)}") // for comparison | ||||
|     testParse("a && b || c", "{(:or (:and :a :b) :c)}") | ||||
|     testParse("a && b || c && d", "{(:or (:and :a :b) (:and :c :d))}") | ||||
|     testParse("a && !b || c", "{(:or (:and :a (:not :b)) :c)}") | ||||
|     testParse("a && b==c || d", "{(:or (:and :a (:equal :b :c)) :d)}") | ||||
|     testParse("a && b!=c || d", "{(:or (:and :a (:unequal :b :c)) :d)}") | ||||
|     testParse("a && !(b==c) || d", "{(:or (:and :a (:not (:equal :b :c))) :d)}") | ||||
|     testParse("a && b>=c || d", "{(:or (:and :a (:largerEq :b :c)) :d)}") | ||||
|     testParse("a && !(b>=c) || d", "{(:or (:and :a (:not (:largerEq :b :c))) :d)}") | ||||
|     testParse("a && b<=c || d", "{(:or (:and :a (:smallerEq :b :c)) :d)}") | ||||
|     testParse("a && b>c || d", "{(:or (:and :a (:larger :b :c)) :d)}") | ||||
|     testParse("a && b<c || d", "{(:or (:and :a (:smaller :b :c)) :d)}") | ||||
|     testParse("a && b<c[i] || d", "{(:or (:and :a (:smaller :b (:$_atIndex_$ :c :i))) :d)}") | ||||
|     testParse("a && b<c.i || d", "{(:or (:and :a (:smaller :b (:$_atIndex_$ :c 'i'))) :d)}") | ||||
|     testParse("a && b<c(i) || d", "{(:or (:and :a (:smaller :b (:c :i))) :d)}") | ||||
|     testParse("a && b<1+2 || d", "{(:or (:and :a (:smaller :b (:add 1 2))) :d)}") | ||||
|     testParse("a && b<1+2*3 || d", "{(:or (:and :a (:smaller :b (:add 1 (:multiply 2 3)))) :d)}") | ||||
|     testParse("true || false", "{(::or true false)}") | ||||
|     testParse("true && false", "{(::and true false)}") | ||||
|     testParse("a * b + c", "{(::add (::multiply :a :b) :c)}") // for comparison | ||||
|     testParse("a && b || c", "{(::or (::and :a :b) :c)}") | ||||
|     testParse("a && b || c && d", "{(::or (::and :a :b) (::and :c :d))}") | ||||
|     testParse("a && !b || c", "{(::or (::and :a (::not :b)) :c)}") | ||||
|     testParse("a && b==c || d", "{(::or (::and :a (::equal :b :c)) :d)}") | ||||
|     testParse("a && b!=c || d", "{(::or (::and :a (::unequal :b :c)) :d)}") | ||||
|     testParse("a && !(b==c) || d", "{(::or (::and :a (::not (::equal :b :c))) :d)}") | ||||
|     testParse("a && b>=c || d", "{(::or (::and :a (::largerEq :b :c)) :d)}") | ||||
|     testParse("a && !(b>=c) || d", "{(::or (::and :a (::not (::largerEq :b :c))) :d)}") | ||||
|     testParse("a && b<=c || d", "{(::or (::and :a (::smallerEq :b :c)) :d)}") | ||||
|     testParse("a && b>c || d", "{(::or (::and :a (::larger :b :c)) :d)}") | ||||
|     testParse("a && b<c || d", "{(::or (::and :a (::smaller :b :c)) :d)}") | ||||
|     testParse("a && b<c[i] || d", "{(::or (::and :a (::smaller :b (::$_atIndex_$ :c :i))) :d)}") | ||||
|     testParse("a && b<c.i || d", "{(::or (::and :a (::smaller :b (::$_atIndex_$ :c 'i'))) :d)}") | ||||
|     testParse("a && b<c(i) || d", "{(::or (::and :a (::smaller :b (::c :i))) :d)}") | ||||
|     testParse("a && b<1+2 || d", "{(::or (::and :a (::smaller :b (::add 1 2))) :d)}") | ||||
|     testParse( | ||||
|       "a && b<1+2*3 || d", | ||||
|       "{(::or (::and :a (::smaller :b (::add 1 (::multiply 2 3)))) :d)}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "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( | ||||
|       "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", () => { | ||||
|     testParse("1 -> add(2)", "{(:add 1 2)}") | ||||
|     testParse("-1 -> add(2)", "{(:add (:unaryMinus 1) 2)}") | ||||
|     testParse("-a[1] -> add(2)", "{(:add (:unaryMinus (:$_atIndex_$ :a 1)) 2)}") | ||||
|     testParse("-f(1) -> add(2)", "{(:add (:unaryMinus (:f 1)) 2)}") | ||||
|     testParse("1 + 2 -> add(3)", "{(:add 1 (:add 2 3))}") | ||||
|     testParse("1 -> add(2) * 3", "{(:multiply (:add 1 2) 3)}") | ||||
|     testParse("1 -> subtract(2)", "{(:subtract 1 2)}") | ||||
|     testParse("-1 -> subtract(2)", "{(:subtract (:unaryMinus 1) 2)}") | ||||
|     testParse("1 -> subtract(2) * 3", "{(:multiply (:subtract 1 2) 3)}") | ||||
|     testParse("1 -> add(2)", "{(::add 1 2)}") | ||||
|     testParse("-1 -> add(2)", "{(::add (::unaryMinus 1) 2)}") | ||||
|     testParse("-a[1] -> add(2)", "{(::add (::unaryMinus (::$_atIndex_$ :a 1)) 2)}") | ||||
|     testParse("-f(1) -> add(2)", "{(::add (::unaryMinus (::f 1)) 2)}") | ||||
|     testParse("1 + 2 -> add(3)", "{(::add 1 (::add 2 3))}") | ||||
|     testParse("1 -> add(2) * 3", "{(::multiply (::add 1 2) 3)}") | ||||
|     testParse("1 -> subtract(2)", "{(::subtract 1 2)}") | ||||
|     testParse("-1 -> subtract(2)", "{(::subtract (::unaryMinus 1) 2)}") | ||||
|     testParse("1 -> subtract(2) * 3", "{(::multiply (::subtract 1 2) 3)}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("elixir pipe", () => { | ||||
|     //handled together with -> so there is no need for seperate tests | ||||
|     testParse("1 |> add(2)", "{(:add 1 2)}") | ||||
|     testParse("1 |> add(2)", "{(::add 1 2)}") | ||||
|   }) | ||||
| 
 | ||||
|   describe("to", () => { | ||||
|     testParse("1 to 2", "{(:credibleIntervalToDistribution 1 2)}") | ||||
|     testParse("-1 to -2", "{(:credibleIntervalToDistribution (:unaryMinus 1) (:unaryMinus 2))}") // lower than unary | ||||
|     testParse("1 to 2", "{(::credibleIntervalToDistribution 1 2)}") | ||||
|     testParse("-1 to -2", "{(::credibleIntervalToDistribution (::unaryMinus 1) (::unaryMinus 2))}") // lower than unary | ||||
|     testParse( | ||||
|       "a[1] to a[2]", | ||||
|       "{(:credibleIntervalToDistribution (:$_atIndex_$ :a 1) (:$_atIndex_$ :a 2))}", | ||||
|       "{(::credibleIntervalToDistribution (::$_atIndex_$ :a 1) (::$_atIndex_$ :a 2))}", | ||||
|     ) // lower than post | ||||
|     testParse( | ||||
|       "a.p1 to a.p2", | ||||
|       "{(:credibleIntervalToDistribution (:$_atIndex_$ :a 'p1') (:$_atIndex_$ :a 'p2'))}", | ||||
|       "{(::credibleIntervalToDistribution (::$_atIndex_$ :a 'p1') (::$_atIndex_$ :a 'p2'))}", | ||||
|     ) // lower than post | ||||
|     testParse("1 to 2 + 3", "{(:credibleIntervalToDistribution 1 (:add 2 3))}") | ||||
|     testParse("1 to 2 + 3", "{(::add (::credibleIntervalToDistribution 1 2) 3)}") // higher than binary operators | ||||
|     testParse( | ||||
|       "1->add(2) to 3->add(4) -> add(4)", | ||||
|       "{(:credibleIntervalToDistribution (:add 1 2) (:add (:add 3 4) 4))}", | ||||
|       "{(::credibleIntervalToDistribution (::add 1 2) (::add (::add 3 4) 4))}", | ||||
|     ) // lower than chain | ||||
|   }) | ||||
| 
 | ||||
|  | @ -196,8 +200,8 @@ describe("Peggy parse", () => { | |||
|   }) | ||||
| 
 | ||||
|   describe("lambda", () => { | ||||
|     testParse("{|x| x}", "{{|:x| :x}}") | ||||
|     testParse("f={|x| x}", "{:f = {|:x| :x}}") | ||||
|     testParse("{|x| x}", "{{|:x| {:x}}}") | ||||
|     testParse("f={|x| x}", "{:f = {{|:x| {:x}}}}") | ||||
|     testParse("f(x)=x", "{:f = {|:x| {:x}}}") // Function definitions are lambda assignments | ||||
|     testParse("f(x)=x ? 1 : 0", "{:f = {|:x| {(::$$_ternary_$$ :x 1 0)}}}") // Function definitions are lambda assignments | ||||
|   }) | ||||
|  | @ -205,25 +209,31 @@ describe("Peggy parse", () => { | |||
|   describe("Using lambda as value", () => { | ||||
|     testParse( | ||||
|       "myadd(x,y)=x+y; z=myadd; z", | ||||
|       "{:myadd = {|:x,:y| {(:add :x :y)}}; :z = {:myadd}; :z}", | ||||
|       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {:myadd}; :z}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "myadd(x,y)=x+y; z=[myadd]; z", | ||||
|       "{:myadd = {|:x,:y| {(:add :x :y)}}; :z = {[:myadd]}; :z}", | ||||
|       "{:myadd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructArray_$ (:myadd))}; :z}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "myaddd(x,y)=x+y; z={x: myaddd}; z", | ||||
|       "{:myaddd = {|:x,:y| {(:add :x :y)}}; :z = {{'x': :myaddd}}; :z}", | ||||
|       "{:myaddd = {|:x,:y| {(::add :x :y)}}; :z = {(::$_constructRecord_$ ('x': :myaddd))}; :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", () => { | ||||
|     testParse("1m", "{(:fromUnit_m 1)}") | ||||
|     testParse("1M", "{(:fromUnit_M 1)}") | ||||
|     testParse("1m+2cm", "{(:add (:fromUnit_m 1) (:fromUnit_cm 2))}") | ||||
|     testParse("1m", "{(::fromUnit_m 1)}") | ||||
|     testParse("1M", "{(::fromUnit_M 1)}") | ||||
|     testParse("1m+2cm", "{(::add (::fromUnit_m 1) (::fromUnit_cm 2))}") | ||||
|   }) | ||||
|   describe("Module", () => { | ||||
|     testParse("x", "{:x}") | ||||
|  | @ -236,7 +246,7 @@ describe("parsing new line", () => { | |||
|     ` | ||||
|   a +  | ||||
|   b`, | ||||
|     "{(:add :a :b)}", | ||||
|     "{(::add :a :b)}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -292,7 +302,7 @@ describe("parsing new line", () => { | |||
|     x+y+z | ||||
|   } | ||||
|   `, | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}}", | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -305,7 +315,7 @@ describe("parsing new line", () => { | |||
|   g=f+4 | ||||
|   g | ||||
|   `, | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}; :g = {(:add :f 4)}; :g}", | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; :g}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -327,7 +337,7 @@ describe("parsing new line", () => { | |||
|     p -> | ||||
|     q  | ||||
|   `, | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (:add (:add :x :y) :z)}; :g = {(:add :f 4)}; (:q (:p (:h :g)))}", | ||||
|     "{:f = {:x = {1}; :y = {2}; :z = {3}; (::add (::add :x :y) :z)}; :g = {(::add :f 4)}; (::q (::p (::h :g)))}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -336,7 +346,7 @@ describe("parsing new line", () => { | |||
|     c |> | ||||
|     d  | ||||
|   `, | ||||
|     "{(:d (:c (:b :a)))}", | ||||
|     "{(::d (::c (::b :a)))}", | ||||
|   ) | ||||
|   testParse( | ||||
|     ` | ||||
|  | @ -346,6 +356,6 @@ describe("parsing new line", () => { | |||
|     d + | ||||
|     e | ||||
|   `, | ||||
|     "{(:add (:d (:c (:b :a))) :e)}", | ||||
|     "{(::add (::d (::c (::b :a))) :e)}", | ||||
|   ) | ||||
| }) | ||||
|  |  | |||
|  | @ -3,83 +3,77 @@ open Reducer_Peggy_TestHelpers | |||
| 
 | ||||
| describe("Peggy parse type", () => { | ||||
|   describe("type of", () => { | ||||
|     testParse("p: number", "{(::$_typeOf_$ :p #number); (::$_endOfOuterBlock_$ () ())}") | ||||
|     testParse("p: number", "{(::$_typeOf_$ :p #number)}") | ||||
|   }) | ||||
|   describe("type alias", () => { | ||||
|     testParse( | ||||
|       "type index=number", | ||||
|       "{(::$_typeAlias_$ #index #number); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|     testParse("type index=number", "{(::$_typeAlias_$ #index #number)}") | ||||
|   }) | ||||
|   describe("type or", () => { | ||||
|     testParse( | ||||
|       "answer: number|string", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ #number #string))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string))))}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type function", () => { | ||||
|     testParse( | ||||
|       "f: number=>number=>number", | ||||
|       "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ #number #number #number))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("high priority contract", () => { | ||||
|     testParse( | ||||
|       "answer: number<-min<-max(100)|string", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "answer: number<-memberOf([1,3,5])", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ 1 3 5))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("low priority contract", () => { | ||||
|     testParse( | ||||
|       "answer: number | string $ opaque", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ #number #string)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type array", () => { | ||||
|     testParse( | ||||
|       "answer: [number]", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeArray_$ #number)); (::$_endOfOuterBlock_$ () ())}", | ||||
|     ) | ||||
|     testParse("answer: [number]", "{(::$_typeOf_$ :answer (::$_typeArray_$ #number))}") | ||||
|   }) | ||||
|   describe("type record", () => { | ||||
|     testParse( | ||||
|       "answer: {a: number, b: string}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string))))}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type constructor", () => { | ||||
|     testParse( | ||||
|       "answer: Age(number)", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ #number))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number))))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "answer: Complex(number, number)", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ #number #number))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number))))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "answer: Person({age: number, name: string})", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ (::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ ((::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))))}", | ||||
|     ) | ||||
|     testParse( | ||||
|       "weekend: Saturday | Sunday", | ||||
|       "{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ (::$_typeConstructor_$ #Saturday (::$_constructArray_$)) (::$_typeConstructor_$ #Sunday (::$_constructArray_$))))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("type parenthesis", () => { | ||||
|     //$ is introduced to avoid parenthesis | ||||
|     testParse( | ||||
|       "answer: (number|string)<-opaque", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ #number #string)))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", | ||||
|     ) | ||||
|   }) | ||||
|   describe("squiggle expressions in type contracts", () => { | ||||
|     testParse( | ||||
|       "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", | ||||
|       "{:odds1 = {(::$_constructArray_$ 1 3 5)}; :odds2 = {(::$_constructArray_$ 7 9)}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2))); (::$_endOfOuterBlock_$ () ())}", | ||||
|       "{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}", | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| module Expression = Reducer_Expression | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| module ExpressionValue = ReducerInterface.InternalExpressionValue | ||||
| module Parse = Reducer_Peggy_Parse | ||||
| module Result = Belt.Result | ||||
| module ToExpression = Reducer_Peggy_ToExpression | ||||
|  | @ -9,12 +10,12 @@ open Jest | |||
| open Expect | ||||
| 
 | ||||
| let expectParseToBe = (expr, answer) => | ||||
|   Parse.parse(expr, "test")->Parse.toStringResult->expect->toBe(answer) | ||||
|   Parse.parse(expr)->Parse.toStringResult->expect->toBe(answer) | ||||
| 
 | ||||
| let testParse = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) | ||||
| 
 | ||||
| let expectExpressionToBe = (expr, answer, ~v="_", ()) => { | ||||
|   let rExpr = Parse.parse(expr, "test")->Result.map(ToExpression.fromNode) | ||||
| let expectToExpressionToBe = (expr, answer, ~v="_", ()) => { | ||||
|   let rExpr = Parse.parse(expr)->Result.map(ToExpression.fromNode) | ||||
|   let a1 = rExpr->ExpressionT.toStringResultOkless | ||||
| 
 | ||||
|   if v == "_" { | ||||
|  | @ -22,24 +23,30 @@ let expectExpressionToBe = (expr, answer, ~v="_", ()) => { | |||
|   } else { | ||||
|     let a2 = | ||||
|       rExpr | ||||
|       ->E.R2.errMap(e => e->SqError.fromParseError) | ||||
|       ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr)) | ||||
|       ->Reducer_Value.toStringResultOkless | ||||
|       ->Result.flatMap(expr => | ||||
|         Expression.reduceExpression( | ||||
|           expr, | ||||
|           ReducerInterface_StdLib.internalStdLib, | ||||
|           ExpressionValue.defaultEnvironment, | ||||
|         ) | ||||
|       ) | ||||
|       ->Reducer_Helpers.rRemoveDefaultsInternal | ||||
|       ->ExpressionValue.toStringResultOkless | ||||
|     (a1, a2)->expect->toEqual((answer, v)) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| let testToExpression = (expr, answer, ~v="_", ()) => | ||||
|   test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) | ||||
|   test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) | ||||
| 
 | ||||
| module MyOnly = { | ||||
|   let testParse = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) | ||||
|   let testToExpression = (expr, answer, ~v="_", ()) => | ||||
|     Only.test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) | ||||
|     Only.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) | ||||
| } | ||||
| 
 | ||||
| module MySkip = { | ||||
|   let testParse = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) | ||||
|   let testToExpression = (expr, answer, ~v="_", ()) => | ||||
|     Skip.test(expr, () => expectExpressionToBe(expr, answer, ~v, ())) | ||||
|     Skip.test(expr, () => expectToExpressionToBe(expr, answer, ~v, ())) | ||||
| } | ||||
|  |  | |||
|  | @ -1,12 +0,0 @@ | |||
| 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,4 +1,5 @@ | |||
| module Bindings = Reducer_Bindings | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| 
 | ||||
| open Jest | ||||
| open Reducer_Peggy_TestHelpers | ||||
|  | @ -6,121 +7,147 @@ open Reducer_Peggy_TestHelpers | |||
| describe("Peggy to Expression", () => { | ||||
|   describe("literals operators parenthesis", () => { | ||||
|     // Note that there is always an outer block. Otherwise, external bindings are ignored at the first statement | ||||
|     testToExpression("1", "1", ~v="1", ()) | ||||
|     testToExpression("'hello'", "'hello'", ~v="'hello'", ()) | ||||
|     testToExpression("true", "true", ~v="true", ()) | ||||
|     testToExpression("1+2", "(add)(1, 2)", ~v="3", ()) | ||||
|     testToExpression("add(1,2)", "(add)(1, 2)", ~v="3", ()) | ||||
|     testToExpression("(1)", "1", ()) | ||||
|     testToExpression("(1+2)", "(add)(1, 2)", ()) | ||||
|     testToExpression("1", "{1}", ~v="1", ()) | ||||
|     testToExpression("'hello'", "{'hello'}", ~v="'hello'", ()) | ||||
|     testToExpression("true", "{true}", ~v="true", ()) | ||||
|     testToExpression("1+2", "{(:add 1 2)}", ~v="3", ()) | ||||
|     testToExpression("add(1,2)", "{(:add 1 2)}", ~v="3", ()) | ||||
|     testToExpression("(1)", "{1}", ()) | ||||
|     testToExpression("(1+2)", "{(:add 1 2)}", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("unary", () => { | ||||
|     testToExpression("-1", "(unaryMinus)(1)", ~v="-1", ()) | ||||
|     testToExpression("!true", "(not)(true)", ~v="false", ()) | ||||
|     testToExpression("1 + -1", "(add)(1, (unaryMinus)(1))", ~v="0", ()) | ||||
|     testToExpression("-a[0]", "(unaryMinus)(($_atIndex_$)(a, 0))", ()) | ||||
|     testToExpression("-1", "{(:unaryMinus 1)}", ~v="-1", ()) | ||||
|     testToExpression("!true", "{(:not true)}", ~v="false", ()) | ||||
|     testToExpression("1 + -1", "{(:add 1 (:unaryMinus 1))}", ~v="0", ()) | ||||
|     testToExpression("-a[0]", "{(:unaryMinus (:$_atIndex_$ :a 0))}", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("multi-line", () => { | ||||
|     testToExpression("x=1; 2", "x = {1}; 2", ~v="2", ()) | ||||
|     testToExpression("x=1; y=2", "x = {1}; y = {2}", ()) | ||||
|     testToExpression("x=1; 2", "{(:$_let_$ :x {1}); 2}", ~v="2", ()) | ||||
|     testToExpression("x=1; y=2", "{(:$_let_$ :x {1}); (:$_let_$ :y {2})}", ~v="@{x: 1,y: 2}", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("variables", () => { | ||||
|     testToExpression("x = 1", "x = {1}", ()) | ||||
|     testToExpression("x", "x", ~v="Error(x is not defined)", ()) //TODO: value should return error | ||||
|     testToExpression("x = 1; x", "x = {1}; x", ~v="1", ()) | ||||
|     testToExpression("x = 1", "{(:$_let_$ :x {1})}", ~v="@{x: 1}", ()) | ||||
|     testToExpression("x", "{:x}", ~v=":x", ()) //TODO: value should return error | ||||
|     testToExpression("x = 1; x", "{(:$_let_$ :x {1}); :x}", ~v="1", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("functions", () => { | ||||
|     testToExpression("identity(x) = x", "identity = {|x| {x}}", ()) // Function definitions become lambda assignments | ||||
|     testToExpression("identity(x)", "(identity)(x)", ()) // Note value returns error properly | ||||
|     testToExpression( | ||||
|       "identity(x) = x", | ||||
|       "{(:$_let_$ :identity (:$$_lambda_$$ [x] {:x}))}", | ||||
|       ~v="@{identity: lambda(x=>internal code)}", | ||||
|       (), | ||||
|     ) // Function definitions become lambda assignments | ||||
|     testToExpression("identity(x)", "{(:identity :x)}", ()) // Note value returns error properly | ||||
|     testToExpression( | ||||
|       "f(x) = x> 2 ? 0 : 1; f(3)", | ||||
|       "f = {|x| {(larger)(x, 2) ? (0) : (1)}}; (f)(3)", | ||||
|       "{(:$_let_$ :f (:$$_lambda_$$ [x] {(:$$_ternary_$$ (:larger :x 2) 0 1)})); (:f 3)}", | ||||
|       ~v="0", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("arrays", () => { | ||||
|     testToExpression("[]", "[]", ~v="[]", ()) | ||||
|     testToExpression("[0, 1, 2]", "[0, 1, 2]", ~v="[0,1,2]", ()) | ||||
|     testToExpression("['hello', 'world']", "['hello', 'world']", ~v="['hello','world']", ()) | ||||
|     testToExpression("([0,1,2])[1]", "($_atIndex_$)([0, 1, 2], 1)", ~v="1", ()) | ||||
|     testToExpression("[]", "{(:$_constructArray_$ ())}", ~v="[]", ()) | ||||
|     testToExpression("[0, 1, 2]", "{(:$_constructArray_$ (0 1 2))}", ~v="[0,1,2]", ()) | ||||
|     testToExpression( | ||||
|       "['hello', 'world']", | ||||
|       "{(:$_constructArray_$ ('hello' 'world'))}", | ||||
|       ~v="['hello','world']", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression("([0,1,2])[1]", "{(:$_atIndex_$ (:$_constructArray_$ (0 1 2)) 1)}", ~v="1", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("records", () => { | ||||
|     testToExpression("{a: 1, b: 2}", "{'a': 1, 'b': 2}", ~v="{a: 1,b: 2}", ()) | ||||
|     testToExpression("{1+0: 1, 2+0: 2}", "{(add)(1, 0): 1, (add)(2, 0): 2}", ()) // key can be any expression | ||||
|     testToExpression("record.property", "($_atIndex_$)(record, 'property')", ()) | ||||
|     testToExpression( | ||||
|       "{a: 1, b: 2}", | ||||
|       "{(:$_constructRecord_$ (('a' 1) ('b' 2)))}", | ||||
|       ~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( | ||||
|       "record={property: 1}; record.property", | ||||
|       "record = {{'property': 1}}; ($_atIndex_$)(record, 'property')", | ||||
|       "{(:$_let_$ :record {(:$_constructRecord_$ (('property' 1)))}); (:$_atIndex_$ :record 'property')}", | ||||
|       ~v="1", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("comments", () => { | ||||
|     testToExpression("1 # This is a line comment", "1", ~v="1", ()) | ||||
|     testToExpression("1 // This is a line comment", "1", ~v="1", ()) | ||||
|     testToExpression("1 /* This is a multi line comment */", "1", ~v="1", ()) | ||||
|     testToExpression("/* This is a multi line comment */ 1", "1", ~v="1", ()) | ||||
|     testToExpression("1 # This is a line comment", "{1}", ~v="1", ()) | ||||
|     testToExpression("1 // This is a line comment", "{1}", ~v="1", ()) | ||||
|     testToExpression("1 /* This is a multi line comment */", "{1}", ~v="1", ()) | ||||
|     testToExpression("/* This is a multi line comment */ 1", "{1}", ~v="1", ()) | ||||
|   }) | ||||
| 
 | ||||
|   describe("ternary operator", () => { | ||||
|     testToExpression("true ? 1 : 0", "true ? (1) : (0)", ~v="1", ()) | ||||
|     testToExpression("false ? 1 : 0", "false ? (1) : (0)", ~v="0", ()) | ||||
|     testToExpression("true ? 1 : false ? 2 : 0", "true ? (1) : (false ? (2) : (0))", ~v="1", ()) // nested ternary | ||||
|     testToExpression("false ? 1 : false ? 2 : 0", "false ? (1) : (false ? (2) : (0))", ~v="0", ()) // nested ternary | ||||
|     describe( | ||||
|       "ternary bindings", | ||||
|       () => { | ||||
|     testToExpression("true ? 1 : 0", "{(:$$_ternary_$$ true 1 0)}", ~v="1", ()) | ||||
|     testToExpression("false ? 1 : 0", "{(:$$_ternary_$$ false 1 0)}", ~v="0", ()) | ||||
|     testToExpression( | ||||
|       "true ? 1 : false ? 2 : 0", | ||||
|       "{(:$$_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", () => { | ||||
|       testToExpression( | ||||
|         // expression binding | ||||
|         "f(a) = a > 5 ? 1 : 0; f(6)", | ||||
|           "f = {|a| {(larger)(a, 5) ? (1) : (0)}}; (f)(6)", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) 1 0)})); (:f 6)}", | ||||
|         ~v="1", | ||||
|         (), | ||||
|       ) | ||||
|       testToExpression( | ||||
|         // when true binding | ||||
|         "f(a) = a > 5 ? a : 0; f(6)", | ||||
|           "f = {|a| {(larger)(a, 5) ? (a) : (0)}}; (f)(6)", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:larger :a 5) :a 0)})); (:f 6)}", | ||||
|         ~v="6", | ||||
|         (), | ||||
|       ) | ||||
|       testToExpression( | ||||
|         // when false binding | ||||
|         "f(a) = a < 5 ? 1 : a; f(6)", | ||||
|           "f = {|a| {(smaller)(a, 5) ? (1) : (a)}}; (f)(6)", | ||||
|         "{(:$_let_$ :f (:$$_lambda_$$ [a] {(:$$_ternary_$$ (:smaller :a 5) 1 :a)})); (:f 6)}", | ||||
|         ~v="6", | ||||
|         (), | ||||
|       ) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
|   }) | ||||
| 
 | ||||
|   describe("if then else", () => { | ||||
|     testToExpression("if true then 2 else 3", "true ? ({2}) : ({3})", ()) | ||||
|     testToExpression("if true then {2} else {3}", "true ? ({2}) : ({3})", ()) | ||||
|     testToExpression("if true then 2 else 3", "{(:$$_ternary_$$ true {2} {3})}", ()) | ||||
|     testToExpression("if true then {2} else {3}", "{(:$$_ternary_$$ true {2} {3})}", ()) | ||||
|     testToExpression( | ||||
|       "if false then {2} else if false then {4} else {5}", | ||||
|       "false ? ({2}) : (false ? ({4}) : ({5}))", | ||||
|       "{(:$$_ternary_$$ false {2} (:$$_ternary_$$ false {4} {5}))}", | ||||
|       (), | ||||
|     ) //nested if | ||||
|   }) | ||||
| 
 | ||||
|   describe("pipe", () => { | ||||
|     testToExpression("1 -> add(2)", "(add)(1, 2)", ~v="3", ()) | ||||
|     testToExpression("-1 -> add(2)", "(add)((unaryMinus)(1), 2)", ~v="1", ()) // note that unary has higher priority naturally | ||||
|     testToExpression("1 -> add(2) * 3", "(multiply)((add)(1, 2), 3)", ~v="9", ()) | ||||
|     testToExpression("1 -> add(2)", "{(:add 1 2)}", ~v="3", ()) | ||||
|     testToExpression("-1 -> add(2)", "{(:add (:unaryMinus 1) 2)}", ~v="1", ()) // note that unary has higher priority naturally | ||||
|     testToExpression("1 -> add(2) * 3", "{(:multiply (:add 1 2) 3)}", ~v="9", ()) | ||||
|   }) | ||||
| 
 | ||||
|   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 | ||||
|  | @ -130,28 +157,43 @@ describe("Peggy to Expression", () => { | |||
|     // Like lambdas they have a local scope. | ||||
|     testToExpression( | ||||
|       "y=99; x={y=1; y}", | ||||
|       "y = {99}; x = {y = {1}; y}", | ||||
|       // "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y}); (:$_endOfOuterBlock_$ () ())}", | ||||
|       "{(:$_let_$ :y {99}); (:$_let_$ :x {(:$_let_$ :y {1}); :y})}", | ||||
|       ~v="@{x: 1,y: 99}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("lambda", () => { | ||||
|     testToExpression("{|x| x}", "{|x| x}", ~v="lambda(x=>internal code)", ()) | ||||
|     testToExpression("f={|x| x}", "f = {|x| x}", ()) | ||||
|     testToExpression("f(x)=x", "f = {|x| {x}}", ()) // Function definitions are lambda assignments | ||||
|     testToExpression("f(x)=x ? 1 : 0", "f = {|x| {x ? (1) : (0)}}", ()) | ||||
|     testToExpression("{|x| x}", "{(:$$_lambda_$$ [x] {:x})}", ~v="lambda(x=>internal code)", ()) | ||||
|     testToExpression( | ||||
|       "f={|x| x}", | ||||
|       "{(:$_let_$ :f {(:$$_lambda_$$ [x] {:x})})}", | ||||
|       ~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", () => { | ||||
|     // testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ()) | ||||
|     // Only.test("stdlibrary", () => { | ||||
|     //  SquiggleLibrary_StdLib.stdLib | ||||
|     //   ReducerInterface_StdLib.internalStdLib | ||||
|     //   ->IEvBindings | ||||
|     //  ->Reducer_Value.toString | ||||
|     //   ->InternalExpressionValue.toString | ||||
|     //   ->expect | ||||
|     //   ->toBe("") | ||||
|     // }) | ||||
|     testToExpression("Math.pi", "Math.pi", ~v="3.141592653589793", ()) | ||||
|     testToExpression("Math.pi", "{:Math.pi}", ~v="3.141592653589793", ()) | ||||
|   }) | ||||
| }) | ||||
|  |  | |||
|  | @ -5,92 +5,92 @@ describe("Peggy Types to Expression", () => { | |||
|   describe("type of", () => { | ||||
|     testToExpression( | ||||
|       "p: number", | ||||
|       "{(:$_typeOf_$ :p #number); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {p: #number}}", | ||||
|       "{(:$_typeOf_$ :p #number)}", | ||||
|       ~v="@{_typeReferences_: {p: #number}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("type alias", () => { | ||||
|     testToExpression( | ||||
|       "type index=number", | ||||
|       "{(:$_typeAlias_$ #index #number); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeAliases_: {index: #number}}", | ||||
|       "{(:$_typeAlias_$ #index #number)}", | ||||
|       ~v="@{_typeAliases_: {index: #number}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("type or", () => { | ||||
|     testToExpression( | ||||
|       "answer: number|string|distribution", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ #number #string #distribution))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution))))}", | ||||
|       ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("type function", () => { | ||||
|     testToExpression( | ||||
|       "f: number=>number=>number", | ||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ #number #number #number))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}", | ||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number))))}", | ||||
|       ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "f: number=>number", | ||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ #number #number))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}", | ||||
|       "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number))))}", | ||||
|       ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("high priority contract", () => { | ||||
|     testToExpression( | ||||
|       "answer: number<-min(1)<-max(100)|string", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}", | ||||
|       ~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-memberOf([1,3,5])", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ 1 3 5))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ (1 3 5))))}", | ||||
|       ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-min(1)", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1)); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1))}", | ||||
|       ~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-max(10)", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10)); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10))}", | ||||
|       ~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-min(1)<-max(10)", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10)); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10))}", | ||||
|       ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|     testToExpression( | ||||
|       "answer: number<-max(10)<-min(1)", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1)); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1))}", | ||||
|       ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("low priority contract", () => { | ||||
|     testToExpression( | ||||
|       "answer: number | string $ opaque", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ #number #string)))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}", | ||||
|       "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}", | ||||
|       ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}", | ||||
|       (), | ||||
|     ) | ||||
|   }) | ||||
|   describe("squiggle expressions in type contracts", () => { | ||||
|     testToExpression( | ||||
|       "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", | ||||
|       "{(:$_let_$ :odds1 {(:$_constructArray_$ 1 3 5)}); (:$_let_$ :odds2 {(:$_constructArray_$ 7 9)}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2))); (:$_endOfOuterBlock_$ () ())}", | ||||
|       // ~v="@{_typeAliases_: {odds: {memberOf: [1,3,5,7,9],typeIdentifier: #number,typeTag: 'typeIdentifier'}},odds1: [1,3,5],odds2: [7,9]}", | ||||
|       "{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}", | ||||
|       ~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", () => { | ||||
|   //literal | ||||
|   testToExpression("()", "()", ~v="()", ()) | ||||
|   testToExpression("()", "{()}", ~v="()", ()) | ||||
|   testToExpression( | ||||
|     "fn()=1", | ||||
|     "fn = {|_| {1}}", | ||||
|     // ~v="@{fn: lambda(_=>internal code)}", | ||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1}))}", | ||||
|     ~v="@{fn: lambda(_=>internal code)}", | ||||
|     (), | ||||
|   ) | ||||
|   testToExpression("fn()=1; fn()", "fn = {|_| {1}}; (fn)(())", ~v="1", ()) | ||||
|   testToExpression("fn()=1; fn()", "{(:$_let_$ :fn (:$$_lambda_$$ [_] {1})); (:fn ())}", ~v="1", ()) | ||||
|   testToExpression( | ||||
|     "fn(a)=(); call fn(1)", | ||||
|     "fn = {|a| {()}}; _ = {(fn)(1)}", | ||||
|     // ~v="@{_: (),fn: lambda(a=>internal code)}", | ||||
|     "{(:$_let_$ :fn (:$$_lambda_$$ [a] {()})); (:$_let_$ :_ {(:fn 1)})}", | ||||
|     ~v="@{_: (),fn: lambda(a=>internal code)}", | ||||
|     (), | ||||
|   ) | ||||
| }) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| module Expression = Reducer_Expression | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue | ||||
| module ErrorValue = Reducer_ErrorValue | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
|  | @ -7,22 +8,30 @@ open Expect | |||
| let unwrapRecord = rValue => | ||||
|   rValue->Belt.Result.flatMap(value => | ||||
|     switch value { | ||||
|     | Reducer_T.IEvRecord(aRecord) => Ok(aRecord) | ||||
|     | _ => SqError.Message.RETodo("TODO: Internal bindings must be returned")->Error | ||||
|     | ExternalExpressionValue.EvRecord(aRecord) => Ok(aRecord) | ||||
|     | _ => ErrorValue.RETodo("TODO: External bindings must be returned")->Error | ||||
|     } | ||||
|   ) | ||||
| 
 | ||||
| let expectParseToBe = (code: string, answer: string) => | ||||
|   Expression.BackCompatible.parse(code)->ExpressionT.toStringResult->expect->toBe(answer) | ||||
| let expectParseToBe = (expr: string, answer: string) => | ||||
|   Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer) | ||||
| 
 | ||||
| let expectEvalToBe = (code: string, answer: string) => | ||||
|   Expression.BackCompatible.evaluateString(code)->Reducer_Value.toStringResult->expect->toBe(answer) | ||||
| 
 | ||||
| let expectEvalError = (code: string) => | ||||
|   Expression.BackCompatible.evaluateString(code) | ||||
|   ->Reducer_Value.toStringResult | ||||
| let expectEvalToBe = (expr: string, answer: string) => | ||||
|   Reducer.evaluate(expr) | ||||
|   ->Reducer_Helpers.rRemoveDefaultsExternal | ||||
|   ->ExternalExpressionValue.toStringResult | ||||
|   ->expect | ||||
|   ->toMatch("Error\\(") | ||||
|   ->toBe(answer) | ||||
| 
 | ||||
| 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 testDescriptionParseToBe = (desc, expr, answer) => | ||||
|  | @ -31,12 +40,18 @@ let testDescriptionParseToBe = (desc, expr, answer) => | |||
| let testEvalError = expr => test(expr, () => expectEvalError(expr)) | ||||
| let testEvalToBe = (expr, answer) => test(expr, () => 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 = { | ||||
|   let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(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 = { | ||||
|   let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(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)) | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,88 @@ | |||
| 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) | ||||
| } | ||||
|  | @ -0,0 +1,52 @@ | |||
| module Expression = Reducer_Expression | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Bindings = Reducer_Bindings | ||||
| module T = Reducer_Type_T | ||||
| module TypeCompile = Reducer_Type_Compile | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| let myIevEval = (aTypeSourceCode: string) => | ||||
|   TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression) | ||||
| let myIevEvalToString = (aTypeSourceCode: string) => | ||||
|   myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult | ||||
| 
 | ||||
| let myIevExpectEqual = (aTypeSourceCode, answer) => | ||||
|   expect(myIevEvalToString(aTypeSourceCode))->toEqual(answer) | ||||
| 
 | ||||
| let myIevTest = (test, aTypeSourceCode, answer) => | ||||
|   test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer)) | ||||
| 
 | ||||
| let myTypeEval = (aTypeSourceCode: string) => | ||||
|   TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression) | ||||
| let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult | ||||
| 
 | ||||
| let myTypeExpectEqual = (aTypeSourceCode, answer) => | ||||
|   expect(myTypeEvalToString(aTypeSourceCode))->toEqual(answer) | ||||
| 
 | ||||
| let myTypeTest = (test, aTypeSourceCode, answer) => | ||||
|   test(aTypeSourceCode, () => myTypeExpectEqual(aTypeSourceCode, answer)) | ||||
| 
 | ||||
| //   | ItTypeIdentifier(string) | ||||
| myTypeTest(test, "number", "number") | ||||
| myTypeTest(test, "(number)", "number") | ||||
| //   | ItModifiedType({modifiedType: iType}) | ||||
| myIevTest(test, "number<-min(0)", "Ok({min: 0,typeIdentifier: #number,typeTag: 'typeIdentifier'})") | ||||
| myTypeTest(test, "number<-min(0)", "number<-min(0)") | ||||
| //   | ItTypeOr({typeOr: array<iType>}) | ||||
| myTypeTest(test, "number | string", "(number | string)") | ||||
| //   | ItTypeFunction({inputs: array<iType>, output: iType}) | ||||
| myTypeTest(test, "number => number => number", "(number => number => number)") | ||||
| //   | ItTypeArray({element: iType}) | ||||
| myIevTest(test, "[number]", "Ok({element: #number,typeTag: 'typeArray'})") | ||||
| myTypeTest(test, "[number]", "[number]") | ||||
| //   | ItTypeTuple({elements: array<iType>}) | ||||
| myTypeTest(test, "[number, string]", "[number, string]") | ||||
| //   | ItTypeRecord({properties: Belt.Map.String.t<iType>}) | ||||
| myIevTest( | ||||
|   test, | ||||
|   "{age: number, name: string}", | ||||
|   "Ok({properties: {age: #number,name: #string},typeTag: 'typeRecord'})", | ||||
| ) | ||||
| myTypeTest(test, "{age: number, name: string}", "{age: number, name: string}") | ||||
|  | @ -0,0 +1,41 @@ | |||
| module Expression = Reducer_Expression | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| module ErrorValue = Reducer_ErrorValue | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Bindings = Reducer_Bindings | ||||
| module T = Reducer_Type_T | ||||
| module TypeChecker = Reducer_Type_TypeChecker | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): result< | ||||
|   'v, | ||||
|   ErrorValue.t, | ||||
| > => { | ||||
|   let reducerFn = Expression.reduceExpression | ||||
|   let rResult = | ||||
|     Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => | ||||
|       reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) | ||||
|     ) | ||||
|   rResult->Belt.Result.flatMap(result => | ||||
|     switch result { | ||||
|     | IEvArray(args) => TypeChecker.checkArguments(aTypeSourceCode, args, reducerFn) | ||||
|     | _ => Js.Exn.raiseError("Arguments has to be an array") | ||||
|     } | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| let myCheckArguments = (aTypeSourceCode: string, sourceCode: string): string => | ||||
|   switch checkArgumentsSourceCode(aTypeSourceCode, sourceCode) { | ||||
|   | Ok(_) => "Ok" | ||||
|   | Error(error) => ErrorValue.errorToString(error) | ||||
|   } | ||||
| 
 | ||||
| let myCheckArgumentsExpectEqual = (aTypeSourceCode, sourceCode, answer) => | ||||
|   expect(myCheckArguments(aTypeSourceCode, sourceCode))->toEqual(answer) | ||||
| 
 | ||||
| let myCheckArgumentsTest = (test, aTypeSourceCode, sourceCode, answer) => | ||||
|   test(aTypeSourceCode, () => myCheckArgumentsExpectEqual(aTypeSourceCode, sourceCode, answer)) | ||||
| 
 | ||||
| myCheckArgumentsTest(test, "number=>number=>number", "[1,2]", "Ok") | ||||
|  | @ -0,0 +1,72 @@ | |||
| module Expression = Reducer_Expression | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| module ErrorValue = Reducer_ErrorValue | ||||
| module InternalExpressionValue = ReducerInterface_InternalExpressionValue | ||||
| module Bindings = Reducer_Bindings | ||||
| module T = Reducer_Type_T | ||||
| module TypeChecker = Reducer_Type_TypeChecker | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| // In development, you are expected to use TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn). | ||||
| // isTypeOfSourceCode is written to use strings instead of expression values. | ||||
| 
 | ||||
| let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result< | ||||
|   'v, | ||||
|   ErrorValue.t, | ||||
| > => { | ||||
|   let reducerFn = Expression.reduceExpression | ||||
|   let rResult = | ||||
|     Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => | ||||
|       reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) | ||||
|     ) | ||||
|   rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn)) | ||||
| } | ||||
| 
 | ||||
| let myTypeCheck = (aTypeSourceCode: string, sourceCode: string): string => | ||||
|   switch isTypeOfSourceCode(aTypeSourceCode, sourceCode) { | ||||
|   | Ok(_) => "Ok" | ||||
|   | Error(error) => ErrorValue.errorToString(error) | ||||
|   } | ||||
| 
 | ||||
| let myTypeCheckExpectEqual = (aTypeSourceCode, sourceCode, answer) => | ||||
|   expect(myTypeCheck(aTypeSourceCode, sourceCode))->toEqual(answer) | ||||
| 
 | ||||
| let myTypeCheckTest = (test, aTypeSourceCode, sourceCode, answer) => | ||||
|   test(aTypeSourceCode, () => myTypeCheckExpectEqual(aTypeSourceCode, sourceCode, answer)) | ||||
| 
 | ||||
| myTypeCheckTest(test, "number", "1", "Ok") | ||||
| myTypeCheckTest(test, "number", "'2'", "Expected type: number but got: '2'") | ||||
| myTypeCheckTest(test, "string", "3", "Expected type: string but got: 3") | ||||
| myTypeCheckTest(test, "string", "'a'", "Ok") | ||||
| myTypeCheckTest(test, "[number]", "[1,2,3]", "Ok") | ||||
| myTypeCheckTest(test, "[number]", "['a','a','a']", "Expected type: number but got: 'a'") | ||||
| myTypeCheckTest(test, "[number]", "[1,'a',3]", "Expected type: number but got: 'a'") | ||||
| myTypeCheckTest(test, "[number, string]", "[1,'a']", "Ok") | ||||
| myTypeCheckTest(test, "[number, string]", "[1, 2]", "Expected type: string but got: 2") | ||||
| myTypeCheckTest( | ||||
|   test, | ||||
|   "[number, string, string]", | ||||
|   "[1,'a']", | ||||
|   "Expected type: [number, string, string] but got: [1,'a']", | ||||
| ) | ||||
| myTypeCheckTest( | ||||
|   test, | ||||
|   "[number, string]", | ||||
|   "[1,'a', 3]", | ||||
|   "Expected type: [number, string] but got: [1,'a',3]", | ||||
| ) | ||||
| myTypeCheckTest(test, "{age: number, name: string}", "{age: 1, name: 'a'}", "Ok") | ||||
| myTypeCheckTest( | ||||
|   test, | ||||
|   "{age: number, name: string}", | ||||
|   "{age: 1, name: 'a', job: 'IT'}", | ||||
|   "Expected type: {age: number, name: string} but got: {age: 1,job: 'IT',name: 'a'}", | ||||
| ) | ||||
| myTypeCheckTest(test, "number | string", "1", "Ok") | ||||
| myTypeCheckTest(test, "date | string", "1", "Expected type: (date | string) but got: 1") | ||||
| myTypeCheckTest(test, "number<-min(10)", "10", "Ok") | ||||
| myTypeCheckTest(test, "number<-min(10)", "0", "Expected type: number<-min(10) but got: 0") | ||||
| myTypeCheckTest(test, "any", "0", "Ok") | ||||
| myTypeCheckTest(test, "any", "'a'", "Ok") | ||||
|  | @ -0,0 +1,123 @@ | |||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| module DispatchT = Reducer_Dispatch_T | ||||
| module Expression = Reducer_Expression | ||||
| module ExpressionT = Reducer_Expression_T | ||||
| module TypeCompile = Reducer_Type_Compile | ||||
| module TypeChecker = Reducer_Type_TypeChecker | ||||
| open ReducerInterface_InternalExpressionValue | ||||
| 
 | ||||
| type errorValue = Reducer_ErrorValue.errorValue | ||||
| 
 | ||||
| // Let's build a function to replace switch statements | ||||
| // In dispatchChainPiece, we execute an return the result of execution if there is a type match. | ||||
| // Otherwise we return None so that the call chain can continue. | ||||
| // So we want to build a function like | ||||
| // dispatchChainPiece = (call: functionCall, environment): option<result<internalExpressionValue, errorValue>> | ||||
| 
 | ||||
| // Now lets make the dispatchChainPiece itself. | ||||
| // Note that I am not passing the reducer to the dispatchChainPiece as an argument because it is in the context anyway. | ||||
| // Keep in mind that reducerFn is necessary for map/reduce so dispatchChainPiece should have a reducerFn in context. | ||||
| 
 | ||||
| let makeMyDispatchChainPiece = (reducer: ExpressionT.reducerFn): DispatchT.dispatchChainPiece => { | ||||
|   // Let's have a pure implementations | ||||
|   module Implementation = { | ||||
|     let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b) | ||||
|     let arrayConcat = ( | ||||
|       a: Js.Array2.t<internalExpressionValue>, | ||||
|       b: Js.Array2.t<internalExpressionValue>, | ||||
|     ): Js.Array2.t<internalExpressionValue> => Js.Array2.concat(a, b) | ||||
|     let plot = _r => "yey, plotted" | ||||
|   } | ||||
| 
 | ||||
|   let extractStringString = args => | ||||
|     switch args { | ||||
|     | [IEvString(a), IEvString(b)] => (a, b) | ||||
|     | _ => raise(Reducer_Exception.ImpossibleException("extractStringString developer error")) | ||||
|     } | ||||
| 
 | ||||
|   let extractArrayArray = args => | ||||
|     switch args { | ||||
|     | [IEvArray(a), IEvArray(b)] => (a, b) | ||||
|     | _ => raise(Reducer_Exception.ImpossibleException("extractArrayArray developer error")) | ||||
|     } | ||||
| 
 | ||||
|   // Let's bridge the pure implementation to expression values | ||||
|   module Bridge = { | ||||
|     let stringConcat: DispatchT.genericIEvFunction = (args, _environment) => { | ||||
|       let (a, b) = extractStringString(args) | ||||
|       Implementation.stringConcat(a, b)->IEvString->Ok | ||||
|     } | ||||
|     let arrayConcat: DispatchT.genericIEvFunction = (args, _environment) => { | ||||
|       let (a, b) = extractArrayArray(args) | ||||
|       Implementation.arrayConcat(a, b)->IEvArray->Ok | ||||
|     } | ||||
|     let plot: DispatchT.genericIEvFunction = (args, _environment) => { | ||||
|       switch args { | ||||
|       // Just assume that we are doing the business of extracting and converting the deep record | ||||
|       | [IEvRecord(_)] => Implementation.plot({"title": "This is a plot"})->IEvString->Ok | ||||
|       | _ => raise(Reducer_Exception.ImpossibleException("plot developer error")) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // concat functions are to illustrate polymoprhism. And the plot function is to illustrate complex types | ||||
|   let jumpTable = [ | ||||
|     ( | ||||
|       "concat", | ||||
|       TypeCompile.fromTypeExpressionExn("string=>string=>string", reducer), | ||||
|       Bridge.stringConcat, | ||||
|     ), | ||||
|     ( | ||||
|       "concat", | ||||
|       TypeCompile.fromTypeExpressionExn("[any]=>[any]=>[any]", reducer), | ||||
|       Bridge.arrayConcat, | ||||
|     ), | ||||
|     ( | ||||
|       "plot", | ||||
|       TypeCompile.fromTypeExpressionExn( | ||||
|         // Nested complex types are available | ||||
|         // records {property: type} | ||||
|         // arrays [type] | ||||
|         // tuples [type, type] | ||||
|         // <- type contracts are available naturally and they become part of dispatching | ||||
|         // Here we are not enumerating the possibilities because type checking has a dedicated test | ||||
|         "{title: string, line: {width: number, color: string}}=>string", | ||||
|         reducer, | ||||
|       ), | ||||
|       Bridge.plot, | ||||
|     ), | ||||
|   ] | ||||
| 
 | ||||
|   //Here we are creating a dispatchChainPiece function that will do the actual dispatch from the jumpTable | ||||
|   Reducer_Dispatch_ChainPiece.makeFromTypes(jumpTable) | ||||
| } | ||||
| 
 | ||||
| // And finally, let's write a library dispatch for our external library | ||||
| // Exactly the same as the one used in real life | ||||
| let _dispatch = ( | ||||
|   call: functionCall, | ||||
|   environment, | ||||
|   reducer: Reducer_Expression_T.reducerFn, | ||||
|   chain, | ||||
| ): result<internalExpressionValue, 'e> => { | ||||
|   let dispatchChainPiece = makeMyDispatchChainPiece(reducer) | ||||
|   dispatchChainPiece(call, environment)->E.O2.defaultFn(() => chain(call, environment, reducer)) | ||||
| } | ||||
| 
 | ||||
| // What is important about this implementation? | ||||
| // A) Exactly the same function jump table can be used to create type guarded lambda functions | ||||
| // Guarded lambda functions will be the basis of the next version of Squiggle | ||||
| // B) Complicated recursive record types are not a problem. | ||||
| 
 | ||||
| describe("Type Dispatch", () => { | ||||
|   let reducerFn = Expression.reduceExpression | ||||
|   let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn) | ||||
|   test("stringConcat", () => { | ||||
|     let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")]) | ||||
| 
 | ||||
|     let result = dispatchChainPiece(call, defaultEnvironment) | ||||
|     expect(result)->toEqual(Some(Ok(IEvString("helloworld")))) | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,14 +0,0 @@ | |||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| describe("ExpressionValue", () => { | ||||
|   test("argsToString", () => | ||||
|     expect([IEvNumber(1.), IEvString("a")]->Reducer_Value.argsToString)->toBe("1,'a'") | ||||
|   ) | ||||
| 
 | ||||
|   test("toStringFunctionCall", () => | ||||
|     expect(("fn", [IEvNumber(1.), IEvString("a")])->Reducer_Value.toStringFunctionCall)->toBe( | ||||
|       "fn(1,'a')", | ||||
|     ) | ||||
|   ) | ||||
| }) | ||||
|  | @ -1,13 +0,0 @@ | |||
| open Jest | ||||
| open Reducer_Peggy_TestHelpers | ||||
| 
 | ||||
| describe("Construct Array", () => { | ||||
|   testToExpression("[1,2]", "[1, 2]", ~v="[1,2]", ()) | ||||
|   testToExpression("[]", "[]", ~v="[]", ()) | ||||
|   testToExpression( | ||||
|     "f(x)=x; g(x)=x; [f, g]", | ||||
|     "f = {|x| {x}}; g = {|x| {x}}; [f, g]", | ||||
|     ~v="[lambda(x=>internal code),lambda(x=>internal code)]", | ||||
|     (), | ||||
|   ) | ||||
| }) | ||||
|  | @ -0,0 +1,14 @@ | |||
| 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,15 +2,11 @@ open Jest | |||
| open Reducer_TestHelpers | ||||
| 
 | ||||
| describe("Parse function assignment", () => { | ||||
|   testParseToBe("f(x)=x", "Ok(f = {|x| {x}})") | ||||
|   testParseToBe("f(x)=2*x", "Ok(f = {|x| {(multiply)(2, x)}})") | ||||
|   testParseToBe("f(x)=x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))})") | ||||
|   testParseToBe("f(x)=2*x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)}))})") | ||||
|   //MathJs does not allow blocks in function definitions | ||||
| }) | ||||
| 
 | ||||
| describe("Evaluate function assignment", () => { | ||||
|   testEvalToBe("f(x)=x; f(1)", "Ok(1)") | ||||
| }) | ||||
| 
 | ||||
| describe("Shadowing", () => { | ||||
|   testEvalToBe("x = 5; f(y) = x*y; x = 6; f(2)", "Ok(10)") | ||||
| }) | ||||
|  |  | |||
|  | @ -34,29 +34,38 @@ describe("symbol not defined", () => { | |||
|   testEvalToBe("f(x)=x(y); f(f)", "Error(y is not defined)") | ||||
|   testEvalToBe("f(x)=x; f(f)", "Ok(lambda(x=>internal code))") | ||||
|   testEvalToBe("f(x)=x(y); f(z)", "Error(z is not defined)") | ||||
|   testEvalToBe("f(x)=x(y); f(2)", "Error(y is not defined)") | ||||
|   testEvalToBe("f(x)=x(y); f(2)", "Error(2 is not a function)") | ||||
|   testEvalToBe("f(x)=x(1); f(2)", "Error(2 is not a function)") | ||||
| }) | ||||
| 
 | ||||
| describe("call and bindings", () => { | ||||
|   testEvalToBe("f(x)=x+1; f(0)", "Ok(1)") | ||||
|   testEvalToBe("f(x)=x+1", "Ok(@{f: lambda(x=>internal code)})") | ||||
|   testEvalToBe("f(x)=x+1; f(1)", "Ok(2)") | ||||
|   testEvalToBe("f=1;y=2", "Ok(())") | ||||
|   testEvalToBe("f(x)=x+1; y=f(1); y", "Ok(2)") | ||||
|   testEvalToBe("f=1;y=2", "Ok(@{f: 1,y: 2})") | ||||
|   testEvalToBe("f(x)=x+1; y=f(1)", "Ok(@{f: lambda(x=>internal code),y: 2})") | ||||
|   testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)") | ||||
|   testEvalToBe("f(x)=x+1; y=f(1); z=f(1); z", "Ok(2)") | ||||
|   testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(0)", "Ok(2)") | ||||
|   testParseToBe("f=99; g(x)=f; g(2)", "Ok(f = {99}; g = {|x| {f}}; (g)(2))") | ||||
|   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; g(x)=f(x)+1", | ||||
|     "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(x)=x; g(x)=f(x); g(2)", "Ok(2)") | ||||
|   testEvalToBe("f(x)=x+1; g(x)=f(x)+1; y=g(2); y", "Ok(4)") | ||||
|   testEvalToBe( | ||||
|     "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)") | ||||
| }) | ||||
| 
 | ||||
| describe("function tricks", () => { | ||||
|   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)=inspect(y)+1;y", "Ok(2)") | ||||
|   testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok(@{g: lambda(x=>internal code),y: 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+x; f(1,2)", "????") // TODO: Duplicate parameters | ||||
|   testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))") | ||||
|  | @ -64,7 +73,10 @@ describe("function tricks", () => { | |||
| }) | ||||
| 
 | ||||
| describe("lambda in structures", () => { | ||||
|   testEvalToBe("myadd(x,y)=x+y; z=[myadd]", "Ok(())") | ||||
|   testEvalToBe( | ||||
|     "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](3,2)", "Ok(5)") | ||||
|   testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})") | ||||
|  |  | |||
|  | @ -5,7 +5,3 @@ Skip.describe("map reduce (sam)", () => { | |||
|   testEvalToBe("addone(x)=x+1; map(2, addone)", "Error???") | ||||
|   testEvalToBe("addone(x)=x+1; map(2, {x: addone})", "Error???") | ||||
| }) | ||||
| 
 | ||||
| describe("map", () => { | ||||
|   testEvalToBe("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])") | ||||
| }) | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ open Jest | |||
| open Reducer_TestHelpers | ||||
| 
 | ||||
| describe("Parse ternary operator", () => { | ||||
|   testParseToBe("true ? 'YES' : 'NO'", "Ok(true ? ('YES') : ('NO'))") | ||||
|   testParseToBe("true ? 'YES' : 'NO'", "Ok({(:$$_ternary_$$ true 'YES' 'NO')})") | ||||
| }) | ||||
| 
 | ||||
| describe("Evaluate ternary operator", () => { | ||||
|  |  | |||
|  | @ -2,29 +2,19 @@ open Jest | |||
| open Reducer_TestHelpers | ||||
| 
 | ||||
| describe("eval", () => { | ||||
|   // All MathJs operators and functions are builtin for string, float and boolean | ||||
|   // .e.g + - / * > >= < <= == /= not and or | ||||
|   // See https://mathjs.org/docs/reference/functions.html | ||||
|   describe("expressions", () => { | ||||
|     testEvalToBe("1", "Ok(1)") | ||||
|     testEvalToBe("-1", "Ok(-1)") | ||||
|     testEvalToBe("1-1", "Ok(0)") | ||||
|     testEvalToBe("1+2", "Ok(3)") | ||||
|     testEvalToBe("(1+2)*3", "Ok(9)") | ||||
|     testEvalToBe("2>1", "Ok(true)") | ||||
|     testEvalToBe("concat('a ', 'b')", "Ok('a b')") | ||||
|     testEvalToBe("concat([3,4], [5,6,7])", "Ok([3,4,5,6,7])") | ||||
|     testEvalToBe("log(10)", "Ok(2.302585092994046)") | ||||
|     testEvalToBe("Math.cos(10)", "Ok(-0.8390715290764524)") | ||||
|     testEvalToBe("cos(10)", "Ok(-0.8390715290764524)") | ||||
|     // TODO more built ins | ||||
|   }) | ||||
| 
 | ||||
|   describe("missing function", () => { | ||||
|     testEvalToBe("testZadanga(1)", "Error(testZadanga is not defined)") | ||||
| 
 | ||||
|     testEvalToBe( | ||||
|       "arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)", | ||||
|       "Error(zarathsuzaWasHere is not defined)", | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("arrays", () => { | ||||
|     test("empty array", () => expectEvalToBe("[]", "Ok([])")) | ||||
|     testEvalToBe("[1, 2, 3]", "Ok([1,2,3])") | ||||
|  | @ -37,16 +27,14 @@ describe("eval", () => { | |||
|     test("index", () => expectEvalToBe("r = {a: 1}; r.a", "Ok(1)")) | ||||
|     test("index", () => expectEvalToBe("r = {a: 1}; r.b", "Error(Record property not found: b)")) | ||||
|     testEvalError("{a: 1}.b") // invalid syntax | ||||
|     test( | ||||
|       "always the same property ending", | ||||
|       () => | ||||
|     test("always the same property ending", () => | ||||
|       expectEvalToBe( | ||||
|         `{ | ||||
|       a: 1,  | ||||
|       b: 2, | ||||
|     }`, | ||||
|         "Ok({a: 1,b: 2})", | ||||
|         ), | ||||
|       ) | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|  | @ -60,11 +48,7 @@ describe("eval", () => { | |||
|     testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") | ||||
|     testEvalError("1; x=1") | ||||
|     testEvalError("1; 1") | ||||
|     testEvalToBe("x=1; x=1; x", "Ok(1)") | ||||
|   }) | ||||
| 
 | ||||
|   describe("blocks", () => { | ||||
|     testEvalToBe("x = { y = { z = 5; z * 2 }; y + 3 }; x", "Ok(13)") | ||||
|     testEvalToBe("x=1; x=1", "Ok(@{x: 1})") | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
|  | @ -80,33 +64,3 @@ describe("test exceptions", () => { | |||
|   //   "Error(TODO: unhandled rescript exception)", | ||||
|   // ) | ||||
| }) | ||||
| 
 | ||||
| describe("stacktraces", () => { | ||||
|   test("nested calls", () => { | ||||
|     open Expect | ||||
| 
 | ||||
|     let error = | ||||
|       Expression.BackCompatible.evaluateString(` | ||||
|   f(x) = { | ||||
|     y = "a" | ||||
|     x + y | ||||
|   } | ||||
|   g = {|x| f(x)} | ||||
|   h(x) = g(x) | ||||
|   h(5) | ||||
| `) | ||||
|       ->E.R.getError | ||||
|       ->E.O2.toExn("oops") | ||||
|       ->SqError.toStringWithStackTrace | ||||
| 
 | ||||
|     expect( | ||||
|       error, | ||||
|     )->toBe(`Error: There are function matches for add(), but with different arguments: [add(number, number)]; [add(distribution, number)]; [add(number, distribution)]; [add(distribution, distribution)]; [add(date, duration)]; [add(duration, duration)] | ||||
| Stack trace: | ||||
|   f at line 4, column 5 | ||||
|   g at line 6, column 12 | ||||
|   h at line 7, column 10 | ||||
|   <top> at line 8, column 3 | ||||
| `) | ||||
|   }) | ||||
| }) | ||||
|  |  | |||
|  | @ -23,7 +23,7 @@ describe("eval on distribution functions", () => { | |||
|     testEval("-normal(5,2)", "Ok(Normal(-5,2))") | ||||
|   }) | ||||
|   describe("to", () => { | ||||
|     testEval("5 to 2", "Error(Error: Low value must be less than high value.)") | ||||
|     testEval("5 to 2", "Error(TODO: Low value must be less than high value.)") | ||||
|     testEval("to(2,5)", "Ok(Lognormal(1.1512925464970227,0.27853260523016377))") | ||||
|     testEval("to(-2,2)", "Ok(Normal(0,1.2159136638235384))") | ||||
|   }) | ||||
|  | @ -98,7 +98,6 @@ describe("eval on distribution functions", () => { | |||
|       "log(normal(5,2), normal(10,1))", | ||||
|       "Error(Distribution Math Error: Logarithm of input error: First input must be completely greater than 0)", | ||||
|     ) | ||||
|     testEval("log(2, SampleSet.fromDist(0.0001 to 5))", "Ok(Sample Set Distribution)") // log with low values, see https://github.com/quantified-uncertainty/squiggle/issues/1098 | ||||
|     testEval("log(uniform(5,8))", "Ok(Sample Set Distribution)") | ||||
|     testEval("log10(uniform(5,8))", "Ok(Sample Set Distribution)") | ||||
|   }) | ||||
|  | @ -120,13 +119,13 @@ describe("eval on distribution functions", () => { | |||
| 
 | ||||
| describe("parse on distribution functions", () => { | ||||
|   describe("power", () => { | ||||
|     testParse("normal(5,2) ^ normal(5,1)", "Ok((pow)((normal)(5, 2), (normal)(5, 1)))") | ||||
|     testParse("3 ^ normal(5,1)", "Ok((pow)(3, (normal)(5, 1)))") | ||||
|     testParse("normal(5,2) ^ 3", "Ok((pow)((normal)(5, 2), 3))") | ||||
|     testParse("normal(5,2) ^ normal(5,1)", "Ok({(:pow (:normal 5 2) (:normal 5 1))})") | ||||
|     testParse("3 ^ normal(5,1)", "Ok({(:pow 3 (:normal 5 1))})") | ||||
|     testParse("normal(5,2) ^ 3", "Ok({(:pow (:normal 5 2) 3)})") | ||||
|   }) | ||||
|   describe("subtraction", () => { | ||||
|     testParse("10 - normal(5,1)", "Ok((subtract)(10, (normal)(5, 1)))") | ||||
|     testParse("normal(5,1) - 10", "Ok((subtract)((normal)(5, 1), 10))") | ||||
|     testParse("10 - normal(5,1)", "Ok({(:subtract 10 (:normal 5 1))})") | ||||
|     testParse("normal(5,1) - 10", "Ok({(:subtract (:normal 5 1) 10)})") | ||||
|   }) | ||||
|   describe("pointwise arithmetic expressions", () => { | ||||
|     testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") | ||||
|  | @ -136,12 +135,12 @@ describe("parse on distribution functions", () => { | |||
|       "Ok((:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1))))", | ||||
|       // TODO: !!! returns "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})" | ||||
|     ) | ||||
|     testParse("normal(5,2) .* normal(5,1)", "Ok((dotMultiply)((normal)(5, 2), (normal)(5, 1)))") | ||||
|     testParse("normal(5,2) ./ normal(5,1)", "Ok((dotDivide)((normal)(5, 2), (normal)(5, 1)))") | ||||
|     testParse("normal(5,2) .^ normal(5,1)", "Ok((dotPow)((normal)(5, 2), (normal)(5, 1)))") | ||||
|     testParse("normal(5,2) .* normal(5,1)", "Ok({(:dotMultiply (:normal 5 2) (:normal 5 1))})") | ||||
|     testParse("normal(5,2) ./ normal(5,1)", "Ok({(:dotDivide (:normal 5 2) (:normal 5 1))})") | ||||
|     testParse("normal(5,2) .^ normal(5,1)", "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})") | ||||
|   }) | ||||
|   describe("equality", () => { | ||||
|     testParse("5 == normal(5,2)", "Ok((equal)(5, (normal)(5, 2)))") | ||||
|     testParse("5 == normal(5,2)", "Ok({(:equal 5 (:normal 5 2))})") | ||||
|   }) | ||||
|   describe("pointwise adding two normals", () => { | ||||
|     testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") | ||||
|  | @ -0,0 +1,11 @@ | |||
| open ReducerInterface.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')") | ||||
|   ) | ||||
| }) | ||||
|  | @ -1,36 +0,0 @@ | |||
| @@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"] | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,111 +0,0 @@ | |||
| @@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")] | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,201 +0,0 @@ | |||
| @@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"] | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,116 +0,0 @@ | |||
| @@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  | ||||
|  | @ -1,113 +0,0 @@ | |||
| @@warning("-44") | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   describe("Multi source", () => { | ||||
|     /* | ||||
|      Case "Running multiple sources" */ | ||||
|     test( | ||||
|       "Chaining", | ||||
|       () => { | ||||
|         let project = Project.createProject() | ||||
|         /* This time let's add 3 sources and chain them together */ | ||||
|         project->Project.setSource("source1", "x=1") | ||||
| 
 | ||||
|         project->Project.setSource("source2", "y=x+1") | ||||
|         /* To run, source2 depends on source1 */ | ||||
|         project->Project.setContinues("source2", ["source1"]) | ||||
| 
 | ||||
|         project->Project.setSource("source3", "z=y+1") | ||||
|         /* To run, source3 depends on source2 */ | ||||
|         project->Project.setContinues("source3", ["source2"]) | ||||
| 
 | ||||
|         /* Now we can run the project */ | ||||
|         project->Project.runAll | ||||
| 
 | ||||
|         /* And let's check the result and bindings of source3 */ | ||||
|         let result3 = project->Project.getResult("source3") | ||||
|         let bindings3 = project->Project.getBindings("source3") | ||||
| 
 | ||||
|         (result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect == | ||||
|           ("Ok(())", "{z: 3}") | ||||
|       }, | ||||
|     ) | ||||
| 
 | ||||
|     test( | ||||
|       "Depending", | ||||
|       () => { | ||||
|         /* Instead of chaining the sources, we could have a dependency tree */ | ||||
|         /* The point here is that any source can depend on multiple sources */ | ||||
|         let project = Project.createProject() | ||||
| 
 | ||||
|         /* This time source1 and source2 are not depending on anything */ | ||||
|         project->Project.setSource("source1", "x=1") | ||||
|         project->Project.setSource("source2", "y=2") | ||||
| 
 | ||||
|         project->Project.setSource("source3", "z=x+y") | ||||
|         /* To run, source3 depends on source1 and source3 together */ | ||||
|         project->Project.setContinues("source3", ["source1", "source2"]) | ||||
| 
 | ||||
|         /* Now we can run the project */ | ||||
|         project->Project.runAll | ||||
| 
 | ||||
|         /* And let's check the result and bindings of source3 */ | ||||
|         let result3 = project->Project.getResult("source3") | ||||
|         let bindings3 = project->Project.getBindings("source3") | ||||
| 
 | ||||
|         (result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect == | ||||
|           ("Ok(())", "{z: 3}") | ||||
|       }, | ||||
|     ) | ||||
| 
 | ||||
|     test( | ||||
|       "Intro to including", | ||||
|       () => { | ||||
|         /* Though it would not be practical for a storybook,  | ||||
|         let's write the same project above with includes. | ||||
|         You will see that parsing includes is setting the dependencies the same way as before. */ | ||||
|         let project = Project.createProject() | ||||
| 
 | ||||
|         /* This time source1 and source2 are not depending on anything */ | ||||
|         project->Project.setSource("source1", "x=1") | ||||
|         project->Project.setSource("source2", "y=2") | ||||
| 
 | ||||
|         project->Project.setSource( | ||||
|           "source3", | ||||
|           ` | ||||
|       #include "source1" | ||||
|       #include "source2" | ||||
|       z=x+y`, | ||||
|         ) | ||||
|         /* We need to parse the includes to set the dependencies */ | ||||
|         project->Project.parseIncludes("source3") | ||||
| 
 | ||||
|         /* Now we can run the project */ | ||||
|         project->Project.runAll | ||||
| 
 | ||||
|         /* And let's check the result and bindings of source3  | ||||
|       This time you are getting all the variables because we are including the other sources  | ||||
|       Behind the scenes parseIncludes is setting the dependencies */ | ||||
|         let result3 = project->Project.getResult("source3") | ||||
|         let bindings3 = project->Project.getBindings("source3") | ||||
| 
 | ||||
|         (result3->Reducer_Value.toStringResult, bindings3->Reducer_Value.toStringRecord)->expect == | ||||
|           ("Ok(())", "{z: 3}") | ||||
|         /* | ||||
|       Doing it like this is too verbose for a storybook  | ||||
|       But I hope you have seen the relation of setContinues and parseIncludes */ | ||||
|         /* | ||||
|          Dealing with includes needs more.  | ||||
|          - There are parse errors | ||||
|          - There are cyclic includes | ||||
|          - And the depended source1 and source2 is not already there in the project | ||||
|          - If you knew the includes before hand there would not be point of the include directive. | ||||
|          More on those on the next section. */ | ||||
|       }, | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,201 +0,0 @@ | |||
| @@warning("-44") | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   /* Case: Includes | ||||
| In the previous tutorial we have set the similarity between setContinues and parseIncludes. | ||||
| Here we will finally proceed to a real life scenario. */ | ||||
| 
 | ||||
|   describe("parseIncludes", () => { | ||||
|     /* Here we investigate the details about parseIncludes, before setting up a real life scenario in the next section. */ | ||||
|     /* Everything happens inside a project, so let's have a project */ | ||||
|     let project = Project.createProject() | ||||
|     project->Project.setSource( | ||||
|       "main", | ||||
|       ` | ||||
|     #include "common" | ||||
|     x=1 | ||||
|     `, | ||||
|     ) | ||||
|     /* We need to parse includes after changing the source */ | ||||
|     project->Project.parseIncludes("main") | ||||
|     test( | ||||
|       "getDependencies", | ||||
|       () => { | ||||
|         /* Parse includes has set the dependencies */ | ||||
|         project->Project.getDependencies("main")->expect == ["common"] | ||||
|         /* If there were no includes than there would be no dependencies */ | ||||
|         /* However if there was a syntax error at includes then would be no dependencies also */ | ||||
|         /* Therefore looking at dependencies is not the right way to load includes */ | ||||
|         /* getDependencies does not distinguish between setContinues or parseIncludes */ | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "getIncludes", | ||||
|       () => { | ||||
|         /* Parse includes has set the includes */ | ||||
|         switch project->Project.getIncludes("main") { | ||||
|         | Ok(includes) => includes->expect == ["common"] | ||||
|         | Error(err) => err->SqError.toString->fail | ||||
|         } | ||||
|         /* If the includes cannot be parsed then you get a syntax error. | ||||
|       Otherwise you get the includes. | ||||
|       If there is no syntax error then you can load that file and use setSource to add it to the project. | ||||
|       And so on recursively... */ | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "getDependents", | ||||
|       () => { | ||||
|         /* For any reason, you are able to query what other sources | ||||
|         include or depend on the current source. | ||||
|         But you don't need to use this to execute the projects. | ||||
|         It is provided for completeness of information. */ | ||||
|         project->Project.getDependents("main")->expect == [] | ||||
|         /* Nothing is depending on or including main */ | ||||
|       }, | ||||
|     ) | ||||
| 
 | ||||
|     describe( | ||||
|       "Real Like", | ||||
|       () => { | ||||
|         /* Now let's look at recursive and possibly cyclic includes */ | ||||
|         /* There is no function provided to load the include files. | ||||
|     Because we have no idea if will it be an ordinary function or will it use promises. | ||||
|     Therefore one has to write a function to load sources recursively and and setSources | ||||
|     while checking for dependencies */ | ||||
| 
 | ||||
|         /* Let's make a dummy loader */ | ||||
|         let loadSource = (sourceName: string) => | ||||
|           switch sourceName { | ||||
|           | "source1" => "x=1" | ||||
|           | "source2" => ` | ||||
|             #include "source1" | ||||
|             y=2` | ||||
|           | "source3" => ` | ||||
|             #include "source2" | ||||
|             z=3` | ||||
|           | _ => `source ${sourceName} not found`->Js.Exn.raiseError | ||||
|           } | ||||
| 
 | ||||
|         /* let's recursively load the sources */ | ||||
|         let rec loadIncludesRecursively = (project, sourceName, visited) => { | ||||
|           if visited->Js.Array2.includes(sourceName) { | ||||
|             /* Oh we have already visited this source. There is an include cycle */ | ||||
|             "Cyclic include ${sourceName}"->Js.Exn.raiseError | ||||
|           } else { | ||||
|             let newVisited = Js.Array2.copy(visited) | ||||
|             let _ = newVisited->Js.Array2.push(sourceName) | ||||
|             /* Let's parse the includes and dive into them */ | ||||
|             Project.parseIncludes(project, sourceName) | ||||
|             let rIncludes = project->Project.getIncludes(sourceName) | ||||
|             switch rIncludes { | ||||
|             /* Maybe there is an include syntax error */ | ||||
|             | Error(err) => err->SqError.toString->Js.Exn.raiseError | ||||
| 
 | ||||
|             | Ok(includes) => | ||||
|               includes->Belt.Array.forEach( | ||||
|                 newIncludeName => { | ||||
|                   /* We have got one of the new includes. | ||||
|                    Let's load it and add it to the project */ | ||||
|                   let newSource = loadSource(newIncludeName) | ||||
|                   project->Project.setSource(newIncludeName, newSource) | ||||
|                   /* The new source is loaded and added to the project. */ | ||||
|                   /* Of course the new source might have includes too. */ | ||||
|                   /* Let's recursively load them */ | ||||
|                   project->loadIncludesRecursively(newIncludeName, newVisited) | ||||
|                 }, | ||||
|               ) | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         /* As we have a fake source loader and a recursive include handler, | ||||
|          We can not set up a real project */ | ||||
| 
 | ||||
|         /* * Here starts our real life project! * */ | ||||
| 
 | ||||
|         let project = Project.createProject() | ||||
| 
 | ||||
|         project->Project.setSource( | ||||
|           "main", | ||||
|           ` | ||||
|         #include "source1" | ||||
|         #include "source2" | ||||
|         #include "source3" | ||||
|         a = x+y+z | ||||
|         b = doubleX | ||||
|         a | ||||
|         `, | ||||
|         ) | ||||
|         /* Setting source requires parsing and loading the includes recursively */ | ||||
|         project->loadIncludesRecursively("main", []) // Not visited yet | ||||
| 
 | ||||
|         /* Let's salt it more. Let's have another source in the project which also has includes */ | ||||
|         /* doubleX includes source1 which is eventually included by main as well */ | ||||
|         project->Project.setSource( | ||||
|           "doubleX", | ||||
|           ` | ||||
|         #include "source1" | ||||
|         doubleX = x * 2 | ||||
|         `, | ||||
|         ) | ||||
|         project->loadIncludesRecursively("doubleX", []) | ||||
|         /* Remember, any time you set a source, you need to load includes recursively */ | ||||
| 
 | ||||
|         /* As doubleX is not included by main, it is not loaded recursively. | ||||
|          So we link it to the project as a dependency */ | ||||
|         project->Project.setContinues("main", ["doubleX"]) | ||||
| 
 | ||||
|         /* Let's run the project */ | ||||
|         project->Project.runAll | ||||
|         let result = project->Project.getResult("main") | ||||
|         let bindings = project->Project.getBindings("main") | ||||
|         /* And see the result and bindings.. */ | ||||
|         test( | ||||
|           "recursive includes", | ||||
|           () => { | ||||
|             ( | ||||
|               result->Reducer_Value.toStringResult, | ||||
|               bindings->Reducer_Value.toStringRecord, | ||||
|             )->expect == ("Ok(6)", "{a: 6,b: 2}") | ||||
|             /* Everything as expected */ | ||||
|           }, | ||||
|         ) | ||||
|       }, | ||||
|     ) | ||||
|   }) | ||||
| 
 | ||||
|   describe("Includes myFile as myVariable", () => { | ||||
|     /* Instead of including into global space you can also put a module into a record variable */ | ||||
|     let project = Project.createProject() | ||||
|     Project.setSource( | ||||
|       project, | ||||
|       "main", | ||||
|       ` | ||||
|     #include "common" as common | ||||
|     x=1 | ||||
|     `, | ||||
|     ) | ||||
|     Project.parseIncludes(project, "main") | ||||
|     test( | ||||
|       "getDependencies", | ||||
|       () => { | ||||
|         Project.getDependencies(project, "main")->expect == ["common"] | ||||
|       }, | ||||
|     ) | ||||
|     test( | ||||
|       "getIncludes", | ||||
|       () => { | ||||
|         switch Project.getIncludes(project, "main") { | ||||
|         | Ok(includes) => includes->expect == ["common"] | ||||
|         | Error(err) => err->SqError.toString->fail | ||||
|         } | ||||
|       }, | ||||
|     ) | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,37 +0,0 @@ | |||
| @@warning("-44") | ||||
| module Project = ForTS_ReducerProject | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   /* Let's build a project that depends on values from the UI */ | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "main", "x+y+z") | ||||
|   /* x, y and z is not defined in the project but they has to come from the user */ | ||||
|   test("Injecting user values", () => { | ||||
|     /* User has input the values */ | ||||
|     let x = 1 | ||||
|     let y = 2 | ||||
|     let z = 3 | ||||
|     /* Then we construct a source code to define those values */ | ||||
|     let userCode = ` | ||||
|       x = ${x->Js.Int.toString} | ||||
|       y = ${y->Js.Int.toString} | ||||
|       z = ${z->Js.Int.toString} | ||||
|     ` | ||||
|     /* We inject the user code into the project */ | ||||
|     Project.setSource(project, "userCode", userCode) | ||||
|     /* "main" is depending on the user code */ | ||||
|     Project.setContinues(project, "main", ["userCode"]) | ||||
|     /* We can now run the project */ | ||||
|     Project.runAll(project) | ||||
|     let result = Project.getResult(project, "main") | ||||
|     result->Reducer_Value.toStringResult->expect == "Ok(6)" | ||||
|   }) | ||||
| }) | ||||
| 
 | ||||
| /* Note that this is not final version of the project */ | ||||
| /* In the future, for safety, we will provide a way to inject values instead of a source code */ | ||||
| /* But time is limited for now... */ | ||||
|  | @ -1,39 +0,0 @@ | |||
| @@warning("-44") | ||||
| module Project = ForTS_ReducerProject | ||||
| module Bindings = Reducer_Bindings | ||||
| 
 | ||||
| open Jest | ||||
| open Expect | ||||
| open Expect.Operators | ||||
| 
 | ||||
| describe("ReducerProject Tutorial", () => { | ||||
|   /* Let's build a project to provide a function. */ | ||||
|   /* But we will call that function on an array of user input. */ | ||||
|   let project = Project.createProject() | ||||
|   Project.setSource(project, "library", "double(x) = x * 2") | ||||
|   /* userCode is not here yet but its dependency is fixed. So we can set it once and for all */ | ||||
|   Project.setContinues(project, "userCode", ["library"]) | ||||
| 
 | ||||
|   let userValues = [1, 2, 3, 4, 5] | ||||
| 
 | ||||
|   let userResults = Belt.Array.map(userValues, aUserValue => { | ||||
|     let userCode = `double(${aUserValue->Js.Int.toString})` | ||||
|     /* Put the constructed source in the project */ | ||||
|     /* We have already set that it depends on "library" */ | ||||
|     Project.setSource(project, "userCode", userCode) | ||||
|     /* Run the project */ | ||||
|     Project.runAll(project) | ||||
|     /* Get the result */ | ||||
|     Project.getResult(project, "userCode") | ||||
|     /* I have to remind you that the "library" is run only once and for all. | ||||
|      The library is not run for each user value. */ | ||||
|   }) | ||||
| 
 | ||||
|   test("userResults", () => { | ||||
|     let userResultsAsString = Belt.Array.map( | ||||
|       userResults, | ||||
|       aResult => aResult->Reducer_Value.toStringResult, | ||||
|     ) | ||||
|     userResultsAsString->expect == ["Ok(2)", "Ok(4)", "Ok(6)", "Ok(8)", "Ok(10)"] | ||||
|   }) | ||||
| }) | ||||
|  | @ -1,41 +0,0 @@ | |||
| open Jest | ||||
| open Expect | ||||
| 
 | ||||
| describe("SqError.Message", () => { | ||||
|   test("toString", () => | ||||
|     expect(SqError.Message.REOther("test error")->SqError.Message.toString)->toBe( | ||||
|       "Error: test error", | ||||
|     ) | ||||
|   ) | ||||
| }) | ||||
| 
 | ||||
| describe("SqError", () => { | ||||
|   test("fromMessage", () => | ||||
|     expect(SqError.Message.REOther("test error")->SqError.fromMessage->SqError.toString)->toBe( | ||||
|       "Error: test error", | ||||
|     ) | ||||
|   ) | ||||
| 
 | ||||
|   test("toStringWithStackTrace with empty stacktrace", () => | ||||
|     expect( | ||||
|       SqError.Message.REOther("test error")->SqError.fromMessage->SqError.toStringWithStackTrace, | ||||
|     )->toBe("Error: test error") | ||||
|   ) | ||||
| 
 | ||||
|   test("toStringWithStackTrace", () => { | ||||
|     let frameStack = | ||||
|       Reducer_FrameStack.make() | ||||
|       ->Reducer_FrameStack.extend("frame1", None) | ||||
|       ->Reducer_FrameStack.extend("frame2", None) | ||||
| 
 | ||||
|     expect( | ||||
|       SqError.Message.REOther("test error") | ||||
|       ->SqError.fromMessageWithFrameStack(frameStack) | ||||
|       ->SqError.toStringWithStackTrace, | ||||
|     )->toBe(`Error: test error | ||||
| Stack trace: | ||||
|   frame2 | ||||
|   frame1 | ||||
| `) | ||||
|   }) | ||||
| }) | ||||
|  | @ -2,18 +2,16 @@ open Jest | |||
| open Expect | ||||
| open Reducer_TestHelpers | ||||
| 
 | ||||
| let expectEvalToBeOk = (code: string) => | ||||
|   Reducer_Expression.BackCompatible.evaluateString(code)->E.R.isOk->expect->toBe(true) | ||||
| let expectEvalToBeOk = (expr: string) => | ||||
|   Reducer.evaluate(expr)->Reducer_Helpers.rRemoveDefaultsExternal->E.R.isOk->expect->toBe(true) | ||||
| 
 | ||||
| let registry = FunctionRegistry_Library.registry | ||||
| let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry)) | ||||
| 
 | ||||
| describe("FunctionRegistry Library", () => { | ||||
|   describe("Regular tests", () => { | ||||
|     testEvalToBe("List.length([3,5,8])", "Ok(3)") | ||||
|     testEvalToBe("List.length([])", "Ok(0)") | ||||
|     testEvalToBe("List.make(3, 'HI')", "Ok(['HI','HI','HI'])") | ||||
|     testEvalToBe("make(3, 'HI')", "Error(make is not defined)") | ||||
|     testEvalToBe("make(3, 'HI')", "Error(Function not found: make(Number,String))") | ||||
|     testEvalToBe("List.upTo(1,3)", "Ok([1,2,3])") | ||||
|     testEvalToBe("List.first([3,5,8])", "Ok(3)") | ||||
|     testEvalToBe("List.last([3,5,8])", "Ok(8)") | ||||
|  | @ -82,43 +80,25 @@ describe("FunctionRegistry Library", () => { | |||
|       "SampleSet.toList(SampleSet.mapN([SampleSet.fromList([1,2,3,4,5,6]), SampleSet.fromList([6, 5, 4, 3, 2, 1])], {|x| x[0] > x[1] ? x[0] : x[1]}))", | ||||
|       "Ok([6,5,4,4,5,6])", | ||||
|     ) | ||||
|     testEvalToBe( | ||||
|       "SampleSet.fromList([1, 2, 3])", | ||||
|       "Error(Error: Too few samples when constructing sample set)", | ||||
|     ) | ||||
| 
 | ||||
|     testEvalToBe("Dict.merge({a: 1, b: 2}, {b: 3, c: 4, d: 5})", "Ok({a: 1,b: 3,c: 4,d: 5})") | ||||
|     testEvalToBe( | ||||
|       "Dict.mergeMany([{a: 1, b: 2}, {c: 3, d: 4}, {c: 5, e: 6}])", | ||||
|       "Ok({a: 1,b: 2,c: 5,d: 4,e: 6})", | ||||
|     ) | ||||
|     testEvalToBe("Dict.keys({a: 1, b: 2})", "Ok(['a','b'])") | ||||
|     testEvalToBe("Dict.values({a: 1, b: 2})", "Ok([1,2])") | ||||
|     testEvalToBe("Dict.toList({a: 1, b: 2})", "Ok([['a',1],['b',2]])") | ||||
|     testEvalToBe("Dict.fromList([['a', 1], ['b', 2]])", "Ok({a: 1,b: 2})") | ||||
|   }) | ||||
| 
 | ||||
|   describe("Fn auto-testing", () => { | ||||
|     testAll( | ||||
|       "tests of validity", | ||||
|       examples, | ||||
|       r => { | ||||
|     testAll("tests of validity", examples, r => { | ||||
|       expectEvalToBeOk(r) | ||||
|       }, | ||||
|     ) | ||||
|     }) | ||||
| 
 | ||||
|     testAll( | ||||
|       "tests of type", | ||||
|       E.A.to_list( | ||||
|         FunctionRegistry_Core.Registry.allExamplesWithFns(registry)->E.A2.filter( | ||||
|           ((fn, _)) => E.O.isSome(fn.output), | ||||
|         FunctionRegistry_Core.Registry.allExamplesWithFns(registry)->E.A2.filter(((fn, _)) => | ||||
|           E.O.isSome(fn.output) | ||||
|         ), | ||||
|       ), | ||||
|       ((fn, example)) => { | ||||
|         let responseType = | ||||
|           example | ||||
|           ->Reducer_Expression.BackCompatible.evaluateString | ||||
|           ->E.R2.fmap(Reducer_Value.valueToValueType) | ||||
|           ->Reducer.evaluate | ||||
|           ->E.R2.fmap(ReducerInterface_InternalExpressionValue.externalValueToValueType) | ||||
|         let expectedOutputType = fn.output |> E.O.toExn("") | ||||
|         expect(responseType)->toEqual(Ok(expectedOutputType)) | ||||
|       }, | ||||
|  |  | |||
|  | @ -1,5 +1,10 @@ | |||
| import { run, SqProject, SqValue, SqValueTag } from "../../src/js"; | ||||
| import { testRun } from "./TestHelpers"; | ||||
| import { | ||||
|   Distribution, | ||||
|   resultMap, | ||||
|   defaultBindings, | ||||
|   mergeBindings, | ||||
| } from "../../src/js/index"; | ||||
| import { testRun, testRunPartial } from "./TestHelpers"; | ||||
| 
 | ||||
| function Ok<b>(x: b) { | ||||
|   return { tag: "Ok", value: x }; | ||||
|  | @ -7,57 +12,97 @@ function Ok<b>(x: b) { | |||
| 
 | ||||
| describe("Simple calculations and results", () => { | ||||
|   test("mean(normal(5,2))", () => { | ||||
|     const result = testRun("mean(normal(5,2))"); // FIXME
 | ||||
|     expect(result.toString()).toEqual("5"); | ||||
|     expect(testRun("mean(normal(5,2))")).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 5, | ||||
|     }); | ||||
|   }); | ||||
|   test("10+10", () => { | ||||
|     let result = testRun("10 + 10"); | ||||
|     expect(result.toString()).toEqual("20"); | ||||
|     let foo = testRun("10 + 10"); | ||||
|     expect(foo).toEqual({ tag: "number", value: 20 }); | ||||
|   }); | ||||
| }); | ||||
| describe("Log function", () => { | ||||
|   test("log(1) = 0", () => { | ||||
|     let foo = testRun("log(1)"); | ||||
|     expect(foo.toString()).toEqual("0"); | ||||
|     expect(foo).toEqual({ tag: "number", value: 0 }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe("Array", () => { | ||||
|   test("nested Array", () => { | ||||
|     expect(testRun("[[ 1 ]]").toString()).toEqual("[[1]]"); | ||||
|     expect(testRun("[[1]]")).toEqual({ | ||||
|       tag: "array", | ||||
|       value: [ | ||||
|         { | ||||
|           tag: "array", | ||||
|           value: [ | ||||
|             { | ||||
|               tag: "number", | ||||
|               value: 1, | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe("Record", () => { | ||||
|   test("Return record", () => { | ||||
|     expect(testRun("{a:1}").toString()).toEqual("{a: 1}"); | ||||
|     expect(testRun("{a: 1}")).toEqual({ | ||||
|       tag: "record", | ||||
|       value: { | ||||
|         a: { | ||||
|           tag: "number", | ||||
|           value: 1, | ||||
|         }, | ||||
|       }, | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe("Continues", () => { | ||||
|   test("Bindings from continues are accessible", () => { | ||||
|     const project = SqProject.create(); | ||||
|     project.setSource("p1", "x = 5"); | ||||
|     project.setSource("p2", "y = x + 2"); | ||||
|     project.setSource("main", "y + 3"); | ||||
|     project.setContinues("main", ["p2"]); | ||||
|     project.setContinues("p2", ["p1"]); | ||||
|     project.run("main"); | ||||
|     const result = project.getResult("main"); | ||||
|     expect(result.tag).toEqual("Ok"); | ||||
|     expect(result.value.toString()).toEqual("10"); | ||||
| describe("Partials", () => { | ||||
|   test("Can pass variables between partials and cells", () => { | ||||
|     let bindings = testRunPartial(`x = 5`); | ||||
|     let bindings2 = testRunPartial(`y = x + 2`, bindings); | ||||
|     expect(testRun(`y + 3`, bindings2)).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 10, | ||||
|     }); | ||||
|   }); | ||||
|   test("Can merge bindings from three partials", () => { | ||||
|     const project = SqProject.create(); | ||||
|     project.setSource("p1", "x = 1"); | ||||
|     project.setSource("p2", "y = 2"); | ||||
|     project.setSource("p3", "z = 3"); | ||||
|     project.setSource("main", "x + y + z"); | ||||
|     project.setContinues("main", ["p1", "p2", "p3"]); | ||||
|     project.run("main"); | ||||
|     const result = project.getResult("main"); | ||||
|     expect(result.tag).toEqual("Ok"); | ||||
|     expect(result.value.toString()).toEqual("6"); | ||||
|     let bindings1 = testRunPartial(`x = 1`); | ||||
|     let bindings2 = testRunPartial(`y = 2`); | ||||
|     let bindings3 = testRunPartial(`z = 3`); | ||||
|     expect( | ||||
|       testRun(`x + y + z`, mergeBindings([bindings1, bindings2, bindings3])) | ||||
|     ).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 6, | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| describe("JS Imports", () => { | ||||
|   test("Can pass parameters into partials and cells", () => { | ||||
|     let bindings = testRunPartial(`y = $x + 2`, defaultBindings, { x: 1 }); | ||||
|     let bindings2 = testRunPartial(`z = y + $a`, bindings, { a: 3 }); | ||||
|     expect(testRun(`z`, bindings2)).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 6, | ||||
|     }); | ||||
|   }); | ||||
|   test("Complicated deep parameters", () => { | ||||
|     expect( | ||||
|       testRun(`$x.y[0][0].w + $x.z + $u.v`, defaultBindings, { | ||||
|         x: { y: [[{ w: 1 }]], z: 2 }, | ||||
|         u: { v: 3 }, | ||||
|       }) | ||||
|     ).toEqual({ | ||||
|       tag: "number", | ||||
|       value: 6, | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
|  | @ -67,62 +112,50 @@ describe("Distribution", () => { | |||
|   let env = { sampleCount: 8, xyPointLength: 100 }; | ||||
|   let dist1Samples = [3, 4, 5, 6, 6, 7, 10, 15, 30]; | ||||
|   let dist1SampleCount = dist1Samples.length; | ||||
| 
 | ||||
|   const buildDist = (samples: number[]) => { | ||||
|     const src = `SampleSet.fromList([${samples.join(",")}])`; | ||||
|     const { result } = run(src, { | ||||
|       environment: env, | ||||
|     }); | ||||
|     if (result.tag !== "Ok") { | ||||
|       throw new Error( | ||||
|         `Failed to build SampleSet: from ${src}: ${result.value}` | ||||
|   let dist = new Distribution( | ||||
|     { tag: "SampleSet", value: [3, 4, 5, 6, 6, 7, 10, 15, 30] }, | ||||
|     env | ||||
|   ); | ||||
|   let dist2 = new Distribution( | ||||
|     { tag: "SampleSet", value: [20, 22, 24, 29, 30, 35, 38, 44, 52] }, | ||||
|     env | ||||
|   ); | ||||
|     } | ||||
|     const dist = result.value; | ||||
|     if (dist.tag !== SqValueTag.Distribution) { | ||||
|       throw new Error("Expected Distribution"); | ||||
|     } | ||||
|     return dist.value; | ||||
|   }; | ||||
| 
 | ||||
|   const dist = buildDist(dist1Samples); | ||||
|   const dist2 = buildDist([20, 22, 24, 29, 30, 35, 38, 44, 52]); | ||||
| 
 | ||||
|   test("mean", () => { | ||||
|     expect(dist.mean(env).value).toBeCloseTo(9.5555555); | ||||
|     expect(dist.mean().value).toBeCloseTo(9.5555555); | ||||
|   }); | ||||
|   test("pdf", () => { | ||||
|     expect(dist.pdf(env, 5.0).value).toBeCloseTo(0.10499097598222966, 1); | ||||
|     expect(dist.pdf(5.0).value).toBeCloseTo(0.10499097598222966, 1); | ||||
|   }); | ||||
|   test("cdf", () => { | ||||
|     expect(dist.cdf(env, 5.0).value).toBeCloseTo( | ||||
|     expect(dist.cdf(5.0).value).toBeCloseTo( | ||||
|       dist1Samples.filter((x) => x <= 5).length / dist1SampleCount, | ||||
|       1 | ||||
|     ); | ||||
|   }); | ||||
|   test("inv", () => { | ||||
|     expect(dist.inv(env, 0.5).value).toBeCloseTo(6); | ||||
|     expect(dist.inv(0.5).value).toBeCloseTo(6); | ||||
|   }); | ||||
|   test("toPointSet", () => { | ||||
|     expect( | ||||
|       resultMap(dist.toPointSet(), (r: Distribution) => r.toString()) | ||||
|     ).toEqual(Ok("Point Set Distribution")); | ||||
|   }); | ||||
|   test("toSparkline", () => { | ||||
|     expect(dist.toSparkline(20).value).toEqual("▁▁▃▇█▇▄▂▂▂▁▁▁▁▁▂▂▁▁▁"); | ||||
|   }); | ||||
|   test("algebraicAdd", () => { | ||||
|     expect( | ||||
|       resultMap(dist.algebraicAdd(dist2), (r: Distribution) => | ||||
|         r.toSparkline(20) | ||||
|       ).value | ||||
|     ).toEqual(Ok("▁▁▂▄▆████▇▆▄▄▃▃▃▂▁▁▁")); | ||||
|   }); | ||||
|   test("pointwiseAdd", () => { | ||||
|     expect( | ||||
|       resultMap(dist.pointwiseAdd(dist2), (r: Distribution) => | ||||
|         r.toSparkline(20) | ||||
|       ).value | ||||
|     ).toEqual(Ok("▁▂██▃▃▃▃▄▅▄▃▃▂▂▂▁▁▁▁")); | ||||
|   }); | ||||
|   // test("toPointSet", () => {
 | ||||
|   //   expect(
 | ||||
|   //     resultMap(dist.toPointSet(), (r: Distribution) => r.toString())
 | ||||
|   //   ).toEqual(Ok("Point Set Distribution"));
 | ||||
|   // });
 | ||||
|   // test("toSparkline", () => {
 | ||||
|   //   expect(dist.toSparkline(20).value).toEqual("▁▁▃▇█▇▄▂▂▂▁▁▁▁▁▂▂▁▁▁");
 | ||||
|   // });
 | ||||
|   // test("algebraicAdd", () => {
 | ||||
|   //   expect(
 | ||||
|   //     resultMap(dist.algebraicAdd(dist2), (r: Distribution) =>
 | ||||
|   //       r.toSparkline(20)
 | ||||
|   //     ).value
 | ||||
|   //   ).toEqual(Ok("▁▁▂▄▆████▇▆▄▄▃▃▃▂▁▁▁"));
 | ||||
|   // });
 | ||||
|   // test("pointwiseAdd", () => {
 | ||||
|   //   expect(
 | ||||
|   //     resultMap(dist.pointwiseAdd(dist2), (r: Distribution) =>
 | ||||
|   //       r.toSparkline(20)
 | ||||
|   //     ).value
 | ||||
|   //   ).toEqual(Ok("▁▂██▃▃▃▃▄▅▄▃▃▂▂▂▁▁▁▁"));
 | ||||
|   // });
 | ||||
| }); | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| // import { errorValueToString } from "../../src/js/index";
 | ||||
| import * as fc from "fast-check"; | ||||
| import { testRun } from "./TestHelpers"; | ||||
| 
 | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user