Merge branch 'develop' into fixes-for-ideopunk

Also some SqDistribution fixes.
This commit is contained in:
Vyacheslav Matyukhin 2022-09-06 23:35:37 +04:00
commit 00a0005e56
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
189 changed files with 8836 additions and 4911 deletions

View File

@ -12,9 +12,13 @@ updates:
commit-message: commit-message:
prefix: "⬆️" prefix: "⬆️"
open-pull-requests-limit: 100 open-pull-requests-limit: 100
labels:
- "dependencies"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:
interval: "weekly" interval: "weekly"
commit-message: commit-message:
prefix: "⬆️" prefix: "⬆️"
labels:
- "dependencies"

87
.github/workflows/ci-cachix.yml vendored Normal file
View File

@ -0,0 +1,87 @@
name: Nix build
on:
push:
branches:
- master
- develop
pull_request:
branches:
- master
- develop
- reducer-dev
- epic-reducer-project
jobs:
flake-lints:
name: All lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install nix
uses: cachix/install-nix-action@v17
with:
nix_path: nixpkgs=channel:nixos-22.05
- name: Use cachix
uses: cachix/cachix-action@v10
with:
name: quantified-uncertainty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Check that lang lints
run: nix build .#lang-lint
- name: Check that components lints
run: nix build .#components-lint
- name: Check that website lints
run: nix build .#docusaurus-lint
- name: Check that vscode extension lints
run: nix build .#vscode-lint
- name: Check that cli lints
run: nix build .#cli-lint
flake-packages:
name: Builds, tests, and bundles
runs-on: ubuntu-latest
needs: flake-lints
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install nix
uses: cachix/install-nix-action@v17
with:
nix_path: nixpkgs=channel:nixos-22.05
- name: Use cachix
uses: cachix/cachix-action@v10
with:
name: quantified-uncertainty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Check all lang tests
run: nix build .#lang-test
- name: Check that lang bundles
run: nix build .#lang-bundle
- name: Check that components builds
run: nix build .#components
- name: Check that components bundles
run: nix build .#components-bundle
flake-devshells:
name: Development shell environment
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install nix
uses: cachix/install-nix-action@v17
with:
nix_path: nixpkgs=channel:nixos-22.05
- name: Use cachix
uses: cachix/cachix-action@v10
with:
name: quantified-uncertainty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Build js devshell
run: nix develop .#js --profile just-js
- name: Build js & wasm devshell
run: nix develop --profile full-shell

View File

@ -10,6 +10,7 @@ on:
- master - master
- develop - develop
- reducer-dev - reducer-dev
- epic-reducer-project
jobs: jobs:
pre_check: pre_check:
@ -48,26 +49,26 @@ jobs:
with: with:
paths: '["packages/cli/**"]' paths: '["packages/cli/**"]'
lang-lint: # lang-lint:
name: Language lint # name: Language lint
runs-on: ubuntu-latest # runs-on: ubuntu-latest
needs: pre_check # needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }} # if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
defaults: # defaults:
run: # run:
shell: bash # shell: bash
working-directory: packages/squiggle-lang # working-directory: packages/squiggle-lang
steps: # steps:
- uses: actions/checkout@v3 # - uses: actions/checkout@v3
- name: Install Dependencies # - name: Install Dependencies
run: cd ../../ && yarn # run: cd ../../ && yarn
- name: Check rescript lint # - name: Check rescript lint
run: yarn lint:rescript # run: yarn lint:rescript
- name: Check javascript, typescript, and markdown lint # - name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2 # uses: creyD/prettier_action@v4.2
with: # with:
dry: true # dry: true
prettier_options: --check packages/squiggle-lang # prettier_options: --check packages/squiggle-lang
lang-build-test-bundle: lang-build-test-bundle:
name: Language build, test, and bundle name: Language build, test, and bundle
@ -97,95 +98,96 @@ jobs:
- name: Upload typescript coverage report - name: Upload typescript coverage report
run: yarn coverage:ts:ci run: yarn coverage:ts:ci
components-lint: # components-lint:
name: Components lint # name: Components lint
runs-on: ubuntu-latest # runs-on: ubuntu-latest
needs: pre_check # needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }} # if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
defaults: # defaults:
run: # run:
shell: bash # shell: bash
working-directory: packages/components # working-directory: packages/components
steps: # steps:
- uses: actions/checkout@v3 # - uses: actions/checkout@v3
- name: Check javascript, typescript, and markdown lint # - name: Check javascript, typescript, and markdown lint
uses: creyD/prettier_action@v4.2 # uses: creyD/prettier_action@v4.2
with: # with:
dry: true # dry: true
prettier_options: --check packages/components --ignore-path packages/components/.prettierignore # 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
components-bundle-build: # website-lint:
name: Components bundle and build # name: Website lint
runs-on: ubuntu-latest # runs-on: ubuntu-latest
needs: pre_check # needs: pre_check
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }} # if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
defaults: # defaults:
run: # run:
shell: bash # shell: bash
working-directory: packages/components # working-directory: packages/website
steps: # steps:
- uses: actions/checkout@v3 # - uses: actions/checkout@v3
- name: Install dependencies from monorepo level # - name: Check javascript, typescript, and markdown lint
run: cd ../../ && yarn # uses: creyD/prettier_action@v4.2
- name: Build rescript codebase in squiggle-lang # with:
run: cd ../squiggle-lang && yarn build # dry: true
- name: Run webpack # prettier_options: --check packages/website
run: yarn bundle #
- name: Build storybook # website-build:
run: yarn build # name: Website build
# runs-on: ubuntu-latest
website-lint: # needs: pre_check
name: Website lint # 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') }}
runs-on: ubuntu-latest # defaults:
needs: pre_check # run:
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }} # shell: bash
defaults: # working-directory: packages/website
run: # steps:
shell: bash # - uses: actions/checkout@v3
working-directory: packages/website # - name: Install dependencies from monorepo level
steps: # run: cd ../../ && yarn
- uses: actions/checkout@v3 # - name: Build rescript in squiggle-lang
- name: Check javascript, typescript, and markdown lint # run: cd ../squiggle-lang && yarn build
uses: creyD/prettier_action@v4.2 # - name: Build components
with: # run: cd ../components && yarn build
dry: true # - name: Build website assets
prettier_options: --check packages/website # run: yarn build
#
website-build: # vscode-ext-lint:
name: Website build # name: VS Code extension lint
runs-on: ubuntu-latest # runs-on: ubuntu-latest
needs: pre_check # 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') }} # if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
defaults: # defaults:
run: # run:
shell: bash # shell: bash
working-directory: packages/website # working-directory: packages/vscode-ext
steps: # steps:
- uses: actions/checkout@v3 # - uses: actions/checkout@v3
- name: Install dependencies from monorepo level # - name: Check javascript, typescript, and markdown lint
run: cd ../../ && yarn # uses: creyD/prettier_action@v4.2
- name: Build rescript in squiggle-lang # with:
run: cd ../squiggle-lang && yarn build # dry: true
- name: Build components # prettier_options: --check packages/vscode-ext
run: cd ../components && yarn build
- name: Build website assets
run: yarn build
vscode-ext-lint:
name: VS Code extension lint
runs-on: ubuntu-latest
needs: pre_check
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
defaults:
run:
shell: bash
working-directory: packages/vscode-ext
steps:
- uses: actions/checkout@v3
- name: Install dependencies from monorepo level
run: cd ../../ && yarn
- name: Lint the VSCode Extension source code
run: yarn lint
vscode-ext-build: vscode-ext-build:
name: VS Code extension build name: VS Code extension build
@ -202,20 +204,19 @@ jobs:
run: cd ../../ && yarn run: cd ../../ && yarn
- name: Build - name: Build
run: yarn compile run: yarn compile
# cli-lint:
cli-lint: # name: CLI lint
name: CLI lint # runs-on: ubuntu-latest
runs-on: ubuntu-latest # needs: pre_check
needs: pre_check # if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }} # defaults:
defaults: # run:
run: # shell: bash
shell: bash # working-directory: packages/cli
working-directory: packages/cli # steps:
steps: # - uses: actions/checkout@v3
- uses: actions/checkout@v3 # - name: Check javascript, typescript, and markdown lint
- name: Check javascript, typescript, and markdown lint # uses: creyD/prettier_action@v4.2
uses: creyD/prettier_action@v4.2 # with:
with: # dry: true
dry: true # prettier_options: --check packages/cli
prettier_options: --check packages/cli

View File

@ -3,7 +3,7 @@ name: Run Release Please
on: on:
push: push:
branches: branches:
- develop - master
jobs: jobs:
pre_check: pre_check:
@ -55,21 +55,22 @@ jobs:
token: ${{secrets.GITHUB_TOKEN}} token: ${{secrets.GITHUB_TOKEN}}
command: manifest-pr command: manifest-pr
path: packages/squiggle-lang path: packages/squiggle-lang
bump-patch-for-minor-pre-major: true # bump-patch-for-minor-pre-major: true
skip-github-release: true skip-github-release: true
# - name: Publish: Checkout source - name: Publish- Checkout source
# uses: actions/checkout@v2 uses: actions/checkout@v3
# # these if statements ensure that a publication only occurs when # these if statements ensure that a publication only occurs when
# # a new release is created: # a new release is created:
# if: ${{ steps.release.outputs.release_created }} if: ${{ steps.release.outputs.release_created }}
# - name: Publish: Install dependencies - name: Publish- Install dependencies
# run: yarn run: yarn
# if: ${{ steps.release.outputs.release_created }} if: ${{ steps.release.outputs.release_created }}
# - name: Publish - name: Publish
# run: cd packages/squiggle-lang && yarn publish run: cd packages/squiggle-lang && yarn publish
# env: env:
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
# if: ${{ steps.release.outputs.release_created }} if: ${{ steps.release.outputs.release_created }}
relplz-components: relplz-components:
name: for components name: for components
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -82,20 +83,20 @@ jobs:
token: ${{secrets.GITHUB_TOKEN}} token: ${{secrets.GITHUB_TOKEN}}
command: manifest-pr command: manifest-pr
path: packages/components path: packages/components
bump-patch-for-minor-pre-major: true # bump-patch-for-minor-pre-major: true
skip-github-release: true skip-github-release: true
# - name: Publish: Checkout source - name: Publish- Checkout source
# uses: actions/checkout@v2 uses: actions/checkout@v3
# # these if statements ensure that a publication only occurs when # these if statements ensure that a publication only occurs when
# # a new release is created: # a new release is created:
# if: ${{ steps.release.outputs.release_created }} if: ${{ steps.release.outputs.release_created }}
# - name: Publish: Install dependencies - name: Publish- Install dependencies
# run: yarn run: yarn
# if: ${{ steps.release.outputs.release_created }} if: ${{ steps.release.outputs.release_created }}
# - name: Publish - name: Publish
# run: cd packages/components && yarn publish run: cd packages/components && yarn publish
# env: env:
# NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
relplz-website: relplz-website:
name: for website name: for website
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -108,7 +109,7 @@ jobs:
token: ${{secrets.GITHUB_TOKEN}} token: ${{secrets.GITHUB_TOKEN}}
command: manifest-pr command: manifest-pr
path: packages/website path: packages/website
bump-patch-for-minor-pre-major: true # bump-patch-for-minor-pre-major: true
skip-github-release: true skip-github-release: true
relplz-vscodeext: relplz-vscodeext:
name: for vscode-ext name: for vscode-ext
@ -122,7 +123,7 @@ jobs:
token: ${{secrets.GITHUB_TOKEN}} token: ${{secrets.GITHUB_TOKEN}}
command: manifest-pr command: manifest-pr
path: packages/vscode-ext path: packages/vscode-ext
bump-patch-for-minor-pre-major: true # bump-patch-for-minor-pre-major: true
skip-github-release: true skip-github-release: true
relplz-cl: relplz-cl:
name: for cli name: for cli

3
.gitignore vendored
View File

@ -9,4 +9,5 @@ yarn-error.log
.log .log
.vscode .vscode
todo.txt todo.txt
result

View File

@ -1,15 +1,16 @@
.direnv .direnv
*.bs.js *.bs.js
*.gen.tsx *.gen.tsx
packages/*/dist
packages/components/storybook-static packages/components/storybook-static
node_modules node_modules
packages/*/node_modules packages/*/node_modules
packages/website/.docusaurus packages/website/.docusaurus
packages/squiggle-lang/lib packages/squiggle-lang/lib
packages/squiggle-lang/.nyc_output/
packages/squiggle-lang/coverage/ packages/squiggle-lang/coverage/
packages/squiggle-lang/.cache/ packages/squiggle-lang/.cache/
packages/website/build/ packages/website/build/
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
packages/vscode-ext/media/vendor/ packages/vscode-ext/media/vendor/
packages/squiggle-lang/.nyc_output/
packages/*/dist
result

79
flake.lock Normal file
View File

@ -0,0 +1,79 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gentype": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1661855866,
"narHash": "sha256-+q0OOTyaq8eOn9BOWdPOCtSDOISW4A59v3mq3JOZyug=",
"owner": "rescript-association",
"repo": "genType",
"rev": "6b5f164b4f6ced456019b7579a0ab7e0a86518ad",
"type": "github"
},
"original": {
"owner": "rescript-association",
"repo": "genType",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1661617163,
"narHash": "sha256-NN9Ky47j8ohgPhA9JZyfkYIbbAo6RJkGz+7h8/exVpE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0ba2543f8c855d7be8e90ef6c8dc89c1617e8a08",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gentype": "gentype",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

99
flake.nix Normal file
View File

@ -0,0 +1,99 @@
{
description = "Squiggle packages";
inputs = {
nixpkgs.url = "nixpkgs/nixos-22.05";
gentype = {
url = "github:rescript-association/genType";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, gentype, flake-utils }:
let
version = builtins.substring 0 8 self.lastModifiedDate;
overlays = [
(final: prev: {
# set the node version here
nodejs = prev.nodejs-18_x;
# The override is the only way to get it into mkYarnModules
})
];
commonFn = pkgs: {
buildInputs = with pkgs; [ nodejs yarn ];
prettier = with pkgs.nodePackages; [ prettier ];
which = [ pkgs.which ];
};
gentypeOutputFn = pkgs: gentype.outputs.packages.${pkgs.system}.default;
langFn = { pkgs, ... }:
# Probably doesn't work on i686-linux
import ./nix/squiggle-lang.nix {
inherit pkgs commonFn gentypeOutputFn;
};
componentsFn = { pkgs, ... }:
import ./nix/squiggle-components.nix { inherit pkgs commonFn langFn; };
websiteFn = { pkgs, ... }:
import ./nix/squiggle-website.nix {
inherit pkgs commonFn langFn componentsFn;
};
vscodeextFn = { pkgs, ... }:
import ./nix/squiggle-vscode.nix {
inherit pkgs commonFn langFn componentsFn;
};
cliFn = { pkgs, ... }:
import ./nix/squiggle-cli.nix {
inherit pkgs commonFn;
};
# local machines
localFlakeOutputs = { pkgs, ... }:
let
lang = langFn pkgs;
components = componentsFn pkgs;
website = websiteFn pkgs;
vscodeext = vscodeextFn pkgs;
cli = cliFn pkgs;
in {
# validating
checks = flake-utils.lib.flattenTree {
lang-lint = lang.lint;
lang-test = lang.test;
components-lint = components.lint;
docusaurus-lint = website.lint;
cli-lint = cli.lint;
};
# building
packages = flake-utils.lib.flattenTree {
default = components.build;
lang = lang.build;
lang-bundle = lang.bundle;
lang-test = lang.test;
components = components.build;
components-bundle = components.bundle;
# Lint
lang-lint = lang.lint;
components-lint = components.lint;
docusaurus-lint = website.lint;
vscode-lint = vscodeext.lint;
cli-lint = cli.lint;
};
# developing
devShells = let shellNix = import ./nix/shell.nix { inherit pkgs; };
in flake-utils.lib.flattenTree {
default = shellNix.all;
js = shellNix.just-js;
};
};
in flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
overlays = overlays;
};
in localFlakeOutputs pkgs);
}

1
nix/README.md Normal file
View File

@ -0,0 +1 @@
Visit `quantified-uncertainty.cachix.org` for information about how to add our binary cache to your local dev environment.

25
nix/shell.nix Normal file
View File

@ -0,0 +1,25 @@
{ pkgs }:
with pkgs;
let
js = [ yarn nodejs nodePackages.ts-node ];
rust = [
wasm-pack
cargo
rustup
pkg-config
libressl
rustfmt
wasmtime
binaryen
wasm-bindgen-cli
];
in {
all = mkShell {
name = "squiggle_yarn-wasm-devshell";
buildInputs = builtins.concatLists [ js rust [ nixfmt ] ];
};
just-js = mkShell {
name = "squiggle_yarn-devshell";
buildInputs = js ++ [ nixfmt ];
};
}

13
nix/squiggle-cli.nix Normal file
View File

@ -0,0 +1,13 @@
{ pkgs, commonFn }:
rec {
common = commonFn pkgs;
lint = pkgs.stdenv.mkDerivation {
name = "squiggle-cli-lint";
buildInputs = common.buildInputs ++ common.prettier;
src = ../packages/cli;
buildPhase = "prettier --check .";
installPhase = "mkdir -p $out";
};
}

View File

@ -0,0 +1,75 @@
{ pkgs, commonFn, langFn }:
rec {
common = commonFn pkgs;
lang = langFn pkgs;
componentsPackageJson = let
raw = pkgs.lib.importJSON ../packages/components/package.json;
modified =
pkgs.lib.recursiveUpdate raw { dependencies.react-dom = "^18.2.0"; };
packageJsonString = builtins.toJSON modified;
in pkgs.writeText "packages/components/patched-package.json"
packageJsonString;
yarn-source = pkgs.mkYarnPackage {
name = "squiggle-components_yarnsource";
buildInputs = common.buildInputs;
src = ../packages/components;
packageJSON = componentsPackageJson;
yarnLock = ../yarn.lock;
packageResolutions."@quri/squiggle-lang" = lang.build;
};
lint = pkgs.stdenv.mkDerivation {
name = "squiggle-components-lint";
src = ../packages/components;
buildInputs = common.buildInputs ++ common.prettier;
buildPhase = "yarn lint";
installPhase = "mkdir -p $out";
};
build = pkgs.stdenv.mkDerivation {
name = "squiggle-components-build";
src = yarn-source + "/libexec/@quri/squiggle-components";
buildInputs = common.buildInputs;
buildPhase = ''
cp -r node_modules/@quri/squiggle-lang deps/@quri
pushd deps/@quri/squiggle-components
yarn --offline build:cjs
yarn --offline build:css
popd
'';
installPhase = ''
mkdir -p $out
# annoying hack because permissions on transitive dependencies later on
mv deps/@quri/squiggle-components/node_modules deps/@quri/squiggle-components/NODE_MODULES
mv node_modules deps/@quri/squiggle-components
# patching .gitignore so flake keeps build artefacts
sed -i /dist/d deps/@quri/squiggle-components/.gitignore
cp -r deps/@quri/squiggle-components/. $out
'';
};
bundle = pkgs.stdenv.mkDerivation {
name = "squiggle-components-bundle";
src = yarn-source + "/libexec/@quri/squiggle-components";
buildInputs = common.buildInputs;
buildPhase = ''
cp -r node_modules/@quri/squiggle-lang deps/@quri
pushd deps/@quri/squiggle-components
yarn --offline bundle
popd
'';
installPhase = ''
mkdir -p $out
# annoying hack because permissions on transitive dependencies later on
mv deps/@quri/squiggle-components/node_modules deps/@quri/squiggle-components/NODE_MODULES
mv node_modules deps/@quri/squiggle-components
# patching .gitignore so flake keeps build artefacts
sed -i /dist/d deps/@quri/squiggle-components/.gitignore
cp -r deps/@quri/squiggle-components/. $out
'';
};
}

125
nix/squiggle-lang.nix Normal file
View File

@ -0,0 +1,125 @@
{ pkgs, commonFn, gentypeOutputFn }:
rec {
common = commonFn pkgs;
langPackageJson = let
raw = pkgs.lib.importJSON ../packages/squiggle-lang/package.json;
modified = pkgs.lib.recursiveUpdate raw {
devDependencies."@types/lodash" = "^4.14.167";
};
packageJsonString = builtins.toJSON modified;
in pkgs.writeText "packages/squiggle-lang/patched-package.json"
packageJsonString;
yarn-source = pkgs.mkYarnPackage {
name = "squiggle-lang_yarnsource";
src = ../packages/squiggle-lang;
packageJSON = langPackageJson;
yarnLock = ../yarn.lock;
pkgConfig = {
rescript = {
buildInputs = common.which
++ (if pkgs.system != "i686-linux" then [ pkgs.gcc_multi ] else [ ]);
postInstall = ''
echo "PATCHELF'ING RESCRIPT EXECUTABLES (INCL NINJA)"
# Patching interpreter for linux/*.exe's
THE_LD=$(patchelf --print-interpreter $(which mkdir))
patchelf --set-interpreter $THE_LD linux/*.exe && echo "- patched interpreter for linux/*.exe's"
# Replacing needed shared library for linux/ninja.exe
THE_SO=$(find /nix/store/*/lib64 -name libstdc++.so.6 | head -n 1)
patchelf --replace-needed libstdc++.so.6 $THE_SO linux/ninja.exe && echo "- replaced needed for linux/ninja.exe"
'';
};
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
cp ${gentypeOutputFn pkgs}/src/GenType.exe gentype.exe
'';
};
};
};
lint = pkgs.stdenv.mkDerivation {
name = "squiggle-lang-lint";
src = yarn-source + "/libexec/@quri/squiggle-lang/deps/@quri/squiggle-lang";
buildInputs = common.buildInputs ++ common.prettier;
buildPhase = ''
yarn lint:prettier
yarn lint:rescript
'';
installPhase = "mkdir -p $out";
};
build = pkgs.stdenv.mkDerivation {
name = "squiggle-lang-build";
# `peggy` is in the `node_modules` that's adjacent to `deps`.
src = yarn-source + "/libexec/@quri/squiggle-lang";
buildInputs = common.buildInputs;
buildPhase = ''
# so that the path to ppx doesn't need to be patched.
mv node_modules deps
pushd deps/@quri/squiggle-lang
yarn --offline build:peggy
yarn --offline build:rescript
yarn --offline build:typescript
# custom gitignore so that the flake keeps build artefacts
mv .gitignore GITIGNORE
sed -i /Reducer_Peggy_GeneratedParser.js/d GITIGNORE
sed -i /\*.bs.js/d GITIGNORE
sed -i /\*.gen.ts/d GITIGNORE
sed -i /\*.gen.tsx/d GITIGNORE
sed -i /\*.gen.js/d GITIGNORE
sed -i /helpers.js/d GITIGNORE
popd
'';
installPhase = ''
mkdir -p $out
# mkdir -p $out/node_modules
mv deps/@quri/squiggle-lang/GITIGNORE deps/@quri/squiggle-lang/.gitignore
# annoying hack because permissions on transitive dependencies later on
mv deps/@quri/squiggle-lang/node_modules deps/@quri/squiggle-lang/NODE_MODULES
mv deps/node_modules deps/@quri/squiggle-lang
# the proper install phase
cp -r deps/@quri/squiggle-lang/. $out
'';
};
test = pkgs.stdenv.mkDerivation {
name = "squiggle-lang-test";
src = build;
buildInputs = common.buildInputs;
buildPhase = ''
yarn --offline test
'';
installPhase = ''
mkdir -p $out
cp -r . $out
'';
};
bundle = pkgs.stdenv.mkDerivation {
name = "squiggle-lang-bundle";
src = test;
buildInputs = common.buildInputs;
buildPhase = ''
yarn --offline bundle
'';
installPhase = ''
mkdir -p $out
cp -r dist $out
cp *.json $out/dist
'';
};
}

24
nix/squiggle-vscode.nix Normal file
View File

@ -0,0 +1,24 @@
{ pkgs, commonFn, langFn, componentsFn }:
rec {
common = commonFn pkgs;
lang = langFn pkgs;
components = componentsFn pkgs;
yarn-source = pkgs.mkYarnPackage {
name = "squiggle-vscodeext_yarnsource";
src = ../packages/vscode-ext;
packageJson = ../packages/vscode-ext/package.json;
yarnLock = ../yarn.lock;
packageResolutions."@quri/squiggle-lang" = lang.build;
packageResolutions."@quri/squiggle-components" = components.build;
};
lint = pkgs.stdenv.mkDerivation {
name = "squiggle-vscode-lint";
buildInputs = common.buildInputs ++ common.prettier;
src =
../packages/vscode-ext; # yarn-source + "/libexec/vscode-squiggle/deps/vscode-squiggle";
buildPhase = "prettier --check .";
installPhase = "mkdir -p $out";
};
}

30
nix/squiggle-website.nix Normal file
View File

@ -0,0 +1,30 @@
{ pkgs, commonFn, langFn, componentsFn }:
rec {
common = commonFn pkgs;
lang = langFn pkgs;
components = componentsFn pkgs;
websitePackageJson = let
raw = pkgs.lib.importJSON ../packages/website/package.json;
modified = pkgs.lib.recursiveUpdate raw {
dependencies.postcss-import = "^14.1.0";
dependencies.tailwindcss = "^3.1.8";
};
packageJsonString = builtins.toJSON modified;
in pkgs.writeText "packages/website/patched-package.json" packageJsonString;
yarn-source = pkgs.mkYarnPackage {
name = "squiggle-website_yarnsource";
src = ../packages/website;
packageJSON = websitePackageJson;
yarnLock = ../yarn.lock;
packageResolutions."@quri/squiggle-lang" = lang.build;
packageResolutions."@quri/squiggle-components" = components.build;
};
lint = pkgs.stdenv.mkDerivation {
name = "squiggle-website-lint";
buildInputs = common.buildInputs ++ common.prettier;
src = ../packages/website;
buildPhase = "yarn lint";
installPhase = "mkdir -p $out";
};
}

View File

@ -5,14 +5,14 @@
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375 # We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
set -x set -x
fhsShellName="squiggle-development" fhsShellName="squiggle-fhs-development"
fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn]; runScript = \"yarn\"; }).env" fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn pkgs.glibc]; runScript = \"yarn\"; }).env"
nix-shell - <<<"$fhsShellDotNix" nix-shell - <<<"$fhsShellDotNix"
theLd=$(patchelf --print-interpreter $(which mkdir)) theLd=$(patchelf --print-interpreter $(which mkdir))
patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe
patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe
patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/ppx patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/ppx
patchelf --set-interpreter $theLd ./node_moduels/bisect_ppx/bisect-ppx-report patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/bisect-ppx-report
theSo=$(find /nix/store/*$fhsShellName*/lib64 -name libstdc++.so.6 | grep $fhsShellName | head -n 1) theSo=$(find /nix/store/*$fhsShellName*/lib64 -name libstdc++.so.6 | head -n 1)
patchelf --replace-needed libstdc++.so.6 $theSo ./node_modules/rescript/linux/ninja.exe patchelf --replace-needed libstdc++.so.6 $theSo ./node_modules/rescript/linux/ninja.exe

View File

@ -1,26 +1,26 @@
{ {
"name": "@quri/squiggle-components", "name": "@quri/squiggle-components",
"version": "0.3.1", "version": "0.4.0-alpha.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@floating-ui/react-dom": "^1.0.0", "@floating-ui/react-dom": "^1.0.0",
"@floating-ui/react-dom-interactions": "^0.9.2", "@floating-ui/react-dom-interactions": "^0.9.3",
"@headlessui/react": "^1.6.6", "@headlessui/react": "^1.6.6",
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^2.9.7", "@hookform/resolvers": "^2.9.7",
"@quri/squiggle-lang": "^0.3.0", "@quri/squiggle-lang": "^0.4.0-alpha.0",
"@react-hook/size": "^2.1.2", "@react-hook/size": "^2.1.2",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"framer-motion": "^7.1.1", "framer-motion": "^7.2.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"react": "^18.1.0", "react": "^18.1.0",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
"react-hook-form": "^7.34.1", "react-hook-form": "^7.34.2",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"react-vega": "^7.6.0", "react-vega": "^7.6.0",
"vega": "^5.22.1", "vega": "^5.22.1",
"vega-embed": "^6.21.0", "vega-embed": "^6.21.0",
"vega-lite": "^5.4.0", "vega-lite": "^5.5.0",
"vscode-uri": "^3.0.3", "vscode-uri": "^3.0.3",
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
@ -35,12 +35,12 @@
"@storybook/preset-create-react-app": "^4.1.2", "@storybook/preset-create-react-app": "^4.1.2",
"@storybook/react": "^6.5.10", "@storybook/react": "^6.5.10",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.4.3",
"@types/jest": "^27.5.0", "@types/jest": "^27.5.0",
"@types/lodash": "^4.14.182", "@types/lodash": "^4.14.184",
"@types/node": "^18.7.4", "@types/node": "^18.7.15",
"@types/react": "^18.0.9", "@types/react": "^18.0.18",
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@types/webpack": "^5.28.0", "@types/webpack": "^5.28.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@ -54,8 +54,8 @@
"tailwindcss": "^3.1.8", "tailwindcss": "^3.1.8",
"ts-loader": "^9.3.0", "ts-loader": "^9.3.0",
"tsconfig-paths-webpack-plugin": "^4.0.0", "tsconfig-paths-webpack-plugin": "^4.0.0",
"typescript": "^4.7.4", "typescript": "^4.8.2",
"web-vitals": "^2.1.4", "web-vitals": "^3.0.1",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.10.0" "webpack-dev-server": "^4.10.0"
@ -66,7 +66,7 @@
}, },
"scripts": { "scripts": {
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public", "start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
"build:cjs": "tsc -b", "build:cjs": "rm -rf dist/src && tsc -b",
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css", "build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
"build:storybook": "build-storybook -s public", "build:storybook": "build-storybook -s public",
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook", "build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
@ -74,7 +74,7 @@
"all": "yarn bundle && yarn build", "all": "yarn bundle && yarn build",
"lint": "prettier --check .", "lint": "prettier --check .",
"format": "prettier --write .", "format": "prettier --write .",
"prepack": "yarn bundle && tsc -b" "prepack": "yarn run build:cjs && yarn run bundle"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [

View File

@ -1,11 +1,12 @@
import * as React from "react"; import * as React from "react";
import { import {
Distribution, SqDistribution,
result, result,
distributionError, SqDistributionError,
distributionErrorToString,
squiggleExpression,
resultMap, resultMap,
SqRecord,
environment,
SqDistributionTag,
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { Vega } from "react-vega"; import { Vega } from "react-vega";
import { ErrorAlert } from "./Alert"; import { ErrorAlert } from "./Alert";
@ -19,7 +20,6 @@ import { NumberShower } from "./NumberShower";
import { Plot, parsePlot } from "../lib/plotParser"; import { Plot, parsePlot } from "../lib/plotParser";
import { flattenResult } from "../lib/utility"; import { flattenResult } from "../lib/utility";
import { hasMassBelowZero } from "../lib/distributionUtils"; import { hasMassBelowZero } from "../lib/distributionUtils";
import { point } from "@quri/squiggle-lang/src/js/distribution";
export type DistributionPlottingSettings = { export type DistributionPlottingSettings = {
/** Whether to show a summary of means, stdev, percentiles etc */ /** Whether to show a summary of means, stdev, percentiles etc */
@ -29,19 +29,18 @@ export type DistributionPlottingSettings = {
export type DistributionChartProps = { export type DistributionChartProps = {
plot: Plot; plot: Plot;
environment: environment;
width?: number; width?: number;
height: number; height: number;
sample?: boolean; sample?: boolean;
xAxisType?: "number" | "dateTime"; xAxisType?: "number" | "dateTime";
} & DistributionPlottingSettings; } & DistributionPlottingSettings;
export function defaultPlot(distribution: Distribution): Plot { export function defaultPlot(distribution: SqDistribution): Plot {
return { distributions: [{ name: "default", distribution }] }; return { distributions: [{ name: "default", distribution }] };
} }
export function makePlot(record: { export function makePlot(record: SqRecord): Plot | void {
[key: string]: squiggleExpression;
}): Plot | void {
const plotResult = parsePlot(record); const plotResult = parsePlot(record);
if (plotResult.tag === "Ok") { if (plotResult.tag === "Ok") {
return plotResult.value; return plotResult.value;
@ -51,23 +50,21 @@ export function makePlot(record: {
export const DistributionChart: React.FC<DistributionChartProps> = (props) => { export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
const { const {
plot, plot,
environment,
height, height,
showSummary, showSummary,
width, width,
logX, logX,
actions = false, actions = false,
xAxisType = "number",
} = props; } = props;
const [sized] = useSize((size) => { const [sized] = useSize((size) => {
const shapes = flattenResult( const shapes = flattenResult(
plot.distributions.map((x) => plot.distributions.map((x) =>
resultMap(x.distribution.pointSet(), (shape) => ({ resultMap(x.distribution.pointSet(environment), (pointSet) => ({
name: x.name, name: x.name,
// color: x.color, // not supported yet // color: x.color, // not supported yet
continuous: shape.continuous, ...pointSet.asShape(),
discrete: shape.discrete,
samples: [] as number[], samples: [] as number[],
// samples: [] as point[],
})) }))
) )
); );
@ -75,25 +72,24 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
if (shapes.tag === "Error") { if (shapes.tag === "Error") {
return ( return (
<ErrorAlert heading="Distribution Error"> <ErrorAlert heading="Distribution Error">
{distributionErrorToString(shapes.value)} {shapes.value.toString()}
</ErrorAlert> </ErrorAlert>
); );
} }
// if this is a sample set, include the samples // if this is a sample set, include the samples
const sampleSets = plot?.distributions.filter( const sampleSets = plot?.distributions.filter(
(dist) => dist.distribution.t.tag === "SampleSet" (dist) => dist.distribution.tag === SqDistributionTag.SampleSet
); );
if (sampleSets.length) { if (sampleSets.length) {
for (const set of sampleSets) { for (const { distribution } of sampleSets) {
if (set.distribution.t.tag === "SampleSet") { if (distribution.tag === SqDistributionTag.SampleSet) {
// this conditional must be duplicated to please typescript, more elegant solution probably exists // this conditional must be duplicated to please typescript, more elegant solution probably exists
shapes.value[0].samples.push(...set.distribution.t.value); shapes.value[0].samples.push(...distribution.value());
} }
} }
} }
console.log(shapes.value);
const spec = buildVegaSpec(props); const spec = buildVegaSpec(props);
let widthProp = width ? width : size.width; let widthProp = width ? width : size.width;
@ -124,7 +120,10 @@ export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
)} )}
<div className="flex justify-center"> <div className="flex justify-center">
{showSummary && plot.distributions.length === 1 && ( {showSummary && plot.distributions.length === 1 && (
<SummaryTable distribution={plot.distributions[0].distribution} /> <SummaryTable
distribution={plot.distributions[0].distribution}
environment={environment}
/>
)} )}
</div> </div>
</div> </div>
@ -148,32 +147,36 @@ const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
); );
type SummaryTableProps = { type SummaryTableProps = {
distribution: Distribution; distribution: SqDistribution;
environment: environment;
}; };
const SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => { const SummaryTable: React.FC<SummaryTableProps> = ({
const mean = distribution.mean(); distribution,
const stdev = distribution.stdev(); environment,
const p5 = distribution.inv(0.05); }) => {
const p10 = distribution.inv(0.1); const mean = distribution.mean(environment);
const p25 = distribution.inv(0.25); const stdev = distribution.stdev(environment);
const p50 = distribution.inv(0.5); const p5 = distribution.inv(environment, 0.05);
const p75 = distribution.inv(0.75); const p10 = distribution.inv(environment, 0.1);
const p90 = distribution.inv(0.9); const p25 = distribution.inv(environment, 0.25);
const p95 = distribution.inv(0.95); const p50 = distribution.inv(environment, 0.5);
const p75 = distribution.inv(environment, 0.75);
const p90 = distribution.inv(environment, 0.9);
const p95 = distribution.inv(environment, 0.95);
const hasResult = (x: result<number, distributionError>): boolean => const hasResult = (x: result<number, SqDistributionError>): boolean =>
x.tag === "Ok"; x.tag === "Ok";
const unwrapResult = ( const unwrapResult = (
x: result<number, distributionError> x: result<number, SqDistributionError>
): React.ReactNode => { ): React.ReactNode => {
if (x.tag === "Ok") { if (x.tag === "Ok") {
return <NumberShower number={x.value} />; return <NumberShower number={x.value} />;
} else { } else {
return ( return (
<ErrorAlert heading="Distribution Error"> <ErrorAlert heading="Distribution Error">
{distributionErrorToString(x.value)} {x.value.toString()}
</ErrorAlert> </ErrorAlert>
); );
} }

View File

@ -1,10 +1,5 @@
import * as React from "react"; import * as React from "react";
import { import { SqLambda, environment, SqValueTag } from "@quri/squiggle-lang";
lambdaValue,
environment,
runForeign,
errorValueToString,
} from "@quri/squiggle-lang";
import { FunctionChart1Dist } from "./FunctionChart1Dist"; import { FunctionChart1Dist } from "./FunctionChart1Dist";
import { FunctionChart1Number } from "./FunctionChart1Number"; import { FunctionChart1Number } from "./FunctionChart1Number";
import { DistributionPlottingSettings } from "./DistributionChart"; import { DistributionPlottingSettings } from "./DistributionChart";
@ -17,7 +12,7 @@ export type FunctionChartSettings = {
}; };
interface FunctionChartProps { interface FunctionChartProps {
fn: lambdaValue; fn: SqLambda;
chartSettings: FunctionChartSettings; chartSettings: FunctionChartSettings;
distributionPlotSettings: DistributionPlottingSettings; distributionPlotSettings: DistributionPlottingSettings;
environment: environment; environment: environment;
@ -38,8 +33,8 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
</MessageAlert> </MessageAlert>
); );
} }
const result1 = runForeign(fn, [chartSettings.start], environment); const result1 = fn.call([chartSettings.start]);
const result2 = runForeign(fn, [chartSettings.stop], environment); const result2 = fn.call([chartSettings.stop]);
const getValidResult = () => { const getValidResult = () => {
if (result1.tag === "Ok") { if (result1.tag === "Ok") {
return result1; return result1;
@ -53,14 +48,12 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
if (validResult.tag === "Error") { if (validResult.tag === "Error") {
return ( return (
<ErrorAlert heading="Error"> <ErrorAlert heading="Error">{validResult.value.toString()}</ErrorAlert>
{errorValueToString(validResult.value)}
</ErrorAlert>
); );
} }
switch (validResult.value.tag) { switch (validResult.value.tag) {
case "distribution": case SqValueTag.Distribution:
return ( return (
<FunctionChart1Dist <FunctionChart1Dist
fn={fn} fn={fn}
@ -70,7 +63,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
distributionPlotSettings={distributionPlotSettings} distributionPlotSettings={distributionPlotSettings}
/> />
); );
case "number": case SqValueTag.Number:
return ( return (
<FunctionChart1Number <FunctionChart1Number
fn={fn} fn={fn}

View File

@ -2,14 +2,13 @@ import * as React from "react";
import _ from "lodash"; import _ from "lodash";
import type { Spec } from "vega"; import type { Spec } from "vega";
import { import {
Distribution, SqDistribution,
result, result,
lambdaValue, SqLambda,
environment, environment,
runForeign, SqError,
squiggleExpression, SqValue,
errorValue, SqValueTag,
errorValueToString,
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega"; import { createClassFromSpec } from "react-vega";
import * as percentilesSpec from "../vega-specs/spec-percentiles.json"; import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
@ -46,7 +45,7 @@ export type FunctionChartSettings = {
}; };
interface FunctionChart1DistProps { interface FunctionChart1DistProps {
fn: lambdaValue; fn: SqLambda;
chartSettings: FunctionChartSettings; chartSettings: FunctionChartSettings;
distributionPlotSettings: DistributionPlottingSettings; distributionPlotSettings: DistributionPlottingSettings;
environment: environment; environment: environment;
@ -77,9 +76,17 @@ type errors = _.Dictionary<
}[] }[]
>; >;
type point = { x: number; value: result<Distribution, string> }; type point = { x: number; value: result<SqDistribution, string> };
let getPercentiles = ({ chartSettings, fn, environment }) => { let getPercentiles = ({
chartSettings,
fn,
environment,
}: {
chartSettings: FunctionChartSettings;
fn: SqLambda;
environment: environment;
}) => {
let chartPointsToRender = _rangeByCount( let chartPointsToRender = _rangeByCount(
chartSettings.start, chartSettings.start,
chartSettings.stop, chartSettings.stop,
@ -87,9 +94,9 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
); );
let chartPointsData: point[] = chartPointsToRender.map((x) => { let chartPointsData: point[] = chartPointsToRender.map((x) => {
let result = runForeign(fn, [x], environment); let result = fn.call([x]);
if (result.tag === "Ok") { if (result.tag === "Ok") {
if (result.value.tag === "distribution") { if (result.value.tag === SqValueTag.Distribution) {
return { x, value: { tag: "Ok", value: result.value.value } }; return { x, value: { tag: "Ok", value: result.value.value } };
} else { } else {
return { return {
@ -104,13 +111,13 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
} else { } else {
return { return {
x, x,
value: { tag: "Error", value: errorValueToString(result.value) }, value: { tag: "Error", value: result.value.toString() },
}; };
} }
}); });
let initialPartition: [ let initialPartition: [
{ x: number; value: Distribution }[], { x: number; value: SqDistribution }[],
{ x: number; value: string }[] { x: number; value: string }[]
] = [[], []]; ] = [[], []];
@ -126,26 +133,23 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
let groupedErrors: errors = _.groupBy(errors, (x) => x.value); let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
let percentiles: percentiles = functionImage.map(({ x, value }) => { let percentiles: percentiles = functionImage.map(({ x, value }) => {
// We convert it to to a pointSet distribution first, so that in case its a sample set const res = {
// distribution, it doesn't internally convert it to a pointSet distribution for every
// single inv() call.
let toPointSet: Distribution = unwrap(value.toPointSet());
return {
x: x, x: x,
p1: unwrap(toPointSet.inv(0.01)), p1: unwrap(value.inv(environment, 0.01)),
p5: unwrap(toPointSet.inv(0.05)), p5: unwrap(value.inv(environment, 0.05)),
p10: unwrap(toPointSet.inv(0.1)), p10: unwrap(value.inv(environment, 0.1)),
p20: unwrap(toPointSet.inv(0.2)), p20: unwrap(value.inv(environment, 0.2)),
p30: unwrap(toPointSet.inv(0.3)), p30: unwrap(value.inv(environment, 0.3)),
p40: unwrap(toPointSet.inv(0.4)), p40: unwrap(value.inv(environment, 0.4)),
p50: unwrap(toPointSet.inv(0.5)), p50: unwrap(value.inv(environment, 0.5)),
p60: unwrap(toPointSet.inv(0.6)), p60: unwrap(value.inv(environment, 0.6)),
p70: unwrap(toPointSet.inv(0.7)), p70: unwrap(value.inv(environment, 0.7)),
p80: unwrap(toPointSet.inv(0.8)), p80: unwrap(value.inv(environment, 0.8)),
p90: unwrap(toPointSet.inv(0.9)), p90: unwrap(value.inv(environment, 0.9)),
p95: unwrap(toPointSet.inv(0.95)), p95: unwrap(value.inv(environment, 0.95)),
p99: unwrap(toPointSet.inv(0.99)), p99: unwrap(value.inv(environment, 0.99)),
}; };
return res;
}); });
return { percentiles, errors: groupedErrors }; return { percentiles, errors: groupedErrors };
@ -168,19 +172,20 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
const signalListeners = { mousemove: handleHover, mouseout: handleOut }; const signalListeners = { mousemove: handleHover, mouseout: handleOut };
//TODO: This custom error handling is a bit hacky and should be improved. //TODO: This custom error handling is a bit hacky and should be improved.
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay let mouseItem: result<SqValue, SqError> = !!mouseOverlay
? runForeign(fn, [mouseOverlay], environment) ? fn.call([mouseOverlay])
: { : {
tag: "Error", tag: "Error",
value: { value: SqError.createOtherError(
tag: "RETodo", "Hover x-coordinate returned NaN. Expected a number."
value: "Hover x-coordinate returned NaN. Expected a number.", ),
},
}; };
let showChart = let showChart =
mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? ( mouseItem.tag === "Ok" &&
mouseItem.value.tag === SqValueTag.Distribution ? (
<DistributionChart <DistributionChart
plot={defaultPlot(mouseItem.value.value)} plot={defaultPlot(mouseItem.value.value)}
environment={environment}
width={400} width={400}
height={50} height={50}
{...distributionPlotSettings} {...distributionPlotSettings}

View File

@ -1,16 +1,11 @@
import * as React from "react"; import * as React from "react";
import _ from "lodash"; import _ from "lodash";
import type { Spec } from "vega"; import type { Spec } from "vega";
import { import { result, SqLambda, environment, SqValueTag } from "@quri/squiggle-lang";
result,
lambdaValue,
environment,
runForeign,
errorValueToString,
} from "@quri/squiggle-lang";
import { createClassFromSpec } from "react-vega"; import { createClassFromSpec } from "react-vega";
import * as lineChartSpec from "../vega-specs/spec-line-chart.json"; import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
import { ErrorAlert } from "./Alert"; import { ErrorAlert } from "./Alert";
import { squiggleValueTag } from "@quri/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag";
let SquiggleLineChart = createClassFromSpec({ let SquiggleLineChart = createClassFromSpec({
spec: lineChartSpec as Spec, spec: lineChartSpec as Spec,
@ -30,7 +25,7 @@ export type FunctionChartSettings = {
}; };
interface FunctionChart1NumberProps { interface FunctionChart1NumberProps {
fn: lambdaValue; fn: SqLambda;
chartSettings: FunctionChartSettings; chartSettings: FunctionChartSettings;
environment: environment; environment: environment;
height: number; height: number;
@ -38,7 +33,15 @@ interface FunctionChart1NumberProps {
type point = { x: number; value: result<number, string> }; type point = { x: number; value: result<number, string> };
let getFunctionImage = ({ chartSettings, fn, environment }) => { let getFunctionImage = ({
chartSettings,
fn,
environment,
}: {
chartSettings: FunctionChartSettings;
fn: SqLambda;
environment: environment;
}) => {
let chartPointsToRender = _rangeByCount( let chartPointsToRender = _rangeByCount(
chartSettings.start, chartSettings.start,
chartSettings.stop, chartSettings.stop,
@ -46,9 +49,9 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => {
); );
let chartPointsData: point[] = chartPointsToRender.map((x) => { let chartPointsData: point[] = chartPointsToRender.map((x) => {
let result = runForeign(fn, [x], environment); let result = fn.call([x]);
if (result.tag === "Ok") { if (result.tag === "Ok") {
if (result.value.tag == "number") { if (result.value.tag === SqValueTag.Number) {
return { x, value: { tag: "Ok", value: result.value.value } }; return { x, value: { tag: "Ok", value: result.value.value } };
} else { } else {
return { return {
@ -62,7 +65,7 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => {
} else { } else {
return { return {
x, x,
value: { tag: "Error", value: errorValueToString(result.value) }, value: { tag: "Error", value: result.value.toString() },
}; };
} }
}); });

View File

@ -1,15 +1,14 @@
import * as React from "react"; import * as React from "react";
import { import {
squiggleExpression, SqValue,
bindings,
environment, environment,
jsImports,
defaultImports,
defaultBindings,
defaultEnvironment, defaultEnvironment,
resultMap,
SqValueTag,
} from "@quri/squiggle-lang"; } from "@quri/squiggle-lang";
import { useSquiggle } from "../lib/hooks"; import { useSquiggle } from "../lib/hooks";
import { SquiggleViewer } from "./SquiggleViewer"; import { SquiggleViewer } from "./SquiggleViewer";
import { JsImports } from "../lib/jsImports";
export interface SquiggleChartProps { export interface SquiggleChartProps {
/** The input string for squiggle */ /** The input string for squiggle */
@ -27,14 +26,12 @@ export interface SquiggleChartProps {
/** If the result is a function, the amount of stops sampled */ /** If the result is a function, the amount of stops sampled */
diagramCount?: number; diagramCount?: number;
/** When the squiggle code gets reevaluated */ /** When the squiggle code gets reevaluated */
onChange?(expr: squiggleExpression | undefined): void; onChange?(expr: SqValue | undefined): void;
/** CSS width of the element */ /** CSS width of the element */
width?: number; width?: number;
height?: number; height?: number;
/** Bindings of previous variables declared */
bindings?: bindings;
/** JS imported parameters */ /** JS imported parameters */
jsImports?: jsImports; jsImports?: JsImports;
/** Whether to show a summary of the distribution */ /** Whether to show a summary of the distribution */
showSummary?: boolean; showSummary?: boolean;
/** Set the x scale to be logarithmic by deault */ /** Set the x scale to be logarithmic by deault */
@ -59,6 +56,7 @@ export interface SquiggleChartProps {
} }
const defaultOnChange = () => {}; const defaultOnChange = () => {};
const defaultImports: JsImports = {};
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo( export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
({ ({
@ -67,7 +65,6 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
environment, environment,
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
height = 200, height = 200,
bindings = defaultBindings,
jsImports = defaultImports, jsImports = defaultImports,
showSummary = false, showSummary = false,
width, width,
@ -85,9 +82,8 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
distributionChartActions, distributionChartActions,
enableLocalSettings = false, enableLocalSettings = false,
}) => { }) => {
const result = useSquiggle({ const { result, bindings } = useSquiggle({
code, code,
bindings,
environment, environment,
jsImports, jsImports,
onChange, onChange,
@ -113,9 +109,13 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
count: diagramCount, count: diagramCount,
}; };
const resultToRender = resultMap(result, (value) =>
value.tag === SqValueTag.Void ? bindings.asValue() : value
);
return ( return (
<SquiggleViewer <SquiggleViewer
result={result} result={resultToRender}
width={width} width={width}
height={height} height={height}
distributionPlotSettings={distributionPlotSettings} distributionPlotSettings={distributionPlotSettings}

View File

@ -1,11 +1,8 @@
import React from "react"; import React from "react";
import { CodeEditor } from "./CodeEditor"; import { CodeEditor } from "./CodeEditor";
import { environment, bindings, jsImports } from "@quri/squiggle-lang";
import { defaultImports, defaultBindings } from "@quri/squiggle-lang";
import { SquiggleContainer } from "./SquiggleContainer"; import { SquiggleContainer } from "./SquiggleContainer";
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart";
import { useSquigglePartial, useMaybeControlledValue } from "../lib/hooks"; import { useMaybeControlledValue } from "../lib/hooks";
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
const WrappedCodeEditor: React.FC<{ const WrappedCodeEditor: React.FC<{
code: string; code: string;
@ -42,51 +39,3 @@ export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
</SquiggleContainer> </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>
);
};

View File

@ -1,52 +0,0 @@
import React from "react";
import { SquiggleEditor } from "./SquiggleEditor";
import type { SquiggleEditorProps } from "./SquiggleEditor";
import { runPartial, defaultBindings } from "@quri/squiggle-lang";
import type {
result,
errorValue,
bindings as bindingsType,
} from "@quri/squiggle-lang";
function resultDefault(x: result<bindingsType, errorValue>): bindingsType {
switch (x.tag) {
case "Ok":
return x.value;
case "Error":
return defaultBindings;
}
}
export type SquiggleEditorWithImportedBindingsProps = SquiggleEditorProps & {
bindingsImportUrl: string;
};
export const SquiggleEditorWithImportedBindings: React.FC<
SquiggleEditorWithImportedBindingsProps
> = (props) => {
const { bindingsImportUrl, ...editorProps } = props;
const [bindingsResult, setBindingsResult] = React.useState({
tag: "Ok",
value: defaultBindings,
} as result<bindingsType, errorValue>);
React.useEffect(() => {
async function retrieveBindings(fileName: string) {
let contents = await fetch(fileName).then((response) => {
return response.text();
});
setBindingsResult(
runPartial(
contents,
editorProps.bindings,
editorProps.environment,
editorProps.jsImports
)
);
}
retrieveBindings(bindingsImportUrl);
}, [bindingsImportUrl]);
const deliveredBindings = resultDefault(bindingsResult);
return (
<SquiggleEditor {...{ ...editorProps, bindings: deliveredBindings }} />
);
};

View File

@ -1,11 +1,11 @@
import { errorValue, errorValueToString } from "@quri/squiggle-lang"; import { SqError } from "@quri/squiggle-lang";
import React from "react"; import React from "react";
import { ErrorAlert } from "./Alert"; import { ErrorAlert } from "./Alert";
type Props = { type Props = {
error: errorValue; error: SqError;
}; };
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => { export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
return <ErrorAlert heading="Error">{errorValueToString(error)}</ErrorAlert>; return <ErrorAlert heading="Error">{error.toString()}</ErrorAlert>;
}; };

View File

@ -24,7 +24,7 @@ import {
} from "@heroicons/react/solid"; } from "@heroicons/react/solid";
import clsx from "clsx"; import clsx from "clsx";
import { defaultBindings, environment } from "@quri/squiggle-lang"; import { environment } from "@quri/squiggle-lang";
import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart"; import { SquiggleChart, SquiggleChartProps } from "./SquiggleChart";
import { CodeEditor } from "./CodeEditor"; import { CodeEditor } from "./CodeEditor";
@ -39,6 +39,7 @@ import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
import { HeadedSection } from "./ui/HeadedSection"; import { HeadedSection } from "./ui/HeadedSection";
import { defaultTickFormat } from "../lib/distributionSpecBuilder"; import { defaultTickFormat } from "../lib/distributionSpecBuilder";
import { Button } from "./ui/Button"; import { Button } from "./ui/Button";
import { JsImports } from "../lib/jsImports";
type PlaygroundProps = SquiggleChartProps & { type PlaygroundProps = SquiggleChartProps & {
/** The initial squiggle string to put in the playground */ /** The initial squiggle string to put in the playground */
@ -112,8 +113,8 @@ const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
); );
const InputVariablesSettings: React.FC<{ const InputVariablesSettings: React.FC<{
initialImports: any; // TODO - any json type initialImports: JsImports;
setImports: (imports: any) => void; setImports: (imports: JsImports) => void;
}> = ({ initialImports, setImports }) => { }> = ({ initialImports, setImports }) => {
const [importString, setImportString] = useState(() => const [importString, setImportString] = useState(() =>
JSON.stringify(initialImports) JSON.stringify(initialImports)
@ -122,7 +123,7 @@ const InputVariablesSettings: React.FC<{
const onChange = (value: string) => { const onChange = (value: string) => {
setImportString(value); setImportString(value);
let imports = {} as any; let imports = {};
try { try {
imports = JSON.parse(value); imports = JSON.parse(value);
setImportsAreValid(true); setImportsAreValid(true);
@ -231,7 +232,7 @@ export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
export const SquigglePlayground: FC<PlaygroundProps> = ({ export const SquigglePlayground: FC<PlaygroundProps> = ({
defaultCode = "", defaultCode = "",
height = 500, height = 500,
showSummary = false, showSummary = true,
logX = false, logX = false,
expY = false, expY = false,
title, title,
@ -251,7 +252,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
onChange: onCodeChange, onChange: onCodeChange,
}); });
const [imports, setImports] = useState({}); const [imports, setImports] = useState<JsImports>({});
const { register, control } = useForm({ const { register, control } = useForm({
resolver: yupResolver(schema), resolver: yupResolver(schema),
@ -309,7 +310,6 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
executionId={executionId} executionId={executionId}
environment={env} environment={env}
{...vars} {...vars}
bindings={defaultBindings}
jsImports={imports} jsImports={imports}
enableLocalSettings={true} enableLocalSettings={true}
/> />

View File

@ -1,5 +1,5 @@
import React from "react"; import React, { useContext } from "react";
import { squiggleExpression, declaration } from "@quri/squiggle-lang"; import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
import { NumberShower } from "../NumberShower"; import { NumberShower } from "../NumberShower";
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart"; import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
import { FunctionChart, FunctionChartSettings } from "../FunctionChart"; import { FunctionChart, FunctionChartSettings } from "../FunctionChart";
@ -8,7 +8,10 @@ import { VariableBox } from "./VariableBox";
import { ItemSettingsMenu } from "./ItemSettingsMenu"; import { ItemSettingsMenu } from "./ItemSettingsMenu";
import { hasMassBelowZero } from "../../lib/distributionUtils"; import { hasMassBelowZero } from "../../lib/distributionUtils";
import { MergedItemSettings } from "./utils"; import { MergedItemSettings } from "./utils";
import { ViewerContext } from "./ViewerContext";
/*
// DISABLED FOR 0.4 branch, for now
function getRange<a>(x: declaration<a>) { function getRange<a>(x: declaration<a>) {
const first = x.args[0]; const first = x.args[0];
switch (first.tag) { switch (first.tag) {
@ -31,15 +34,21 @@ function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
count: 20, count: 20,
}; };
} }
*/
const VariableList: React.FC<{ const VariableList: React.FC<{
path: string[]; value: SqValue;
heading: string; heading: string;
children: (settings: MergedItemSettings) => React.ReactNode; children: (settings: MergedItemSettings) => React.ReactNode;
}> = ({ path, heading, children }) => ( }> = ({ value, heading, children }) => (
<VariableBox path={path} heading={heading}> <VariableBox value={value} heading={heading}>
{(settings) => ( {(settings) => (
<div className={clsx("space-y-3", path.length ? "pt-1 mt-1" : null)}> <div
className={clsx(
"space-y-3",
value.location.path.items.length ? "pt-1 mt-1" : null
)}
>
{children(settings)} {children(settings)}
</div> </div>
)} )}
@ -48,51 +57,44 @@ const VariableList: React.FC<{
export interface Props { export interface Props {
/** The output of squiggle's run */ /** The output of squiggle's run */
expression: squiggleExpression; value: SqValue;
/** Path to the current item, e.g. `['foo', 'bar', '3']` for `foo.bar[3]`; can be empty on the top-level item. */
path: string[];
width?: number; width?: number;
} }
export const ExpressionViewer: React.FC<Props> = ({ export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
path, const { getMergedSettings } = useContext(ViewerContext);
expression,
width, switch (value.tag) {
}) => { case SqValueTag.Number:
if (typeof expression !== "object") {
return (
<VariableList path={path} heading="Error">
{() => `Unknown expression: ${expression}`}
</VariableList>
);
}
switch (expression.tag) {
case "number":
return ( return (
<VariableBox path={path} heading="Number"> <VariableBox value={value} heading="Number">
{() => ( {() => (
<div className="font-semibold text-slate-600"> <div className="font-semibold text-slate-600">
<NumberShower precision={3} number={expression.value} /> <NumberShower precision={3} number={value.value} />
</div> </div>
)} )}
</VariableBox> </VariableBox>
); );
case "distribution": { case SqValueTag.Distribution: {
const distType = expression.value.type(); const distType = value.value.tag;
return ( return (
<VariableBox <VariableBox
path={path} value={value}
heading={`Distribution (${distType})\n${ heading={`Distribution (${distType})\n${
distType === "Symbolic" ? expression.value.toString() : "" distType === SqDistributionTag.Symbolic
? value.value.toString()
: ""
}`} }`}
renderSettingsMenu={({ onChange }) => { renderSettingsMenu={({ onChange }) => {
const shape = expression.value.pointSet(); const shape = value.value.pointSet(
getMergedSettings(value.location).environment
);
return ( return (
<ItemSettingsMenu <ItemSettingsMenu
path={path} value={value}
onChange={onChange} onChange={onChange}
disableLogX={ disableLogX={
shape.tag === "Ok" && hasMassBelowZero(shape.value) shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape())
} }
withFunctionSettings={false} withFunctionSettings={false}
/> />
@ -102,7 +104,8 @@ export const ExpressionViewer: React.FC<Props> = ({
{(settings) => { {(settings) => {
return ( return (
<DistributionChart <DistributionChart
plot={defaultPlot(expression.value)} plot={defaultPlot(value.value)}
environment={settings.environment}
{...settings.distributionPlotSettings} {...settings.distributionPlotSettings}
height={settings.height} height={settings.height}
width={width} width={width}
@ -112,77 +115,77 @@ export const ExpressionViewer: React.FC<Props> = ({
</VariableBox> </VariableBox>
); );
} }
case "string": case SqValueTag.String:
return ( return (
<VariableBox path={path} heading="String"> <VariableBox value={value} heading="String">
{() => ( {() => (
<> <>
<span className="text-slate-400">"</span> <span className="text-slate-400">"</span>
<span className="text-slate-600 font-semibold font-mono"> <span className="text-slate-600 font-semibold font-mono">
{expression.value} {value.value}
</span> </span>
<span className="text-slate-400">"</span> <span className="text-slate-400">"</span>
</> </>
)} )}
</VariableBox> </VariableBox>
); );
case "boolean": case SqValueTag.Bool:
return ( return (
<VariableBox path={path} heading="Boolean"> <VariableBox value={value} heading="Boolean">
{() => expression.value.toString()} {() => value.value.toString()}
</VariableBox> </VariableBox>
); );
case "symbol": case SqValueTag.Symbol:
return ( return (
<VariableBox path={path} heading="Symbol"> <VariableBox value={value} heading="Symbol">
{() => ( {() => (
<> <>
<span className="text-slate-500 mr-2">Undefined Symbol:</span> <span className="text-slate-500 mr-2">Undefined Symbol:</span>
<span className="text-slate-600">{expression.value}</span> <span className="text-slate-600">{value.value}</span>
</> </>
)} )}
</VariableBox> </VariableBox>
); );
case "call": case SqValueTag.Call:
return ( return (
<VariableBox path={path} heading="Call"> <VariableBox value={value} heading="Call">
{() => expression.value} {() => value.value}
</VariableBox> </VariableBox>
); );
case "arraystring": case SqValueTag.ArrayString:
return ( return (
<VariableBox path={path} heading="Array String"> <VariableBox value={value} heading="Array String">
{() => expression.value.map((r) => `"${r}"`).join(", ")} {() => value.value.map((r) => `"${r}"`).join(", ")}
</VariableBox> </VariableBox>
); );
case "date": case SqValueTag.Date:
return ( return (
<VariableBox path={path} heading="Date"> <VariableBox value={value} heading="Date">
{() => expression.value.toDateString()} {() => value.value.toDateString()}
</VariableBox> </VariableBox>
); );
case "void": case SqValueTag.Void:
return ( return (
<VariableBox path={path} heading="Void"> <VariableBox value={value} heading="Void">
{() => "Void"} {() => "Void"}
</VariableBox> </VariableBox>
); );
case "timeDuration": { case SqValueTag.TimeDuration: {
return ( return (
<VariableBox path={path} heading="Time Duration"> <VariableBox value={value} heading="Time Duration">
{() => <NumberShower precision={3} number={expression.value} />} {() => <NumberShower precision={3} number={value.value} />}
</VariableBox> </VariableBox>
); );
} }
case "lambda": case SqValueTag.Lambda:
return ( return (
<VariableBox <VariableBox
path={path} value={value}
heading="Function" heading="Function"
renderSettingsMenu={({ onChange }) => { renderSettingsMenu={({ onChange }) => {
return ( return (
<ItemSettingsMenu <ItemSettingsMenu
path={path} value={value}
onChange={onChange} onChange={onChange}
withFunctionSettings={true} withFunctionSettings={true}
/> />
@ -191,11 +194,11 @@ export const ExpressionViewer: React.FC<Props> = ({
> >
{(settings) => ( {(settings) => (
<> <>
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join( <div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${value.value
"," .parameters()
)})`}</div> .join(",")})`}</div>
<FunctionChart <FunctionChart
fn={expression.value} fn={value.value}
chartSettings={settings.chartSettings} chartSettings={settings.chartSettings}
distributionPlotSettings={settings.distributionPlotSettings} distributionPlotSettings={settings.distributionPlotSettings}
height={settings.height} height={settings.height}
@ -208,47 +211,48 @@ export const ExpressionViewer: React.FC<Props> = ({
)} )}
</VariableBox> </VariableBox>
); );
case "lambdaDeclaration": { case SqValueTag.Declaration: {
return ( return (
<VariableBox <VariableBox
path={path} value={value}
heading="Function Declaration" heading="Function Declaration"
renderSettingsMenu={({ onChange }) => { renderSettingsMenu={({ onChange }) => {
return ( return (
<ItemSettingsMenu <ItemSettingsMenu
onChange={onChange} onChange={onChange}
path={path} value={value}
withFunctionSettings={true} withFunctionSettings={true}
/> />
); );
}} }}
> >
{(settings) => ( {(settings) => (
<FunctionChart <div>NOT IMPLEMENTED IN 0.4 YET</div>
fn={expression.value.fn} // <FunctionChart
chartSettings={getChartSettings(expression.value)} // fn={expression.value.fn}
distributionPlotSettings={settings.distributionPlotSettings} // chartSettings={getChartSettings(expression.value)}
height={settings.height} // distributionPlotSettings={settings.distributionPlotSettings}
environment={{ // height={settings.height}
sampleCount: settings.environment.sampleCount / 10, // environment={{
xyPointLength: settings.environment.xyPointLength / 10, // sampleCount: settings.environment.sampleCount / 10,
}} // xyPointLength: settings.environment.xyPointLength / 10,
/> // }}
// />
)} )}
</VariableBox> </VariableBox>
); );
} }
case "module": { case SqValueTag.Module: {
return ( return (
<VariableList path={path} heading="Module"> <VariableList value={value} heading="Module">
{(_) => {(_) =>
Object.entries(expression.value) value.value
.filter(([key, _]) => !key.match(/^(Math|System)\./)) .entries()
.filter(([key, _]) => !key.match(/^(__result__)$/))
.map(([key, r]) => ( .map(([key, r]) => (
<ExpressionViewer <ExpressionViewer
key={key} key={key}
path={[...path, key]} value={r}
expression={r}
width={width !== undefined ? width - 20 : width} width={width !== undefined ? width - 20 : width}
/> />
)) ))
@ -256,23 +260,26 @@ export const ExpressionViewer: React.FC<Props> = ({
</VariableList> </VariableList>
); );
} }
case "record": case SqValueTag.Record:
const plot = makePlot(expression.value); const plot = makePlot(value.value);
if (plot) { if (plot) {
return ( return (
<VariableBox <VariableBox
path={path} value={value}
heading={"Plot"} heading="Plot"
renderSettingsMenu={({ onChange }) => { renderSettingsMenu={({ onChange }) => {
let disableLogX = plot.distributions.some((x) => { let disableLogX = plot.distributions.some((x) => {
let pointSet = x.distribution.pointSet(); let pointSet = x.distribution.pointSet(
getMergedSettings(value.location).environment
);
return ( return (
pointSet.tag === "Ok" && hasMassBelowZero(pointSet.value) pointSet.tag === "Ok" &&
hasMassBelowZero(pointSet.value.asShape())
); );
}); });
return ( return (
<ItemSettingsMenu <ItemSettingsMenu
path={path} value={value}
onChange={onChange} onChange={onChange}
disableLogX={disableLogX} disableLogX={disableLogX}
withFunctionSettings={false} withFunctionSettings={false}
@ -284,6 +291,7 @@ export const ExpressionViewer: React.FC<Props> = ({
return ( return (
<DistributionChart <DistributionChart
plot={plot} plot={plot}
environment={settings.environment}
{...settings.distributionPlotSettings} {...settings.distributionPlotSettings}
height={settings.height} height={settings.height}
width={width} width={width}
@ -294,44 +302,44 @@ export const ExpressionViewer: React.FC<Props> = ({
); );
} else { } else {
return ( return (
<VariableList path={path} heading="Record"> <VariableList value={value} heading="Record">
{(_) => {(_) =>
Object.entries(expression.value).map(([key, r]) => ( value.value
<ExpressionViewer .entries()
key={key} .map(([key, r]) => (
path={[...path, key]} <ExpressionViewer
expression={r} key={key}
width={width !== undefined ? width - 20 : width} value={r}
/> width={width !== undefined ? width - 20 : width}
)) />
))
} }
</VariableList> </VariableList>
); );
} }
case "array": case SqValueTag.Array:
return ( return (
<VariableList path={path} heading="Array"> <VariableList value={value} heading="Array">
{(_) => {(_) =>
expression.value.map((r, i) => ( value.value
<ExpressionViewer .getValues()
key={i} .map((r, i) => (
path={[...path, String(i)]} <ExpressionViewer
expression={r} key={i}
width={width !== undefined ? width - 20 : width} value={r}
/> width={width !== undefined ? width - 20 : width}
)) />
))
} }
</VariableList> </VariableList>
); );
default: { default: {
return ( return (
<VariableList path={path} heading="Error"> <VariableList value={value} heading="Error">
{() => ( {() => (
<div> <div>
<span>No display for type: </span>{" "} <span>No display for type: </span>{" "}
<span className="font-semibold text-slate-600"> <span className="font-semibold text-slate-600">{value.tag}</span>
{expression.tag}
</span>
</div> </div>
)} )}
</VariableList> </VariableList>

View File

@ -4,13 +4,14 @@ import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from "@hookform/resolvers/yup";
import { Modal } from "../ui/Modal"; import { Modal } from "../ui/Modal";
import { ViewSettings, viewSettingsSchema } from "../ViewSettings"; import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
import { Path, pathAsString } from "./utils";
import { ViewerContext } from "./ViewerContext"; import { ViewerContext } from "./ViewerContext";
import { defaultTickFormat } from "../../lib/distributionSpecBuilder"; import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
import { PlaygroundContext } from "../SquigglePlayground"; import { PlaygroundContext } from "../SquigglePlayground";
import { SqValue } from "@quri/squiggle-lang";
import { locationAsString } from "./utils";
type Props = { type Props = {
path: Path; value: SqValue;
onChange: () => void; onChange: () => void;
disableLogX?: boolean; disableLogX?: boolean;
withFunctionSettings: boolean; withFunctionSettings: boolean;
@ -19,7 +20,7 @@ type Props = {
const ItemSettingsModal: React.FC< const ItemSettingsModal: React.FC<
Props & { close: () => void; resetScroll: () => void } Props & { close: () => void; resetScroll: () => void }
> = ({ > = ({
path, value,
onChange, onChange,
disableLogX, disableLogX,
withFunctionSettings, withFunctionSettings,
@ -29,7 +30,7 @@ const ItemSettingsModal: React.FC<
const { setSettings, getSettings, getMergedSettings } = const { setSettings, getSettings, getMergedSettings } =
useContext(ViewerContext); useContext(ViewerContext);
const mergedSettings = getMergedSettings(path); const mergedSettings = getMergedSettings(value.location);
const { register, watch } = useForm({ const { register, watch } = useForm({
resolver: yupResolver(viewSettingsSchema), resolver: yupResolver(viewSettingsSchema),
@ -53,8 +54,8 @@ const ItemSettingsModal: React.FC<
}); });
useEffect(() => { useEffect(() => {
const subscription = watch((vars) => { const subscription = watch((vars) => {
const settings = getSettings(path); // get the latest version const settings = getSettings(value.location); // get the latest version
setSettings(path, { setSettings(value.location, {
...settings, ...settings,
distributionPlotSettings: { distributionPlotSettings: {
showSummary: vars.showSummary, showSummary: vars.showSummary,
@ -75,7 +76,7 @@ const ItemSettingsModal: React.FC<
onChange(); onChange();
}); });
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
}, [getSettings, setSettings, onChange, path, watch]); }, [getSettings, setSettings, onChange, value.location, watch]);
const { getLeftPanelElement } = useContext(PlaygroundContext); const { getLeftPanelElement } = useContext(PlaygroundContext);
@ -83,7 +84,7 @@ const ItemSettingsModal: React.FC<
<Modal container={getLeftPanelElement()} close={close}> <Modal container={getLeftPanelElement()} close={close}>
<Modal.Header> <Modal.Header>
Chart settings Chart settings
{path.length ? ( {value.location.path.items.length ? (
<> <>
{" for "} {" for "}
<span <span
@ -91,7 +92,7 @@ const ItemSettingsModal: React.FC<
className="cursor-pointer" className="cursor-pointer"
onClick={resetScroll} onClick={resetScroll}
> >
{pathAsString(path)} {locationAsString(value.location)}
</span>{" "} </span>{" "}
</> </>
) : ( ) : (
@ -120,7 +121,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
if (!enableLocalSettings) { if (!enableLocalSettings) {
return null; return null;
} }
const settings = getSettings(props.path); const settings = getSettings(props.value.location);
const resetScroll = () => { const resetScroll = () => {
if (!ref.current) return; if (!ref.current) return;
@ -139,7 +140,7 @@ export const ItemSettingsMenu: React.FC<Props> = (props) => {
{settings.distributionPlotSettings || settings.chartSettings ? ( {settings.distributionPlotSettings || settings.chartSettings ? (
<button <button
onClick={() => { onClick={() => {
setSettings(props.path, { setSettings(props.value.location, {
...settings, ...settings,
distributionPlotSettings: undefined, distributionPlotSettings: undefined,
chartSettings: undefined, chartSettings: undefined,

View File

@ -1,3 +1,4 @@
import { SqValue, SqValueLocation } from "@quri/squiggle-lang";
import React, { useContext, useReducer } from "react"; import React, { useContext, useReducer } from "react";
import { Tooltip } from "../ui/Tooltip"; import { Tooltip } from "../ui/Tooltip";
import { LocalItemSettings, MergedItemSettings } from "./utils"; import { LocalItemSettings, MergedItemSettings } from "./utils";
@ -8,14 +9,14 @@ type SettingsMenuParams = {
}; };
type VariableBoxProps = { type VariableBoxProps = {
path: string[]; value: SqValue;
heading: string; heading: string;
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode; renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
children: (settings: MergedItemSettings) => React.ReactNode; children: (settings: MergedItemSettings) => React.ReactNode;
}; };
export const VariableBox: React.FC<VariableBoxProps> = ({ export const VariableBox: React.FC<VariableBoxProps> = ({
path, value: { location },
heading = "Error", heading = "Error",
renderSettingsMenu, renderSettingsMenu,
children, children,
@ -27,10 +28,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
// So we use `forceUpdate` to force rerendering. // So we use `forceUpdate` to force rerendering.
const [_, forceUpdate] = useReducer((x) => x + 1, 0); const [_, forceUpdate] = useReducer((x) => x + 1, 0);
const settings = getSettings(path); const settings = getSettings(location);
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => { const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
setSettings(path, newSettings); setSettings(location, newSettings);
forceUpdate(); forceUpdate();
}; };
@ -38,8 +39,10 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed }); setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
}; };
const isTopLevel = path.length === 0; const isTopLevel = location.path.items.length === 0;
const name = isTopLevel ? "Result" : path[path.length - 1]; const name = isTopLevel
? { result: "Result", bindings: "Bindings" }[location.path.root]
: location.path.items[location.path.items.length - 1];
return ( return (
<div> <div>
@ -65,13 +68,13 @@ export const VariableBox: React.FC<VariableBoxProps> = ({
</header> </header>
{settings.collapsed ? null : ( {settings.collapsed ? null : (
<div className="flex w-full"> <div className="flex w-full">
{path.length ? ( {location.path.items.length ? (
<div <div
className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer" className="border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
onClick={toggleCollapsed} onClick={toggleCollapsed}
></div> ></div>
) : null} ) : null}
<div className="grow">{children(getMergedSettings(path))}</div> <div className="grow">{children(getMergedSettings(location))}</div>
</div> </div>
)} )}
</div> </div>

View File

@ -1,14 +1,14 @@
import { defaultEnvironment } from "@quri/squiggle-lang"; import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
import React from "react"; import React from "react";
import { LocalItemSettings, MergedItemSettings, Path } from "./utils"; import { LocalItemSettings, MergedItemSettings } from "./utils";
type ViewerContextShape = { type ViewerContextShape = {
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update). // Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
// Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch. // Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
// See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this. // See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
getSettings(path: Path): LocalItemSettings; getSettings(location: SqValueLocation): LocalItemSettings;
getMergedSettings(path: Path): MergedItemSettings; getMergedSettings(location: SqValueLocation): MergedItemSettings;
setSettings(path: Path, value: LocalItemSettings): void; setSettings(location: SqValueLocation, value: LocalItemSettings): void;
enableLocalSettings: boolean; // show local settings icon in the UI enableLocalSettings: boolean; // show local settings icon in the UI
}; };

View File

@ -1,21 +1,20 @@
import React, { useCallback, useRef } from "react"; import React, { useCallback, useRef } from "react";
import { environment } from "@quri/squiggle-lang"; import { environment, SqValueLocation } from "@quri/squiggle-lang";
import { DistributionPlottingSettings } from "../DistributionChart"; import { DistributionPlottingSettings } from "../DistributionChart";
import { FunctionChartSettings } from "../FunctionChart"; import { FunctionChartSettings } from "../FunctionChart";
import { ExpressionViewer } from "./ExpressionViewer"; import { ExpressionViewer } from "./ExpressionViewer";
import { ViewerContext } from "./ViewerContext"; import { ViewerContext } from "./ViewerContext";
import { import {
LocalItemSettings, LocalItemSettings,
locationAsString,
MergedItemSettings, MergedItemSettings,
Path,
pathAsString,
} from "./utils"; } from "./utils";
import { useSquiggle } from "../../lib/hooks"; import { useSquiggle } from "../../lib/hooks";
import { SquiggleErrorAlert } from "../SquiggleErrorAlert"; import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
type Props = { type Props = {
/** The output of squiggle's run */ /** The output of squiggle's run */
result: ReturnType<typeof useSquiggle>; result: ReturnType<typeof useSquiggle>["result"];
width?: number; width?: number;
height: number; height: number;
distributionPlotSettings: DistributionPlottingSettings; distributionPlotSettings: DistributionPlottingSettings;
@ -45,22 +44,22 @@ export const SquiggleViewer: React.FC<Props> = ({
const settingsRef = useRef<Settings>({}); const settingsRef = useRef<Settings>({});
const getSettings = useCallback( const getSettings = useCallback(
(path: Path) => { (location: SqValueLocation) => {
return settingsRef.current[pathAsString(path)] || defaultSettings; return settingsRef.current[locationAsString(location)] || defaultSettings;
}, },
[settingsRef] [settingsRef]
); );
const setSettings = useCallback( const setSettings = useCallback(
(path: Path, value: LocalItemSettings) => { (location: SqValueLocation, value: LocalItemSettings) => {
settingsRef.current[pathAsString(path)] = value; settingsRef.current[locationAsString(location)] = value;
}, },
[settingsRef] [settingsRef]
); );
const getMergedSettings = useCallback( const getMergedSettings = useCallback(
(path: Path) => { (location: SqValueLocation) => {
const localSettings = getSettings(path); const localSettings = getSettings(location);
const result: MergedItemSettings = { const result: MergedItemSettings = {
distributionPlotSettings: { distributionPlotSettings: {
...distributionPlotSettings, ...distributionPlotSettings,
@ -91,7 +90,7 @@ export const SquiggleViewer: React.FC<Props> = ({
}} }}
> >
{result.tag === "Ok" ? ( {result.tag === "Ok" ? (
<ExpressionViewer path={[]} expression={result.value} width={width} /> <ExpressionViewer value={result.value} width={width} />
) : ( ) : (
<SquiggleErrorAlert error={result.value} /> <SquiggleErrorAlert error={result.value} />
)} )}

View File

@ -1,6 +1,6 @@
import { DistributionPlottingSettings } from "../DistributionChart"; import { DistributionPlottingSettings } from "../DistributionChart";
import { FunctionChartSettings } from "../FunctionChart"; import { FunctionChartSettings } from "../FunctionChart";
import { environment } from "@quri/squiggle-lang"; import { environment, SqValueLocation } from "@quri/squiggle-lang";
export type LocalItemSettings = { export type LocalItemSettings = {
collapsed: boolean; collapsed: boolean;
@ -17,6 +17,5 @@ export type MergedItemSettings = {
environment: environment; environment: environment;
}; };
export type Path = string[]; export const locationAsString = (location: SqValueLocation) =>
location.path.items.join(".");
export const pathAsString = (path: Path) => path.join(".");

View File

@ -1,8 +1,8 @@
import clsx from "clsx"; import clsx from "clsx";
import React from "react"; import React from "react";
import { Path, UseFormRegister } from "react-hook-form"; import { Path, UseFormRegister, FieldValues } from "react-hook-form";
export function Checkbox<T>({ export function Checkbox<T extends FieldValues>({
name, name,
label, label,
register, register,

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { Path, UseFormRegister } from "react-hook-form"; import { Path, UseFormRegister, FieldValues } from "react-hook-form";
export function InputItem<T>({ export function InputItem<T extends FieldValues>({
name, name,
label, label,
type, type,

View File

@ -1,7 +1,4 @@
export { SquiggleChart } from "./components/SquiggleChart"; export { SquiggleChart } from "./components/SquiggleChart";
export { SquiggleEditor, SquigglePartial } from "./components/SquiggleEditor"; export { SquiggleEditor } from "./components/SquiggleEditor";
export { SquigglePlayground } from "./components/SquigglePlayground"; export { SquigglePlayground } from "./components/SquigglePlayground";
export { SquiggleContainer } from "./components/SquiggleContainer"; export { SquiggleContainer } from "./components/SquiggleContainer";
export { SquiggleEditorWithImportedBindings } from "./components/SquiggleEditorWithImportedBindings";
export { mergeBindings } from "@quri/squiggle-lang";

View File

@ -1,5 +1,5 @@
import { shape } from "@quri/squiggle-lang"; import { SqShape } from "@quri/squiggle-lang";
export const hasMassBelowZero = (shape: shape) => export const hasMassBelowZero = (shape: SqShape) =>
shape.continuous.some((x) => x.x <= 0) || shape.continuous.some((x) => x.x <= 0) ||
shape.discrete.some((x) => x.x <= 0); shape.discrete.some((x) => x.x <= 0);

View File

@ -1,3 +1,3 @@
export { useMaybeControlledValue } from "./useMaybeControlledValue"; export { useMaybeControlledValue } from "./useMaybeControlledValue";
export { useSquiggle, useSquigglePartial } from "./useSquiggle"; export { useSquiggle } from "./useSquiggle";
export { useRunnerState } from "./useRunnerState"; export { useRunnerState } from "./useRunnerState";

View File

@ -1,53 +1,42 @@
import { import { environment, SqProject, SqValue } from "@quri/squiggle-lang";
bindings,
environment,
jsImports,
run,
runPartial,
} from "@quri/squiggle-lang";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = { type SquiggleArgs = {
code: string; code: string;
executionId?: number; executionId?: number;
bindings?: bindings; jsImports?: JsImports;
jsImports?: jsImports;
environment?: environment; environment?: environment;
onChange?: (expr: Extract<T, { tag: "Ok" }>["value"] | undefined) => void; onChange?: (expr: SqValue | undefined) => void;
}; };
const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>( export const useSquiggle = (args: SquiggleArgs) => {
args: SquiggleArgs<T>, const result = useMemo(
f: (...args: Parameters<typeof run>) => T () => {
) => { const project = SqProject.create();
const result: T = useMemo<T>( project.setSource("main", args.code);
() => f(args.code, args.bindings, args.environment, args.jsImports), if (args.environment) {
project.setEnvironment(args.environment);
}
if (args.jsImports && Object.keys(args.jsImports).length) {
const importsSource = jsImportsToSquiggleCode(args.jsImports);
project.setSource("imports", importsSource);
project.setContinues("main", ["imports"]);
}
project.run("main");
const result = project.getResult("main");
const bindings = project.getBindings("main");
return { result, bindings };
},
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
[ [args.code, args.environment, args.jsImports, args.executionId]
f,
args.code,
args.bindings,
args.environment,
args.jsImports,
args.executionId,
]
); );
const { onChange } = args; const { onChange } = args;
useEffect(() => { useEffect(() => {
onChange?.(result.tag === "Ok" ? result.value : undefined); onChange?.(result.result.tag === "Ok" ? result.result.value : undefined);
}, [result, onChange]); }, [result, onChange]);
return result; return result;
}; };
export const useSquigglePartial = (
args: SquiggleArgs<ReturnType<typeof runPartial>>
) => {
return useSquiggleAny(args, runPartial);
};
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
return useSquiggleAny(args, run);
};

View File

@ -0,0 +1,51 @@
type JsImportsValue =
| number
| string
| JsImportsValue[]
| {
[k: string]: JsImportsValue;
};
export type JsImports = {
[k: string]: JsImportsValue;
};
const quote = (arg: string) => `"${arg.replace(new RegExp('"', "g"), '\\"')}"`;
const jsImportsValueToSquiggleCode = (v: JsImportsValue): string => {
if (typeof v === "number") {
return String(v);
} else if (typeof v === "string") {
return quote(v);
} else if (v instanceof Array) {
return "[" + v.map((x) => jsImportsValueToSquiggleCode(x)) + "]";
} else {
if (Object.keys(v).length) {
return (
"{" +
Object.entries(v)
.map(([k, v]) => `${quote(k)}:${jsImportsValueToSquiggleCode(v)},`)
.join("") +
"}"
);
} else {
return "0"; // squiggle doesn't support empty `{}`
}
}
};
export const jsImportsToSquiggleCode = (v: JsImports) => {
const validId = new RegExp("[a-zA-Z][[a-zA-Z0-9]*");
let result = Object.entries(v)
.map(([k, v]) => {
if (!k.match(validId)) {
return ""; // skipping without warnings; can be improved
}
return `$${k} = ${jsImportsValueToSquiggleCode(v)}\n`;
})
.join("");
if (!result) {
result = "$__no_valid_imports__ = 1"; // without this generated squiggle code can be invalid
}
return result;
};

View File

@ -1,9 +1,9 @@
import * as yup from "yup"; import * as yup from "yup";
import { Distribution, result, squiggleExpression } from "@quri/squiggle-lang"; import { SqDistribution, result, SqRecord } from "@quri/squiggle-lang";
export type LabeledDistribution = { export type LabeledDistribution = {
name: string; name: string;
distribution: Distribution; distribution: SqDistribution;
color?: string; color?: string;
}; };
@ -53,9 +53,7 @@ const schema = yup
}), }),
}); });
export function parsePlot(record: { export function parsePlot(record: SqRecord): result<Plot, string> {
[key: string]: squiggleExpression;
}): result<Plot, string> {
try { try {
const plotRecord = schema.validateSync(record); const plotRecord = schema.validateSync(record);
return ok({ return ok({

View File

@ -0,0 +1,21 @@
open Jest
open TestHelpers
describe("E.A.getByFmap", () => {
makeTest("Empty list returns None", E.A.getByFmap([], x => x + 1, x => mod(x, 2) == 0), None)
makeTest(
"Never predicate returns None",
E.A.getByFmap([1, 2, 3, 4, 5, 6], x => x + 1, _ => false),
None,
)
makeTest(
"function evaluates",
E.A.getByFmap([1, 1, 1, 1, 1, 1, 1, 2, 1, 1], x => 3 * x, x => x > 4),
Some(6),
)
makeTest(
"always predicate returns fn(fst(a))",
E.A.getByFmap([0, 1, 2, 3, 4, 5, 6], x => 10 + x, _ => true),
Some(10),
)
})

View File

@ -0,0 +1,27 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings
open Jest
open Expect
open Expect.Operators
describe("Name Space", () => {
let value = InternalExpressionValue.IEvNumber(1967.0)
let nameSpace = Bindings.emptyNameSpace->Bindings.set("value", value)
test("get", () => {
expect(Bindings.get(nameSpace, "value")) == Some(value)
})
test("chain and get", () => {
let mainNameSpace = Bindings.emptyNameSpace->Bindings.chainTo([nameSpace])
expect(Bindings.get(mainNameSpace, "value")) == Some(value)
})
test("chain and set", () => {
let mainNameSpace0 = Bindings.emptyNameSpace->Bindings.chainTo([nameSpace])
let mainNameSpace =
mainNameSpace0->Bindings.set("value", InternalExpressionValue.IEvNumber(1968.0))
expect(Bindings.get(mainNameSpace, "value")) == Some(InternalExpressionValue.IEvNumber(1968.0))
})
})

View File

@ -1,10 +1,14 @@
module ExpressionValue = ReducerInterface.ExternalExpressionValue module ExpressionValue = ReducerInterface.InternalExpressionValue
module Expression = Reducer_Expression
open Jest open Jest
open Expect open Expect
let expectEvalToBe = (expr: string, answer: string) => let expectEvalToBe = (sourceCode: string, answer: string) =>
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer) Expression.BackCompatible.evaluateString(sourceCode)
->ExpressionValue.toStringResult
->expect
->toBe(answer)
let testEval = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) let testEval = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))

View File

@ -1,6 +1,5 @@
// Reducer_Helpers // Reducer_Helpers
module ErrorValue = Reducer_ErrorValue module ErrorValue = Reducer_ErrorValue
module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue
module InternalExpressionValue = ReducerInterface.InternalExpressionValue module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
@ -15,8 +14,4 @@ let removeDefaultsInternal = (iev: InternalExpressionValue.t) => {
} }
} }
let removeDefaultsExternal = (ev: ExternalExpressionValue.t): ExternalExpressionValue.t =>
ev->InternalExpressionValue.toInternal->removeDefaultsInternal->InternalExpressionValue.toExternal
let rRemoveDefaultsInternal = r => Belt.Result.map(r, removeDefaultsInternal) let rRemoveDefaultsInternal = r => Belt.Result.map(r, removeDefaultsInternal)
let rRemoveDefaultsExternal = r => Belt.Result.map(r, removeDefaultsExternal)

View File

@ -1,5 +1,5 @@
module MathJs = Reducer_MathJs module MathJs = Reducer_MathJs
module ErrorValue = Reducer.ErrorValue module ErrorValue = Reducer_ErrorValue
open Jest open Jest
open ExpectJs open ExpectJs

View File

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

View File

@ -3,77 +3,83 @@ open Reducer_Peggy_TestHelpers
describe("Peggy parse type", () => { describe("Peggy parse type", () => {
describe("type of", () => { describe("type of", () => {
testParse("p: number", "{(::$_typeOf_$ :p #number)}") testParse("p: number", "{(::$_typeOf_$ :p #number); (::$_endOfOuterBlock_$ () ())}")
}) })
describe("type alias", () => { describe("type alias", () => {
testParse("type index=number", "{(::$_typeAlias_$ #index #number)}") testParse(
"type index=number",
"{(::$_typeAlias_$ #index #number); (::$_endOfOuterBlock_$ () ())}",
)
}) })
describe("type or", () => { describe("type or", () => {
testParse( testParse(
"answer: number|string", "answer: number|string",
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (#number #string))))}", "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ #number #string))); (::$_endOfOuterBlock_$ () ())}",
) )
}) })
describe("type function", () => { describe("type function", () => {
testParse( testParse(
"f: number=>number=>number", "f: number=>number=>number",
"{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ (#number #number #number))))}", "{(::$_typeOf_$ :f (::$_typeFunction_$ (::$_constructArray_$ #number #number #number))); (::$_endOfOuterBlock_$ () ())}",
) )
}) })
describe("high priority contract", () => { describe("high priority contract", () => {
testParse( testParse(
"answer: number<-min<-max(100)|string", "answer: number<-min<-max(100)|string",
"{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ ((::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))))}", "{(::$_typeOf_$ :answer (::$_typeOr_$ (::$_constructArray_$ (::$_typeModifier_max_$ (::$_typeModifier_min_$ #number) 100) #string))); (::$_endOfOuterBlock_$ () ())}",
) )
testParse( testParse(
"answer: number<-memberOf([1,3,5])", "answer: number<-memberOf([1,3,5])",
"{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ (1 3 5))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_memberOf_$ #number (::$_constructArray_$ 1 3 5))); (::$_endOfOuterBlock_$ () ())}",
) )
}) })
describe("low priority contract", () => { describe("low priority contract", () => {
testParse( testParse(
"answer: number | string $ opaque", "answer: number | string $ opaque",
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ #number #string)))); (::$_endOfOuterBlock_$ () ())}",
) )
}) })
describe("type array", () => { describe("type array", () => {
testParse("answer: [number]", "{(::$_typeOf_$ :answer (::$_typeArray_$ #number))}") testParse(
"answer: [number]",
"{(::$_typeOf_$ :answer (::$_typeArray_$ #number)); (::$_endOfOuterBlock_$ () ())}",
)
}) })
describe("type record", () => { describe("type record", () => {
testParse( testParse(
"answer: {a: number, b: string}", "answer: {a: number, b: string}",
"{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string))))}", "{(::$_typeOf_$ :answer (::$_typeRecord_$ (::$_constructRecord_$ ('a': #number 'b': #string)))); (::$_endOfOuterBlock_$ () ())}",
) )
}) })
describe("type constructor", () => { describe("type constructor", () => {
testParse( testParse(
"answer: Age(number)", "answer: Age(number)",
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ (#number))))}", "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Age (::$_constructArray_$ #number))); (::$_endOfOuterBlock_$ () ())}",
) )
testParse( testParse(
"answer: Complex(number, number)", "answer: Complex(number, number)",
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ (#number #number))))}", "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Complex (::$_constructArray_$ #number #number))); (::$_endOfOuterBlock_$ () ())}",
) )
testParse( testParse(
"answer: Person({age: number, name: string})", "answer: Person({age: number, name: string})",
"{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ ((::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))))}", "{(::$_typeOf_$ :answer (::$_typeConstructor_$ #Person (::$_constructArray_$ (::$_typeRecord_$ (::$_constructRecord_$ ('age': #number 'name': #string)))))); (::$_endOfOuterBlock_$ () ())}",
) )
testParse( testParse(
"weekend: Saturday | Sunday", "weekend: Saturday | Sunday",
"{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ ((::$_typeConstructor_$ #Saturday (::$_constructArray_$ ())) (::$_typeConstructor_$ #Sunday (::$_constructArray_$ ()))))))}", "{(::$_typeOf_$ :weekend (::$_typeOr_$ (::$_constructArray_$ (::$_typeConstructor_$ #Saturday (::$_constructArray_$)) (::$_typeConstructor_$ #Sunday (::$_constructArray_$))))); (::$_endOfOuterBlock_$ () ())}",
) )
}) })
describe("type parenthesis", () => { describe("type parenthesis", () => {
//$ is introduced to avoid parenthesis //$ is introduced to avoid parenthesis
testParse( testParse(
"answer: (number|string)<-opaque", "answer: (number|string)<-opaque",
"{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ (#number #string)))))}", "{(::$_typeOf_$ :answer (::$_typeModifier_opaque_$ (::$_typeOr_$ (::$_constructArray_$ #number #string)))); (::$_endOfOuterBlock_$ () ())}",
) )
}) })
describe("squiggle expressions in type contracts", () => { describe("squiggle expressions in type contracts", () => {
testParse( testParse(
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
"{:odds1 = {(::$_constructArray_$ (1 3 5))}; :odds2 = {(::$_constructArray_$ (7 9))}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2)))}", "{:odds1 = {(::$_constructArray_$ 1 3 5)}; :odds2 = {(::$_constructArray_$ 7 9)}; (::$_typeAlias_$ #odds (::$_typeModifier_memberOf_$ #number (::concat :odds1 :odds2))); (::$_endOfOuterBlock_$ () ())}",
) )
}) })
}) })

View File

@ -23,13 +23,7 @@ let expectToExpressionToBe = (expr, answer, ~v="_", ()) => {
} else { } else {
let a2 = let a2 =
rExpr rExpr
->Result.flatMap(expr => ->Result.flatMap(expr => Expression.BackCompatible.evaluate(expr))
Expression.reduceExpression(
expr,
ReducerInterface_StdLib.internalStdLib,
ExpressionValue.defaultEnvironment,
)
)
->Reducer_Helpers.rRemoveDefaultsInternal ->Reducer_Helpers.rRemoveDefaultsInternal
->ExpressionValue.toStringResultOkless ->ExpressionValue.toStringResultOkless
(a1, a2)->expect->toEqual((answer, v)) (a1, a2)->expect->toEqual((answer, v))

View File

@ -0,0 +1,23 @@
module Bindings = Reducer_Bindings
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
open Jest
open Reducer_Peggy_TestHelpers
describe("Peggy Outer Block", () => {
testToExpression("1", "{(:$_endOfOuterBlock_$ () 1)}", ~v="1", ())
testToExpression("x=1", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () ())}", ~v="()", ())
testToExpression(
"x=1; y=2",
"{(:$_let_$ :x {1}); (:$_let_$ :y {2}); (:$_endOfOuterBlock_$ () ())}",
~v="()",
(),
)
testToExpression("x=1; 2", "{(:$_let_$ :x {1}); (:$_endOfOuterBlock_$ () 2)}", ~v="2", ())
testToExpression(
"x={a=1; a}; x",
"{(:$_let_$ :x {(:$_let_$ :a {1}); :a}); (:$_endOfOuterBlock_$ () :x)}",
~v="1",
(),
)
})

View File

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

View File

@ -5,92 +5,92 @@ describe("Peggy Types to Expression", () => {
describe("type of", () => { describe("type of", () => {
testToExpression( testToExpression(
"p: number", "p: number",
"{(:$_typeOf_$ :p #number)}", "{(:$_typeOf_$ :p #number); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {p: #number}}", // ~v="@{_typeReferences_: {p: #number}}",
(), (),
) )
}) })
describe("type alias", () => { describe("type alias", () => {
testToExpression( testToExpression(
"type index=number", "type index=number",
"{(:$_typeAlias_$ #index #number)}", "{(:$_typeAlias_$ #index #number); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeAliases_: {index: #number}}", // ~v="@{_typeAliases_: {index: #number}}",
(), (),
) )
}) })
describe("type or", () => { describe("type or", () => {
testToExpression( testToExpression(
"answer: number|string|distribution", "answer: number|string|distribution",
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (#number #string #distribution))))}", "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ #number #string #distribution))); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}", // ~v="@{_typeReferences_: {answer: {typeOr: [#number,#string,#distribution],typeTag: 'typeOr'}}}",
(), (),
) )
}) })
describe("type function", () => { describe("type function", () => {
testToExpression( testToExpression(
"f: number=>number=>number", "f: number=>number=>number",
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number #number))))}", "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ #number #number #number))); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}", // ~v="@{_typeReferences_: {f: {inputs: [#number,#number],output: #number,typeTag: 'typeFunction'}}}",
(), (),
) )
testToExpression( testToExpression(
"f: number=>number", "f: number=>number",
"{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ (#number #number))))}", "{(:$_typeOf_$ :f (:$_typeFunction_$ (:$_constructArray_$ #number #number))); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}", // ~v="@{_typeReferences_: {f: {inputs: [#number],output: #number,typeTag: 'typeFunction'}}}",
(), (),
) )
}) })
describe("high priority contract", () => { describe("high priority contract", () => {
testToExpression( testToExpression(
"answer: number<-min(1)<-max(100)|string", "answer: number<-min(1)<-max(100)|string",
"{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ ((:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))))}", "{(:$_typeOf_$ :answer (:$_typeOr_$ (:$_constructArray_$ (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 100) #string))); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}", // ~v="@{_typeReferences_: {answer: {typeOr: [{max: 100,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'},#string],typeTag: 'typeOr'}}}",
(), (),
) )
testToExpression( testToExpression(
"answer: number<-memberOf([1,3,5])", "answer: number<-memberOf([1,3,5])",
"{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ (1 3 5))))}", "{(:$_typeOf_$ :answer (:$_typeModifier_memberOf_$ #number (:$_constructArray_$ 1 3 5))); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", // ~v="@{_typeReferences_: {answer: {memberOf: [1,3,5],typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
(), (),
) )
testToExpression( testToExpression(
"answer: number<-min(1)", "answer: number<-min(1)",
"{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1))}", "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ #number 1)); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", // ~v="@{_typeReferences_: {answer: {min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
(), (),
) )
testToExpression( testToExpression(
"answer: number<-max(10)", "answer: number<-max(10)",
"{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10))}", "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ #number 10)); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", // ~v="@{_typeReferences_: {answer: {max: 10,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
(), (),
) )
testToExpression( testToExpression(
"answer: number<-min(1)<-max(10)", "answer: number<-min(1)<-max(10)",
"{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10))}", "{(:$_typeOf_$ :answer (:$_typeModifier_max_$ (:$_typeModifier_min_$ #number 1) 10)); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", // ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
(), (),
) )
testToExpression( testToExpression(
"answer: number<-max(10)<-min(1)", "answer: number<-max(10)<-min(1)",
"{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1))}", "{(:$_typeOf_$ :answer (:$_typeModifier_min_$ (:$_typeModifier_max_$ #number 10) 1)); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}", // ~v="@{_typeReferences_: {answer: {max: 10,min: 1,typeIdentifier: #number,typeTag: 'typeIdentifier'}}}",
(), (),
) )
}) })
describe("low priority contract", () => { describe("low priority contract", () => {
testToExpression( testToExpression(
"answer: number | string $ opaque", "answer: number | string $ opaque",
"{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ (#number #string)))))}", "{(:$_typeOf_$ :answer (:$_typeModifier_opaque_$ (:$_typeOr_$ (:$_constructArray_$ #number #string)))); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}", // ~v="@{_typeReferences_: {answer: {opaque: true,typeOr: [#number,#string],typeTag: 'typeOr'}}}",
(), (),
) )
}) })
describe("squiggle expressions in type contracts", () => { describe("squiggle expressions in type contracts", () => {
testToExpression( testToExpression(
"odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))", "odds1 = [1,3,5]; odds2 = [7, 9]; type odds = number<-memberOf(concat(odds1, odds2))",
"{(:$_let_$ :odds1 {(:$_constructArray_$ (1 3 5))}); (:$_let_$ :odds2 {(:$_constructArray_$ (7 9))}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2)))}", "{(:$_let_$ :odds1 {(:$_constructArray_$ 1 3 5)}); (:$_let_$ :odds2 {(:$_constructArray_$ 7 9)}); (:$_typeAlias_$ #odds (:$_typeModifier_memberOf_$ #number (:concat :odds1 :odds2))); (:$_endOfOuterBlock_$ () ())}",
~v="@{_typeAliases_: {odds: {memberOf: [1,3,5,7,9],typeIdentifier: #number,typeTag: 'typeIdentifier'}},odds1: [1,3,5],odds2: [7,9]}", // ~v="@{_typeAliases_: {odds: {memberOf: [1,3,5,7,9],typeIdentifier: #number,typeTag: 'typeIdentifier'}},odds1: [1,3,5],odds2: [7,9]}",
(), (),
) )
}) })

View File

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

View File

@ -1,6 +1,7 @@
module ExpressionT = Reducer_Expression_T
module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue
module ErrorValue = Reducer_ErrorValue module ErrorValue = Reducer_ErrorValue
module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
open Jest open Jest
open Expect open Expect
@ -8,30 +9,26 @@ open Expect
let unwrapRecord = rValue => let unwrapRecord = rValue =>
rValue->Belt.Result.flatMap(value => rValue->Belt.Result.flatMap(value =>
switch value { switch value {
| ExternalExpressionValue.EvRecord(aRecord) => Ok(aRecord) | InternalExpressionValue.IEvRecord(aRecord) => Ok(aRecord)
| _ => ErrorValue.RETodo("TODO: External bindings must be returned")->Error | _ => ErrorValue.RETodo("TODO: Internal bindings must be returned")->Error
} }
) )
let expectParseToBe = (expr: string, answer: string) => let expectParseToBe = (code: string, answer: string) =>
Reducer.parse(expr)->ExpressionT.toStringResult->expect->toBe(answer) Expression.BackCompatible.parse(code)->ExpressionT.toStringResult->expect->toBe(answer)
let expectEvalToBe = (expr: string, answer: string) => let expectEvalToBe = (code: string, answer: string) =>
Reducer.evaluate(expr) Expression.BackCompatible.evaluateString(code)
->Reducer_Helpers.rRemoveDefaultsExternal ->Reducer_Helpers.rRemoveDefaultsInternal
->ExternalExpressionValue.toStringResult ->InternalExpressionValue.toStringResult
->expect ->expect
->toBe(answer) ->toBe(answer)
let expectEvalError = (expr: string) => let expectEvalError = (code: string) =>
Reducer.evaluate(expr)->ExternalExpressionValue.toStringResult->expect->toMatch("Error\(") Expression.BackCompatible.evaluateString(code)
->InternalExpressionValue.toStringResult
let expectEvalBindingsToBe = (expr: string, bindings: Reducer.externalBindings, answer: string) =>
Reducer.evaluateUsingOptions(expr, ~externalBindings=Some(bindings), ~environment=None)
->Reducer_Helpers.rRemoveDefaultsExternal
->ExternalExpressionValue.toStringResult
->expect ->expect
->toBe(answer) ->toMatch("Error\(")
let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer)) let testParseToBe = (expr, answer) => test(expr, () => expectParseToBe(expr, answer))
let testDescriptionParseToBe = (desc, expr, answer) => let testDescriptionParseToBe = (desc, expr, answer) =>
@ -40,18 +37,12 @@ let testDescriptionParseToBe = (desc, expr, answer) =>
let testEvalError = expr => test(expr, () => expectEvalError(expr)) let testEvalError = expr => test(expr, () => expectEvalError(expr))
let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer)) let testEvalToBe = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer)) let testDescriptionEvalToBe = (desc, expr, answer) => test(desc, () => expectEvalToBe(expr, answer))
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
module MySkip = { module MySkip = {
let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer)) let testParseToBe = (expr, answer) => Skip.test(expr, () => expectParseToBe(expr, answer))
let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(expr, answer)) let testEvalToBe = (expr, answer) => Skip.test(expr, () => expectEvalToBe(expr, answer))
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
Skip.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
} }
module MyOnly = { module MyOnly = {
let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer)) let testParseToBe = (expr, answer) => Only.test(expr, () => expectParseToBe(expr, answer))
let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(expr, answer)) let testEvalToBe = (expr, answer) => Only.test(expr, () => expectEvalToBe(expr, answer))
let testEvalBindingsToBe = (expr, bindingsList, answer) =>
Only.test(expr, () => expectEvalBindingsToBe(expr, bindingsList->Js.Dict.fromList, answer))
} }

View File

@ -1,14 +1,14 @@
open Jest open Jest
open Expect open Expect
module Bindings = Reducer_Bindings
module BindingsReplacer = Reducer_Expression_BindingsReplacer module BindingsReplacer = Reducer_Expression_BindingsReplacer
module Expression = Reducer_Expression module Expression = Reducer_Expression
// module ExpressionValue = ReducerInterface.ExpressionValue
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module ExpressionWithContext = Reducer_ExpressionWithContext module ExpressionWithContext = Reducer_ExpressionWithContext
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
module Macro = Reducer_Expression_Macro module Macro = Reducer_Expression_Macro
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module T = Reducer_Expression_T module T = Reducer_Expression_T
module Bindings = Reducer_Bindings
let testMacro_ = ( let testMacro_ = (
tester, tester,
@ -21,8 +21,8 @@ let testMacro_ = (
expr expr
->Macro.expandMacroCall( ->Macro.expandMacroCall(
bindings, bindings,
InternalExpressionValue.defaultEnvironment, ProjectAccessorsT.identityAccessors,
Expression.reduceExpression, Expression.reduceExpressionInProject,
) )
->ExpressionWithContext.toStringResult ->ExpressionWithContext.toStringResult
->expect ->expect
@ -41,8 +41,8 @@ let testMacroEval_ = (
expr expr
->Macro.doMacroCall( ->Macro.doMacroCall(
bindings, bindings,
InternalExpressionValue.defaultEnvironment, ProjectAccessorsT.identityAccessors,
Expression.reduceExpression, Expression.reduceExpressionInProject,
) )
->InternalExpressionValue.toStringResult ->InternalExpressionValue.toStringResult
->expect ->expect

View File

@ -8,7 +8,7 @@ open Jest
open Expect open Expect
let myIevEval = (aTypeSourceCode: string) => let myIevEval = (aTypeSourceCode: string) =>
TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpression) TypeCompile.ievFromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject)
let myIevEvalToString = (aTypeSourceCode: string) => let myIevEvalToString = (aTypeSourceCode: string) =>
myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult myIevEval(aTypeSourceCode)->InternalExpressionValue.toStringResult
@ -19,7 +19,7 @@ let myIevTest = (test, aTypeSourceCode, answer) =>
test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer)) test(aTypeSourceCode, () => myIevExpectEqual(aTypeSourceCode, answer))
let myTypeEval = (aTypeSourceCode: string) => let myTypeEval = (aTypeSourceCode: string) =>
TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpression) TypeCompile.fromTypeExpression(aTypeSourceCode, Expression.reduceExpressionInProject)
let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult let myTypeEvalToString = (aTypeSourceCode: string) => myTypeEval(aTypeSourceCode)->T.toStringResult
let myTypeExpectEqual = (aTypeSourceCode, answer) => let myTypeExpectEqual = (aTypeSourceCode, answer) =>

View File

@ -1,8 +1,9 @@
module Bindings = Reducer_Bindings
module ErrorValue = Reducer_ErrorValue
module Expression = Reducer_Expression module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module ErrorValue = Reducer_ErrorValue
module InternalExpressionValue = ReducerInterface_InternalExpressionValue module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module T = Reducer_Type_T module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker module TypeChecker = Reducer_Type_TypeChecker
@ -13,10 +14,10 @@ let checkArgumentsSourceCode = (aTypeSourceCode: string, sourceCode: string): re
'v, 'v,
ErrorValue.t, ErrorValue.t,
> => { > => {
let reducerFn = Expression.reduceExpression let reducerFn = Expression.reduceExpressionInProject
let rResult = let rResult =
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => Expression.BackCompatible.parse(sourceCode)->Belt.Result.flatMap(expr =>
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors)
) )
rResult->Belt.Result.flatMap(result => rResult->Belt.Result.flatMap(result =>
switch result { switch result {

View File

@ -5,6 +5,7 @@ module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Bindings = Reducer_Bindings module Bindings = Reducer_Bindings
module T = Reducer_Type_T module T = Reducer_Type_T
module TypeChecker = Reducer_Type_TypeChecker module TypeChecker = Reducer_Type_TypeChecker
module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
open Jest open Jest
open Expect open Expect
@ -16,10 +17,10 @@ let isTypeOfSourceCode = (aTypeSourceCode: string, sourceCode: string): result<
'v, 'v,
ErrorValue.t, ErrorValue.t,
> => { > => {
let reducerFn = Expression.reduceExpression let reducerFn = Expression.reduceExpressionInProject
let rResult = let rResult =
Reducer.parse(sourceCode)->Belt.Result.flatMap(expr => Expression.BackCompatible.parse(sourceCode)->Belt.Result.flatMap(expr =>
reducerFn(expr, Bindings.emptyBindings, InternalExpressionValue.defaultEnvironment) reducerFn(expr, Bindings.emptyBindings, ProjectAccessorsT.identityAccessors)
) )
rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn)) rResult->Belt.Result.flatMap(result => TypeChecker.isTypeOf(aTypeSourceCode, result, reducerFn))
} }

View File

@ -4,8 +4,11 @@ open Expect
module DispatchT = Reducer_Dispatch_T module DispatchT = Reducer_Dispatch_T
module Expression = Reducer_Expression module Expression = Reducer_Expression
module ExpressionT = Reducer_Expression_T module ExpressionT = Reducer_Expression_T
module TypeCompile = Reducer_Type_Compile module ProjectAccessorsT = ReducerProject_ProjectAccessors_T
module ProjectReducerFnT = ReducerProject_ReducerFn_T
module TypeChecker = Reducer_Type_TypeChecker module TypeChecker = Reducer_Type_TypeChecker
module TypeCompile = Reducer_Type_Compile
open ReducerInterface_InternalExpressionValue open ReducerInterface_InternalExpressionValue
type errorValue = Reducer_ErrorValue.errorValue type errorValue = Reducer_ErrorValue.errorValue
@ -14,13 +17,14 @@ type errorValue = Reducer_ErrorValue.errorValue
// In dispatchChainPiece, we execute an return the result of execution if there is a type match. // 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. // Otherwise we return None so that the call chain can continue.
// So we want to build a function like // So we want to build a function like
// dispatchChainPiece = (call: functionCall, environment): option<result<internalExpressionValue, errorValue>> // dispatchChainPiece = (call: functionCall, accessors): option<result<internalExpressionValue, errorValue>>
// Use accessors.environment to get the environment finally.
// Now lets make the dispatchChainPiece itself. // 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. // 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. // 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 makeMyDispatchChainPiece = (reducer: ProjectReducerFnT.t): DispatchT.dispatchChainPiece => {
// Let's have a pure implementations // Let's have a pure implementations
module Implementation = { module Implementation = {
let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b) let stringConcat = (a: string, b: string): string => Js.String2.concat(a, b)
@ -45,15 +49,15 @@ let makeMyDispatchChainPiece = (reducer: ExpressionT.reducerFn): DispatchT.dispa
// Let's bridge the pure implementation to expression values // Let's bridge the pure implementation to expression values
module Bridge = { module Bridge = {
let stringConcat: DispatchT.genericIEvFunction = (args, _environment) => { let stringConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
let (a, b) = extractStringString(args) let (a, b) = extractStringString(args)
Implementation.stringConcat(a, b)->IEvString->Ok Implementation.stringConcat(a, b)->IEvString->Ok
} }
let arrayConcat: DispatchT.genericIEvFunction = (args, _environment) => { let arrayConcat: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
let (a, b) = extractArrayArray(args) let (a, b) = extractArrayArray(args)
Implementation.arrayConcat(a, b)->IEvArray->Ok Implementation.arrayConcat(a, b)->IEvArray->Ok
} }
let plot: DispatchT.genericIEvFunction = (args, _environment) => { let plot: DispatchT.genericIEvFunction = (args, _accessors: ProjectAccessorsT.t) => {
switch args { switch args {
// Just assume that we are doing the business of extracting and converting the deep record // 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 | [IEvRecord(_)] => Implementation.plot({"title": "This is a plot"})->IEvString->Ok
@ -98,12 +102,12 @@ let makeMyDispatchChainPiece = (reducer: ExpressionT.reducerFn): DispatchT.dispa
// Exactly the same as the one used in real life // Exactly the same as the one used in real life
let _dispatch = ( let _dispatch = (
call: functionCall, call: functionCall,
environment, accessors: ProjectAccessorsT.t,
reducer: Reducer_Expression_T.reducerFn, reducer: ProjectReducerFnT.t,
chain, chain,
): result<internalExpressionValue, 'e> => { ): result<internalExpressionValue, 'e> => {
let dispatchChainPiece = makeMyDispatchChainPiece(reducer) let dispatchChainPiece = makeMyDispatchChainPiece(reducer)
dispatchChainPiece(call, environment)->E.O2.defaultFn(() => chain(call, environment, reducer)) dispatchChainPiece(call, accessors)->E.O2.defaultFn(() => chain(call, accessors, reducer))
} }
// What is important about this implementation? // What is important about this implementation?
@ -112,12 +116,12 @@ let _dispatch = (
// B) Complicated recursive record types are not a problem. // B) Complicated recursive record types are not a problem.
describe("Type Dispatch", () => { describe("Type Dispatch", () => {
let reducerFn = Expression.reduceExpression let reducerFn = Expression.reduceExpressionInProject
let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn) let dispatchChainPiece = makeMyDispatchChainPiece(reducerFn)
test("stringConcat", () => { test("stringConcat", () => {
let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")]) let call: functionCall = ("concat", [IEvString("hello"), IEvString("world")])
let result = dispatchChainPiece(call, defaultEnvironment) let result = dispatchChainPiece(call, ProjectAccessorsT.identityAccessors)
expect(result)->toEqual(Some(Ok(IEvString("helloworld")))) expect(result)->toEqual(Some(Ok(IEvString("helloworld"))))
}) })
}) })

View File

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

View File

@ -1,14 +0,0 @@
open Jest
open Reducer_TestHelpers
describe("Eval with Bindings", () => {
testEvalBindingsToBe("x", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(1)")
testEvalBindingsToBe("x+1", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(2)")
testParseToBe("y = x+1; y", "Ok({(:$_let_$ :y {(:add :x 1)}); :y})")
testEvalBindingsToBe("y = x+1; y", list{("x", ExternalExpressionValue.EvNumber(1.))}, "Ok(2)")
testEvalBindingsToBe(
"y = x+1",
list{("x", ExternalExpressionValue.EvNumber(1.))},
"Ok(@{x: 1,y: 2})",
)
})

View File

@ -2,8 +2,14 @@ open Jest
open Reducer_TestHelpers open Reducer_TestHelpers
describe("Parse function assignment", () => { describe("Parse function assignment", () => {
testParseToBe("f(x)=x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x}))})") testParseToBe(
testParseToBe("f(x)=2*x", "Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)}))})") "f(x)=x",
"Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {:x})); (:$_endOfOuterBlock_$ () ())})",
)
testParseToBe(
"f(x)=2*x",
"Ok({(:$_let_$ :f (:$$_lambda_$$ [x] {(:multiply 2 :x)})); (:$_endOfOuterBlock_$ () ())})",
)
//MathJs does not allow blocks in function definitions //MathJs does not allow blocks in function definitions
}) })

View File

@ -39,33 +39,27 @@ describe("symbol not defined", () => {
}) })
describe("call and bindings", () => { describe("call and bindings", () => {
testEvalToBe("f(x)=x+1", "Ok(@{f: lambda(x=>internal code)})") testEvalToBe("f(x)=x+1; f(0)", "Ok(1)")
testEvalToBe("f(x)=x+1; f(1)", "Ok(2)") testEvalToBe("f(x)=x+1; f(1)", "Ok(2)")
testEvalToBe("f=1;y=2", "Ok(@{f: 1,y: 2})") testEvalToBe("f=1;y=2", "Ok(())")
testEvalToBe("f(x)=x+1; y=f(1)", "Ok(@{f: lambda(x=>internal code),y: 2})") testEvalToBe("f(x)=x+1; y=f(1); y", "Ok(2)")
testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)") testEvalToBe("f(x)=x+1; y=f(1); f(1)", "Ok(2)")
testEvalToBe("f(x)=x+1; y=f(1); z=f(1)", "Ok(@{f: lambda(x=>internal code),y: 2,z: 2})") testEvalToBe("f(x)=x+1; y=f(1); z=f(1); z", "Ok(2)")
testEvalToBe( testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(0)", "Ok(2)")
"f(x)=x+1; g(x)=f(x)+1",
"Ok(@{f: lambda(x=>internal code),g: lambda(x=>internal code)})",
)
testParseToBe( testParseToBe(
"f=99; g(x)=f; g(2)", "f=99; g(x)=f; g(2)",
"Ok({(:$_let_$ :f {99}); (:$_let_$ :g (:$$_lambda_$$ [x] {:f})); (:g 2)})", "Ok({(:$_let_$ :f {99}); (:$_let_$ :g (:$$_lambda_$$ [x] {:f})); (:$_endOfOuterBlock_$ () (:g 2))})",
) )
testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)") testEvalToBe("f=99; g(x)=f; g(2)", "Ok(99)")
testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)") testEvalToBe("f(x)=x; g(x)=f(x); g(2)", "Ok(2)")
testEvalToBe( testEvalToBe("f(x)=x+1; g(x)=f(x)+1; y=g(2); y", "Ok(4)")
"f(x)=x+1; g(x)=f(x)+1; y=g(2)",
"Ok(@{f: lambda(x=>internal code),g: lambda(x=>internal code),y: 4})",
)
testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(2)", "Ok(4)") testEvalToBe("f(x)=x+1; g(x)=f(x)+1; g(2)", "Ok(4)")
}) })
describe("function tricks", () => { describe("function tricks", () => {
testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed testEvalError("f(x)=f(y)=2; f(2)") //Error because chain assignment is not allowed
testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)") testEvalToBe("y=2;g(x)=y+1;g(2)", "Ok(3)")
testEvalToBe("y=2;g(x)=inspect(y)+1", "Ok(@{g: lambda(x=>internal code),y: 2})") testEvalToBe("y=2;g(x)=inspect(y)+1;y", "Ok(2)")
MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout? MySkip.testEvalToBe("f(x) = x(x); f(f)", "????") // TODO: Infinite loop. Any solution? Catching proper exception or timeout?
MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters MySkip.testEvalToBe("f(x, x)=x+x; f(1,2)", "????") // TODO: Duplicate parameters
testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))") testEvalToBe("myadd(x,y)=x+y; z=myadd; z", "Ok(lambda(x,y=>internal code))")
@ -73,10 +67,7 @@ describe("function tricks", () => {
}) })
describe("lambda in structures", () => { describe("lambda in structures", () => {
testEvalToBe( testEvalToBe("myadd(x,y)=x+y; z=[myadd]", "Ok(())")
"myadd(x,y)=x+y; z=[myadd]",
"Ok(@{myadd: lambda(x,y=>internal code),z: [lambda(x,y=>internal code)]})",
)
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))") testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0]", "Ok(lambda(x,y=>internal code))")
testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)") testEvalToBe("myadd(x,y)=x+y; z=[myadd]; z[0](3,2)", "Ok(5)")
testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})") testEvalToBe("myaddd(x,y)=x+y; z={x: myaddd}; z", "Ok({x: lambda(x,y=>internal code)})")

View File

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

View File

@ -48,7 +48,7 @@ describe("eval", () => {
testEvalToBe("x=1; y=x+1; y+1", "Ok(3)") testEvalToBe("x=1; y=x+1; y+1", "Ok(3)")
testEvalError("1; x=1") testEvalError("1; x=1")
testEvalError("1; 1") testEvalError("1; 1")
testEvalToBe("x=1; x=1", "Ok(@{x: 1})") testEvalToBe("x=1; x=1; x", "Ok(1)")
}) })
}) })

View File

@ -74,6 +74,7 @@ describe("eval on distribution functions", () => {
testEval("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)") testEval("truncateLeft(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("truncateRight(normal(5,2), 3)", "Ok(Point Set Distribution)") testEval("truncateRight(normal(5,2), 3)", "Ok(Point Set Distribution)")
testEval("truncate(normal(5,2), 3, 8)", "Ok(Point Set Distribution)") testEval("truncate(normal(5,2), 3, 8)", "Ok(Point Set Distribution)")
testEval("truncate(normal(5,2) |> SampleSet.fromDist, 3, 8)", "Ok(Sample Set Distribution)")
testEval("isNormalized(truncate(normal(5,2), 3, 8))", "Ok(true)") testEval("isNormalized(truncate(normal(5,2), 3, 8))", "Ok(true)")
}) })
@ -118,28 +119,40 @@ describe("eval on distribution functions", () => {
describe("parse on distribution functions", () => { describe("parse on distribution functions", () => {
describe("power", () => { describe("power", () => {
testParse("normal(5,2) ^ normal(5,1)", "Ok({(:pow (:normal 5 2) (:normal 5 1))})") testParse(
testParse("3 ^ normal(5,1)", "Ok({(:pow 3 (:normal 5 1))})") "normal(5,2) ^ normal(5,1)",
testParse("normal(5,2) ^ 3", "Ok({(:pow (:normal 5 2) 3)})") "Ok({(:$_endOfOuterBlock_$ () (:pow (:normal 5 2) (:normal 5 1)))})",
)
testParse("3 ^ normal(5,1)", "Ok({(:$_endOfOuterBlock_$ () (:pow 3 (:normal 5 1)))})")
testParse("normal(5,2) ^ 3", "Ok({(:$_endOfOuterBlock_$ () (:pow (:normal 5 2) 3))})")
}) })
describe("subtraction", () => { describe("subtraction", () => {
testParse("10 - normal(5,1)", "Ok({(:subtract 10 (:normal 5 1))})") testParse("10 - normal(5,1)", "Ok({(:$_endOfOuterBlock_$ () (:subtract 10 (:normal 5 1)))})")
testParse("normal(5,1) - 10", "Ok({(:subtract (:normal 5 1) 10)})") testParse("normal(5,1) - 10", "Ok({(:$_endOfOuterBlock_$ () (:subtract (:normal 5 1) 10))})")
}) })
describe("pointwise arithmetic expressions", () => { describe("pointwise arithmetic expressions", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")
testParse( testParse(
~skip=true, ~skip=true,
"normal(5,2) .- normal(5,1)", "normal(5,2) .- normal(5,1)",
"Ok((:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1))))", "Ok((:$_endOfOuterBlock_$ () (:$$_block_$$ (:dotSubtract (:normal 5 2) (:normal 5 1)))))",
// TODO: !!! returns "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})" // 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(
testParse("normal(5,2) ./ normal(5,1)", "Ok({(:dotDivide (:normal 5 2) (:normal 5 1))})") "normal(5,2) .* normal(5,1)",
testParse("normal(5,2) .^ normal(5,1)", "Ok({(:dotPow (:normal 5 2) (:normal 5 1))})") "Ok({(:$_endOfOuterBlock_$ () (:dotMultiply (:normal 5 2) (:normal 5 1)))})",
)
testParse(
"normal(5,2) ./ normal(5,1)",
"Ok({(:$_endOfOuterBlock_$ () (:dotDivide (:normal 5 2) (:normal 5 1)))})",
)
testParse(
"normal(5,2) .^ normal(5,1)",
"Ok({(:$_endOfOuterBlock_$ () (:dotPow (:normal 5 2) (:normal 5 1)))})",
)
}) })
describe("equality", () => { describe("equality", () => {
testParse("5 == normal(5,2)", "Ok({(:equal 5 (:normal 5 2))})") testParse("5 == normal(5,2)", "Ok({(:$_endOfOuterBlock_$ () (:equal 5 (:normal 5 2)))})")
}) })
describe("pointwise adding two normals", () => { describe("pointwise adding two normals", () => {
testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))") testParse(~skip=true, "normal(5,2) .+ normal(5,1)", "Ok((:dotAdd (:normal 5 2) (:normal 5 1)))")

View File

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

View File

@ -0,0 +1,36 @@
@@warning("-44")
module Topology = ReducerProject_Topology
open Jest
open Expect
open Expect.Operators
describe("Topology Diff", () => {
test("when equal 1x", () => {
Topology.runOrderDiff(["a"], ["a"])->expect == []
})
test("when equal 3x", () => {
Topology.runOrderDiff(["a", "b", "c"], ["a", "b", "c"])->expect == []
})
test("less dependents", () => {
Topology.runOrderDiff(["a", "b"], ["a", "b", "c", "d"])->expect == []
})
test("more dependents", () => {
Topology.runOrderDiff(["a", "b", "c", "d"], ["a", "b"])->expect == ["c", "d"]
})
test("change midway", () => {
Topology.runOrderDiff(["a", "b", "bb", "c", "d"], ["a", "b", "c", "d"])->expect == [
"bb",
"c",
"d",
]
})
test("swap", () => {
Topology.runOrderDiff(["a", "b", "c", "d"], ["a", "c", "b", "d"])->expect == ["b", "c", "d"]
})
})

View File

@ -0,0 +1,121 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
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->Reducer_ErrorValue.errorToString)
}
})
let internalProject = project->Project.T.Private.castToInternalProject
test("past chain", () => {
expect(Project.Private.getPastChain(internalProject, "main")) == ["common"]
})
test("import as variables", () => {
expect(Project.Private.getIncludesAsVariables(internalProject, "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->Reducer_ErrorValue.errorToString)
}
})
let internalProject = project->Project.T.Private.castToInternalProject
test("direct past chain", () => {
expect(Project.Private.getPastChain(internalProject, "main")) == ["common"]
})
test("direct includes", () => {
expect(Project.Private.getDirectIncludes(internalProject, "main")) == ["common"]
})
test("include as variables", () => {
expect(Project.Private.getIncludesAsVariables(internalProject, "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->Reducer_ErrorValue.errorToString)
}
})
let internalProject = project->Project.T.Private.castToInternalProject
test("direct past chain", () => {
expect(Project.getPastChain(project, "main")) == ["common", "common2"]
})
test("include as variables", () => {
expect(Project.Private.getIncludesAsVariables(internalProject, "main")) == [
("myVariable", "myModule"),
]
})
})

View File

@ -0,0 +1,195 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
open Jest
open Expect
open Expect.Operators
// test("", () => expect(1)->toBe(1))
let runFetchResult = (project, sourceId) => {
Project.run(project, sourceId)
Project.getResult(project, sourceId)->InternalExpressionValue.toStringResult
}
let runFetchFlatBindings = (project, sourceId) => {
Project.run(project, sourceId)
Project.getBindings(project, sourceId)
->Bindings.removeResult
->InternalExpressionValue.toStringBindings
}
test("setting continuation", () => {
let project = Project.createProject()
let privateProject = project->Project.T.Private.castToInternalProject
let sampleBindings = Bindings.emptyBindings->Bindings.set("test", IEvVoid)
Project.Private.setContinuation(privateProject, "main", sampleBindings)
let answer = Project.Private.getContinuation(privateProject, "main")
expect(answer)->toBe(sampleBindings)
})
test("test result true", () => {
let project = Project.createProject()
Project.setSource(project, "main", "true")
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"])
let internalProject = project->Project.T.Private.castToInternalProject
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(Project.Private.getPastChain(internalProject, "first")) == []
})
test("past chain main", () => {
expect(Project.Private.getPastChain(internalProject, "main")) == ["first"]
})
test("test result", () => {
runFetchResult(project, "main")->expect->toBe("Ok(1)")
})
test("test bindings", () => {
runFetchFlatBindings(project, "first")->expect->toBe("@{x: 1}")
})
})
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", "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", () => {
runFetchFlatBindings(project, "main")->expect->toBe("@{x: 1,y: 2}")
})
})
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", "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", () => {
runFetchFlatBindings(project, "main")->expect->toBe("@{common: 0,x: 1,y: 2}")
})
})
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"]
})
})

View File

@ -0,0 +1,109 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
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 is not already done else already existing results will be presented.
The dependencies will be automatically compiled and run. So you don't need to worry about that in a multi source project.
In summary you issue a run command on the whole project or on a specific source to ensure that there is a result for that source.
*/
let project = Project.createProject()
/* Every source has a name. This is used for debugging, dependencies and error messages. */
Project.setSource(project, "main", "1 + 2")
/* Let's run "main" source. */
project->Project.run("main")
/* Now you have a result for "main" source.
Running one by one is necessary for UI to navigate among the sources and to see the results by source.
And you're free to run any source you want.
You will look at the results of this source and you don't want to run the others if not required.
*/
/* However, you could also run the whole project.
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")->Bindings.removeResult
/* Let's display the result and bindings */
(
result->InternalExpressionValue.toStringResult,
bindings->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(3)", "@{}")
/* You've got 3 with empty bindings. */
})
test("run summary", () => {
let project = Project.createProject()
Project.setSource(project, "main", "1 + 2")
Project.runAll(project)
let result = Project.getResult(project, "main")
let bindings = Project.getBindings(project, "main")->Bindings.removeResult
/* Now you have external bindings and external result. */
(
result->InternalExpressionValue.toStringResult,
bindings->InternalExpressionValue.IEvBindings->InternalExpressionValue.toString,
)->expect == ("Ok(3)", "@{}")
})
test("run with an environment", () => {
/* Running the source code like above allows you to set a custom environment */
let project = Project.createProject()
/* Optional. Set your custom environment anytime before running */
Project.setEnvironment(project, InternalExpressionValue.defaultEnvironment)
Project.setSource(project, "main", "1 + 2")
Project.runAll(project)
let result = Project.getResult(project, "main")
let _bindings = Project.getBindings(project, "main")
result->InternalExpressionValue.toStringResult->expect == "Ok(3)"
})
test("shortcut", () => {
/* If you are running single source without includes and you don't need a custom environment, you can use the shortcut. */
/* Examples above was to prepare you for the multi source tutorial. */
let (result, bindings) = Project.evaluate("1+2")
(
result->InternalExpressionValue.toStringResult,
bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings,
)->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

View File

@ -0,0 +1,112 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
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.setSource(project, "source1", "x=1")
Project.setSource(project, "source2", "y=2")
/* To run, source2 depends on source1 */
Project.setContinues(project, "source2", ["source1"])
Project.setSource(project, "source3", "z=3")
/* To run, source3 depends on source2 */
Project.setContinues(project, "source3", ["source2"])
/* Now we can run the project */
Project.runAll(project)
/* And let's check the result and bindings of source3 */
let result3 = Project.getResult(project, "source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult
(
result3->InternalExpressionValue.toStringResult,
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
})
test("Depending", () => {
/* Instead of chaining the sources, we could have a dependency tree */
/* The point here is that any source can depend on multiple sources */
let project = Project.createProject()
/* This time source1 and source2 are not depending on anything */
Project.setSource(project, "source1", "x=1")
Project.setSource(project, "source2", "y=2")
Project.setSource(project, "source3", "z=3")
/* To run, source3 depends on source1 and source3 together */
Project.setContinues(project, "source3", ["source1", "source2"])
/* Now we can run the project */
Project.runAll(project)
/* And let's check the result and bindings of source3 */
let result3 = Project.getResult(project, "source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult
(
result3->InternalExpressionValue.toStringResult,
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
})
test("Intro to including", () => {
/* Though it would not be practical for a storybook,
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.setSource(project, "source1", "x=1")
Project.setSource(project, "source2", "y=2")
Project.setSource(
project,
"source3",
`
#include "source1"
#include "source2"
z=3`,
)
/* We need to parse the includes to set the dependencies */
Project.parseIncludes(project, "source3")
/* Now we can run the project */
Project.runAll(project)
/* And let's check the result and bindings of source3
This time you are getting all the variables because we are including the other sources
Behind the scenes parseIncludes is setting the dependencies */
let result3 = Project.getResult(project, "source3")
let bindings3 = Project.getBindings(project, "source3")->Bindings.removeResult
(
result3->InternalExpressionValue.toStringResult,
bindings3->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(())", "@{x: 1,y: 2,z: 3}")
/*
Doing it like this is too verbose for a storybook
But I hope you have seen the relation of setContinues and parseIncludes */
/*
Dealing with includes needs more.
- There are parse errors
- There are cyclic includes
- And the depended source1 and source2 is not already there in the project
- If you knew the includes before hand there would not be point of the include directive.
More on those on the next section. */
})
})
})

View File

@ -0,0 +1,179 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
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.setSource(
project,
"main",
`
#include "common"
x=1
`,
)
/* We need to parse includes after changing the source */
Project.parseIncludes(project, "main")
test("getDependencies", () => {
/* Parse includes has set the dependencies */
Project.getDependencies(project, "main")->expect == ["common"]
/* If there were no includes than there would be no dependencies */
/* However if there was a syntax error at includes then would be no dependencies also */
/* Therefore looking at dependencies is not the right way to load includes */
/* getDependencies does not distinguish between setContinues or parseIncludes */
})
test("getIncludes", () => {
/* Parse includes has set the includes */
switch Project.getIncludes(project, "main") {
| Ok(includes) => includes->expect == ["common"]
| Error(err) => err->Reducer_ErrorValue.errorToString->fail
}
/* If the includes cannot be parsed then you get a syntax error.
Otherwise you get the includes.
If there is no syntax error then you can load that file and use setSource to add it to the project.
And so on recursively... */
})
test("getDependents", () => {
/* For any reason, you are able to query what other sources
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.getDependents(project, "main")->expect == []
/* Nothing is depending on or including main */
})
describe("Real Like", () => {
/* Now let's look at recursive and possibly cyclic includes */
/* There is no function provided to load the include files.
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 Js.Array2.includes(visited, sourceName) {
/* Oh we have already visited this source. There is an include cycle */
"Cyclic include ${sourceName}"->Js.Exn.raiseError
} else {
let newVisited = Js.Array2.copy(visited)
let _ = Js.Array2.push(newVisited, sourceName)
/* Let's parse the includes and dive into them */
Project.parseIncludes(project, sourceName)
let rIncludes = Project.getIncludes(project, sourceName)
switch rIncludes {
/* Maybe there is an include syntax error */
| Error(err) => err->Reducer_ErrorValue.errorToString->Js.Exn.raiseError
| Ok(includes) =>
Belt.Array.forEach(includes, newIncludeName => {
/* We have got one of the new includes.
Let's load it and add it to the project */
let newSource = loadSource(newIncludeName)
Project.setSource(project, newIncludeName, newSource)
/* The new source is loaded and added to the project. */
/* Of course the new source might have includes too. */
/* Let's recursively load them */
loadIncludesRecursively(project, newIncludeName, newVisited)
})
}
}
}
/* As we have a fake source loader and a recursive include handler,
We can not set up a real project */
/* * Here starts our real life project! * */
let project = Project.createProject()
/* main includes source3 which includes source2 which includes source1 */
Project.setSource(
project,
"main",
`
#include "source3"
x+y+z
`,
)
/* Setting source requires parsing and loading the includes recursively */
loadIncludesRecursively(project, "main", []) //No visited yet
/* Let's salt it more. Let's have another source in the project which also has includes */
/* doubleX includes source1 which is eventually included by main as well */
Project.setSource(
project,
"doubleX",
`
#include "source1"
doubleX = x * 2
`,
)
loadIncludesRecursively(project, "doubleX", [])
/* Remember, any time you set a source, you need to load includes recursively */
/* As doubleX is not included by main, it is not loaded recursively.
So we link it to the project as a dependency */
Project.setContinues(project, "main", ["doubleX"])
/* Let's run the project */
Project.runAll(project)
let result = Project.getResult(project, "main")
let bindings = Project.getBindings(project, "main")
/* And see the result and bindings.. */
test("recursive includes", () => {
(
result->InternalExpressionValue.toStringResult,
bindings->Bindings.removeResult->InternalExpressionValue.toStringBindings,
)->expect == ("Ok(6)", "@{doubleX: 2,x: 1,y: 2,z: 3}")
/* Everything as expected */
})
})
})
describe("Includes myFile as myVariable", () => {
/* 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->Reducer_ErrorValue.errorToString->fail
}
})
})
})

View File

@ -0,0 +1,39 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
module Project = ForTS_ReducerProject
module Bindings = Reducer_Bindings
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->InternalExpressionValue.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... */

View File

@ -0,0 +1,39 @@
@@warning("-44")
module InternalExpressionValue = ReducerInterface_InternalExpressionValue
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->InternalExpressionValue.toStringResult
)
userResultsAsString->expect == ["Ok(2)", "Ok(4)", "Ok(6)", "Ok(8)", "Ok(10)"]
})
})

View File

@ -2,8 +2,12 @@ open Jest
open Expect open Expect
open Reducer_TestHelpers open Reducer_TestHelpers
let expectEvalToBeOk = (expr: string) => let expectEvalToBeOk = (code: string) =>
Reducer.evaluate(expr)->Reducer_Helpers.rRemoveDefaultsExternal->E.R.isOk->expect->toBe(true) Reducer_Expression.BackCompatible.evaluateString(code)
->Reducer_Helpers.rRemoveDefaultsInternal
->E.R.isOk
->expect
->toBe(true)
let registry = FunctionRegistry_Library.registry let registry = FunctionRegistry_Library.registry
let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry)) let examples = E.A.to_list(FunctionRegistry_Core.Registry.allExamples(registry))
@ -63,6 +67,15 @@ describe("FunctionRegistry Library", () => {
testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)") testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)")
testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)") testEvalToBe("SampleSet.fromList([3,5,2,3,5,2,3,5,2,3,3,5])", "Ok(Sample Set Distribution)")
testEvalToBe("SampleSet.fromFn({|| sample(normal(5,2))})", "Ok(Sample Set Distribution)") testEvalToBe("SampleSet.fromFn({|| sample(normal(5,2))})", "Ok(Sample Set Distribution)")
testEvalToBe(
"SampleSet.min(SampleSet.fromDist(normal(50,2)), 2)",
"Ok(Sample Set Distribution)",
)
testEvalToBe("mean(SampleSet.min(SampleSet.fromDist(normal(50,2)), 2))", "Ok(2)")
testEvalToBe(
"SampleSet.max(SampleSet.fromDist(normal(50,2)), 10)",
"Ok(Sample Set Distribution)",
)
testEvalToBe( testEvalToBe(
"addOne(t)=t+1; SampleSet.toList(SampleSet.map(SampleSet.fromList([1,2,3,4,5,6]), addOne))", "addOne(t)=t+1; SampleSet.toList(SampleSet.map(SampleSet.fromList([1,2,3,4,5,6]), addOne))",
"Ok([2,3,4,5,6,7])", "Ok([2,3,4,5,6,7])",
@ -88,8 +101,8 @@ describe("FunctionRegistry Library", () => {
((fn, example)) => { ((fn, example)) => {
let responseType = let responseType =
example example
->Reducer.evaluate ->Reducer_Expression.BackCompatible.evaluateString
->E.R2.fmap(ReducerInterface_InternalExpressionValue.externalValueToValueType) ->E.R2.fmap(ReducerInterface_InternalExpressionValue.valueToValueType)
let expectedOutputType = fn.output |> E.O.toExn("") let expectedOutputType = fn.output |> E.O.toExn("")
expect(responseType)->toEqual(Ok(expectedOutputType)) expect(responseType)->toEqual(Ok(expectedOutputType))
}, },

View File

@ -0,0 +1,20 @@
open Jest
open Expect
let makeTest = (~only=false, str, item1, item2) =>
only
? Only.test(str, () => expect(item1)->toEqual(item2))
: test(str, () => expect(item1)->toEqual(item2))
describe("Stdlib", () => {
makeTest(
"Length of Random.sample",
Stdlib.Random.sample([1.0, 2.0], {probs: [0.5, 0.5], size: 10})->E.A.length,
10,
)
makeTest(
"Random.sample returns elements from input array (will fail with very slim probability)",
Stdlib.Random.sample([1.0, 2.0], {probs: [0.5, 0.5], size: 10})->E.A.uniq->E.A.Floats.sort,
[1.0, 2.0],
)
})

View File

@ -1,10 +1,5 @@
import { import { run, SqProject, SqValue, SqValueTag } from "../../src/js";
Distribution, import { testRun } from "./TestHelpers";
resultMap,
defaultBindings,
mergeBindings,
} from "../../src/js/index";
import { testRun, testRunPartial } from "./TestHelpers";
function Ok<b>(x: b) { function Ok<b>(x: b) {
return { tag: "Ok", value: x }; return { tag: "Ok", value: x };
@ -12,97 +7,57 @@ function Ok<b>(x: b) {
describe("Simple calculations and results", () => { describe("Simple calculations and results", () => {
test("mean(normal(5,2))", () => { test("mean(normal(5,2))", () => {
expect(testRun("mean(normal(5,2))")).toEqual({ const result = testRun("mean(normal(5,2))"); // FIXME
tag: "number", expect(result.toString()).toEqual("5");
value: 5,
});
}); });
test("10+10", () => { test("10+10", () => {
let foo = testRun("10 + 10"); let result = testRun("10 + 10");
expect(foo).toEqual({ tag: "number", value: 20 }); expect(result.toString()).toEqual("20");
}); });
}); });
describe("Log function", () => { describe("Log function", () => {
test("log(1) = 0", () => { test("log(1) = 0", () => {
let foo = testRun("log(1)"); let foo = testRun("log(1)");
expect(foo).toEqual({ tag: "number", value: 0 }); expect(foo.toString()).toEqual("0");
}); });
}); });
describe("Array", () => { describe("Array", () => {
test("nested Array", () => { test("nested Array", () => {
expect(testRun("[[1]]")).toEqual({ expect(testRun("[[ 1 ]]").toString()).toEqual("[[1]]");
tag: "array",
value: [
{
tag: "array",
value: [
{
tag: "number",
value: 1,
},
],
},
],
});
}); });
}); });
describe("Record", () => { describe("Record", () => {
test("Return record", () => { test("Return record", () => {
expect(testRun("{a: 1}")).toEqual({ expect(testRun("{a:1}").toString()).toEqual("{a: 1}");
tag: "record",
value: {
a: {
tag: "number",
value: 1,
},
},
});
}); });
}); });
describe("Partials", () => { describe("Continues", () => {
test("Can pass variables between partials and cells", () => { test("Bindings from continues are accessible", () => {
let bindings = testRunPartial(`x = 5`); const project = SqProject.create();
let bindings2 = testRunPartial(`y = x + 2`, bindings); project.setSource("p1", "x = 5");
expect(testRun(`y + 3`, bindings2)).toEqual({ project.setSource("p2", "y = x + 2");
tag: "number", project.setSource("main", "y + 3");
value: 10, 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");
}); });
test("Can merge bindings from three partials", () => { test("Can merge bindings from three partials", () => {
let bindings1 = testRunPartial(`x = 1`); const project = SqProject.create();
let bindings2 = testRunPartial(`y = 2`); project.setSource("p1", "x = 1");
let bindings3 = testRunPartial(`z = 3`); project.setSource("p2", "y = 2");
expect( project.setSource("p3", "z = 3");
testRun(`x + y + z`, mergeBindings([bindings1, bindings2, bindings3])) project.setSource("main", "x + y + z");
).toEqual({ project.setContinues("main", ["p1", "p2", "p3"]);
tag: "number", project.run("main");
value: 6, const result = project.getResult("main");
}); expect(result.tag).toEqual("Ok");
}); expect(result.value.toString()).toEqual("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,
});
}); });
}); });
@ -112,50 +67,62 @@ describe("Distribution", () => {
let env = { sampleCount: 8, xyPointLength: 100 }; let env = { sampleCount: 8, xyPointLength: 100 };
let dist1Samples = [3, 4, 5, 6, 6, 7, 10, 15, 30]; let dist1Samples = [3, 4, 5, 6, 6, 7, 10, 15, 30];
let dist1SampleCount = dist1Samples.length; let dist1SampleCount = dist1Samples.length;
let dist = new Distribution(
{ tag: "SampleSet", value: [3, 4, 5, 6, 6, 7, 10, 15, 30] }, const buildDist = (samples: number[]) => {
env const src = `SampleSet.fromList([${samples.join(",")}])`;
); const { result } = run(src, {
let dist2 = new Distribution( environment: env,
{ tag: "SampleSet", value: [20, 22, 24, 29, 30, 35, 38, 44, 52] }, });
env if (result.tag !== "Ok") {
); throw new Error(
`Failed to build SampleSet: from ${src}: ${result.value}`
);
}
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", () => { test("mean", () => {
expect(dist.mean().value).toBeCloseTo(9.5555555); expect(dist.mean(env).value).toBeCloseTo(9.5555555);
}); });
test("pdf", () => { test("pdf", () => {
expect(dist.pdf(5.0).value).toBeCloseTo(0.10499097598222966, 1); expect(dist.pdf(env, 5.0).value).toBeCloseTo(0.10499097598222966, 1);
}); });
test("cdf", () => { test("cdf", () => {
expect(dist.cdf(5.0).value).toBeCloseTo( expect(dist.cdf(env, 5.0).value).toBeCloseTo(
dist1Samples.filter((x) => x <= 5).length / dist1SampleCount, dist1Samples.filter((x) => x <= 5).length / dist1SampleCount,
1 1
); );
}); });
test("inv", () => { test("inv", () => {
expect(dist.inv(0.5).value).toBeCloseTo(6); expect(dist.inv(env, 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("▁▂██▃▃▃▃▄▅▄▃▃▂▂▂▁▁▁▁"));
// });
}); });

View File

@ -1,4 +1,3 @@
// import { errorValueToString } from "../../src/js/index";
import * as fc from "fast-check"; import * as fc from "fast-check";
import { testRun } from "./TestHelpers"; import { testRun } from "./TestHelpers";

View File

@ -1,9 +1,3 @@
import {
run,
squiggleExpression,
errorValue,
result,
} from "../../src/js/index";
import { testRun } from "./TestHelpers"; import { testRun } from "./TestHelpers";
import * as fc from "fast-check"; import * as fc from "fast-check";
@ -42,9 +36,9 @@ describe("Squiggle's parser is whitespace insensitive", () => {
whitespaceGen(), whitespaceGen(),
whitespaceGen(), whitespaceGen(),
(a, b, c, d, e, f, g, h) => { (a, b, c, d, e, f, g, h) => {
expect(testRun(squiggleString(a, b, c, d, e, f, g, h))).toEqual( expect(
squiggleOutput testRun(squiggleString(a, b, c, d, e, f, g, h))
); ).toEqualSqValue(squiggleOutput);
} }
) )
); );

View File

@ -1,5 +1,4 @@
// import { errorValueToString } from "../../src/js/index"; import { testRun, expectErrorToBeBounded, SqValueTag } from "./TestHelpers";
import { testRun, expectErrorToBeBounded } from "./TestHelpers";
import * as fc from "fast-check"; import * as fc from "fast-check";
describe("Mean of mixture is weighted average of means", () => { describe("Mean of mixture is weighted average of means", () => {
@ -20,7 +19,7 @@ describe("Mean of mixture is weighted average of means", () => {
let lognormalWeight = y / weightDenom; let lognormalWeight = y / weightDenom;
let betaMean = 1 / (1 + b / a); let betaMean = 1 / (1 + b / a);
let lognormalMean = m + s ** 2 / 2; let lognormalMean = m + s ** 2 / 2;
if (res.tag == "number") { if (res.tag === SqValueTag.Number) {
expectErrorToBeBounded( expectErrorToBeBounded(
res.value, res.value,
betaWeight * betaMean + lognormalWeight * lognormalMean, betaWeight * betaMean + lognormalWeight * lognormalMean,

View File

@ -1,12 +1,13 @@
import { Distribution } from "../../src/js/index"; import { expectErrorToBeBounded, testRun, SqValueTag } from "./TestHelpers";
import { expectErrorToBeBounded, failDefault, testRun } from "./TestHelpers";
import * as fc from "fast-check"; import * as fc from "fast-check";
// Beware: float64Array makes it appear in an infinite loop. // Beware: float64Array makes it appear in an infinite loop.
let arrayGen = () => let arrayGen = () =>
fc fc
.float32Array({ .float64Array({
minLength: 10, minLength: 10,
max: 999999999999999,
min: -999999999999999,
maxLength: 10000, maxLength: 10000,
noDefaultInfinity: true, noDefaultInfinity: true,
noNaN: true, noNaN: true,
@ -14,36 +15,41 @@ let arrayGen = () =>
.filter( .filter(
(xs_) => Math.min(...Array.from(xs_)) != Math.max(...Array.from(xs_)) (xs_) => Math.min(...Array.from(xs_)) != Math.max(...Array.from(xs_))
); );
describe("cumulative density function", () => {
let n = 10000;
let makeSampleSet = (samples: number[]) => {
let sampleList = samples.map((x) => x.toFixed(20)).join(",");
let result = testRun(`SampleSet.fromList([${sampleList}])`);
if (result.tag === SqValueTag.Distribution) {
return result.value;
} else {
fail("Expected to be distribution");
}
};
const env = { sampleCount: 10000, xyPointLength: 100 };
describe("cumulative density function", () => {
// We should fix this. // We should fix this.
test.skip("'s codomain is bounded above", () => { test.skip("'s codomain is bounded above", () => {
fc.assert( fc.assert(
fc.property(arrayGen(), fc.float(), (xs_, x) => { fc.property(arrayGen(), fc.float(), (xs_, x) => {
let xs = Array.from(xs_); let xs = Array.from(xs_);
// Should compute with squiggle strings once interpreter has `sample` // Should compute with squiggle strings once interpreter has `sample`
let dist = new Distribution( let result = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let cdfValue = result.cdf(env, x).value;
{ sampleCount: n, xyPointLength: 100 }
);
let cdfValue = dist.cdf(x).value;
let epsilon = 5e-7; let epsilon = 5e-7;
expect(cdfValue).toBeLessThanOrEqual(1 + epsilon); expect(cdfValue).toBeLessThanOrEqual(1 + epsilon);
}) })
); );
}); });
test("'s codomain is bounded below", () => { test.skip("'s codomain is bounded below", () => {
fc.assert( fc.assert(
fc.property(arrayGen(), fc.float(), (xs_, x) => { fc.property(arrayGen(), fc.float(), (xs_, x) => {
let xs = Array.from(xs_); let xs = Array.from(xs_);
// Should compute with squiggle strings once interpreter has `sample` // Should compute with squiggle strings once interpreter has `sample`
let dist = new Distribution( let result = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let cdfValue = result.cdf(env, x).value;
{ sampleCount: n, xyPointLength: 100 }
);
let cdfValue = dist.cdf(x).value;
expect(cdfValue).toBeGreaterThanOrEqual(0); expect(cdfValue).toBeGreaterThanOrEqual(0);
}) })
); );
@ -57,11 +63,8 @@ describe("cumulative density function", () => {
let xs = Array.from(xs_); let xs = Array.from(xs_);
let max = Math.max(...xs); let max = Math.max(...xs);
// Should compute with squiggle strings once interpreter has `sample` // Should compute with squiggle strings once interpreter has `sample`
let dist = new Distribution( let result = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let cdfValue = result.cdf(env, max).value;
{ sampleCount: n, xyPointLength: 100 }
);
let cdfValue = dist.cdf(max).value;
expect(cdfValue).toBeCloseTo(1.0, 2); expect(cdfValue).toBeCloseTo(1.0, 2);
}) })
); );
@ -74,11 +77,8 @@ describe("cumulative density function", () => {
let xs = Array.from(xs_); let xs = Array.from(xs_);
let min = Math.min(...xs); let min = Math.min(...xs);
// Should compute with squiggle strings once interpreter has `sample` // Should compute with squiggle strings once interpreter has `sample`
let dist = new Distribution( let result = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let cdfValue = result.cdf(env, min).value;
{ sampleCount: n, xyPointLength: 100 }
);
let cdfValue = dist.cdf(min).value;
let max = Math.max(...xs); let max = Math.max(...xs);
let epsilon = 5e-3; let epsilon = 5e-3;
if (max - min < epsilon) { if (max - min < epsilon) {
@ -95,11 +95,8 @@ describe("cumulative density function", () => {
fc.assert( fc.assert(
fc.property(arrayGen(), fc.float(), (xs_, x) => { fc.property(arrayGen(), fc.float(), (xs_, x) => {
let xs = Array.from(xs_); let xs = Array.from(xs_);
let dist = new Distribution( let dist = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let cdfValue = dist.cdf(env, x).value;
{ sampleCount: n, xyPointLength: 100 }
);
let cdfValue = dist.cdf(x).value;
let max = Math.max(...xs); let max = Math.max(...xs);
if (x > max) { if (x > max) {
let epsilon = (x - max) / x; let epsilon = (x - max) / x;
@ -107,21 +104,18 @@ describe("cumulative density function", () => {
} else if (typeof cdfValue == "number") { } else if (typeof cdfValue == "number") {
expect(Math.round(1e5 * cdfValue) / 1e5).toBeLessThanOrEqual(1); expect(Math.round(1e5 * cdfValue) / 1e5).toBeLessThanOrEqual(1);
} else { } else {
failDefault(); fail();
} }
}) })
); );
}); });
test("is non-negative everywhere with zero when x is lower than the min", () => { test.skip("is non-negative everywhere with zero when x is lower than the min", () => {
fc.assert( fc.assert(
fc.property(arrayGen(), fc.float(), (xs_, x) => { fc.property(arrayGen(), fc.float(), (xs_, x) => {
let xs = Array.from(xs_); let xs = Array.from(xs_);
let dist = new Distribution( let dist = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let cdfValue = dist.cdf(env, x).value;
{ sampleCount: n, xyPointLength: 100 }
);
let cdfValue = dist.cdf(x).value;
expect(cdfValue).toBeGreaterThanOrEqual(0); expect(cdfValue).toBeGreaterThanOrEqual(0);
}) })
); );
@ -130,7 +124,7 @@ describe("cumulative density function", () => {
// I no longer believe this is true. // I no longer believe this is true.
describe("probability density function", () => { describe("probability density function", () => {
let n = 1000; const env = { sampleCount: 1000, xyPointLength: 100 };
test.skip("assigns to the max at most the weight of the mean", () => { test.skip("assigns to the max at most the weight of the mean", () => {
fc.assert( fc.assert(
@ -139,12 +133,9 @@ describe("probability density function", () => {
let max = Math.max(...xs); let max = Math.max(...xs);
let mean = xs.reduce((a, b) => a + b, 0.0) / xs.length; let mean = xs.reduce((a, b) => a + b, 0.0) / xs.length;
// Should be from squiggleString once interpreter exposes sampleset // Should be from squiggleString once interpreter exposes sampleset
let dist = new Distribution( let dist = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let pdfValueMean = dist.pdf(env, mean).value;
{ sampleCount: n, xyPointLength: 100 } let pdfValueMax = dist.pdf(env, max).value;
);
let pdfValueMean = dist.pdf(mean).value;
let pdfValueMax = dist.pdf(max).value;
if (typeof pdfValueMean == "number" && typeof pdfValueMax == "number") { if (typeof pdfValueMean == "number" && typeof pdfValueMax == "number") {
expect(pdfValueMax).toBeLessThanOrEqual(pdfValueMean); expect(pdfValueMax).toBeLessThanOrEqual(pdfValueMean);
} else { } else {
@ -164,11 +155,9 @@ describe("mean is mean", () => {
(xs_) => { (xs_) => {
let xs = Array.from(xs_); let xs = Array.from(xs_);
let n = xs.length; let n = xs.length;
let dist = new Distribution( let dist = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let myEnv = { sampleCount: 2 * n, xyPointLength: 4 * n };
{ sampleCount: 2 * n, xyPointLength: 4 * n } let mean = dist.mean(myEnv);
);
let mean = dist.mean();
if (typeof mean.value == "number") { if (typeof mean.value == "number") {
expectErrorToBeBounded( expectErrorToBeBounded(
mean.value, mean.value,
@ -177,7 +166,7 @@ describe("mean is mean", () => {
1 1
); );
} else { } else {
failDefault(); fail();
} }
} }
) )
@ -191,11 +180,9 @@ describe("mean is mean", () => {
(xs_) => { (xs_) => {
let xs = Array.from(xs_); let xs = Array.from(xs_);
let n = xs.length; let n = xs.length;
let dist = new Distribution( let dist = makeSampleSet(xs);
{ tag: "SampleSet", value: xs }, let myEnv = { sampleCount: Math.floor(n / 2), xyPointLength: 4 * n };
{ sampleCount: Math.floor(n / 2), xyPointLength: 4 * n } let mean = dist.mean(myEnv);
);
let mean = dist.mean();
if (typeof mean.value == "number") { if (typeof mean.value == "number") {
expectErrorToBeBounded( expectErrorToBeBounded(
mean.value, mean.value,
@ -204,7 +191,7 @@ describe("mean is mean", () => {
1 1
); );
} else { } else {
failDefault(); fail();
} }
} }
) )

View File

@ -5,7 +5,7 @@ import * as fc from "fast-check";
describe("Scalar manipulation is well-modeled by javascript math", () => { describe("Scalar manipulation is well-modeled by javascript math", () => {
test("in the case of natural logarithms", () => { test("in the case of natural logarithms", () => {
fc.assert( fc.assert(
fc.property(fc.integer(), (x) => { fc.property(fc.nat(), (x) => {
let squiggleString = `log(${x})`; let squiggleString = `log(${x})`;
let squiggleResult = testRun(squiggleString); let squiggleResult = testRun(squiggleString);
if (x == 0) { if (x == 0) {

View File

@ -1,68 +1,31 @@
import { import { run, SqValueTag } from "../../src/js";
run, export { SqValueTag };
runPartial,
bindings,
squiggleExpression,
errorValueToString,
defaultImports,
defaultBindings,
jsImports,
} from "../../src/js/index";
export function testRun( expect.extend({
x: string, toEqualSqValue(x, y) {
bindings: bindings = defaultBindings, // hack via https://github.com/facebook/jest/issues/10329#issuecomment-820656061
imports: jsImports = defaultImports const { getMatchers } = require("expect/build/jestMatchersObject");
): squiggleExpression { return getMatchers().toEqual.call(this, x.toString(), y.toString());
let squiggleResult = run( },
x, });
bindings,
{ export function testRun(x: string) {
const { result, bindings } = run(x, {
environment: {
sampleCount: 1000, sampleCount: 1000,
xyPointLength: 100, xyPointLength: 100,
}, },
imports });
);
if (squiggleResult.tag === "Ok") { if (result.tag === "Ok") {
return squiggleResult.value; return result.value;
} else { } else {
throw new Error( throw new Error(
`Expected squiggle expression to evaluate but got error: ${errorValueToString( `Expected squiggle expression to evaluate but got error: ${result.value}`
squiggleResult.value
)}`
); );
} }
} }
export function testRunPartial(
x: string,
bindings: bindings = defaultBindings,
imports: jsImports = defaultImports
): bindings {
let squiggleResult = runPartial(
x,
bindings,
{
sampleCount: 1000,
xyPointLength: 100,
},
imports
);
if (squiggleResult.tag === "Ok") {
return squiggleResult.value;
} else {
throw new Error(
`Expected squiggle expression to evaluate but got error: ${errorValueToString(
squiggleResult.value
)}`
);
}
}
export function failDefault() {
expect("be reached").toBe("codepath should never");
}
/** /**
* This appears also in `TestHelpers.res`. According to https://www.math.net/percent-error, it computes * This appears also in `TestHelpers.res`. According to https://www.math.net/percent-error, it computes
* absolute error when numerical stability concerns make me not want to compute relative error. * absolute error when numerical stability concerns make me not want to compute relative error.

View File

@ -1,6 +1,6 @@
{ {
"name": "@quri/squiggle-lang", "name": "@quri/squiggle-lang",
"version": "0.3.0", "version": "0.4.0-alpha.0",
"homepage": "https://squiggle-language.com", "homepage": "https://squiggle-language.com",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -18,6 +18,7 @@
"benchmark": "ts-node benchmark/conversion_tests.ts", "benchmark": "ts-node benchmark/conversion_tests.ts",
"test": "jest", "test": "jest",
"test:ts": "jest __tests__/TS/", "test:ts": "jest __tests__/TS/",
"test:stdlib": "jest __tests__/Stdlib_test.bs.js",
"test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*", "test:rescript": "jest --modulePathIgnorePatterns=__tests__/TS/*",
"test:watch": "jest --watchAll", "test:watch": "jest --watchAll",
"test:fnRegistry": "jest __tests__/SquiggleLibrary/SquiggleLibrary_FunctionRegistryLibrary_test.bs.js", "test:fnRegistry": "jest __tests__/SquiggleLibrary/SquiggleLibrary_FunctionRegistryLibrary_test.bs.js",
@ -44,18 +45,18 @@
"@stdlib/stats": "^0.0.13", "@stdlib/stats": "^0.0.13",
"jstat": "^1.9.5", "jstat": "^1.9.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mathjs": "^11.0.1", "mathjs": "^11.1.0",
"pdfast": "^0.2.0" "pdfast": "^0.2.0"
}, },
"devDependencies": { "devDependencies": {
"@glennsl/rescript-jest": "^0.9.0", "@glennsl/rescript-jest": "^0.9.2",
"@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/jest": "^27.5.0", "@types/jest": "^27.5.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"bisect_ppx": "^2.7.1", "bisect_ppx": "^2.7.1",
"chalk": "^5.0.1", "chalk": "^5.0.1",
"codecov": "^3.8.3", "codecov": "^3.8.3",
"fast-check": "^3.1.1", "fast-check": "^3.1.2",
"gentype": "^4.5.0", "gentype": "^4.5.0",
"jest": "^27.5.1", "jest": "^27.5.1",
"moduleserve": "^0.9.1", "moduleserve": "^0.9.1",
@ -68,7 +69,7 @@
"ts-jest": "^27.1.4", "ts-jest": "^27.1.4",
"ts-loader": "^9.3.0", "ts-loader": "^9.3.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^4.7.4", "typescript": "^4.8.2",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-cli": "^4.10.0" "webpack-cli": "^4.10.0"
}, },

View File

@ -0,0 +1,15 @@
import * as RSArray from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Array.gen";
import { wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
type T = RSArray.squiggleValue_Array;
export class SqArray {
constructor(private _value: T, public location: SqValueLocation) {}
getValues() {
return RSArray.getValues(this._value).map((v, i) =>
wrapValue(v, this.location.extend(i))
);
}
}

View File

@ -0,0 +1,116 @@
import * as RSDistribution from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen";
import { distributionTag as Tag } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_tag";
import { environment } from "../rescript/ForTS/ForTS__Types.gen";
import { SqDistributionError } from "./SqDistributionError";
import { wrapPointSetDist } from "./SqPointSetDist";
import { resultMap2 } from "./types";
type T = RSDistribution.distribution;
export { Tag as SqDistributionTag };
export const wrapDistribution = (value: T): SqDistribution => {
const tag = RSDistribution.getTag(value);
return new tagToClass[tag](value);
};
abstract class SqAbstractDistribution {
abstract tag: Tag;
constructor(private _value: T) {}
protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
const value = rsMethod(this._value);
if (!value) throw new Error("Internal casting error");
return value;
};
pointSet(env: environment) {
const innerResult = RSDistribution.toPointSet(this._value, env);
return resultMap2(
innerResult,
wrapPointSetDist,
(v: RSDistribution.distributionError) => new SqDistributionError(v)
);
}
toString() {
RSDistribution.toString(this._value);
}
mean(env: environment) {
return resultMap2(
RSDistribution.mean({ env }, this._value),
(v: number) => v,
(e: RSDistribution.distributionError) => new SqDistributionError(e)
);
}
pdf(env: environment, n: number) {
return resultMap2(
RSDistribution.pdf({ env }, this._value, n),
(v: number) => v,
(e: RSDistribution.distributionError) => new SqDistributionError(e)
);
}
cdf(env: environment, n: number) {
return resultMap2(
RSDistribution.cdf({ env }, this._value, n),
(v: number) => v,
(e: RSDistribution.distributionError) => new SqDistributionError(e)
);
}
inv(env: environment, n: number) {
return resultMap2(
RSDistribution.inv({ env }, this._value, n),
(v: number) => v,
(e: RSDistribution.distributionError) => new SqDistributionError(e)
);
}
stdev(env: environment) {
return resultMap2(
RSDistribution.stdev({ env }, this._value),
(v: number) => v,
(e: RSDistribution.distributionError) => new SqDistributionError(e)
);
}
}
export class SqPointSetDistribution extends SqAbstractDistribution {
tag = Tag.PointSet as const;
value() {
return wrapPointSetDist(this.valueMethod(RSDistribution.getPointSet));
}
}
export class SqSampleSetDistribution extends SqAbstractDistribution {
tag = Tag.SampleSet as const;
value(): number[] {
return this.valueMethod(RSDistribution.getSampleSet);
}
}
export class SqSymbolicDistribution extends SqAbstractDistribution {
tag = Tag.Symbolic as const;
// not wrapped for TypeScript yet
// value() {
// return this.valueMethod(RSDistribution.getSymbolic);
// }
}
const tagToClass = {
[Tag.PointSet]: SqPointSetDistribution,
[Tag.SampleSet]: SqSampleSetDistribution,
[Tag.Symbolic]: SqSymbolicDistribution,
} as const;
export type SqDistribution =
| SqPointSetDistribution
| SqSampleSetDistribution
| SqSymbolicDistribution;

View File

@ -0,0 +1,11 @@
import * as RSDistributionError from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Error.gen";
type T = RSDistributionError.distributionError;
export class SqDistributionError {
constructor(private _value: T) {}
toString() {
return RSDistributionError.toString(this._value);
}
}

View File

@ -0,0 +1,17 @@
import * as RSErrorValue from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen";
export class SqError {
constructor(private _value: RSErrorValue.reducerErrorValue) {}
toString() {
return RSErrorValue.toString(this._value);
}
static createTodoError(v: string) {
return new SqError(RSErrorValue.createTodoError(v));
}
static createOtherError(v: string) {
return new SqError(RSErrorValue.createOtherError(v));
}
}

View File

@ -0,0 +1,48 @@
import * as RSLambda from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Lambda.gen";
import { SqError } from "./SqError";
import { SqValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
import { result } from "./types";
type T = RSLambda.squiggleValue_Lambda;
export class SqLambda {
constructor(private _value: T, public location: SqValueLocation) {}
parameters() {
return RSLambda.parameters(this._value);
}
call(args: (number | string)[]): result<SqValue, SqError> {
const { project, sourceId } = this.location;
// Might be good to use uuid instead, but there's no way to remove sources from projects.
// So this is not thread-safe.
const callId = "__lambda__";
const quote = (arg: string) =>
`"${arg.replace(new RegExp('"', "g"), '\\"')}"`;
const argsSource = args
.map((arg) => (typeof arg === "number" ? arg : quote(arg)))
.join(",");
// end expression values are exposed in bindings via secret `__result__` variable and we can access them through it
const pathItems = [
...(this.location.path.root === "result" ? ["__result__"] : []),
...this.location.path.items,
];
// full function name, e.g. foo.bar[3].baz
const functionNameSource = pathItems
.map((item, i) =>
typeof item === "string" ? (i ? "." + item : item) : `[${item}]`
)
.join("");
// something like: foo.bar[3].baz(1,2,3)
const source = `${functionNameSource}(${argsSource})`;
project.setSource(callId, source);
project.setContinues(callId, [sourceId]);
project.run(callId);
return project.getResult(callId);
}
}

View File

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

View File

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

View File

@ -0,0 +1,101 @@
import * as _ from "lodash";
import { wrapDistribution } from "./SqDistribution";
import * as RSPointSetDist from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_PointSetDistribution.gen";
import { pointSetDistributionTag as Tag } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_PointSetDistribution_tag";
type T = RSPointSetDist.pointSetDistribution;
export type SqPoint = { x: number; y: number };
export type SqShape = {
continuous: SqPoint[];
discrete: SqPoint[];
};
const shapePoints = (
x: RSPointSetDist.continuousShape | RSPointSetDist.discreteShape
): SqPoint[] => {
let xs = x.xyShape.xs;
let ys = x.xyShape.ys;
return _.zipWith(xs, ys, (x, y) => ({ x, y }));
};
export const wrapPointSetDist = (value: T) => {
const tag = RSPointSetDist.getTag(value);
return new tagToClass[tag](value);
};
abstract class SqAbstractPointSetDist {
constructor(private _value: T) {}
abstract asShape(): SqShape;
protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
const value = rsMethod(this._value);
if (!value) throw new Error("Internal casting error");
return value;
};
asDistribution() {
return wrapDistribution(RSPointSetDist.toDistribution(this._value));
}
}
export class SqMixedPointSetDist extends SqAbstractPointSetDist {
tag = Tag.Mixed as const;
get value(): RSPointSetDist.mixedShape {
return this.valueMethod(RSPointSetDist.getMixed);
}
asShape() {
const v = this.value;
return {
discrete: shapePoints(v.discrete),
continuous: shapePoints(v.continuous),
};
}
}
export class SqDiscretePointSetDist extends SqAbstractPointSetDist {
tag = Tag.Discrete as const;
get value(): RSPointSetDist.discreteShape {
return this.valueMethod(RSPointSetDist.getDiscrete);
}
asShape() {
const v = this.value;
return {
discrete: shapePoints(v),
continuous: [],
};
}
}
export class SqContinuousPointSetDist extends SqAbstractPointSetDist {
tag = Tag.Continuous as const;
get value(): RSPointSetDist.continuousShape {
return this.valueMethod(RSPointSetDist.getContinues);
}
asShape() {
const v = this.value;
return {
discrete: [],
continuous: shapePoints(v),
};
}
}
const tagToClass = {
[Tag.Mixed]: SqMixedPointSetDist,
[Tag.Discrete]: SqDiscretePointSetDist,
[Tag.Continuous]: SqContinuousPointSetDist,
} as const;
export type SqPointSetDist =
| SqMixedPointSetDist
| SqDiscretePointSetDist
| SqContinuousPointSetDist;

View File

@ -0,0 +1,114 @@
import * as RSProject from "../rescript/ForTS/ForTS_ReducerProject.gen";
import { reducerErrorValue } from "../rescript/ForTS/ForTS_Reducer_ErrorValue.gen";
import { environment } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution_Environment.gen";
import { SqError } from "./SqError";
import { SqModule } from "./SqModule";
import { wrapValue } from "./SqValue";
import { resultMap2 } from "./types";
import { SqValueLocation } from "./SqValueLocation";
export class SqProject {
constructor(private _value: RSProject.reducerProject) {}
static create() {
return new SqProject(RSProject.createProject());
}
getSourceIds() {
return RSProject.getSourceIds(this._value);
}
setSource(sourceId: string, value: string) {
return RSProject.setSource(this._value, sourceId, value);
}
getSource(sourceId: string) {
return RSProject.getSource(this._value, sourceId);
}
touchSource(sourceId: string) {
return RSProject.touchSource(this._value, sourceId);
}
clean(sourceId: string) {
return RSProject.clean(this._value, sourceId);
}
cleanAll() {
return RSProject.cleanAll(this._value);
}
cleanResults(sourceId: string) {
return RSProject.cleanResults(this._value, sourceId);
}
cleanAllResults() {
return RSProject.cleanAllResults(this._value);
}
getIncludes(sourceId: string) {
return resultMap2(
RSProject.getIncludes(this._value, sourceId),
(a) => a,
(v: reducerErrorValue) => new SqError(v)
);
}
getContinues(sourceId: string) {
return RSProject.getContinues(this._value, sourceId);
}
setContinues(sourceId: string, continues: string[]) {
return RSProject.setContinues(this._value, sourceId, continues);
}
getRunOrder() {
return RSProject.getRunOrder(this._value);
}
getRunOrderFor(sourceId: string) {
return RSProject.getRunOrderFor(this._value, sourceId);
}
parseIncludes(sourceId: string) {
return RSProject.parseIncludes(this._value, sourceId);
}
run(sourceId: string) {
return RSProject.run(this._value, sourceId);
}
runAll() {
return RSProject.runAll(this._value);
}
getBindings(sourceId: string) {
return new SqModule(
RSProject.getBindings(this._value, sourceId),
new SqValueLocation(this, sourceId, {
root: "bindings",
items: [],
})
);
}
getResult(sourceId: string) {
const innerResult = RSProject.getResult(this._value, sourceId);
return resultMap2(
innerResult,
(v) =>
wrapValue(
v,
new SqValueLocation(this, sourceId, {
root: "result",
items: [],
})
),
(v: reducerErrorValue) => new SqError(v)
);
}
setEnvironment(environment: environment) {
RSProject.setEnvironment(this._value, environment);
}
}

View File

@ -0,0 +1,15 @@
import * as RSRecord from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_Record.gen";
import { wrapValue } from "./SqValue";
import { SqValueLocation } from "./SqValueLocation";
type T = RSRecord.squiggleValue_Record;
export class SqRecord {
constructor(private _value: T, public location: SqValueLocation) {}
entries() {
return RSRecord.getKeyValuePairs(this._value).map(
([k, v]) => [k, wrapValue(v, this.location.extend(k))] as const
);
}
}

View File

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

View File

@ -0,0 +1,216 @@
import * as RSValue from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue.gen";
import { squiggleValueTag as Tag } from "../rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag";
import { wrapDistribution } from "./SqDistribution";
import { SqLambda } from "./SqLambda";
import { SqLambdaDeclaration } from "./SqLambdaDeclaration";
import { SqModule } from "./SqModule";
import { SqRecord } from "./SqRecord";
import { SqArray } from "./SqArray";
import { SqType } from "./SqType";
import { SqProject } from "./SqProject";
import { SqValueLocation } from "./SqValueLocation";
export { Tag as SqValueTag };
type T = RSValue.squiggleValue;
export const wrapValue = (value: T, location: SqValueLocation): SqValue => {
const tag = RSValue.getTag(value);
return new tagToClass[tag](value, location);
};
export abstract class SqAbstractValue {
abstract tag: Tag;
constructor(private _value: T, public location: SqValueLocation) {}
protected valueMethod = <IR>(rsMethod: (v: T) => IR | null | undefined) => {
const value = rsMethod(this._value);
if (value === undefined || value === null) {
throw new Error("Internal casting error");
}
return value;
};
toString() {
return RSValue.toString(this._value);
}
}
export class SqArrayValue extends SqAbstractValue {
tag = Tag.Array as const;
get value() {
return new SqArray(this.valueMethod(RSValue.getArray), this.location);
}
}
export class SqArrayStringValue extends SqAbstractValue {
tag = Tag.ArrayString as const;
get value() {
return this.valueMethod(RSValue.getArrayString);
}
}
export class SqBoolValue extends SqAbstractValue {
tag = Tag.Bool as const;
get value() {
return this.valueMethod(RSValue.getBool);
}
}
export class SqCallValue extends SqAbstractValue {
tag = Tag.Call as const;
get value() {
return this.valueMethod(RSValue.getCall);
}
}
export class SqDateValue extends SqAbstractValue {
tag = Tag.Date as const;
get value() {
return this.valueMethod(RSValue.getDate);
}
}
export class SqDeclarationValue extends SqAbstractValue {
tag = Tag.Declaration as const;
get value() {
return new SqLambdaDeclaration(this.valueMethod(RSValue.getDeclaration));
}
}
export class SqDistributionValue extends SqAbstractValue {
tag = Tag.Distribution as const;
get value() {
return wrapDistribution(this.valueMethod(RSValue.getDistribution));
}
}
export class SqLambdaValue extends SqAbstractValue {
tag = Tag.Lambda as const;
get value() {
return new SqLambda(this.valueMethod(RSValue.getLambda), this.location);
}
}
export class SqModuleValue extends SqAbstractValue {
tag = Tag.Module as const;
get value() {
return new SqModule(this.valueMethod(RSValue.getModule), this.location);
}
}
export class SqNumberValue extends SqAbstractValue {
tag = Tag.Number as const;
get value() {
return this.valueMethod(RSValue.getNumber);
}
}
export class SqRecordValue extends SqAbstractValue {
tag = Tag.Record as const;
get value() {
return new SqRecord(this.valueMethod(RSValue.getRecord), this.location);
}
}
export class SqStringValue extends SqAbstractValue {
tag = Tag.String as const;
get value(): string {
return this.valueMethod(RSValue.getString);
}
}
export class SqSymbolValue extends SqAbstractValue {
tag = Tag.Symbol as const;
get value(): string {
return this.valueMethod(RSValue.getSymbol);
}
}
export class SqTimeDurationValue extends SqAbstractValue {
tag = Tag.TimeDuration as const;
get value() {
return this.valueMethod(RSValue.getTimeDuration);
}
}
export class SqTypeValue extends SqAbstractValue {
tag = Tag.Type as const;
get value() {
return new SqType(this.valueMethod(RSValue.getType));
}
}
export class SqTypeIdentifierValue extends SqAbstractValue {
tag = Tag.TypeIdentifier as const;
get value() {
return this.valueMethod(RSValue.getTypeIdentifier);
}
}
export class SqVoidValue extends SqAbstractValue {
tag = Tag.Void as const;
get value() {
return null;
}
}
const tagToClass = {
[Tag.Array]: SqArrayValue,
[Tag.ArrayString]: SqArrayStringValue,
[Tag.Bool]: SqBoolValue,
[Tag.Call]: SqCallValue,
[Tag.Date]: SqDateValue,
[Tag.Declaration]: SqDeclarationValue,
[Tag.Distribution]: SqDistributionValue,
[Tag.Lambda]: SqLambdaValue,
[Tag.Module]: SqModuleValue,
[Tag.Number]: SqNumberValue,
[Tag.Record]: SqRecordValue,
[Tag.String]: SqStringValue,
[Tag.Symbol]: SqSymbolValue,
[Tag.TimeDuration]: SqTimeDurationValue,
[Tag.Type]: SqTypeValue,
[Tag.TypeIdentifier]: SqTypeIdentifierValue,
[Tag.Void]: SqVoidValue,
} as const;
// FIXME
// type SqValue = typeof tagToClass[keyof typeof tagToClass];
export type SqValue =
| SqArrayValue
| SqArrayStringValue
| SqBoolValue
| SqCallValue
| SqDateValue
| SqDeclarationValue
| SqDistributionValue
| SqLambdaValue
| SqModuleValue
| SqNumberValue
| SqRecordValue
| SqStringValue
| SqSymbolValue
| SqTimeDurationValue
| SqTypeValue
| SqTypeIdentifierValue
| SqVoidValue;

View File

@ -0,0 +1,24 @@
import { isParenthesisNode } from "mathjs";
import { SqProject } from "./SqProject";
type PathItem = string | number;
type SqValuePath = {
root: "result" | "bindings";
items: PathItem[];
};
export class SqValueLocation {
constructor(
public project: SqProject,
public sourceId: string,
public path: SqValuePath
) {}
extend(item: PathItem) {
return new SqValueLocation(this.project, this.sourceId, {
root: this.path.root,
items: [...this.path.items, item],
});
}
}

View File

@ -1,252 +0,0 @@
import * as _ from "lodash";
import {
genericDist,
continuousShape,
discreteShape,
environment,
distributionError,
toPointSet,
distributionErrorToString,
} from "../rescript/TypescriptInterface.gen";
import { result, resultMap, Ok } from "./types";
import {
Constructors_mean,
Constructors_stdev,
Constructors_sample,
Constructors_pdf,
Constructors_cdf,
Constructors_inv,
Constructors_normalize,
Constructors_isNormalized,
Constructors_toPointSet,
Constructors_toSampleSet,
Constructors_truncate,
Constructors_inspect,
Constructors_toString,
Constructors_toSparkline,
Constructors_algebraicAdd,
Constructors_algebraicMultiply,
Constructors_algebraicDivide,
Constructors_algebraicSubtract,
Constructors_algebraicLogarithm,
Constructors_algebraicPower,
Constructors_pointwiseAdd,
Constructors_pointwiseMultiply,
Constructors_pointwiseDivide,
Constructors_pointwiseSubtract,
Constructors_pointwiseLogarithm,
Constructors_pointwisePower,
} from "../rescript/Distributions/DistributionOperation.gen";
export type point = { x: number; y: number };
function shapePoints(x: continuousShape | discreteShape): point[] {
let xs = x.xyShape.xs;
let ys = x.xyShape.ys;
return _.zipWith(xs, ys, (x, y) => ({ x, y }));
}
export type shape = {
continuous: point[];
discrete: point[];
};
export class Distribution {
t: genericDist;
env: environment;
constructor(t: genericDist, env: environment) {
this.t = t;
this.env = env;
return this;
}
mapResultDist(
r: result<genericDist, distributionError>
): result<Distribution, distributionError> {
return resultMap(r, (v: genericDist) => new Distribution(v, this.env));
}
mean(): result<number, distributionError> {
return Constructors_mean({ env: this.env }, this.t);
}
stdev(): result<number, distributionError> {
return Constructors_stdev({ env: this.env }, this.t);
}
sample(): result<number, distributionError> {
return Constructors_sample({ env: this.env }, this.t);
}
pdf(n: number): result<number, distributionError> {
return Constructors_pdf({ env: this.env }, this.t, n);
}
cdf(n: number): result<number, distributionError> {
return Constructors_cdf({ env: this.env }, this.t, n);
}
inv(n: number): result<number, distributionError> {
return Constructors_inv({ env: this.env }, this.t, n);
}
isNormalized(): result<boolean, distributionError> {
return Constructors_isNormalized({ env: this.env }, this.t);
}
normalize(): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_normalize({ env: this.env }, this.t)
);
}
type() {
return this.t.tag;
}
pointSet(): result<shape, distributionError> {
let pointSet = toPointSet(
this.t,
{
xyPointLength: this.env.xyPointLength,
sampleCount: this.env.sampleCount,
},
undefined
);
if (pointSet.tag === "Ok") {
let distribution = pointSet.value;
if (distribution.tag === "Continuous") {
return Ok({
continuous: shapePoints(distribution.value),
discrete: [],
});
} else if (distribution.tag === "Discrete") {
return Ok({
discrete: shapePoints(distribution.value),
continuous: [],
});
} else {
return Ok({
discrete: shapePoints(distribution.value.discrete),
continuous: shapePoints(distribution.value.continuous),
});
}
} else {
return pointSet;
}
}
toPointSet(): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_toPointSet({ env: this.env }, this.t)
);
}
toSampleSet(n: number): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_toSampleSet({ env: this.env }, this.t, n)
);
}
truncate(
left: number,
right: number
): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_truncate({ env: this.env }, this.t, left, right)
);
}
inspect(): result<Distribution, distributionError> {
return this.mapResultDist(Constructors_inspect({ env: this.env }, this.t));
}
toString(): string {
let result = Constructors_toString({ env: this.env }, this.t);
if (result.tag === "Ok") {
return result.value;
} else {
return distributionErrorToString(result.value);
}
}
toSparkline(n: number): result<string, distributionError> {
return Constructors_toSparkline({ env: this.env }, this.t, n);
}
algebraicAdd(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_algebraicAdd({ env: this.env }, this.t, d2.t)
);
}
algebraicMultiply(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_algebraicMultiply({ env: this.env }, this.t, d2.t)
);
}
algebraicDivide(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_algebraicDivide({ env: this.env }, this.t, d2.t)
);
}
algebraicSubtract(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_algebraicSubtract({ env: this.env }, this.t, d2.t)
);
}
algebraicLogarithm(
d2: Distribution
): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_algebraicLogarithm({ env: this.env }, this.t, d2.t)
);
}
algebraicPower(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_algebraicPower({ env: this.env }, this.t, d2.t)
);
}
pointwiseAdd(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_pointwiseAdd({ env: this.env }, this.t, d2.t)
);
}
pointwiseMultiply(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_pointwiseMultiply({ env: this.env }, this.t, d2.t)
);
}
pointwiseDivide(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_pointwiseDivide({ env: this.env }, this.t, d2.t)
);
}
pointwiseSubtract(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_pointwiseSubtract({ env: this.env }, this.t, d2.t)
);
}
pointwiseLogarithm(
d2: Distribution
): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_pointwiseLogarithm({ env: this.env }, this.t, d2.t)
);
}
pointwisePower(d2: Distribution): result<Distribution, distributionError> {
return this.mapResultDist(
Constructors_pointwisePower({ env: this.env }, this.t, d2.t)
);
}
}

View File

@ -1,205 +1,36 @@
import * as _ from "lodash"; import { environment } from "../rescript/ForTS/ForTS_ReducerProject.gen";
import type { import { SqProject } from "./SqProject";
environment, import { SqValue, SqValueTag } from "./SqValue";
expressionValue, export { SqValueLocation } from "./SqValueLocation";
externalBindings, export { result } from "../rescript/ForTS/ForTS_Result_tag";
errorValue, export { SqDistribution, SqDistributionTag } from "./SqDistribution";
} from "../rescript/TypescriptInterface.gen"; export { SqDistributionError } from "./SqDistributionError";
import { export { SqRecord } from "./SqRecord";
defaultEnvironment, export { SqLambda } from "./SqLambda";
evaluatePartialUsingExternalBindings, export { SqProject };
evaluateUsingOptions, export { SqValue, SqValueTag };
foreignFunctionInterface,
} from "../rescript/TypescriptInterface.gen";
export { export {
makeSampleSetDist, environment,
errorValueToString, defaultEnvironment,
distributionErrorToString, } from "../rescript/ForTS/ForTS_Distribution/ForTS_Distribution.gen";
} from "../rescript/TypescriptInterface.gen"; export { SqError } from "./SqError";
export type { export { SqShape } from "./SqPointSetDist";
distributionError,
declarationArg,
declaration,
} from "../rescript/TypescriptInterface.gen";
export type { errorValue, externalBindings as bindings, jsImports };
import {
jsValueToBinding,
jsValueToExpressionValue,
jsValue,
rescriptExport,
squiggleExpression,
convertRawToTypescript,
lambdaValue,
} from "./rescript_interop";
import { result, resultMap, tag, tagged } from "./types";
import { Distribution, shape } from "./distribution";
export { Distribution, resultMap, defaultEnvironment }; export { resultMap } from "./types";
export type { result, shape, environment, lambdaValue, squiggleExpression };
export { parse } from "./parse"; export const run = (
code: string,
export let defaultSamplingInputs: environment = { options?: {
sampleCount: 10000, environment?: environment;
xyPointLength: 10000,
};
export function run(
squiggleString: string,
bindings?: externalBindings,
environment?: environment,
imports?: jsImports
): result<squiggleExpression, errorValue> {
let b = bindings ? bindings : defaultBindings;
let i = imports ? imports : defaultImports;
let e = environment ? environment : defaultEnvironment;
let res: result<expressionValue, errorValue> = evaluateUsingOptions(
{ externalBindings: mergeImportsWithBindings(b, i), environment: e },
squiggleString
);
return resultMap(res, (x) => createTsExport(x, e));
}
// Run Partial. A partial is a block of code that doesn't return a value
export function runPartial(
squiggleString: string,
bindings?: externalBindings,
environment?: environment,
imports?: jsImports
): result<externalBindings, errorValue> {
let b = bindings ? bindings : defaultBindings;
let i = imports ? imports : defaultImports;
let e = environment ? environment : defaultEnvironment;
return evaluatePartialUsingExternalBindings(
squiggleString,
mergeImportsWithBindings(b, i),
e
);
}
export function runForeign(
fn: lambdaValue,
args: jsValue[],
environment?: environment
): result<squiggleExpression, errorValue> {
let e = environment ? environment : defaultEnvironment;
let res: result<expressionValue, errorValue> = foreignFunctionInterface(
fn,
args.map(jsValueToExpressionValue),
e
);
return resultMap(res, (x) => createTsExport(x, e));
}
function mergeImportsWithBindings(
bindings: externalBindings,
imports: jsImports
): externalBindings {
let transformedImports = Object.fromEntries(
Object.entries(imports).map(([key, value]) => [
"$" + key,
jsValueToBinding(value),
])
);
return _.merge(bindings, transformedImports);
}
type jsImports = { [key: string]: jsValue };
export let defaultImports: jsImports = {};
export let defaultBindings: externalBindings = {};
export function mergeBindings(
allBindings: externalBindings[]
): externalBindings {
return allBindings.reduce((acc, x) => ({ ...acc, ...x }));
}
function createTsExport(
x: expressionValue,
environment: environment
): squiggleExpression {
switch (x) {
case "EvVoid":
return tag("void", "");
default: {
switch (x.tag) {
case "EvArray":
// genType doesn't convert anything more than 2 layers down into {tag: x, value: x}
// format, leaving it as the raw values. This converts the raw values
// directly into typescript values.
//
// The casting here is because genType is about the types of the returned
// values, claiming they are fully recursive when that's not actually the
// case
return tag(
"array",
x.value.map(
(arrayItem): squiggleExpression =>
convertRawToTypescript(
arrayItem as unknown as rescriptExport,
environment
)
)
);
case "EvArrayString":
return tag("arraystring", x.value);
case "EvBool":
return tag("boolean", x.value);
case "EvCall":
return tag("call", x.value);
case "EvLambda":
return tag("lambda", x.value);
case "EvDistribution":
return tag("distribution", new Distribution(x.value, environment));
case "EvNumber":
return tag("number", x.value);
case "EvRecord":
// genType doesn't support records, so we have to do the raw conversion ourself
let result: tagged<"record", { [key: string]: squiggleExpression }> =
tag(
"record",
_.mapValues(x.value, (x: unknown) =>
convertRawToTypescript(x as rescriptExport, environment)
)
);
return result;
case "EvString":
return tag("string", x.value);
case "EvSymbol":
return tag("symbol", x.value);
case "EvDate":
return tag("date", x.value);
case "EvTimeDuration":
return tag("timeDuration", x.value);
case "EvDeclaration":
return tag("lambdaDeclaration", x.value);
case "EvTypeIdentifier":
return tag("typeIdentifier", x.value);
case "EvType":
let typeResult: tagged<
"type",
{ [key: string]: squiggleExpression }
> = tag(
"type",
_.mapValues(x.value, (x: unknown) =>
convertRawToTypescript(x as rescriptExport, environment)
)
);
return typeResult;
case "EvModule":
let moduleResult: tagged<
"module",
{ [key: string]: squiggleExpression }
> = tag(
"module",
_.mapValues(x.value, (x: unknown) =>
convertRawToTypescript(x as rescriptExport, environment)
)
);
return moduleResult;
}
}
} }
} ) => {
const project = SqProject.create();
project.setSource("main", code);
if (options?.environment) {
project.setEnvironment(options.environment);
}
project.run("main");
const result = project.getResult("main");
const bindings = project.getBindings("main");
return { result, bindings };
};

View File

@ -1,23 +1,23 @@
import { // import {
errorValue, // errorValue,
parse as parseRescript, // parse as parseRescript,
} from "../rescript/TypescriptInterface.gen"; // } from "../rescript/TypescriptInterface.gen";
import { result } from "./types"; // import { result } from "./types";
import { AnyPeggyNode } from "../rescript/Reducer/Reducer_Peggy/helpers"; // import { AnyPeggyNode } from "../rescript/Reducer/Reducer_Peggy/helpers";
export function parse( // export function parse(
squiggleString: string // squiggleString: string
): result<AnyPeggyNode, Extract<errorValue, { tag: "RESyntaxError" }>> { // ): result<AnyPeggyNode, Extract<errorValue, { tag: "RESyntaxError" }>> {
const maybeExpression = parseRescript(squiggleString); // const maybeExpression = parseRescript(squiggleString);
if (maybeExpression.tag === "Ok") { // if (maybeExpression.tag === "Ok") {
return { tag: "Ok", value: maybeExpression.value as AnyPeggyNode }; // return { tag: "Ok", value: maybeExpression.value as AnyPeggyNode };
} else { // } else {
if ( // if (
typeof maybeExpression.value !== "object" || // typeof maybeExpression.value !== "object" ||
maybeExpression.value.tag !== "RESyntaxError" // maybeExpression.value.tag !== "RESyntaxError"
) { // ) {
throw new Error("Expected syntax error"); // throw new Error("Expected syntax error");
} // }
return { tag: "Error", value: maybeExpression.value }; // return { tag: "Error", value: maybeExpression.value };
} // }
} // }

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